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:
@@ -20,7 +20,7 @@ const util = require('util');
|
||||
const execAsync = util.promisify(exec);
|
||||
const execFileAsync = util.promisify(execFile);
|
||||
|
||||
const { db, UserDB, ShareDB, SettingsDB, VerificationDB, PasswordResetTokenDB } = require('./database');
|
||||
const { db, UserDB, ShareDB, SettingsDB, VerificationDB, PasswordResetTokenDB, SystemLogDB } = require('./database');
|
||||
const { generateToken, authMiddleware, adminMiddleware, isJwtSecretSecure } = require('./auth');
|
||||
|
||||
const app = express();
|
||||
@@ -359,6 +359,96 @@ function getProtocol(req) {
|
||||
return req.protocol || (req.secure ? 'https' : 'http');
|
||||
}
|
||||
|
||||
// ===== 系统日志工具函数 =====
|
||||
|
||||
// 从请求中提取日志信息
|
||||
function getLogInfoFromReq(req) {
|
||||
return {
|
||||
ipAddress: req.ip || req.socket?.remoteAddress || 'unknown',
|
||||
userAgent: req.get('User-Agent') || 'unknown',
|
||||
userId: req.user?.id || null,
|
||||
username: req.user?.username || null
|
||||
};
|
||||
}
|
||||
|
||||
// 记录认证日志
|
||||
function logAuth(req, action, message, details = null, level = 'info') {
|
||||
const info = getLogInfoFromReq(req);
|
||||
SystemLogDB.log({
|
||||
level,
|
||||
category: 'auth',
|
||||
action,
|
||||
message,
|
||||
...info,
|
||||
details
|
||||
});
|
||||
}
|
||||
|
||||
// 记录用户管理日志
|
||||
function logUser(req, action, message, details = null, level = 'info') {
|
||||
const info = getLogInfoFromReq(req);
|
||||
SystemLogDB.log({
|
||||
level,
|
||||
category: 'user',
|
||||
action,
|
||||
message,
|
||||
...info,
|
||||
details
|
||||
});
|
||||
}
|
||||
|
||||
// 记录文件操作日志
|
||||
function logFile(req, action, message, details = null, level = 'info') {
|
||||
const info = getLogInfoFromReq(req);
|
||||
SystemLogDB.log({
|
||||
level,
|
||||
category: 'file',
|
||||
action,
|
||||
message,
|
||||
...info,
|
||||
details
|
||||
});
|
||||
}
|
||||
|
||||
// 记录分享操作日志
|
||||
function logShare(req, action, message, details = null, level = 'info') {
|
||||
const info = getLogInfoFromReq(req);
|
||||
SystemLogDB.log({
|
||||
level,
|
||||
category: 'share',
|
||||
action,
|
||||
message,
|
||||
...info,
|
||||
details
|
||||
});
|
||||
}
|
||||
|
||||
// 记录系统操作日志
|
||||
function logSystem(req, action, message, details = null, level = 'info') {
|
||||
const info = req ? getLogInfoFromReq(req) : {};
|
||||
SystemLogDB.log({
|
||||
level,
|
||||
category: 'system',
|
||||
action,
|
||||
message,
|
||||
...info,
|
||||
details
|
||||
});
|
||||
}
|
||||
|
||||
// 记录安全事件日志
|
||||
function logSecurity(req, action, message, details = null, level = 'warn') {
|
||||
const info = getLogInfoFromReq(req);
|
||||
SystemLogDB.log({
|
||||
level,
|
||||
category: 'security',
|
||||
action,
|
||||
message,
|
||||
...info,
|
||||
details
|
||||
});
|
||||
}
|
||||
|
||||
// 文件上传配置(临时存储)
|
||||
const upload = multer({
|
||||
dest: path.join(__dirname, 'uploads'),
|
||||
@@ -1148,6 +1238,9 @@ app.post('/api/register',
|
||||
});
|
||||
}
|
||||
|
||||
// 记录注册日志
|
||||
logAuth(req, 'register', `新用户注册: ${username}`, { userId, email });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '注册成功,请查收邮箱完成验证',
|
||||
@@ -1155,6 +1248,7 @@ app.post('/api/register',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error);
|
||||
logAuth(req, 'register_failed', `用户注册失败: ${req.body.username || 'unknown'}`, { error: error.message }, 'error');
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '注册失败: ' + error.message
|
||||
@@ -1448,6 +1542,9 @@ app.post('/api/login',
|
||||
}
|
||||
|
||||
if (!UserDB.verifyPassword(password, user.password)) {
|
||||
// 记录登录失败安全日志
|
||||
logSecurity(req, 'login_failed', `登录失败(密码错误): ${username}`, { userId: user.id });
|
||||
|
||||
// 记录失败尝试
|
||||
if (req.rateLimitKeys) {
|
||||
const result = loginLimiter.recordFailure(req.rateLimitKeys.ipKey);
|
||||
@@ -1487,6 +1584,9 @@ app.post('/api/login',
|
||||
path: '/' // 限制Cookie作用域
|
||||
});
|
||||
|
||||
// 记录登录成功日志
|
||||
logAuth(req, 'login', `用户登录成功: ${user.username}`, { userId: user.id, isAdmin: user.is_admin });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
@@ -1506,6 +1606,7 @@ app.post('/api/login',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
logAuth(req, 'login_error', `登录异常: ${req.body.username || 'unknown'}`, { error: error.message }, 'error');
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '登录失败: ' + error.message
|
||||
@@ -3720,6 +3821,91 @@ app.get('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取系统日志
|
||||
app.get('/api/admin/logs', authMiddleware, adminMiddleware, (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
level,
|
||||
category,
|
||||
userId,
|
||||
startDate,
|
||||
endDate,
|
||||
keyword
|
||||
} = req.query;
|
||||
|
||||
const result = SystemLogDB.query({
|
||||
page: parseInt(page),
|
||||
pageSize: Math.min(parseInt(pageSize) || 50, 200), // 限制最大每页200条
|
||||
level: level || null,
|
||||
category: category || null,
|
||||
userId: userId ? parseInt(userId) : null,
|
||||
startDate: startDate || null,
|
||||
endDate: endDate || null,
|
||||
keyword: keyword || null
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
...result
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取系统日志失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取系统日志失败: ' + error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取日志统计
|
||||
app.get('/api/admin/logs/stats', authMiddleware, adminMiddleware, (req, res) => {
|
||||
try {
|
||||
const categoryStats = SystemLogDB.getStatsByCategory();
|
||||
const dateStats = SystemLogDB.getStatsByDate(7);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
stats: {
|
||||
byCategory: categoryStats,
|
||||
byDate: dateStats
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取日志统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取日志统计失败: ' + error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 清理旧日志
|
||||
app.post('/api/admin/logs/cleanup', authMiddleware, adminMiddleware, (req, res) => {
|
||||
try {
|
||||
const { keepDays = 90 } = req.body;
|
||||
const days = Math.max(7, Math.min(parseInt(keepDays) || 90, 365)); // 最少保留7天,最多365天
|
||||
|
||||
const deletedCount = SystemLogDB.cleanup(days);
|
||||
|
||||
// 记录清理操作
|
||||
logSystem(req, 'logs_cleanup', `管理员清理了 ${deletedCount} 条日志(保留 ${days} 天)`, { deletedCount, keepDays: days });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `已清理 ${deletedCount} 条日志`,
|
||||
deletedCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('清理日志失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '清理日志失败: ' + error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 封禁/解封用户
|
||||
app.post('/api/admin/users/:id/ban', authMiddleware, adminMiddleware, (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user