Files
nxxmdata/backend/middleware/autoOperationLogger.js
2025-11-17 09:18:31 +08:00

321 lines
8.8 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.

/**
* 自动操作日志中间件
* @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
};