Files
nxxmdata/backend/middleware/operationLogger.js

217 lines
6.1 KiB
JavaScript
Raw Normal View History

2025-09-12 20:08:42 +08:00
/**
* 操作日志中间件
* @file operationLogger.js
* @description 自动记录用户操作的中间件
*/
const { OperationLog } = require('../models');
/**
* 操作日志中间件
* @param {Object} options 配置选项
* @param {string} options.moduleName 模块名称
* @param {string} options.tableName 数据表名
* @param {Function} options.getRecordId 获取记录ID的函数
* @param {Function} options.getOperationDesc 获取操作描述的函数
* @param {Function} options.getOldData 获取操作前数据的函数可选
* @param {Function} options.getNewData 获取操作后数据的函数可选
* @returns {Function} 中间件函数
*/
const operationLogger = (options = {}) => {
const {
moduleName,
tableName,
getRecordId = () => null,
getOperationDesc = () => '未知操作',
getOldData = () => null,
getNewData = () => null
} = options;
return 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 {
const executionTime = Date.now() - startTime;
const operationType = getOperationType(req.method);
if (!operationType) {
return; // 只记录增删改操作
}
const recordId = getRecordId(req, res);
const operationDesc = getOperationDesc(req, res);
const oldData = getOldData(req, res);
const newData = getNewData(req, res);
// 获取用户信息
const user = req.user;
if (!user) {
return; // 没有用户信息不记录日志
}
// 获取IP地址
const ipAddress = 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';
// 记录操作日志
await OperationLog.recordOperation({
userId: user.id,
username: user.username,
userRole: user.roles,
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
});
} catch (error) {
console.error('记录操作日志失败:', error);
// 不抛出错误,避免影响主业务
}
});
next();
};
};
/**
* 根据HTTP方法获取操作类型
* @param {string} method HTTP方法
* @returns {string|null} 操作类型
*/
const getOperationType = (method) => {
switch (method.toUpperCase()) {
case 'POST':
return 'CREATE';
case 'PUT':
case 'PATCH':
return 'UPDATE';
case 'DELETE':
return 'DELETE';
default:
return null;
}
};
/**
* 创建操作日志记录器
* @param {Object} options 配置选项
* @returns {Function} 中间件函数
*/
const createOperationLogger = (options) => {
return operationLogger(options);
};
/**
* 批量操作日志记录器
* @param {Array} operations 操作配置数组
* @returns {Function} 中间件函数
*/
const createBatchOperationLogger = (operations) => {
return 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 {
const executionTime = Date.now() - startTime;
const operationType = getOperationType(req.method);
if (!operationType) {
return;
}
const user = req.user;
if (!user) {
return;
}
const ipAddress = 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';
// 为每个操作配置记录日志
for (const operation of operations) {
const {
moduleName,
tableName,
getRecordId = () => null,
getOperationDesc = () => '未知操作',
getOldData = () => null,
getNewData = () => null
} = operation;
const recordId = getRecordId(req, res);
const operationDesc = getOperationDesc(req, res);
const oldData = getOldData(req, res);
const newData = getNewData(req, res);
await OperationLog.recordOperation({
userId: user.id,
username: user.username,
userRole: user.roles,
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
});
}
} catch (error) {
console.error('记录批量操作日志失败:', error);
}
});
next();
};
};
module.exports = {
operationLogger,
createOperationLogger,
createBatchOperationLogger,
getOperationType
};