feat: 添加操作日志、危险操作确认和业务关联功能
1. 新增操作日志记录功能,记录关键操作 2. 实现危险操作确认对话框,防止误删 3. 添加业务和库房管理模块 4. 支持设备标记为空闲状态 5. 完善API文档和健康检查 6. 优化前端删除操作的确认流程 7. 添加Swagger API文档支持 8. 实现设备与业务的关联功能 9. 改进设备模型,添加空闲相关字段 10. 优化用户、角色管理操作日志
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
const express = require('express');
|
||||
const { Op } = require('sequelize');
|
||||
const { sequelize } = require('../db');
|
||||
const OperationLog = require('../models/OperationLog');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
module,
|
||||
operationType,
|
||||
targetId,
|
||||
operatorId,
|
||||
keyword,
|
||||
startDate,
|
||||
endDate,
|
||||
result
|
||||
} = req.query;
|
||||
|
||||
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
||||
const limit = Math.min(parseInt(pageSize), 100);
|
||||
|
||||
const where = {};
|
||||
|
||||
if (module && module !== 'all') {
|
||||
where.module = module;
|
||||
}
|
||||
|
||||
if (operationType && operationType !== 'all') {
|
||||
where.operationType = operationType;
|
||||
}
|
||||
|
||||
if (targetId) {
|
||||
where.targetId = { [Op.like]: `%${targetId}%` };
|
||||
}
|
||||
|
||||
if (operatorId) {
|
||||
where.operatorId = operatorId;
|
||||
}
|
||||
|
||||
if (keyword) {
|
||||
where[Op.or] = [
|
||||
{ operationDescription: { [Op.like]: `%${keyword}%` } },
|
||||
{ targetName: { [Op.like]: `%${keyword}%` } },
|
||||
{ operatorName: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
if (result && result !== 'all') {
|
||||
where.result = result;
|
||||
}
|
||||
|
||||
if (startDate || endDate) {
|
||||
where.createdAt = {};
|
||||
if (startDate) {
|
||||
where.createdAt[Op.gte] = new Date(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
const endOfDay = new Date(endDate);
|
||||
endOfDay.setHours(23, 59, 59, 999);
|
||||
where.createdAt[Op.lte] = endOfDay;
|
||||
}
|
||||
}
|
||||
|
||||
const { count, rows: logs } = await OperationLog.findAndCountAll({
|
||||
where,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset,
|
||||
limit
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
logs
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作日志失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取操作日志失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/modules', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const modules = await OperationLog.findAll({
|
||||
attributes: ['module'],
|
||||
group: ['module']
|
||||
});
|
||||
|
||||
const moduleList = modules.map(m => ({
|
||||
value: m.module,
|
||||
label: getModuleName(m.module)
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: moduleList
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取模块列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取模块列表失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/types', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { module } = req.query;
|
||||
|
||||
const where = {};
|
||||
if (module && module !== 'all') {
|
||||
where.module = module;
|
||||
}
|
||||
|
||||
const types = await OperationLog.findAll({
|
||||
where,
|
||||
attributes: ['operationType'],
|
||||
group: ['operationType']
|
||||
});
|
||||
|
||||
const typeList = types.map(t => ({
|
||||
value: t.operationType,
|
||||
label: getOperationTypeName(t.operationType)
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: typeList
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作类型列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取操作类型列表失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/statistics', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { startDate, endDate } = req.query;
|
||||
|
||||
const where = {};
|
||||
if (startDate || endDate) {
|
||||
where.createdAt = {};
|
||||
if (startDate) {
|
||||
where.createdAt[Op.gte] = new Date(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
const endOfDay = new Date(endDate);
|
||||
endOfDay.setHours(23, 59, 59, 999);
|
||||
where.createdAt[Op.lte] = endOfDay;
|
||||
}
|
||||
}
|
||||
|
||||
const [moduleStats, typeStats, dailyStats] = await Promise.all([
|
||||
OperationLog.findAll({
|
||||
where,
|
||||
attributes: ['module', [sequelize.fn('COUNT', sequelize.col('module')), 'count']],
|
||||
group: ['module']
|
||||
}),
|
||||
OperationLog.findAll({
|
||||
where,
|
||||
attributes: ['operationType', [sequelize.fn('COUNT', sequelize.col('operationType')), 'count']],
|
||||
group: ['operationType']
|
||||
}),
|
||||
OperationLog.findAll({
|
||||
where,
|
||||
attributes: [
|
||||
[sequelize.fn('DATE', sequelize.col('createdAt')), 'date'],
|
||||
[sequelize.fn('COUNT', '*'), 'count']
|
||||
],
|
||||
group: [sequelize.fn('DATE', sequelize.col('createdAt'))],
|
||||
order: [[sequelize.fn('DATE', sequelize.col('createdAt')), 'DESC']],
|
||||
limit: 30
|
||||
})
|
||||
]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
byModule: moduleStats.map(s => ({ module: s.module, count: s.get('count') })),
|
||||
byType: typeStats.map(s => ({ type: s.operationType, count: s.get('count') })),
|
||||
byDay: dailyStats.map(s => ({ date: s.get('date'), count: s.get('count') }))
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作日志统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取操作日志统计失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:recordId', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const log = await OperationLog.findByPk(req.params.recordId);
|
||||
|
||||
if (!log) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '日志记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: log
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作日志详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取操作日志详情失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function getModuleName(module) {
|
||||
const moduleNames = {
|
||||
device: '设备管理',
|
||||
user: '用户管理',
|
||||
role: '角色管理',
|
||||
consumable: '耗材管理',
|
||||
rack: '机柜管理',
|
||||
room: '机房管理',
|
||||
ticket: '工单管理',
|
||||
backup: '备份管理'
|
||||
};
|
||||
return moduleNames[module] || module;
|
||||
}
|
||||
|
||||
function getOperationTypeName(type) {
|
||||
const typeNames = {
|
||||
create: '创建',
|
||||
update: '更新',
|
||||
delete: '删除',
|
||||
batch_delete: '批量删除',
|
||||
batch_update: '批量更新',
|
||||
status_change: '状态变更',
|
||||
move: '移动',
|
||||
permission_change: '权限变更',
|
||||
import: '导入',
|
||||
export: '导出'
|
||||
};
|
||||
return typeNames[type] || type;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user