1. 新增操作日志记录功能,记录关键操作 2. 实现危险操作确认对话框,防止误删 3. 添加业务和库房管理模块 4. 支持设备标记为空闲状态 5. 完善API文档和健康检查 6. 优化前端删除操作的确认流程 7. 添加Swagger API文档支持 8. 实现设备与业务的关联功能 9. 改进设备模型,添加空闲相关字段 10. 优化用户、角色管理操作日志
205 lines
5.1 KiB
JavaScript
205 lines
5.1 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const LOG_DIR = path.join(__dirname, '../../logs');
|
|
const DANGEROUS_OPERATIONS_LOG = path.join(LOG_DIR, 'dangerous-operations.log');
|
|
|
|
const ensureLogDir = () => {
|
|
if (!fs.existsSync(LOG_DIR)) {
|
|
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
}
|
|
};
|
|
|
|
const formatLogEntry = (entry) => {
|
|
const timestamp = new Date().toISOString();
|
|
return JSON.stringify({
|
|
timestamp,
|
|
...entry,
|
|
}) + '\n';
|
|
};
|
|
|
|
const logDangerousOperation = async (req, {
|
|
operationType,
|
|
operationName,
|
|
targetType,
|
|
targetId,
|
|
targetName,
|
|
beforeState,
|
|
metadata = {},
|
|
success = true,
|
|
errorMessage = null,
|
|
}) => {
|
|
ensureLogDir();
|
|
|
|
const clientIp = req?.ip || req?.connection?.remoteAddress || 'unknown';
|
|
const userAgent = req?.get?.('User-Agent') || 'unknown';
|
|
const userId = req?.user?.userId || req?.session?.userId || 'anonymous';
|
|
const username = req?.user?.username || req?.session?.username || 'anonymous';
|
|
|
|
const logEntry = {
|
|
operationType,
|
|
operationName,
|
|
targetType,
|
|
targetId,
|
|
targetName,
|
|
beforeState: beforeState ? JSON.stringify(beforeState) : null,
|
|
metadata,
|
|
success,
|
|
errorMessage,
|
|
clientIp,
|
|
userAgent,
|
|
userId,
|
|
username,
|
|
riskLevel: metadata.riskLevel || 'UNKNOWN',
|
|
relatedDataCount: metadata.relatedDataCount || 0,
|
|
itemCount: metadata.itemCount || 1,
|
|
};
|
|
|
|
try {
|
|
fs.appendFileSync(DANGEROUS_OPERATIONS_LOG, formatLogEntry(logEntry));
|
|
console.log(`[DANGEROUS-OP] ${logEntry.operationName} by ${logEntry.username} - ${success ? 'SUCCESS' : 'FAILED'}`);
|
|
} catch (error) {
|
|
console.error('Failed to write dangerous operation log:', error);
|
|
}
|
|
};
|
|
|
|
const getDangerousOperationsLogs = (filters = {}) => {
|
|
ensureLogDir();
|
|
|
|
if (!fs.existsSync(DANGEROUS_OPERATIONS_LOG)) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const content = fs.readFileSync(DANGEROUS_OPERATIONS_LOG, 'utf-8');
|
|
const lines = content.split('\n').filter(line => line.trim());
|
|
|
|
let logs = lines.map(line => {
|
|
try {
|
|
return JSON.parse(line);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}).filter(log => log !== null);
|
|
|
|
if (filters.operationType) {
|
|
logs = logs.filter(log => log.operationType === filters.operationType);
|
|
}
|
|
|
|
if (filters.targetType) {
|
|
logs = logs.filter(log => log.targetType === filters.targetType);
|
|
}
|
|
|
|
if (filters.success !== undefined) {
|
|
logs = logs.filter(log => log.success === filters.success);
|
|
}
|
|
|
|
if (filters.startDate) {
|
|
logs = logs.filter(log => new Date(log.timestamp) >= new Date(filters.startDate));
|
|
}
|
|
|
|
if (filters.endDate) {
|
|
logs = logs.filter(log => new Date(log.timestamp) <= new Date(filters.endDate));
|
|
}
|
|
|
|
if (filters.username) {
|
|
logs = logs.filter(log => log.username?.toLowerCase().includes(filters.username.toLowerCase()));
|
|
}
|
|
|
|
if (filters.riskLevel) {
|
|
logs = logs.filter(log => log.riskLevel === filters.riskLevel);
|
|
}
|
|
|
|
return logs.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
} catch (error) {
|
|
console.error('Failed to read dangerous operations log:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
const cleanOldLogs = (daysToKeep = 90) => {
|
|
ensureLogDir();
|
|
|
|
if (!fs.existsSync(DANGEROUS_OPERATIONS_LOG)) {
|
|
return { deletedCount: 0 };
|
|
}
|
|
|
|
try {
|
|
const content = fs.readFileSync(DANGEROUS_OPERATIONS_LOG, 'utf-8');
|
|
const lines = content.split('\n').filter(line => line.trim());
|
|
|
|
const cutoffDate = new Date();
|
|
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
|
|
const remainingLogs = [];
|
|
let deletedCount = 0;
|
|
|
|
for (const line of lines) {
|
|
try {
|
|
const logEntry = JSON.parse(line);
|
|
if (new Date(logEntry.timestamp) >= cutoffDate) {
|
|
remainingLogs.push(line);
|
|
} else {
|
|
deletedCount++;
|
|
}
|
|
} catch {
|
|
deletedCount++;
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(DANGEROUS_OPERATIONS_LOG, remainingLogs.join('\n') + '\n');
|
|
|
|
return { deletedCount, remainingCount: remainingLogs.length };
|
|
} catch (error) {
|
|
console.error('Failed to clean old logs:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const DANGEROUS_OPERATION_TYPES = {
|
|
DELETE_SINGLE: 'DELETE_SINGLE',
|
|
DELETE_BATCH: 'DELETE_BATCH',
|
|
DELETE_ALL: 'DELETE_ALL',
|
|
UPDATE_BATCH: 'UPDATE_BATCH',
|
|
RESTORE: 'RESTORE',
|
|
PURGE: 'PURGE',
|
|
BATCH_RESTORE: 'BATCH_RESTORE',
|
|
};
|
|
|
|
const RISK_LEVELS = {
|
|
EXTREME: 'EXTREME',
|
|
HIGH: 'HIGH',
|
|
MEDIUM: 'MEDIUM',
|
|
LOW: 'LOW',
|
|
};
|
|
|
|
const calculateRiskLevel = (operationType, itemCount, options = {}) => {
|
|
const { hasRelatedData = false, isSystemLevel = false } = options;
|
|
|
|
if (operationType === DANGEROUS_OPERATION_TYPES.DELETE_ALL || isSystemLevel) {
|
|
return RISK_LEVELS.EXTREME;
|
|
}
|
|
|
|
if (operationType === DANGEROUS_OPERATION_TYPES.DELETE_BATCH) {
|
|
if (itemCount > 10) {
|
|
return RISK_LEVELS.EXTREME;
|
|
}
|
|
return itemCount > 3 ? RISK_LEVELS.HIGH : RISK_LEVELS.MEDIUM;
|
|
}
|
|
|
|
if (hasRelatedData) {
|
|
return RISK_LEVELS.MEDIUM;
|
|
}
|
|
|
|
return RISK_LEVELS.LOW;
|
|
};
|
|
|
|
module.exports = {
|
|
logDangerousOperation,
|
|
getDangerousOperationsLogs,
|
|
cleanOldLogs,
|
|
DANGEROUS_OPERATION_TYPES,
|
|
RISK_LEVELS,
|
|
calculateRiskLevel,
|
|
};
|