const Database = require('better-sqlite3'); const bcrypt = require('bcryptjs'); const path = require('path'); // 创建或连接数据库 const db = new Database(path.join(__dirname, 'ftp-manager.db')); // 启用外键约束 db.pragma('foreign_keys = ON'); // 初始化数据库表 function initDatabase() { // 用户表 db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, password TEXT NOT NULL, -- FTP配置(可选) ftp_host TEXT, ftp_port INTEGER DEFAULT 22, ftp_user TEXT, ftp_password TEXT, http_download_base_url TEXT, -- 上传工具API密钥 upload_api_key TEXT, -- 用户状态 is_admin INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, is_banned INTEGER DEFAULT 0, has_ftp_config INTEGER DEFAULT 0, -- 时间戳 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // 分享链接表 db.exec(` CREATE TABLE IF NOT EXISTS shares ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, share_code TEXT UNIQUE NOT NULL, share_path TEXT NOT NULL, share_type TEXT DEFAULT 'file', share_password TEXT, -- 分享统计 view_count INTEGER DEFAULT 0, download_count INTEGER DEFAULT 0, -- 时间戳 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ) `); // 系统设置表 db.exec(` CREATE TABLE IF NOT EXISTS system_settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // 密码重置请求表 db.exec(` CREATE TABLE IF NOT EXISTS password_reset_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, new_password TEXT NOT NULL, status TEXT DEFAULT 'pending', -- pending, approved, rejected created_at DATETIME DEFAULT CURRENT_TIMESTAMP, reviewed_at DATETIME, reviewed_by INTEGER, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, FOREIGN KEY (reviewed_by) REFERENCES users (id) ) `); // 创建索引 db.exec(` CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_shares_code ON shares(share_code); CREATE INDEX IF NOT EXISTS idx_shares_user ON shares(user_id); CREATE INDEX IF NOT EXISTS idx_reset_requests_user ON password_reset_requests(user_id); CREATE INDEX IF NOT EXISTS idx_reset_requests_status ON password_reset_requests(status); `); // 数据库迁移:添加upload_api_key字段(如果不存在) try { const columns = db.prepare("PRAGMA table_info(users)").all(); const hasUploadApiKey = columns.some(col => col.name === 'upload_api_key'); if (!hasUploadApiKey) { db.exec(`ALTER TABLE users ADD COLUMN upload_api_key TEXT`); console.log('数据库迁移:添加upload_api_key字段完成'); } } catch (error) { console.error('数据库迁移失败:', error); } // 数据库迁移:添加share_type字段(如果不存在) try { const shareColumns = db.prepare("PRAGMA table_info(shares)").all(); const hasShareType = shareColumns.some(col => col.name === 'share_type'); if (!hasShareType) { db.exec(`ALTER TABLE shares ADD COLUMN share_type TEXT DEFAULT 'file'`); console.log('数据库迁移:添加share_type字段完成'); } } catch (error) { console.error('数据库迁移(share_type)失败:', error); } console.log('数据库初始化完成'); } // 创建默认管理员账号 function createDefaultAdmin() { const adminExists = db.prepare('SELECT id FROM users WHERE is_admin = 1').get(); if (!adminExists) { // 从环境变量读取管理员账号密码,如果没有则使用默认值 const adminUsername = process.env.ADMIN_USERNAME || 'admin'; const adminPassword = process.env.ADMIN_PASSWORD || 'admin123'; const hashedPassword = bcrypt.hashSync(adminPassword, 10); db.prepare(` INSERT INTO users ( username, email, password, is_admin, is_active, has_ftp_config ) VALUES (?, ?, ?, ?, ?, ?) `).run( adminUsername, `${adminUsername}@example.com`, hashedPassword, 1, 1, 0 // 管理员不需要FTP配置 ); console.log('默认管理员账号已创建'); console.log('用户名:', adminUsername); console.log('密码: ********'); console.log('⚠️ 请登录后立即修改密码!'); } } // 用户相关操作 const UserDB = { // 创建用户 create(userData) { const hashedPassword = bcrypt.hashSync(userData.password, 10); const hasFtpConfig = userData.ftp_host && userData.ftp_user && userData.ftp_password ? 1 : 0; const stmt = db.prepare(` INSERT INTO users ( username, email, password, ftp_host, ftp_port, ftp_user, ftp_password, http_download_base_url, has_ftp_config ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); const result = stmt.run( userData.username, userData.email, hashedPassword, userData.ftp_host || null, userData.ftp_port || 22, userData.ftp_user || null, userData.ftp_password || null, userData.http_download_base_url || null, hasFtpConfig ); return result.lastInsertRowid; }, // 根据用户名查找 findByUsername(username) { return db.prepare('SELECT * FROM users WHERE username = ?').get(username); }, // 根据邮箱查找 findByEmail(email) { return db.prepare('SELECT * FROM users WHERE email = ?').get(email); }, // 根据ID查找 findById(id) { return db.prepare('SELECT * FROM users WHERE id = ?').get(id); }, // 验证密码 verifyPassword(plainPassword, hashedPassword) { return bcrypt.compareSync(plainPassword, hashedPassword); }, // 更新用户 update(id, updates) { const fields = []; const values = []; for (const [key, value] of Object.entries(updates)) { if (key === 'password') { fields.push(`${key} = ?`); values.push(bcrypt.hashSync(value, 10)); } else { fields.push(`${key} = ?`); values.push(value); } } fields.push('updated_at = CURRENT_TIMESTAMP'); values.push(id); const stmt = db.prepare(`UPDATE users SET ${fields.join(', ')} WHERE id = ?`); return stmt.run(...values); }, // 获取所有用户 getAll(filters = {}) { let query = 'SELECT * FROM users WHERE 1=1'; const params = []; if (filters.is_admin !== undefined) { query += ' AND is_admin = ?'; params.push(filters.is_admin); } if (filters.is_banned !== undefined) { query += ' AND is_banned = ?'; params.push(filters.is_banned); } query += ' ORDER BY created_at DESC'; return db.prepare(query).all(...params); }, // 删除用户 delete(id) { return db.prepare('DELETE FROM users WHERE id = ?').run(id); }, // 封禁/解封用户 setBanStatus(id, isBanned) { return db.prepare('UPDATE users SET is_banned = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?') .run(isBanned ? 1 : 0, id); } }; // 分享链接相关操作 const ShareDB = { // 生成随机分享码 generateShareCode(length = 8) { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let code = ''; for (let i = 0; i < length; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); } return code; }, // 创建分享链接 // 创建分享链接 create(userId, options = {}) { const { share_type = 'file', file_path = '', file_name = '', password = null, expiry_days = null } = options; let shareCode; let attempts = 0; // 尝试生成唯一的分享码 do { shareCode = this.generateShareCode(); attempts++; if (attempts > 10) { shareCode = this.generateShareCode(10); // 增加长度 } } while (this.findByCode(shareCode) && attempts < 20); // 计算过期时间 let expiresAt = null; if (expiry_days) { const expireDate = new Date(); expireDate.setDate(expireDate.getDate() + parseInt(expiry_days)); expiresAt = expireDate.toISOString(); } const stmt = db.prepare(` INSERT INTO shares (user_id, share_code, share_path, share_type, share_password, expires_at) VALUES (?, ?, ?, ?, ?, ?) `); const hashedPassword = password ? bcrypt.hashSync(password, 10) : null; const sharePath = share_type === 'file' ? file_path : '/'; const result = stmt.run( userId, shareCode, sharePath, share_type, hashedPassword, expiresAt ); return { id: result.lastInsertRowid, share_code: shareCode, share_type: share_type, expires_at: expiresAt, }; }, // 根据分享码查找 findByCode(shareCode) { // 调试日志: findByCode 调用 const currentTime = db.prepare("SELECT datetime('now') as now").get(); console.log('[ShareDB.findByCode]', { shareCode, currentTime: currentTime.now, timestamp: new Date().toISOString() }); const result = db.prepare(` SELECT s.*, u.username, u.ftp_host, u.ftp_port, u.ftp_user, u.ftp_password, u.http_download_base_url FROM shares s JOIN users u ON s.user_id = u.id WHERE s.share_code = ? AND (s.expires_at IS NULL OR s.expires_at > datetime('now')) `).get(shareCode); // 调试日志: SQL查询结果 console.log('[ShareDB.findByCode] SQL结果:', { found: !!result, shareCode: result?.share_code || null, expires_at: result?.expires_at || null, share_type: result?.share_type || null }); return result; }, // 根据ID查找 findById(id) { return db.prepare('SELECT * FROM shares WHERE id = ?').get(id); }, // 验证分享密码 verifyPassword(plainPassword, hashedPassword) { return bcrypt.compareSync(plainPassword, hashedPassword); }, // 获取用户的所有分享 getUserShares(userId) { return db.prepare(` SELECT * FROM shares WHERE user_id = ? ORDER BY created_at DESC `).all(userId); }, // 增加查看次数 incrementViewCount(shareCode) { return db.prepare(` UPDATE shares SET view_count = view_count + 1 WHERE share_code = ? `).run(shareCode); }, // 增加下载次数 incrementDownloadCount(shareCode) { return db.prepare(` UPDATE shares SET download_count = download_count + 1 WHERE share_code = ? `).run(shareCode); }, // 删除分享 delete(id, userId = null) { if (userId) { return db.prepare('DELETE FROM shares WHERE id = ? AND user_id = ?').run(id, userId); } return db.prepare('DELETE FROM shares WHERE id = ?').run(id); }, // 获取所有分享(管理员) getAll() { return db.prepare(` SELECT s.*, u.username FROM shares s JOIN users u ON s.user_id = u.id ORDER BY s.created_at DESC `).all(); } }; // 系统设置管理 const SettingsDB = { // 获取设置 get(key) { const row = db.prepare('SELECT value FROM system_settings WHERE key = ?').get(key); return row ? row.value : null; }, // 设置值 set(key, value) { db.prepare(` INSERT INTO system_settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP `).run(key, value); }, // 获取所有设置 getAll() { return db.prepare('SELECT key, value FROM system_settings').all(); } }; // 密码重置请求管理 const PasswordResetDB = { // 创建密码重置请求 create(userId, newPassword) { const hashedPassword = bcrypt.hashSync(newPassword, 10); // 删除该用户之前的pending请求 db.prepare('DELETE FROM password_reset_requests WHERE user_id = ? AND status = ?') .run(userId, 'pending'); const stmt = db.prepare(` INSERT INTO password_reset_requests (user_id, new_password, status) VALUES (?, ?, 'pending') `); const result = stmt.run(userId, hashedPassword); return result.lastInsertRowid; }, // 获取待审核的请求 getPending() { return db.prepare(` SELECT r.*, u.username, u.email FROM password_reset_requests r JOIN users u ON r.user_id = u.id WHERE r.status = 'pending' ORDER BY r.created_at DESC `).all(); }, // 审核请求(批准或拒绝) review(requestId, adminId, approved) { const request = db.prepare('SELECT * FROM password_reset_requests WHERE id = ?').get(requestId); if (!request || request.status !== 'pending') { throw new Error('请求不存在或已被处理'); } const newStatus = approved ? 'approved' : 'rejected'; db.prepare(` UPDATE password_reset_requests SET status = ?, reviewed_at = CURRENT_TIMESTAMP, reviewed_by = ? WHERE id = ? `).run(newStatus, adminId, requestId); // 如果批准,更新用户密码 if (approved) { db.prepare('UPDATE users SET password = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?') .run(request.new_password, request.user_id); } return true; }, // 获取用户的所有请求 getUserRequests(userId) { return db.prepare(` SELECT * FROM password_reset_requests WHERE user_id = ? ORDER BY created_at DESC `).all(userId); }, // 检查用户是否有待处理的请求 hasPendingRequest(userId) { const request = db.prepare(` SELECT id FROM password_reset_requests WHERE user_id = ? AND status = 'pending' `).get(userId); return !!request; } }; // 初始化默认设置 function initDefaultSettings() { // 默认上传限制为10GB if (!SettingsDB.get('max_upload_size')) { SettingsDB.set('max_upload_size', '10737418240'); // 10GB in bytes } } // 数据库版本迁移 - v2.0 本地存储功能 function migrateToV2() { try { const columns = db.prepare("PRAGMA table_info(users)").all(); const hasStoragePermission = columns.some(col => col.name === 'storage_permission'); if (!hasStoragePermission) { console.log('[数据库迁移] 检测到旧版本数据库,开始升级到 v2.0...'); // 添加本地存储相关字段 db.exec(` ALTER TABLE users ADD COLUMN storage_permission TEXT DEFAULT 'sftp_only'; ALTER TABLE users ADD COLUMN current_storage_type TEXT DEFAULT 'sftp'; ALTER TABLE users ADD COLUMN local_storage_quota INTEGER DEFAULT 1073741824; ALTER TABLE users ADD COLUMN local_storage_used INTEGER DEFAULT 0; `); // 更新现有用户为SFTP模式(保持兼容) const updateStmt = db.prepare("UPDATE users SET current_storage_type = 'sftp' WHERE has_ftp_config = 1"); updateStmt.run(); console.log('[数据库迁移] ✓ 用户表已升级'); // 为分享表添加存储类型字段 const shareColumns = db.prepare("PRAGMA table_info(shares)").all(); const hasShareStorageType = shareColumns.some(col => col.name === 'storage_type'); if (!hasShareStorageType) { db.exec(`ALTER TABLE shares ADD COLUMN storage_type TEXT DEFAULT 'sftp';`); console.log('[数据库迁移] ✓ 分享表已升级'); } console.log('[数据库迁移] ✅ 数据库升级到 v2.0 完成!本地存储功能已启用'); } } catch (error) { console.error('[数据库迁移] 迁移失败:', error); throw error; } } // 初始化数据库 initDatabase(); createDefaultAdmin(); initDefaultSettings(); migrateToV2(); // 执行数据库迁移 module.exports = { db, UserDB, ShareDB, SettingsDB, PasswordResetDB };