Files
vue-driven-cloud-storage/backend/database.js
yuyx accccf261d 🕐 修复系统日志时区问题
- 日志写入时使用 datetime('now', 'localtime') 替代 CURRENT_TIMESTAMP
- 日志统计和清理查询也使用本地时间
- 解决日志时间比实际时间慢8小时的问题(UTC → 北京时间)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 22:20:33 +08:00

811 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 加载环境变量(确保在 server.js 之前也能读取)
require('dotenv').config();
const Database = require('better-sqlite3');
const bcrypt = require('bcryptjs');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
// 数据库路径配置
// 优先使用环境变量 DATABASE_PATH默认为 ./data/database.db
const DEFAULT_DB_PATH = path.join(__dirname, 'data', 'database.db');
const dbPath = process.env.DATABASE_PATH
? path.resolve(__dirname, process.env.DATABASE_PATH)
: DEFAULT_DB_PATH;
// 确保数据库目录存在
const dbDir = path.dirname(dbPath);
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true });
console.log(`[数据库] 创建目录: ${dbDir}`);
}
console.log(`[数据库] 路径: ${dbPath}`);
// 创建或连接数据库
const db = new Database(dbPath);
// 启用外键约束
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 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);
`);
// 数据库迁移添加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);
}
// 数据库迁移:邮箱验证字段
try {
const columns = db.prepare("PRAGMA table_info(users)").all();
const hasVerified = columns.some(col => col.name === 'is_verified');
const hasVerifyToken = columns.some(col => col.name === 'verification_token');
const hasVerifyExpires = columns.some(col => col.name === 'verification_expires_at');
if (!hasVerified) {
db.exec(`ALTER TABLE users ADD COLUMN is_verified INTEGER DEFAULT 0`);
}
if (!hasVerifyToken) {
db.exec(`ALTER TABLE users ADD COLUMN verification_token TEXT`);
}
if (!hasVerifyExpires) {
db.exec(`ALTER TABLE users ADD COLUMN verification_expires_at DATETIME`);
}
// 注意:不再自动将未验证用户设为已验证
// 仅修复 is_verified 为 NULL 的旧数据(添加字段前创建的用户)
// 这些用户没有 verification_token说明是在邮箱验证功能上线前注册的
db.exec(`UPDATE users SET is_verified = 1 WHERE is_verified IS NULL AND verification_token IS NULL`);
} catch (error) {
console.error('数据库迁移(邮箱验证)失败:', error);
}
// 数据库迁移密码重置Token表
try {
db.exec(`
CREATE TABLE IF NOT EXISTS password_reset_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
token TEXT UNIQUE NOT NULL,
expires_at DATETIME NOT NULL,
used INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_reset_tokens_token ON password_reset_tokens(token);`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_reset_tokens_user ON password_reset_tokens(user_id);`);
} catch (error) {
console.error('数据库迁移密码重置Token失败:', error);
}
// 系统日志表
db.exec(`
CREATE TABLE IF NOT EXISTS system_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
level TEXT NOT NULL DEFAULT 'info',
category TEXT NOT NULL,
action TEXT NOT NULL,
message TEXT NOT NULL,
user_id INTEGER,
username TEXT,
ip_address TEXT,
user_agent TEXT,
details TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
)
`);
// 日志表索引
db.exec(`
CREATE INDEX IF NOT EXISTS idx_logs_created_at ON system_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_logs_category ON system_logs(category);
CREATE INDEX IF NOT EXISTS idx_logs_level ON system_logs(level);
CREATE INDEX IF NOT EXISTS idx_logs_user_id ON system_logs(user_id);
`);
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, is_verified
) VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(
adminUsername,
`${adminUsername}@example.com`,
hashedPassword,
1,
1,
0, // 管理员不需要FTP配置
1 // 管理员默认已验证
);
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;
// 对验证令牌进行哈希存储(与 VerificationDB.setVerification 保持一致)
const hashedVerificationToken = userData.verification_token
? crypto.createHash('sha256').update(userData.verification_token).digest('hex')
: null;
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,
is_verified, verification_token, verification_expires_at
) 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,
userData.is_verified !== undefined ? userData.is_verified : 0,
hashedVerificationToken,
userData.verification_expires_at || null
);
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';
const bytes = crypto.randomBytes(length);
let code = '';
for (let i = 0; i < length; i++) {
code += chars[bytes[i] % 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));
// 使用本地时区时间,而不是UTC时间
// 这样前端解析时会正确显示为本地时间
const year = expireDate.getFullYear();
const month = String(expireDate.getMonth() + 1).padStart(2, '0');
const day = String(expireDate.getDate()).padStart(2, '0');
const hours = String(expireDate.getHours()).padStart(2, '0');
const minutes = String(expireDate.getMinutes()).padStart(2, '0');
const seconds = String(expireDate.getSeconds()).padStart(2, '0');
expiresAt = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
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;
// 修复:正确处理不同类型的分享路径
let sharePath;
if (share_type === 'file') {
// 单文件分享:使用完整文件路径
sharePath = file_path;
} else if (share_type === 'directory') {
// 文件夹分享:使用文件夹路径
sharePath = file_path;
} else {
// all类型分享根目录
sharePath = '/';
}
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', 'localtime') 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', 'localtime'))
`).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 VerificationDB = {
setVerification(userId, token, expiresAtMs) {
// 对令牌进行哈希后存储
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
db.prepare(`
UPDATE users
SET verification_token = ?, verification_expires_at = ?, is_verified = 0, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(hashedToken, expiresAtMs, userId);
},
consumeVerificationToken(token) {
// 对用户提供的令牌进行哈希
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
const row = db.prepare(`
SELECT * FROM users
WHERE verification_token = ?
AND (
verification_expires_at IS NULL
OR verification_expires_at = ''
OR verification_expires_at > strftime('%s','now')*1000 -- 数值时间戳ms
OR verification_expires_at > CURRENT_TIMESTAMP -- 兼容旧的字符串时间
)
AND is_verified = 0
`).get(hashedToken);
if (!row) return null;
db.prepare(`
UPDATE users
SET is_verified = 1, verification_token = NULL, verification_expires_at = NULL, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(row.id);
return row;
}
};
// 密码重置 Token 管理(增强安全:哈希存储)
const PasswordResetTokenDB = {
// 创建令牌时存储哈希值
create(userId, token, expiresAtMs) {
// 对令牌进行哈希后存储(防止数据库泄露时令牌被直接使用)
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
db.prepare(`
INSERT INTO password_reset_tokens (user_id, token, expires_at, used)
VALUES (?, ?, ?, 0)
`).run(userId, hashedToken, expiresAtMs);
},
// 验证令牌时先哈希再比较
use(token) {
// 对用户提供的令牌进行哈希
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
const row = db.prepare(`
SELECT * FROM password_reset_tokens
WHERE token = ? AND used = 0 AND (
expires_at > strftime('%s','now')*1000 -- 数值时间戳
OR expires_at > CURRENT_TIMESTAMP -- 兼容旧的字符串时间
)
`).get(hashedToken);
if (!row) return null;
// 立即标记为已使用(防止重复使用)
db.prepare(`UPDATE password_reset_tokens SET used = 1 WHERE id = ?`).run(row.id);
return row;
}
};
// 初始化默认设置
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;
}
}
// 系统日志操作
const SystemLogDB = {
// 日志级别常量
LEVELS: {
DEBUG: 'debug',
INFO: 'info',
WARN: 'warn',
ERROR: 'error'
},
// 日志分类常量
CATEGORIES: {
AUTH: 'auth', // 认证相关(登录、登出、注册)
USER: 'user', // 用户管理(创建、修改、删除、封禁)
FILE: 'file', // 文件操作(上传、下载、删除、重命名)
SHARE: 'share', // 分享操作(创建、删除、访问)
SYSTEM: 'system', // 系统操作(设置修改、服务启动)
SECURITY: 'security' // 安全事件(登录失败、暴力破解、异常访问)
},
// 写入日志
log({ level = 'info', category, action, message, userId = null, username = null, ipAddress = null, userAgent = null, details = null }) {
try {
const stmt = db.prepare(`
INSERT INTO system_logs (level, category, action, message, user_id, username, ip_address, user_agent, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now', 'localtime'))
`);
const detailsStr = details ? (typeof details === 'string' ? details : JSON.stringify(details)) : null;
stmt.run(level, category, action, message, userId, username, ipAddress, userAgent, detailsStr);
} catch (error) {
console.error('写入日志失败:', error);
}
},
// 查询日志(支持分页和筛选)
query({ page = 1, pageSize = 50, level = null, category = null, userId = null, startDate = null, endDate = null, keyword = null }) {
let sql = 'SELECT * FROM system_logs WHERE 1=1';
let countSql = 'SELECT COUNT(*) as total FROM system_logs WHERE 1=1';
const params = [];
if (level) {
sql += ' AND level = ?';
countSql += ' AND level = ?';
params.push(level);
}
if (category) {
sql += ' AND category = ?';
countSql += ' AND category = ?';
params.push(category);
}
if (userId) {
sql += ' AND user_id = ?';
countSql += ' AND user_id = ?';
params.push(userId);
}
if (startDate) {
sql += ' AND created_at >= ?';
countSql += ' AND created_at >= ?';
params.push(startDate);
}
if (endDate) {
sql += ' AND created_at <= ?';
countSql += ' AND created_at <= ?';
params.push(endDate);
}
if (keyword) {
sql += ' AND (message LIKE ? OR username LIKE ? OR action LIKE ?)';
countSql += ' AND (message LIKE ? OR username LIKE ? OR action LIKE ?)';
const kw = `%${keyword}%`;
params.push(kw, kw, kw);
}
// 获取总数
const totalResult = db.prepare(countSql).get(...params);
const total = totalResult ? totalResult.total : 0;
// 分页查询
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
const offset = (page - 1) * pageSize;
const logs = db.prepare(sql).all(...params, pageSize, offset);
return {
logs,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
},
// 获取最近的日志
getRecent(limit = 100) {
return db.prepare('SELECT * FROM system_logs ORDER BY created_at DESC LIMIT ?').all(limit);
},
// 按分类统计
getStatsByCategory() {
return db.prepare(`
SELECT category, COUNT(*) as count
FROM system_logs
GROUP BY category
ORDER BY count DESC
`).all();
},
// 按日期统计最近7天
getStatsByDate(days = 7) {
return db.prepare(`
SELECT DATE(created_at) as date, COUNT(*) as count
FROM system_logs
WHERE created_at >= datetime('now', 'localtime', '-' || ? || ' days')
GROUP BY DATE(created_at)
ORDER BY date DESC
`).all(days);
},
// 清理旧日志(保留指定天数)
cleanup(keepDays = 90) {
const result = db.prepare(`
DELETE FROM system_logs
WHERE created_at < datetime('now', 'localtime', '-' || ? || ' days')
`).run(keepDays);
return result.changes;
}
};
// 初始化数据库
initDatabase();
createDefaultAdmin();
initDefaultSettings();
migrateToV2(); // 执行数据库迁移
module.exports = {
db,
UserDB,
ShareDB,
SettingsDB,
VerificationDB,
PasswordResetTokenDB,
SystemLogDB
};