""" 技术论坛数据库模型 - SQLite """ import sqlite3 import json import uuid import datetime from pathlib import Path from werkzeug.security import generate_password_hash, check_password_hash from contextlib import contextmanager class Database: def __init__(self, db_path='data/tech_forum.db'): self.db_path = Path(db_path) self.db_path.parent.mkdir(parents=True, exist_ok=True) self._init_tables() @contextmanager def get_conn(self): conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row try: yield conn finally: conn.close() def _init_tables(self): with self.get_conn() as conn: # 用户表 conn.execute(''' CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, phone TEXT, password TEXT NOT NULL, avatar TEXT, bio TEXT, created_at TEXT, updated_at TEXT ) ''') # 帖子表 conn.execute(''' CREATE TABLE IF NOT EXISTS posts ( id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT, type TEXT DEFAULT 'discussion', author_id TEXT, tags TEXT, likes TEXT DEFAULT '[]', views INTEGER DEFAULT 0, is_pinned INTEGER DEFAULT 0, created_at TEXT, updated_at TEXT, FOREIGN KEY (author_id) REFERENCES users(id) ) ''') # 回复表 conn.execute(''' CREATE TABLE IF NOT EXISTS replies ( id TEXT PRIMARY KEY, post_id TEXT, content TEXT, author_id TEXT, likes TEXT DEFAULT '[]', reply_to TEXT, created_at TEXT, FOREIGN KEY (post_id) REFERENCES posts(id), FOREIGN KEY (author_id) REFERENCES users(id) ) ''') # 主题表(工具分享) conn.execute(''' CREATE TABLE IF NOT EXISTS topics ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, icon TEXT DEFAULT '🔧', author_id TEXT, followers TEXT DEFAULT '[]', created_at TEXT, FOREIGN KEY (author_id) REFERENCES users(id) ) ''') # 子主题表 conn.execute(''' CREATE TABLE IF NOT EXISTS sub_topics ( id TEXT PRIMARY KEY, topic_id TEXT, title TEXT, content TEXT, author_id TEXT, created_at TEXT, FOREIGN KEY (topic_id) REFERENCES topics(id), FOREIGN KEY (author_id) REFERENCES users(id) ) ''') # 问题表 conn.execute(''' CREATE TABLE IF NOT EXISTS questions ( id TEXT PRIMARY KEY, topic_id TEXT, title TEXT, content TEXT, author_id TEXT, views INTEGER DEFAULT 0, created_at TEXT, FOREIGN KEY (topic_id) REFERENCES topics(id), FOREIGN KEY (author_id) REFERENCES users(id) ) ''') # 回答表 conn.execute(''' CREATE TABLE IF NOT EXISTS answers ( id TEXT PRIMARY KEY, question_id TEXT, content TEXT, author_id TEXT, likes TEXT DEFAULT '[]', created_at TEXT, FOREIGN KEY (question_id) REFERENCES questions(id), FOREIGN KEY (author_id) REFERENCES users(id) ) ''') conn.commit() class UserModel: def __init__(self, db): self.db = db def create(self, username, email, phone, password): user_id = str(uuid.uuid4()) avatar = f'https://api.dicebear.com/7.x/avataaars/svg?seed={username}' now = datetime.datetime.now().isoformat() with self.db.get_conn() as conn: conn.execute(''' INSERT INTO users (id, username, email, phone, password, avatar, bio, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, '', ?, ?) ''', (user_id, username, email, phone, generate_password_hash(password), avatar, now, now)) conn.commit() return user_id def get_by_id(self, user_id): with self.db.get_conn() as conn: row = conn.execute('SELECT * FROM users WHERE id = ?', (user_id,)).fetchone() return dict(row) if row else None def get_by_username(self, username): with self.db.get_conn() as conn: row = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone() return dict(row) if row else None def get_by_email(self, email): with self.db.get_conn() as conn: row = conn.execute('SELECT * FROM users WHERE email = ?', (email,)).fetchone() return dict(row) if row else None def verify_password(self, user, password): return check_password_hash(user['password'], password) def get_all(self): with self.db.get_conn() as conn: rows = conn.execute('SELECT * FROM users ORDER BY created_at DESC').fetchall() return [dict(row) for row in rows] def delete(self, user_id): with self.db.get_conn() as conn: # 删除用户的所有帖子 conn.execute('DELETE FROM replies WHERE author_id = ?', (user_id,)) conn.execute('DELETE FROM posts WHERE author_id = ?', (user_id,)) conn.execute('DELETE FROM users WHERE id = ?', (user_id,)) conn.commit() def get_posts_count(self, user_id): with self.db.get_conn() as conn: return conn.execute('SELECT COUNT(*) FROM posts WHERE author_id = ?', (user_id,)).fetchone()[0] def get_replies_count(self, user_id): with self.db.get_conn() as conn: return conn.execute('SELECT COUNT(*) FROM replies WHERE author_id = ?', (user_id,)).fetchone()[0] class PostModel: def __init__(self, db): self.db = db def create(self, title, content, post_type, author_id, tags): post_id = str(uuid.uuid4()) now = datetime.datetime.now().isoformat() tags_json = json.dumps(tags) with self.db.get_conn() as conn: conn.execute(''' INSERT INTO posts (id, title, content, type, author_id, tags, likes, views, is_pinned, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, '[]', 0, 0, ?, ?) ''', (post_id, title, content, post_type, author_id, tags_json, now, now)) conn.commit() return post_id def get_by_id(self, post_id): with self.db.get_conn() as conn: row = conn.execute('SELECT * FROM posts WHERE id = ?', (post_id,)).fetchone() if row: post = dict(row) post['tags'] = json.loads(post['tags'] or '[]') post['likes'] = json.loads(post['likes'] or '[]') return post return None def get_all(self, post_type=None, tag=None, page=1, per_page=20): with self.db.get_conn() as conn: query = 'SELECT * FROM posts WHERE 1=1' params = [] if post_type: query += ' AND type = ?' params.append(post_type) query += ' ORDER BY is_pinned DESC, created_at DESC' rows = conn.execute(query, params).fetchall() posts = [] for row in rows: post = dict(row) post['tags'] = json.loads(post['tags'] or '[]') post['likes'] = json.loads(post['likes'] or '[]') posts.append(post) # 分页 start = (page - 1) * per_page return posts[start:start + per_page], len(posts) def increment_views(self, post_id): with self.db.get_conn() as conn: conn.execute('UPDATE posts SET views = views + 1 WHERE id = ?', (post_id,)) conn.commit() def add_like(self, post_id, user_id): with self.db.get_conn() as conn: post = self.get_by_id(post_id) likes = post['likes'] if user_id in likes: likes.remove(user_id) liked = False else: likes.append(user_id) liked = True conn.execute('UPDATE posts SET likes = ? WHERE id = ?', (json.dumps(likes), post_id)) conn.commit() return liked, len(likes) def delete(self, post_id): with self.db.get_conn() as conn: conn.execute('DELETE FROM replies WHERE post_id = ?', (post_id,)) conn.execute('DELETE FROM posts WHERE id = ?', (post_id,)) conn.commit() def toggle_pin(self, post_id): with self.db.get_conn() as conn: row = conn.execute('SELECT is_pinned FROM posts WHERE id = ?', (post_id,)).fetchone() new_pin = 1 if row['is_pinned'] == 0 else 0 conn.execute('UPDATE posts SET is_pinned = ? WHERE id = ?', (new_pin, post_id)) conn.commit() return new_pin def get_tags_stats(self): with self.db.get_conn() as conn: rows = conn.execute('SELECT tags FROM posts').fetchall() tag_counts = {} for row in rows: tags = json.loads(row['tags'] or '[]') for tag in tags: tag_counts[tag] = tag_counts.get(tag, 0) + 1 return sorted(tag_counts.items(), key=lambda x: x[1], reverse=True) class ReplyModel: def __init__(self, db): self.db = db def create(self, post_id, content, author_id, reply_to=None): reply_id = str(uuid.uuid4()) now = datetime.datetime.now().isoformat() with self.db.get_conn() as conn: conn.execute(''' INSERT INTO replies (id, post_id, content, author_id, likes, reply_to, created_at) VALUES (?, ?, ?, ?, '[]', ?, ?) ''', (reply_id, post_id, content, author_id, reply_to, now)) conn.commit() return reply_id def get_by_post(self, post_id): with self.db.get_conn() as conn: rows = conn.execute('SELECT * FROM replies WHERE post_id = ? ORDER BY created_at', (post_id,)).fetchall() replies = [] for row in rows: reply = dict(row) reply['likes'] = json.loads(reply['likes'] or '[]') replies.append(reply) return replies class TopicModel: def __init__(self, db): self.db = db def create(self, name, description, icon, author_id): topic_id = str(uuid.uuid4()) now = datetime.datetime.now().isoformat() with self.db.get_conn() as conn: conn.execute(''' INSERT INTO topics (id, name, description, icon, author_id, followers, created_at) VALUES (?, ?, ?, ?, ?, '[]', ?) ''', (topic_id, name, description, icon, author_id, now)) conn.commit() return topic_id def get_by_id(self, topic_id): with self.db.get_conn() as conn: row = conn.execute('SELECT * FROM topics WHERE id = ?', (topic_id,)).fetchone() if row: topic = dict(row) topic['followers'] = json.loads(topic['followers'] or '[]') return topic return None def get_all(self): with self.db.get_conn() as conn: rows = conn.execute('SELECT * FROM topics ORDER BY created_at DESC').fetchall() topics = [] for row in rows: topic = dict(row) topic['followers'] = json.loads(topic['followers'] or '[]') topics.append(topic) return topics def delete(self, topic_id): with self.db.get_conn() as conn: conn.execute('DELETE FROM answers WHERE question_id IN (SELECT id FROM questions WHERE topic_id = ?)', (topic_id,)) conn.execute('DELETE FROM questions WHERE topic_id = ?', (topic_id,)) conn.execute('DELETE FROM sub_topics WHERE topic_id = ?', (topic_id,)) conn.execute('DELETE FROM topics WHERE id = ?', (topic_id,)) conn.commit() def add_follower(self, topic_id, user_id): with self.db.get_conn() as conn: topic = self.get_by_id(topic_id) followers = topic['followers'] if user_id in followers: followers.remove(user_id) followed = False else: followers.append(user_id) followed = True conn.execute('UPDATE topics SET followers = ? WHERE id = ?', (json.dumps(followers), topic_id)) conn.commit() return followed, len(followers) def get_sub_topics(self, topic_id): with self.db.get_conn() as conn: rows = conn.execute('SELECT * FROM sub_topics WHERE topic_id = ? ORDER BY created_at', (topic_id,)).fetchall() return [dict(row) for row in rows] def add_sub_topic(self, topic_id, title, content, author_id): sub_id = str(uuid.uuid4()) now = datetime.datetime.now().isoformat() with self.db.get_conn() as conn: conn.execute(''' INSERT INTO sub_topics (id, topic_id, title, content, author_id, created_at) VALUES (?, ?, ?, ?, ?, ?) ''', (sub_id, topic_id, title, content, author_id, now)) conn.commit() return sub_id def get_questions(self, topic_id): with self.db.get_conn() as conn: rows = conn.execute('SELECT * FROM questions WHERE topic_id = ? ORDER BY created_at DESC', (topic_id,)).fetchall() questions = [] for row in rows: q = dict(row) # 获取回答 ans_rows = conn.execute('SELECT * FROM answers WHERE question_id = ? ORDER BY created_at', (q['id'],)).fetchall() answers = [] for ans in ans_rows: a = dict(ans) a['likes'] = json.loads(a['likes'] or '[]') answers.append(a) q['answers'] = answers questions.append(q) return questions def add_question(self, topic_id, title, content, author_id): q_id = str(uuid.uuid4()) now = datetime.datetime.now().isoformat() with self.db.get_conn() as conn: conn.execute(''' INSERT INTO questions (id, topic_id, title, content, author_id, views, created_at) VALUES (?, ?, ?, ?, ?, 0, ?) ''', (q_id, topic_id, title, content, author_id, now)) conn.commit() return q_id def add_answer(self, question_id, content, author_id): ans_id = str(uuid.uuid4()) now = datetime.datetime.now().isoformat() with self.db.get_conn() as conn: conn.execute(''' INSERT INTO answers (id, question_id, content, author_id, likes, created_at) VALUES (?, ?, ?, ?, '[]', ?) ''', (ans_id, question_id, content, author_id, now)) conn.commit() return ans_id