/** * 操作日志控制器 * @file operationLogController.js * @description 处理操作日志相关的API请求 */ const { OperationLog, User } = require('../models'); const { validationResult } = require('express-validator'); const { Op } = require('sequelize'); /** * 获取操作日志列表 * @param {Object} req 请求对象 * @param {Object} res 响应对象 */ const getOperationLogs = async (req, res) => { try { const { page = 1, pageSize = 20, userId, username, operationType, moduleName, tableName, startDate, endDate, sortBy = 'created_at', sortOrder = 'DESC' } = req.query; // 构建查询条件 const whereClause = {}; if (userId) { whereClause.user_id = userId; } if (username) { whereClause.username = { [Op.like]: `%${username}%` }; } if (operationType) { whereClause.operation_type = operationType; } if (moduleName) { whereClause.module_name = { [Op.like]: `%${moduleName}%` }; } if (tableName) { whereClause.table_name = tableName; } if (startDate && endDate) { whereClause.created_at = { [Op.between]: [startDate, endDate] }; } // 构建排序条件 const orderClause = [[sortBy, sortOrder.toUpperCase()]]; // 执行分页查询 const result = await OperationLog.paginate( { where: whereClause, order: orderClause, include: [ { model: User, as: 'user', attributes: ['id', 'username', 'email', 'roles'] } ] }, parseInt(page), parseInt(pageSize) ); res.json({ success: true, data: result.data, pagination: result.pagination, message: '获取操作日志列表成功' }); } catch (error) { console.error('获取操作日志列表失败:', error); res.status(500).json({ success: false, message: '获取操作日志列表失败', error: error.message }); } }; /** * 获取操作日志详情 * @param {Object} req 请求对象 * @param {Object} res 响应对象 */ const getOperationLogById = async (req, res) => { try { const { id } = req.params; const operationLog = await OperationLog.findByPk(id, { include: [ { model: User, as: 'user', attributes: ['id', 'username', 'email', 'roles'] } ] }); if (!operationLog) { return res.status(404).json({ success: false, message: '操作日志不存在' }); } res.json({ success: true, data: operationLog, message: '获取操作日志详情成功' }); } catch (error) { console.error('获取操作日志详情失败:', error); res.status(500).json({ success: false, message: '获取操作日志详情失败', error: error.message }); } }; /** * 获取操作统计 * @param {Object} req 请求对象 * @param {Object} res 响应对象 */ const getOperationStats = async (req, res) => { try { const { type, userId, moduleName, startDate, endDate } = req.query; let stats = {}; if (type === 'user' && userId) { stats = await OperationLog.getUserOperationStats(userId, startDate, endDate); } else if (type === 'module' && moduleName) { stats = await OperationLog.getModuleOperationStats(moduleName, startDate, endDate); } else { // 获取总体统计 const whereClause = {}; if (startDate && endDate) { whereClause.created_at = { [Op.between]: [startDate, endDate] }; } const result = await OperationLog.findAll({ where: whereClause, attributes: [ 'operation_type', [OperationLog.sequelize.fn('COUNT', OperationLog.sequelize.col('id')), 'count'] ], group: ['operation_type'], raw: true }); stats = result.reduce((acc, stat) => { acc[stat.operation_type] = parseInt(stat.count); return acc; }, {}); } res.json({ success: true, data: stats, message: '获取操作统计成功' }); } catch (error) { console.error('获取操作统计失败:', error); res.status(500).json({ success: false, message: '获取操作统计失败', error: error.message }); } }; /** * 获取操作日志图表数据 * @param {Object} req 请求对象 * @param {Object} res 响应对象 */ const getOperationChartData = async (req, res) => { try { const { type = 'daily', startDate, endDate } = req.query; let dateFormat, groupBy; switch (type) { case 'hourly': dateFormat = '%Y-%m-%d %H:00:00'; groupBy = 'DATE_FORMAT(created_at, "%Y-%m-%d %H:00:00")'; break; case 'daily': dateFormat = '%Y-%m-%d'; groupBy = 'DATE(created_at)'; break; case 'monthly': dateFormat = '%Y-%m'; groupBy = 'DATE_FORMAT(created_at, "%Y-%m")'; break; default: dateFormat = '%Y-%m-%d'; groupBy = 'DATE(created_at)'; } const whereClause = {}; if (startDate && endDate) { whereClause.created_at = { [Op.between]: [startDate, endDate] }; } const result = await OperationLog.findAll({ where: whereClause, attributes: [ [OperationLog.sequelize.fn('DATE_FORMAT', OperationLog.sequelize.col('created_at'), dateFormat), 'date'], 'operation_type', [OperationLog.sequelize.fn('COUNT', OperationLog.sequelize.col('id')), 'count'] ], group: ['date', 'operation_type'], order: [['date', 'ASC']], raw: true }); // 处理图表数据 const chartData = { dates: [], series: { CREATE: [], UPDATE: [], DELETE: [] } }; const dateSet = new Set(); result.forEach(item => { dateSet.add(item.date); }); chartData.dates = Array.from(dateSet).sort(); chartData.dates.forEach(date => { const createItem = result.find(item => item.date === date && item.operation_type === 'CREATE'); const updateItem = result.find(item => item.date === date && item.operation_type === 'UPDATE'); const deleteItem = result.find(item => item.date === date && item.operation_type === 'DELETE'); chartData.series.CREATE.push(createItem ? parseInt(createItem.count) : 0); chartData.series.UPDATE.push(updateItem ? parseInt(updateItem.count) : 0); chartData.series.DELETE.push(deleteItem ? parseInt(deleteItem.count) : 0); }); res.json({ success: true, data: chartData, message: '获取操作日志图表数据成功' }); } catch (error) { console.error('获取操作日志图表数据失败:', error); res.status(500).json({ success: false, message: '获取操作日志图表数据失败', error: error.message }); } }; /** * 清理过期日志 * @param {Object} req 请求对象 * @param {Object} res 响应对象 */ const cleanExpiredLogs = async (req, res) => { try { const { daysToKeep = 90 } = req.body; const deletedCount = await OperationLog.cleanExpiredLogs(daysToKeep); res.json({ success: true, data: { deletedCount }, message: `成功清理了 ${deletedCount} 条过期日志` }); } catch (error) { console.error('清理过期日志失败:', error); res.status(500).json({ success: false, message: '清理过期日志失败', error: error.message }); } }; /** * 导出操作日志 * @param {Object} req 请求对象 * @param {Object} res 响应对象 */ const exportOperationLogs = async (req, res) => { try { const { userId, username, operationType, moduleName, tableName, startDate, endDate } = req.query; // 构建查询条件 const whereClause = {}; if (userId) { whereClause.user_id = userId; } if (username) { whereClause.username = { [Op.like]: `%${username}%` }; } if (operationType) { whereClause.operation_type = operationType; } if (moduleName) { whereClause.module_name = { [Op.like]: `%${moduleName}%` }; } if (tableName) { whereClause.table_name = tableName; } if (startDate && endDate) { whereClause.created_at = { [Op.between]: [startDate, endDate] }; } const logs = await OperationLog.findAll({ where: whereClause, order: [['created_at', 'DESC']], include: [ { model: User, as: 'user', attributes: ['id', 'username', 'email', 'roles'] } ] }); // 转换为CSV格式 const csvHeaders = [ 'ID', '操作用户', '用户角色', '操作类型', '模块名称', '数据表名', '记录ID', '操作描述', 'IP地址', '请求URL', '请求方法', '响应状态', '执行时间(ms)', '创建时间' ]; const csvRows = logs.map(log => [ log.id, log.username, log.user_role, log.operation_type, log.module_name, log.table_name, log.record_id || '', log.operation_desc, log.ip_address || '', log.request_url || '', log.request_method || '', log.response_status || '', log.execution_time || '', log.created_at ]); const csvContent = [csvHeaders, ...csvRows] .map(row => row.map(field => `"${field}"`).join(',')) .join('\n'); res.setHeader('Content-Type', 'text/csv; charset=utf-8'); res.setHeader('Content-Disposition', 'attachment; filename=operation_logs.csv'); res.send('\ufeff' + csvContent); // 添加BOM以支持中文 } catch (error) { console.error('导出操作日志失败:', error); res.status(500).json({ success: false, message: '导出操作日志失败', error: error.message }); } }; module.exports = { getOperationLogs, getOperationLogById, getOperationStats, getOperationChartData, cleanExpiredLogs, exportOperationLogs };