""" 数据库操作模块 """ import os import sqlite3 import hashlib from datetime import datetime, timezone, timedelta from config import config def beijing_now_str(fmt='%Y-%m-%d %H:%M:%S'): """返回北京时间字符串""" return datetime.now(timezone(timedelta(hours=8))).strftime(fmt) class Database: def __init__(self): self.db_path = config.get('database') db_dir = os.path.dirname(self.db_path) if db_dir and not os.path.exists(db_dir): os.makedirs(db_dir, exist_ok=True) self.init_database() def get_connection(self): conn = sqlite3.connect(self.db_path, check_same_thread=False) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA foreign_keys=ON") return conn def init_database(self): conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), avatar VARCHAR(200) DEFAULT 'default.png', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )''') cursor.execute(''' CREATE TABLE IF NOT EXISTS friendships ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, friend_id INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (friend_id) REFERENCES users(id), UNIQUE(user_id, friend_id) )''') cursor.execute(''' CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, sender_id INTEGER NOT NULL, receiver_id INTEGER, group_id INTEGER, content TEXT NOT NULL, msg_type VARCHAR(20) DEFAULT 'text', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (sender_id) REFERENCES users(id) )''') cursor.execute(''' CREATE TABLE IF NOT EXISTS groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL, creator_id INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (creator_id) REFERENCES users(id) )''') cursor.execute(''' CREATE TABLE IF NOT EXISTS group_members ( id INTEGER PRIMARY KEY AUTOINCREMENT, group_id INTEGER NOT NULL, user_id INTEGER NOT NULL, join_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (group_id) REFERENCES groups(id), FOREIGN KEY (user_id) REFERENCES users(id), UNIQUE(group_id, user_id) )''') # 默认管理员与测试用户 default_users = [ ('admin', '123456', '系统管理员'), ('user1', '123456', '用户一'), ('user2', '123456', '用户二'), ('user3', '123456', '用户三'), ] for username, password, nickname in default_users: cursor.execute("SELECT id FROM users WHERE username=?", (username,)) if not cursor.fetchone(): pw = hashlib.md5(password.encode()).hexdigest() cursor.execute( "INSERT INTO users (username, password, nickname) VALUES (?,?,?)", (username, pw, nickname)) conn.commit() # 为新数据库填充测试数据:好友关系、群组、聊天记录 self._seed_test_data(conn) conn.close() def _seed_test_data(self, conn): """为新数据库填充测试数据(仅在数据为空时执行)""" cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM messages") if cursor.fetchone()[0] > 0: return # 已有数据,跳过 now = beijing_now_str() cursor.execute("SELECT id, username FROM users") users = {r[1]: r[0] for r in cursor.fetchall()} uid = users.get # 好友关系 friendships = [ ('admin', 'user1'), ('admin', 'user2'), ('user1', 'user2'), ('user1', 'user3'), ('user2', 'user3'), ] for a, b in friendships: if a in users and b in users: cursor.execute("INSERT OR IGNORE INTO friendships (user_id, friend_id) VALUES (?,?)", (users[a], users[b])) cursor.execute("INSERT OR IGNORE INTO friendships (user_id, friend_id) VALUES (?,?)", (users[b], users[a])) # 群组 cursor.execute("INSERT INTO groups (name, creator_id) VALUES (?,?)", ('技术交流群', users['admin'])) gid = cursor.lastrowid for name in ['admin', 'user1', 'user2', 'user3']: if name in users: cursor.execute("INSERT OR IGNORE INTO group_members (group_id, user_id) VALUES (?,?)", (gid, users[name])) # 私聊消息 private_msgs = [ ('admin', 'user1', '你好 user1,欢迎使用 SimpleChat!'), ('user1', 'admin', '你好管理员,这个系统看起来很不错!'), ('admin', 'user1', '是的,支持私聊、群聊、好友管理等功能'), ('user1', 'admin', '太棒了,我试试看群聊功能'), ('admin', 'user2', 'user2,最近在忙什么呢?'), ('user2', 'admin', '在研究 Python 网络编程,这个聊天系统是用什么写的?'), ('admin', 'user2', '用 Python + PyQt5 写的,TCP 通信'), ('user2', 'admin', '厉害!我也想学习一下'), ('user1', 'user2', 'hi user2,一起加个群聊吧'), ('user2', 'user1', '好啊,我正想找人聊天呢'), ('user1', 'user3', 'user3,好久不见'), ('user3', 'user1', '是啊好久不见,最近怎么样?'), ('user1', 'user3', '挺好的,这个聊天系统不错'), ('user3', 'user1', '确实,界面也挺好看的'), ] for i, (sen, rec, content) in enumerate(private_msgs): if sen in users and rec in users: ts = f'2026-05-14 {9 + i // 6:02d}:{30 + i % 6 * 8:02d}:00' cursor.execute( "INSERT INTO messages (sender_id, receiver_id, content, created_at) VALUES (?,?,?,?)", (users[sen], users[rec], content, ts)) # 群聊消息 group_msgs = [ ('admin', '欢迎大家加入技术交流群!'), ('user1', '大家好,我是 user1,很高兴加入'), ('user2', '大家好!我是 user2'), ('user3', '各位好,我是 user3'), ('admin', '大家可以在群里交流技术问题'), ('user1', 'Python 的异步编程大家有研究吗?'), ('user2', '我最近在看 asyncio,感觉挺有意思的'), ('admin', 'asyncio 确实强大,适合高并发场景'), ('user3', '我主要做前端,PyQt5 的界面开发也可以交流'), ] for i, (sen, content) in enumerate(group_msgs): if sen in users: ts = f'2026-05-14 {9 + i // 4:02d}:{40 + i % 4 * 8:02d}:00' cursor.execute( "INSERT INTO messages (sender_id, group_id, content, created_at) VALUES (?,?,?,?)", (users[sen], gid, content, ts)) conn.commit() # ── 用户 ────────────────────────────────────────────── def user_register(self, username, password, nickname=None): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute("SELECT id FROM users WHERE username=?", (username,)) if cursor.fetchone(): return False, "用户名已存在" pw = hashlib.md5(password.encode()).hexdigest() cursor.execute( "INSERT INTO users (username, password, nickname) VALUES (?,?,?)", (username, pw, nickname or username)) conn.commit() return True, "注册成功" except Exception as e: return False, f"注册失败: {e}" finally: conn.close() def user_login(self, username, password): conn = self.get_connection() cursor = conn.cursor() try: pw = hashlib.md5(password.encode()).hexdigest() cursor.execute( "SELECT id, username, nickname, avatar FROM users WHERE username=? AND password=?", (username, pw)) user = cursor.fetchone() if user: return True, "登录成功", dict(user) return False, "用户名或密码错误", None except Exception as e: return False, f"登录失败: {e}", None finally: conn.close() def change_username(self, user_id, new_username): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute("SELECT id FROM users WHERE username=? AND id!=?", (new_username, user_id)) if cursor.fetchone(): return False, "用户名已被占用" cursor.execute("UPDATE users SET username=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", (new_username, user_id)) conn.commit() return True, "用户名修改成功" except Exception as e: return False, f"修改失败: {e}" finally: conn.close() def change_nickname(self, user_id, new_nickname): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute("UPDATE users SET nickname=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", (new_nickname, user_id)) conn.commit() return True, "昵称修改成功" except Exception as e: return False, f"修改失败: {e}" finally: conn.close() def change_password(self, user_id, old_password, new_password): conn = self.get_connection() cursor = conn.cursor() try: old_pw = hashlib.md5(old_password.encode()).hexdigest() cursor.execute("SELECT id FROM users WHERE id=? AND password=?", (user_id, old_pw)) if not cursor.fetchone(): return False, "原密码错误" new_pw = hashlib.md5(new_password.encode()).hexdigest() cursor.execute("UPDATE users SET password=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", (new_pw, user_id)) conn.commit() return True, "密码修改成功" except Exception as e: return False, f"修改失败: {e}" finally: conn.close() def get_user_by_id(self, user_id): conn = self.get_connection() cursor = conn.cursor() cursor.execute( "SELECT id, username, nickname, avatar FROM users WHERE id=?", (user_id,)) user = cursor.fetchone() conn.close() return dict(user) if user else None def get_user_by_username(self, username): conn = self.get_connection() cursor = conn.cursor() cursor.execute( "SELECT id, username, nickname, avatar FROM users WHERE username=?", (username,)) user = cursor.fetchone() conn.close() return dict(user) if user else None def get_all_users(self, exclude_id=None): conn = self.get_connection() cursor = conn.cursor() if exclude_id: cursor.execute( "SELECT id, username, nickname, avatar FROM users WHERE id!=? ORDER BY username", (exclude_id,)) else: cursor.execute( "SELECT id, username, nickname, avatar FROM users ORDER BY username") users = [dict(r) for r in cursor.fetchall()] conn.close() return users def search_users(self, keyword, exclude_id=None): """搜索用户(用户名或昵称模糊匹配)""" conn = self.get_connection() cursor = conn.cursor() like = f"%{keyword}%" if exclude_id: cursor.execute( "SELECT id, username, nickname, avatar FROM users " "WHERE id!=? AND (username LIKE ? OR nickname LIKE ?) ORDER BY username", (exclude_id, like, like)) else: cursor.execute( "SELECT id, username, nickname, avatar FROM users " "WHERE username LIKE ? OR nickname LIKE ? ORDER BY username", (like, like)) users = [dict(r) for r in cursor.fetchall()] conn.close() return users # ── 好友 ────────────────────────────────────────────── def add_friend(self, user_id, friend_username): conn = self.get_connection() cursor = conn.cursor() try: friend = self.get_user_by_username(friend_username) if not friend: return False, "用户不存在" friend_id = friend['id'] if user_id == friend_id: return False, "不能添加自己为好友" cursor.execute( "SELECT id FROM friendships WHERE (user_id=? AND friend_id=?) OR (user_id=? AND friend_id=?)", (user_id, friend_id, friend_id, user_id)) if cursor.fetchone(): return False, "已经是好友" cursor.execute("INSERT INTO friendships (user_id, friend_id) VALUES (?,?)", (user_id, friend_id)) cursor.execute("INSERT INTO friendships (user_id, friend_id) VALUES (?,?)", (friend_id, user_id)) conn.commit() return True, "添加好友成功" except Exception as e: return False, f"添加好友失败: {e}" finally: conn.close() def get_friends(self, user_id): conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT u.id, u.username, u.nickname, u.avatar FROM users u JOIN friendships f ON u.id = f.friend_id WHERE f.user_id=? ORDER BY u.username ''', (user_id,)) friends = [dict(r) for r in cursor.fetchall()] conn.close() return friends def remove_friend(self, user_id, friend_id): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute( "DELETE FROM friendships WHERE (user_id=? AND friend_id=?) OR (user_id=? AND friend_id=?)", (user_id, friend_id, friend_id, user_id)) conn.commit() return True, "已删除好友" except Exception as e: return False, str(e) finally: conn.close() # ── 消息 ────────────────────────────────────────────── def save_message(self, sender_id, receiver_id, content, msg_type='text', group_id=None): conn = self.get_connection() cursor = conn.cursor() try: now = beijing_now_str() cursor.execute( "INSERT INTO messages (sender_id, receiver_id, group_id, content, msg_type, created_at) VALUES (?,?,?,?,?,?)", (sender_id, receiver_id, group_id, content, msg_type, now)) conn.commit() return True, cursor.lastrowid except Exception as e: return False, str(e) finally: conn.close() def get_chat_history(self, user1_id, user2_id, limit=50): conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT m.id, m.content, m.created_at, u.username, u.nickname FROM messages m JOIN users u ON m.sender_id = u.id WHERE ((m.sender_id=? AND m.receiver_id=?) OR (m.sender_id=? AND m.receiver_id=?)) AND m.group_id IS NULL ORDER BY m.created_at ASC LIMIT ? ''', (user1_id, user2_id, user2_id, user1_id, limit)) messages = [dict(r) for r in cursor.fetchall()] conn.close() return messages def get_group_history(self, group_id, limit=50): conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT m.id, m.content, m.created_at, u.username, u.nickname FROM messages m JOIN users u ON m.sender_id = u.id WHERE m.group_id=? ORDER BY m.created_at ASC LIMIT ? ''', (group_id, limit)) messages = [dict(r) for r in cursor.fetchall()] conn.close() return messages # ── 群组 ────────────────────────────────────────────── def create_group(self, group_name, creator_id): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute("INSERT INTO groups (name, creator_id) VALUES (?,?)", (group_name, creator_id)) group_id = cursor.lastrowid cursor.execute("INSERT INTO group_members (group_id, user_id) VALUES (?,?)", (group_id, creator_id)) conn.commit() return True, group_id except Exception as e: return False, str(e) finally: conn.close() def get_user_groups(self, user_id): conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT g.id, g.name, g.creator_id FROM groups g JOIN group_members gm ON g.id = gm.group_id WHERE gm.user_id=? ORDER BY g.name ''', (user_id,)) groups = [dict(r) for r in cursor.fetchall()] conn.close() return groups def get_group_members(self, group_id): conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT u.id, u.username, u.nickname, u.avatar FROM users u JOIN group_members gm ON u.id = gm.user_id WHERE gm.group_id=? ORDER BY u.username ''', (group_id,)) members = [dict(r) for r in cursor.fetchall()] conn.close() return members def join_group(self, group_id, user_id): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute("SELECT id FROM groups WHERE id=?", (group_id,)) if not cursor.fetchone(): return False, "群组不存在" cursor.execute( "INSERT OR IGNORE INTO group_members (group_id, user_id) VALUES (?,?)", (group_id, user_id)) conn.commit() return True, "加入成功" except Exception as e: return False, str(e) finally: conn.close() def get_all_groups(self): conn = self.get_connection() cursor = conn.cursor() cursor.execute("SELECT id, name, creator_id FROM groups ORDER BY name") groups = [dict(r) for r in cursor.fetchall()] conn.close() return groups def leave_group(self, group_id, user_id): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute("SELECT id FROM groups WHERE id=?", (group_id,)) if not cursor.fetchone(): return False, "群组不存在" cursor.execute( "DELETE FROM group_members WHERE group_id=? AND user_id=?", (group_id, user_id)) if cursor.rowcount == 0: return False, "你不在该群组中" conn.commit() return True, "已退出群组" except Exception as e: return False, str(e) finally: conn.close() def invite_to_group(self, group_id, username): conn = self.get_connection() cursor = conn.cursor() try: cursor.execute("SELECT id FROM groups WHERE id=?", (group_id,)) if not cursor.fetchone(): return False, "群组不存在" user = self.get_user_by_username(username) if not user: return False, "用户不存在" cursor.execute( "SELECT id FROM group_members WHERE group_id=? AND user_id=?", (group_id, user['id'])) if cursor.fetchone(): return False, "用户已在群组中" cursor.execute( "INSERT INTO group_members (group_id, user_id) VALUES (?,?)", (group_id, user['id'])) conn.commit() return True, f"已邀请 {username} 加入群组" except Exception as e: return False, str(e) finally: conn.close() def get_all_user_history(self, user_id, limit=200): """获取用户的所有聊天记录(私聊+群聊),用于全局历史浏览""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT m.id, m.content, m.created_at, m.msg_type, u.username as sender_name, u.nickname as sender_nick, CASE WHEN m.group_id IS NOT NULL THEN g.name ELSE ru.username END as target_name, CASE WHEN m.group_id IS NOT NULL THEN 'group' ELSE 'private' END as chat_type, m.group_id, m.receiver_id FROM messages m JOIN users u ON m.sender_id = u.id LEFT JOIN groups g ON m.group_id = g.id LEFT JOIN users ru ON m.receiver_id = ru.id WHERE (m.sender_id = ? OR m.receiver_id = ? OR m.group_id IN (SELECT group_id FROM group_members WHERE user_id = ?)) ORDER BY m.created_at DESC LIMIT ? ''', (user_id, user_id, user_id, limit)) messages = [dict(r) for r in cursor.fetchall()] conn.close() return messages def get_recent_history(self, user_id, chat_type='private', target_id=None, limit=50): """获取与特定对象或群组的聊天记录""" conn = self.get_connection() cursor = conn.cursor() if chat_type == 'private': friend = self.get_user_by_id(target_id) if target_id else None if not friend: return [] cursor.execute(''' SELECT m.id, m.content, m.created_at, u.username, u.nickname FROM messages m JOIN users u ON m.sender_id = u.id WHERE m.group_id IS NULL AND ((m.sender_id=? AND m.receiver_id=?) OR (m.sender_id=? AND m.receiver_id=?)) ORDER BY m.created_at DESC LIMIT ? ''', (user_id, friend['id'], friend['id'], user_id, limit)) else: cursor.execute(''' SELECT m.id, m.content, m.created_at, u.username, u.nickname FROM messages m JOIN users u ON m.sender_id = u.id WHERE m.group_id=? ORDER BY m.created_at DESC LIMIT ? ''', (target_id, limit)) messages = [dict(r) for r in cursor.fetchall()] conn.close() return messages def search_messages(self, user_id, keyword, chat_type='private', target_id=None, limit=100): conn = self.get_connection() cursor = conn.cursor() like = f"%{keyword}%" if chat_type == 'private': # target_id may be a username string (from client) or user_id int friend = None if isinstance(target_id, str) and not target_id.isdigit(): friend = self.get_user_by_username(target_id) elif target_id is not None: friend = self.get_user_by_id(int(target_id)) if not friend: return [] friend_id = friend['id'] cursor.execute(''' SELECT m.id, m.content, m.created_at, m.msg_type, u.username, u.nickname FROM messages m JOIN users u ON m.sender_id = u.id WHERE m.group_id IS NULL AND ((m.sender_id=? AND m.receiver_id=?) OR (m.sender_id=? AND m.receiver_id=?)) AND m.content LIKE ? ORDER BY m.created_at DESC LIMIT ? ''', (user_id, friend_id, friend_id, user_id, like, limit)) else: cursor.execute(''' SELECT m.id, m.content, m.created_at, m.msg_type, u.username, u.nickname FROM messages m JOIN users u ON m.sender_id = u.id WHERE m.group_id=? AND m.content LIKE ? ORDER BY m.created_at DESC LIMIT ? ''', (target_id, like, limit)) messages = [dict(r) for r in cursor.fetchall()] conn.close() return messages