/** * 自动操作日志中间件 * @file autoOperationLogger.js * @description 自动记录所有API操作日志的中间件 */ const { OperationLog } = require('../models'); /** * 自动操作日志中间件 * 自动记录所有经过认证的API操作 */ const autoOperationLogger = async (req, res, next) => { const startTime = Date.now(); const originalSend = res.send; let responseBody = null; // 拦截响应数据 res.send = function(data) { responseBody = data; return originalSend.call(this, data); }; // 在响应完成后记录日志 res.on('finish', async () => { try { // 只记录经过认证的操作 if (!req.user) { console.log(`[操作日志] 跳过记录 - 无用户信息: ${req.method} ${req.originalUrl}`); return; } const executionTime = Date.now() - startTime; const operationType = getOperationType(req.method, req.originalUrl); if (!operationType) { console.log(`[操作日志] 跳过记录 - 不支持的操作类型: ${req.method} ${req.originalUrl}`); return; // 不记录的操作类型 } console.log(`[操作日志] 开始记录: ${req.user.username} ${operationType} ${req.method} ${req.originalUrl}`); // 获取模块名称 const moduleName = getModuleName(req.originalUrl); // 获取表名 const tableName = getTableName(req.originalUrl); // 获取记录ID const recordId = getRecordId(req); // 生成操作描述 const operationDesc = generateOperationDesc(req, responseBody); // 获取操作前后数据 const { oldData, newData } = await getOperationData(req, res, operationType); // 获取IP地址 const ipAddress = getClientIP(req); // 记录操作日志 await OperationLog.recordOperation({ userId: req.user.id, username: req.user.username, userRole: req.user.role || 'unknown', operationType, moduleName, tableName, recordId, operationDesc, oldData, newData, ipAddress, userAgent: req.get('User-Agent'), requestUrl: req.originalUrl, requestMethod: req.method, responseStatus: res.statusCode, executionTime, errorMessage: res.statusCode >= 400 ? (responseBody?.message || '操作失败') : null }); console.log(`[操作日志] ${req.user.username} ${operationType} ${moduleName} - ${operationDesc}`); } catch (error) { console.error('记录操作日志失败:', error); // 不抛出错误,避免影响主业务 } }); next(); }; /** * 根据HTTP方法和URL获取操作类型 */ const getOperationType = (method, url) => { const methodUpper = method.toUpperCase(); // 特殊URL处理 if (url.includes('/auth/login')) { return 'LOGIN'; } if (url.includes('/auth/logout')) { return 'LOGOUT'; } if (url.includes('/export')) { return 'EXPORT'; } if (url.includes('/import')) { return 'IMPORT'; } if (url.includes('/batch-delete')) { return 'BATCH_DELETE'; } if (url.includes('/batch-update')) { return 'BATCH_UPDATE'; } // 标准HTTP方法映射 switch (methodUpper) { case 'POST': return 'CREATE'; case 'PUT': case 'PATCH': return 'UPDATE'; case 'DELETE': return 'DELETE'; case 'GET': // GET请求记录所有操作 return 'READ'; default: return null; } }; /** * 根据URL获取模块名称 */ const getModuleName = (url) => { const pathSegments = url.split('/').filter(segment => segment); if (pathSegments.length < 2) { return '未知模块'; } const moduleMap = { 'users': '用户管理', 'farms': '农场管理', 'animals': '动物管理', 'devices': '设备管理', 'alerts': '告警管理', 'products': '产品管理', 'orders': '订单管理', 'auth': '认证管理', 'stats': '统计分析', 'map': '地图服务', 'operation-logs': '操作日志', 'roles': '角色管理', 'permissions': '权限管理', 'menus': '菜单管理', 'cattle-batches': '牛只批次管理', 'cattle-pens': '牛舍管理', 'cattle-transfer-records': '牛只转移记录', 'cattle-exit-records': '牛只出栏记录', 'electronic-fences': '电子围栏', 'electronic-fence-points': '围栏点位', 'pens': '圈舍管理' }; const moduleKey = pathSegments[1]; return moduleMap[moduleKey] || moduleKey || '未知模块'; }; /** * 根据URL获取表名 */ const getTableName = (url) => { const pathSegments = url.split('/').filter(segment => segment); if (pathSegments.length < 2) { return 'unknown'; } const tableMap = { 'users': 'users', 'farms': 'farms', 'animals': 'animals', 'devices': 'devices', 'alerts': 'alerts', 'products': 'products', 'orders': 'orders', 'cattle-batches': 'cattle_batches', 'cattle-pens': 'cattle_pens', 'cattle-transfer-records': 'cattle_transfer_records', 'cattle-exit-records': 'cattle_exit_records', 'electronic-fences': 'electronic_fences', 'electronic-fence-points': 'electronic_fence_points', 'pens': 'pens', 'roles': 'roles', 'permissions': 'permissions', 'menus': 'menu_permissions' }; const tableKey = pathSegments[1]; return tableMap[tableKey] || tableKey || 'unknown'; }; /** * 获取记录ID */ const getRecordId = (req) => { // 从URL参数中获取ID const id = req.params.id || req.params.farmId || req.params.animalId || req.params.deviceId || req.params.alertId || req.params.productId || req.params.orderId || req.params.batchId || req.params.penId; return id ? parseInt(id) : null; }; /** * 生成操作描述 */ const generateOperationDesc = (req, responseBody) => { const method = req.method.toUpperCase(); const url = req.originalUrl; const moduleName = getModuleName(url); // 根据操作类型生成描述 switch (method) { case 'POST': if (url.includes('/auth/login')) { return '用户登录'; } if (url.includes('/auth/logout')) { return '用户登出'; } if (url.includes('/export')) { return `导出${moduleName}数据`; } if (url.includes('/import')) { return `导入${moduleName}数据`; } if (url.includes('/batch-delete')) { return `批量删除${moduleName}数据`; } if (url.includes('/batch-update')) { return `批量更新${moduleName}数据`; } return `新增${moduleName}记录`; case 'PUT': case 'PATCH': return `更新${moduleName}记录`; case 'DELETE': if (url.includes('/batch-delete')) { return `批量删除${moduleName}数据`; } return `删除${moduleName}记录`; case 'GET': if (url.includes('/stats') || url.includes('/analytics')) { return `查看${moduleName}统计`; } if (url.includes('/reports')) { return `查看${moduleName}报表`; } // 如果是获取单个记录详情,可能是用于编辑 if (url.match(/\/\d+$/)) { return `获取${moduleName}详情`; } return `查看${moduleName}数据`; default: return `${method}操作${moduleName}`; } }; /** * 获取操作前后数据 */ const getOperationData = async (req, res, operationType) => { let oldData = null; let newData = null; try { // 对于更新和删除操作,尝试获取操作前的数据 if (operationType === 'UPDATE' || operationType === 'DELETE') { // 这里可以根据需要实现获取旧数据的逻辑 // 由于需要查询数据库,暂时返回null oldData = null; } // 对于新增和更新操作,获取操作后的数据 if (operationType === 'CREATE' || operationType === 'UPDATE') { // 从响应体中获取新数据 if (res.responseBody && res.responseBody.data) { newData = res.responseBody.data; } } } catch (error) { console.error('获取操作数据失败:', error); } return { oldData, newData }; }; /** * 获取客户端IP地址 */ const getClientIP = (req) => { return req.ip || req.connection.remoteAddress || req.socket.remoteAddress || (req.connection.socket ? req.connection.socket.remoteAddress : null) || req.headers['x-forwarded-for']?.split(',')[0]?.trim() || 'unknown'; }; module.exports = { autoOperationLogger };