Files
nxxmdata/backend/controllers/operationLogController.js

433 lines
10 KiB
JavaScript
Raw Normal View History

2025-09-12 20:08:42 +08:00
/**
* 操作日志控制器
* @file operationLogController.js
* @description 处理操作日志相关的API请求
*/
const { OperationLog, User } = require('../models');
const { validationResult } = require('express-validator');
const { Op } = require('sequelize');
/**
* 获取操作日志列表
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const getOperationLogs = async (req, res) => {
try {
const {
page = 1,
pageSize = 20,
userId,
username,
operationType,
moduleName,
tableName,
startDate,
endDate,
sortBy = 'created_at',
sortOrder = 'DESC'
} = req.query;
// 构建查询条件
const whereClause = {};
if (userId) {
whereClause.user_id = userId;
}
if (username) {
whereClause.username = {
[Op.like]: `%${username}%`
};
}
if (operationType) {
whereClause.operation_type = operationType;
}
if (moduleName) {
whereClause.module_name = {
[Op.like]: `%${moduleName}%`
};
}
if (tableName) {
whereClause.table_name = tableName;
}
if (startDate && endDate) {
whereClause.created_at = {
[Op.between]: [startDate, endDate]
};
}
// 构建排序条件
const orderClause = [[sortBy, sortOrder.toUpperCase()]];
// 执行分页查询
const result = await OperationLog.paginate(
{
where: whereClause,
order: orderClause,
include: [
{
model: User,
as: 'user',
attributes: ['id', 'username', 'email', 'roles']
}
]
},
parseInt(page),
parseInt(pageSize)
);
res.json({
success: true,
data: result.data,
pagination: result.pagination,
message: '获取操作日志列表成功'
});
} catch (error) {
console.error('获取操作日志列表失败:', error);
res.status(500).json({
success: false,
message: '获取操作日志列表失败',
error: error.message
});
}
};
/**
* 获取操作日志详情
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const getOperationLogById = async (req, res) => {
try {
const { id } = req.params;
const operationLog = await OperationLog.findByPk(id, {
include: [
{
model: User,
as: 'user',
attributes: ['id', 'username', 'email', 'roles']
}
]
});
if (!operationLog) {
return res.status(404).json({
success: false,
message: '操作日志不存在'
});
}
res.json({
success: true,
data: operationLog,
message: '获取操作日志详情成功'
});
} catch (error) {
console.error('获取操作日志详情失败:', error);
res.status(500).json({
success: false,
message: '获取操作日志详情失败',
error: error.message
});
}
};
/**
* 获取操作统计
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const getOperationStats = async (req, res) => {
try {
const { type, userId, moduleName, startDate, endDate } = req.query;
let stats = {};
if (type === 'user' && userId) {
stats = await OperationLog.getUserOperationStats(userId, startDate, endDate);
} else if (type === 'module' && moduleName) {
stats = await OperationLog.getModuleOperationStats(moduleName, startDate, endDate);
} else {
// 获取总体统计
const whereClause = {};
if (startDate && endDate) {
whereClause.created_at = {
[Op.between]: [startDate, endDate]
};
}
const result = await OperationLog.findAll({
where: whereClause,
attributes: [
'operation_type',
[OperationLog.sequelize.fn('COUNT', OperationLog.sequelize.col('id')), 'count']
],
group: ['operation_type'],
raw: true
});
stats = result.reduce((acc, stat) => {
acc[stat.operation_type] = parseInt(stat.count);
return acc;
}, {});
}
res.json({
success: true,
data: stats,
message: '获取操作统计成功'
});
} catch (error) {
console.error('获取操作统计失败:', error);
res.status(500).json({
success: false,
message: '获取操作统计失败',
error: error.message
});
}
};
/**
* 获取操作日志图表数据
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const getOperationChartData = async (req, res) => {
try {
const { type = 'daily', startDate, endDate } = req.query;
let dateFormat, groupBy;
switch (type) {
case 'hourly':
dateFormat = '%Y-%m-%d %H:00:00';
groupBy = 'DATE_FORMAT(created_at, "%Y-%m-%d %H:00:00")';
break;
case 'daily':
dateFormat = '%Y-%m-%d';
groupBy = 'DATE(created_at)';
break;
case 'monthly':
dateFormat = '%Y-%m';
groupBy = 'DATE_FORMAT(created_at, "%Y-%m")';
break;
default:
dateFormat = '%Y-%m-%d';
groupBy = 'DATE(created_at)';
}
const whereClause = {};
if (startDate && endDate) {
whereClause.created_at = {
[Op.between]: [startDate, endDate]
};
}
const result = await OperationLog.findAll({
where: whereClause,
attributes: [
[OperationLog.sequelize.fn('DATE_FORMAT', OperationLog.sequelize.col('created_at'), dateFormat), 'date'],
'operation_type',
[OperationLog.sequelize.fn('COUNT', OperationLog.sequelize.col('id')), 'count']
],
group: ['date', 'operation_type'],
order: [['date', 'ASC']],
raw: true
});
// 处理图表数据
const chartData = {
dates: [],
series: {
CREATE: [],
UPDATE: [],
DELETE: []
}
};
const dateSet = new Set();
result.forEach(item => {
dateSet.add(item.date);
});
chartData.dates = Array.from(dateSet).sort();
chartData.dates.forEach(date => {
const createItem = result.find(item => item.date === date && item.operation_type === 'CREATE');
const updateItem = result.find(item => item.date === date && item.operation_type === 'UPDATE');
const deleteItem = result.find(item => item.date === date && item.operation_type === 'DELETE');
chartData.series.CREATE.push(createItem ? parseInt(createItem.count) : 0);
chartData.series.UPDATE.push(updateItem ? parseInt(updateItem.count) : 0);
chartData.series.DELETE.push(deleteItem ? parseInt(deleteItem.count) : 0);
});
res.json({
success: true,
data: chartData,
message: '获取操作日志图表数据成功'
});
} catch (error) {
console.error('获取操作日志图表数据失败:', error);
res.status(500).json({
success: false,
message: '获取操作日志图表数据失败',
error: error.message
});
}
};
/**
* 清理过期日志
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const cleanExpiredLogs = async (req, res) => {
try {
const { daysToKeep = 90 } = req.body;
const deletedCount = await OperationLog.cleanExpiredLogs(daysToKeep);
res.json({
success: true,
data: { deletedCount },
message: `成功清理了 ${deletedCount} 条过期日志`
});
} catch (error) {
console.error('清理过期日志失败:', error);
res.status(500).json({
success: false,
message: '清理过期日志失败',
error: error.message
});
}
};
/**
* 导出操作日志
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
const exportOperationLogs = async (req, res) => {
try {
const {
userId,
username,
operationType,
moduleName,
tableName,
startDate,
endDate
} = req.query;
// 构建查询条件
const whereClause = {};
if (userId) {
whereClause.user_id = userId;
}
if (username) {
whereClause.username = {
[Op.like]: `%${username}%`
};
}
if (operationType) {
whereClause.operation_type = operationType;
}
if (moduleName) {
whereClause.module_name = {
[Op.like]: `%${moduleName}%`
};
}
if (tableName) {
whereClause.table_name = tableName;
}
if (startDate && endDate) {
whereClause.created_at = {
[Op.between]: [startDate, endDate]
};
}
const logs = await OperationLog.findAll({
where: whereClause,
order: [['created_at', 'DESC']],
include: [
{
model: User,
as: 'user',
attributes: ['id', 'username', 'email', 'roles']
}
]
});
// 转换为CSV格式
const csvHeaders = [
'ID',
'操作用户',
'用户角色',
'操作类型',
'模块名称',
'数据表名',
'记录ID',
'操作描述',
'IP地址',
'请求URL',
'请求方法',
'响应状态',
'执行时间(ms)',
'创建时间'
];
const csvRows = logs.map(log => [
log.id,
log.username,
log.user_role,
log.operation_type,
log.module_name,
log.table_name,
log.record_id || '',
log.operation_desc,
log.ip_address || '',
log.request_url || '',
log.request_method || '',
log.response_status || '',
log.execution_time || '',
log.created_at
]);
const csvContent = [csvHeaders, ...csvRows]
.map(row => row.map(field => `"${field}"`).join(','))
.join('\n');
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename=operation_logs.csv');
res.send('\ufeff' + csvContent); // 添加BOM以支持中文
} catch (error) {
console.error('导出操作日志失败:', error);
res.status(500).json({
success: false,
message: '导出操作日志失败',
error: error.message
});
}
};
module.exports = {
getOperationLogs,
getOperationLogById,
getOperationStats,
getOperationChartData,
cleanExpiredLogs,
exportOperationLogs
};