feat(admin): 添加系统日志功能
## 新功能 1. **系统日志数据库** - 新增 system_logs 表 - 支持日志级别:debug/info/warn/error - 支持日志分类:auth/user/file/share/system/security - 记录用户ID、用户名、IP地址、User-Agent 2. **日志记录** - 用户注册成功/失败 - 用户登录成功/失败(密码错误) - 系统操作(日志清理等) 3. **管理员API** - GET /api/admin/logs - 查询日志(支持分页和筛选) - GET /api/admin/logs/stats - 获取日志统计 - POST /api/admin/logs/cleanup - 清理旧日志 4. **前端界面** - 日志列表展示(时间、级别、分类、内容、用户、IP) - 筛选功能(级别、分类、关键词搜索) - 分页导航 - 清理旧日志功能 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -148,6 +148,32 @@ function initDatabase() {
|
||||
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('数据库初始化完成');
|
||||
}
|
||||
|
||||
@@ -610,6 +636,141 @@ function migrateToV2() {
|
||||
}
|
||||
}
|
||||
|
||||
// 系统日志操作
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
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', '-' || ? || ' 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', '-' || ? || ' days')
|
||||
`).run(keepDays);
|
||||
|
||||
return result.changes;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化数据库
|
||||
initDatabase();
|
||||
createDefaultAdmin();
|
||||
@@ -622,5 +783,6 @@ module.exports = {
|
||||
ShareDB,
|
||||
SettingsDB,
|
||||
VerificationDB,
|
||||
PasswordResetTokenDB
|
||||
PasswordResetTokenDB,
|
||||
SystemLogDB
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user