修改管理后台
This commit is contained in:
432
backend/controllers/operationLogController.js
Normal file
432
backend/controllers/operationLogController.js
Normal file
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* 操作日志控制器
|
||||
* @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
|
||||
};
|
||||
Reference in New Issue
Block a user