修改管理后台
This commit is contained in:
216
backend/middleware/operationLogger.js
Normal file
216
backend/middleware/operationLogger.js
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 操作日志中间件
|
||||
* @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
|
||||
};
|
||||
Reference in New Issue
Block a user