文件
2026-06-04 22:42:27 +08:00

453 行
18 KiB
PHP

<?php
class Database {
private static $instance = null;
private $connection;
private static $initialized = false;
private function __construct() {
try {
// 确保data目录存在
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$dbPath = $dataDir . '/parlz.db';
$this->connection = new SQLite3($dbPath);
// 只在第一次初始化时执行
if (!self::$initialized) {
$this->initialize();
self::$initialized = true;
}
} catch (Exception $e) {
// 处理异常,确保即使出现问题也能给出明确的错误信息
die('数据库初始化失败: ' . $e->getMessage());
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Database();
}
return self::$instance;
}
public function getConnection() {
return $this->connection;
}
/**
* 检查表是否存在
*/
private function tableExists($tableName) {
$result = $this->connection->query("SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName'");
return $result && $result->fetchArray() !== false;
}
/**
* 检查列是否存在
*/
private function columnExists($tableName, $columnName) {
$result = $this->connection->query("PRAGMA table_info($tableName)");
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
if ($row['name'] === $columnName) {
return true;
}
}
return false;
}
/**
* 添加列到表
*/
private function addColumn($tableName, $columnDefinition) {
try {
$this->connection->exec("ALTER TABLE $tableName ADD COLUMN $columnDefinition");
return true;
} catch (Exception $e) {
// 忽略错误,因为列可能已经存在
return false;
}
}
/**
* 创建表(如果不存在)
*/
private function createTable($tableName, $tableDefinition) {
try {
$sql = "CREATE TABLE IF NOT EXISTS $tableName ($tableDefinition)";
$this->connection->exec($sql);
return true;
} catch (Exception $e) {
// 忽略错误,因为表可能已经存在
return false;
}
}
private function initialize() {
// 创建用户表
$this->createTable('users', "
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
created_at INTEGER NOT NULL,
last_login INTEGER NOT NULL,
online INTEGER DEFAULT 0,
avatar TEXT DEFAULT '',
is_admin INTEGER DEFAULT 0,
is_banned INTEGER DEFAULT 0
");
// 为 users 表添加缺失的列
// 确保 users 表包含所有必要的列,这一步必须在创建其他表之前执行
if (!$this->columnExists('users', 'is_admin')) {
$this->addColumn('users', 'is_admin INTEGER DEFAULT 0');
}
if (!$this->columnExists('users', 'is_banned')) {
$this->addColumn('users', 'is_banned INTEGER DEFAULT 0');
}
if (!$this->columnExists('users', 'avatar')) {
$this->addColumn('users', 'avatar TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'online')) {
$this->addColumn('users', 'online INTEGER DEFAULT 0');
}
if (!$this->columnExists('users', 'city')) {
$this->addColumn('users', 'city TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'province')) {
$this->addColumn('users', 'province TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'country')) {
$this->addColumn('users', 'country TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'getip')) {
$this->addColumn('users', 'getip INTEGER DEFAULT 1');
}
if (!$this->columnExists('users', 'email')) {
$this->addColumn('users', 'email TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'remember_token')) {
$this->addColumn('users', 'remember_token TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'remember_token_expires')) {
$this->addColumn('users', 'remember_token_expires INTEGER DEFAULT 0');
}
if (!$this->columnExists('users', 'email_verified')) {
$this->addColumn('users', 'email_verified INTEGER DEFAULT 0');
}
if (!$this->columnExists('users', 'verification_code')) {
$this->addColumn('users', 'verification_code TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'qq_openid')) {
$this->addColumn('users', 'qq_openid TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'qq_nickname')) {
$this->addColumn('users', 'qq_nickname TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'github_id')) {
$this->addColumn('users', 'github_id TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'github_username')) {
$this->addColumn('users', 'github_username TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'microsoft_id')) {
$this->addColumn('users', 'microsoft_id TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'microsoft_username')) {
$this->addColumn('users', 'microsoft_username TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'steam_id')) {
$this->addColumn('users', 'steam_id TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'steam_username')) {
$this->addColumn('users', 'steam_username TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'discord_id')) {
$this->addColumn('users', 'discord_id TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'discord_username')) {
$this->addColumn('users', 'discord_username TEXT DEFAULT \'\'');
}
if (!$this->columnExists('users', 'bio')) {
$this->addColumn('users', 'bio TEXT DEFAULT \'\'');
}
// 创建好友表
$this->createTable('friends', "
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
friend_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
status TEXT DEFAULT 'accepted',
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (friend_id) REFERENCES users(id)
");
// 创建群组表
$this->createTable('groups', "
id TEXT PRIMARY KEY,
group_id TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
creator_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
avatar TEXT DEFAULT '',
FOREIGN KEY (creator_id) REFERENCES users(id)
");
// 为现有表添加缺失的group_id列(如果不存在)
try {
// 检查groups表是否存在group_id列
if (!$this->columnExists('groups', 'group_id')) {
// 先添加可为空的列
$this->addColumn('groups', 'group_id TEXT');
// 更新现有记录的group_id值
$this->connection->exec("UPDATE groups SET group_id = 'G' || upper(substr(hex(randomblob(4)), 0, 8)) WHERE group_id IS NULL");
// 修改列为非空
$this->connection->exec("CREATE TABLE IF NOT EXISTS groups_new (
id TEXT PRIMARY KEY,
group_id TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
creator_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
avatar TEXT DEFAULT '',
FOREIGN KEY (creator_id) REFERENCES users(id)
)");
$this->connection->exec("INSERT INTO groups_new SELECT * FROM groups");
$this->connection->exec("DROP TABLE groups");
$this->connection->exec("ALTER TABLE groups_new RENAME TO groups");
}
// 创建唯一索引
$this->connection->exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_groups_group_id ON groups(group_id)");
} catch (Exception $e) {
// 忽略错误,因为列可能已经存在
}
// 创建群成员表
$this->createTable('group_members', "
group_id TEXT NOT NULL,
user_id TEXT NOT NULL,
role TEXT DEFAULT 'member',
joined_at INTEGER NOT NULL,
PRIMARY KEY (group_id, user_id),
FOREIGN KEY (group_id) REFERENCES groups(id),
FOREIGN KEY (user_id) REFERENCES users(id)
");
// 创建群公告表
$this->createTable('group_announcements', "
id TEXT PRIMARY KEY,
group_id TEXT NOT NULL,
content TEXT NOT NULL,
created_by TEXT NOT NULL,
created_at INTEGER NOT NULL,
FOREIGN KEY (group_id) REFERENCES groups(id),
FOREIGN KEY (created_by) REFERENCES users(id)
");
// 创建消息表
$this->createTable('messages', "
id TEXT PRIMARY KEY,
sender_id TEXT NOT NULL,
receiver_id TEXT NOT NULL,
content TEXT NOT NULL,
timestamp INTEGER NOT NULL,
read INTEGER DEFAULT 0,
delivered INTEGER DEFAULT 0,
is_image INTEGER DEFAULT 0,
is_group INTEGER DEFAULT 0,
recalled INTEGER DEFAULT 0,
FOREIGN KEY (sender_id) REFERENCES users(id)
");
// 创建消息引用表
$this->createTable('message_quotes', "
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT NOT NULL,
sender TEXT NOT NULL,
content TEXT NOT NULL,
quoted_message_id TEXT DEFAULT NULL,
FOREIGN KEY (message_id) REFERENCES messages(id)
");
// 创建举报表
$this->createTable('reports', "
id TEXT PRIMARY KEY,
message_id TEXT NOT NULL,
reporter_id TEXT NOT NULL,
reason TEXT DEFAULT '',
status TEXT DEFAULT 'pending',
created_at INTEGER NOT NULL,
processed_by TEXT DEFAULT NULL,
processed_at INTEGER DEFAULT NULL,
FOREIGN KEY (message_id) REFERENCES messages(id),
FOREIGN KEY (reporter_id) REFERENCES users(id),
FOREIGN KEY (processed_by) REFERENCES users(id)
");
// 示例:添加新功能所需的表(例如用户设置表)
$this->createTable('user_settings', "
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
setting_key TEXT NOT NULL,
setting_value TEXT DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
UNIQUE(user_id, setting_key)
");
// 创建备注表
$this->createTable('notes', "
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
target_id TEXT NOT NULL,
target_type TEXT NOT NULL,
note TEXT DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
UNIQUE(user_id, target_id, target_type)
");
// 示例:添加新功能所需的表(例如聊天记录归档表)
$this->createTable('archived_messages', "
id TEXT PRIMARY KEY,
original_message_id TEXT NOT NULL,
sender_id TEXT NOT NULL,
receiver_id TEXT NOT NULL,
content TEXT NOT NULL,
timestamp INTEGER NOT NULL,
archived_at INTEGER NOT NULL,
FOREIGN KEY (original_message_id) REFERENCES messages(id),
FOREIGN KEY (sender_id) REFERENCES users(id)
");
// 创建系统设置表
$this->createTable('system_settings', "
id INTEGER PRIMARY KEY AUTOINCREMENT,
setting_key TEXT NOT NULL UNIQUE,
setting_value TEXT DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
");
// 创建用户协议同意表
$this->createTable('user_terms_agreements', "
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
terms_version TEXT NOT NULL,
agreed_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
UNIQUE(user_id, terms_version)
");
// 为其他表添加缺失的列
// 确保 groups 表包含所有必要的列
if (!$this->columnExists('groups', 'group_id')) {
$this->addColumn('groups', 'group_id TEXT');
}
// 确保 messages 表包含所有必要的列
if (!$this->columnExists('messages', 'delivered')) {
$this->addColumn('messages', 'delivered INTEGER DEFAULT 0');
}
// 确保 users 表包含IP字段
if (!$this->columnExists('users', 'last_ip')) {
$this->addColumn('users', 'last_ip TEXT DEFAULT \'\'');
}
// 创建举报表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_reports_message_id ON reports(message_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_reports_reporter_id ON reports(reporter_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_reports_status ON reports(status)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_reports_created_at ON reports(created_at)");
// 添加索引以优化查询性能
// 用户表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_users_online ON users(online)");
// 好友表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_friends_user_id ON friends(user_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_friends_friend_id ON friends(friend_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_friends_status ON friends(status)");
// 群组表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_groups_creator_id ON groups(creator_id)");
// 群成员表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_group_members_group_id ON group_members(group_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_group_members_user_id ON group_members(user_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_group_members_role ON group_members(role)");
// 群公告表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_group_announcements_group_id ON group_announcements(group_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_group_announcements_created_at ON group_announcements(created_at)");
// 消息表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_messages_sender_id ON messages(sender_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_messages_receiver_id ON messages(receiver_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_messages_is_group ON messages(is_group)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_messages_read ON messages(read)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_messages_sender_receiver ON messages(sender_id, receiver_id)");
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_messages_receiver_read ON messages(receiver_id, read)");
// 消息引用表索引
$this->connection->exec("CREATE INDEX IF NOT EXISTS idx_message_quotes_message_id ON message_quotes(message_id)");
}
public function query($sql, $params = []) {
$stmt = $this->connection->prepare($sql);
if (!$stmt) {
return false;
}
foreach ($params as $key => $value) {
$stmt->bindValue(':' . $key, $value);
}
$result = $stmt->execute();
if (!$result) {
return false;
}
$rows = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$rows[] = $row;
}
return $rows;
}
public function execute($sql, $params = []) {
$stmt = $this->connection->prepare($sql);
if (!$stmt) {
return false;
}
foreach ($params as $key => $value) {
$stmt->bindValue(':' . $key, $value);
}
$result = $stmt->execute();
return $result !== false;
}
public function lastInsertRowID() {
return $this->connection->lastInsertRowID();
}
public function close() {
if ($this->connection) {
$this->connection->close();
}
}
}
?>