完善保险端前后端

This commit is contained in:
shenquanyi
2025-09-24 18:12:37 +08:00
parent 111ebaec84
commit b17bdcc24c
56 changed files with 9862 additions and 1111 deletions

View File

@@ -11,10 +11,22 @@ const jwtAuth = (req, res, next) => {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 检查Token类型只接受访问令牌
if (decoded.type && decoded.type !== 'access') {
return res.status(401).json(responseFormat.error('无效的令牌类型'));
}
req.user = decoded;
next();
} catch (error) {
return res.status(401).json(responseFormat.error('认证令牌无效或已过期'));
if (error.name === 'TokenExpiredError') {
return res.status(401).json(responseFormat.error('认证令牌已过期', 'TOKEN_EXPIRED'));
} else if (error.name === 'JsonWebTokenError') {
return res.status(401).json(responseFormat.error('认证令牌无效', 'TOKEN_INVALID'));
} else {
return res.status(401).json(responseFormat.error('认证失败', 'AUTH_FAILED'));
}
}
};

View File

@@ -0,0 +1,302 @@
const { OperationLog } = require('../models');
/**
* 操作日志记录中间件
* 自动记录用户的操作行为
*/
class OperationLogger {
/**
* 记录操作日志的中间件
*/
static logOperation(options = {}) {
return async (req, res, next) => {
const startTime = Date.now();
// 保存原始的res.json方法
const originalJson = res.json;
// 重写res.json方法以捕获响应数据
res.json = function(data) {
const endTime = Date.now();
const executionTime = endTime - startTime;
// 异步记录操作日志,不阻塞响应
setImmediate(async () => {
try {
await OperationLogger.recordLog(req, res, data, executionTime, options);
} catch (error) {
console.error('记录操作日志失败:', error);
}
});
// 调用原始的json方法
return originalJson.call(this, data);
};
next();
};
}
/**
* 记录操作日志
*/
static async recordLog(req, res, responseData, executionTime, options) {
try {
// 如果用户未登录,不记录日志
if (!req.user || !req.user.id) {
return;
}
// 获取操作类型
const operationType = OperationLogger.getOperationType(req.method, req.url, options);
// 获取操作模块
const operationModule = OperationLogger.getOperationModule(req.url, options);
// 获取操作内容
const operationContent = OperationLogger.getOperationContent(req, operationType, operationModule, options);
// 获取操作目标
const operationTarget = OperationLogger.getOperationTarget(req, responseData, options);
// 获取操作状态
const status = OperationLogger.getOperationStatus(res.statusCode, responseData);
// 获取错误信息
const errorMessage = OperationLogger.getErrorMessage(responseData, status);
// 记录操作日志
await OperationLog.logOperation({
user_id: req.user.id,
operation_type: operationType,
operation_module: operationModule,
operation_content: operationContent,
operation_target: operationTarget,
request_method: req.method,
request_url: req.originalUrl || req.url,
request_params: {
query: req.query,
body: OperationLogger.sanitizeRequestBody(req.body),
params: req.params
},
response_status: res.statusCode,
response_data: OperationLogger.sanitizeResponseData(responseData),
ip_address: OperationLogger.getClientIP(req),
user_agent: req.get('User-Agent') || '',
execution_time: executionTime,
status: status,
error_message: errorMessage
});
} catch (error) {
console.error('记录操作日志时发生错误:', error);
}
}
/**
* 获取操作类型
*/
static getOperationType(method, url, options) {
if (options.operation_type) {
return options.operation_type;
}
// 根据URL和HTTP方法推断操作类型
if (url.includes('/login')) return 'login';
if (url.includes('/logout')) return 'logout';
if (url.includes('/export')) return 'export';
if (url.includes('/import')) return 'import';
if (url.includes('/approve')) return 'approve';
if (url.includes('/reject')) return 'reject';
switch (method.toUpperCase()) {
case 'GET':
return 'view';
case 'POST':
return 'create';
case 'PUT':
case 'PATCH':
return 'update';
case 'DELETE':
return 'delete';
default:
return 'other';
}
}
/**
* 获取操作模块
*/
static getOperationModule(url, options) {
if (options.operation_module) {
return options.operation_module;
}
// 从URL中提取模块名
const pathSegments = url.split('/').filter(segment => segment && segment !== 'api');
if (pathSegments.length > 0) {
return pathSegments[0].replace(/-/g, '_');
}
return 'unknown';
}
/**
* 获取操作内容
*/
static getOperationContent(req, operationType, operationModule, options) {
if (options.operation_content) {
return options.operation_content;
}
const actionMap = {
'login': '用户登录',
'logout': '用户退出',
'view': '查看',
'create': '创建',
'update': '更新',
'delete': '删除',
'export': '导出',
'import': '导入',
'approve': '审批通过',
'reject': '审批拒绝'
};
const moduleMap = {
'users': '用户',
'roles': '角色',
'insurance': '保险',
'policies': '保单',
'claims': '理赔',
'system': '系统',
'operation_logs': '操作日志',
'devices': '设备',
'device_alerts': '设备告警'
};
const action = actionMap[operationType] || operationType;
const module = moduleMap[operationModule] || operationModule;
return `${action}${module}`;
}
/**
* 获取操作目标
*/
static getOperationTarget(req, responseData, options) {
if (options.operation_target) {
return options.operation_target;
}
// 尝试从请求参数中获取ID
if (req.params.id) {
return `ID: ${req.params.id}`;
}
// 尝试从响应数据中获取信息
if (responseData && responseData.data) {
if (responseData.data.id) {
return `ID: ${responseData.data.id}`;
}
if (responseData.data.name) {
return `名称: ${responseData.data.name}`;
}
if (responseData.data.username) {
return `用户: ${responseData.data.username}`;
}
}
return '';
}
/**
* 获取操作状态
*/
static getOperationStatus(statusCode, responseData) {
if (statusCode >= 200 && statusCode < 300) {
return 'success';
} else if (statusCode >= 400 && statusCode < 500) {
return 'failed';
} else {
return 'error';
}
}
/**
* 获取错误信息
*/
static getErrorMessage(responseData, status) {
if (status === 'success') {
return null;
}
if (responseData && responseData.message) {
return responseData.message;
}
if (responseData && responseData.error) {
return responseData.error;
}
return null;
}
/**
* 清理请求体数据(移除敏感信息)
*/
static sanitizeRequestBody(body) {
if (!body || typeof body !== 'object') {
return body;
}
const sanitized = { ...body };
const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth'];
sensitiveFields.forEach(field => {
if (sanitized[field]) {
sanitized[field] = '***';
}
});
return sanitized;
}
/**
* 清理响应数据(移除敏感信息)
*/
static sanitizeResponseData(data) {
if (!data || typeof data !== 'object') {
return data;
}
// 只保留基本的响应信息,不保存完整的响应数据
return {
status: data.status,
message: data.message,
code: data.code
};
}
/**
* 获取客户端IP地址
*/
static getClientIP(req) {
return req.ip ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
(req.connection.socket ? req.connection.socket.remoteAddress : null) ||
'127.0.0.1';
}
/**
* 创建特定操作的日志记录器
*/
static createLogger(operationType, operationModule, operationContent) {
return OperationLogger.logOperation({
operation_type: operationType,
operation_module: operationModule,
operation_content: operationContent
});
}
}
module.exports = OperationLogger;