feat: 实现ID生成器并重构模型ID生成逻辑
refactor: 统一ID生成方式,使用新的idGenerator模块 refactor: 重构模型ID生成逻辑,允许空ID并在创建前自动生成 fix(roomSchema): 使机房ID字段变为可选并更新前端表单验证 style: 格式化代码并优化导入语句 test: 添加操作日志集成测试文件 docs: 添加错误处理模块文档
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
class AppError extends Error {
|
||||
constructor(code, message, statusCode = 500, details = null) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.statusCode = statusCode;
|
||||
this.details = details;
|
||||
this.isOperational = true;
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
const ERROR_CODES = {
|
||||
BAD_REQUEST: 'BAD_REQUEST',
|
||||
UNAUTHORIZED: 'UNAUTHORIZED',
|
||||
FORBIDDEN: 'FORBIDDEN',
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
CONFLICT: 'CONFLICT',
|
||||
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
||||
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
||||
DATABASE_ERROR: 'DATABASE_ERROR',
|
||||
NETWORK_ERROR: 'NETWORK_ERROR',
|
||||
};
|
||||
|
||||
const STATUS_CODE_MAP = {
|
||||
400: ERROR_CODES.BAD_REQUEST,
|
||||
401: ERROR_CODES.UNAUTHORIZED,
|
||||
403: ERROR_CODES.FORBIDDEN,
|
||||
404: ERROR_CODES.NOT_FOUND,
|
||||
409: ERROR_CODES.CONFLICT,
|
||||
500: ERROR_CODES.INTERNAL_ERROR,
|
||||
};
|
||||
|
||||
const FRIENDLY_MESSAGES = {
|
||||
[ERROR_CODES.BAD_REQUEST]: '请求参数错误,请检查输入',
|
||||
[ERROR_CODES.UNAUTHORIZED]: '未授权访问,请先登录',
|
||||
[ERROR_CODES.FORBIDDEN]: '无权执行此操作',
|
||||
[ERROR_CODES.NOT_FOUND]: '请求的资源不存在',
|
||||
[ERROR_CODES.CONFLICT]: '数据冲突,请刷新后重试',
|
||||
[ERROR_CODES.VALIDATION_ERROR]: '数据验证失败',
|
||||
[ERROR_CODES.INTERNAL_ERROR]: '服务器内部错误,请稍后重试',
|
||||
[ERROR_CODES.DATABASE_ERROR]: '数据库操作失败',
|
||||
[ERROR_CODES.NETWORK_ERROR]: '网络连接错误',
|
||||
};
|
||||
|
||||
function logError(error, req = null) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logData = {
|
||||
timestamp,
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
statusCode: error.statusCode,
|
||||
stack: error.stack,
|
||||
details: error.details,
|
||||
userAgent: req?.headers?.['user-agent'],
|
||||
ip: req?.ip,
|
||||
path: req?.path,
|
||||
method: req?.method,
|
||||
};
|
||||
|
||||
console.error('=== ERROR LOG ===');
|
||||
console.error(JSON.stringify(logData, null, 2));
|
||||
}
|
||||
|
||||
function buildErrorResponse(error) {
|
||||
const code = error.code || STATUS_CODE_MAP[error.statusCode] || ERROR_CODES.INTERNAL_ERROR;
|
||||
const message =
|
||||
error.message || FRIENDLY_MESSAGES[code] || FRIENDLY_MESSAGES[ERROR_CODES.INTERNAL_ERROR];
|
||||
|
||||
const response = {
|
||||
success: false,
|
||||
error: {
|
||||
code,
|
||||
message,
|
||||
},
|
||||
};
|
||||
|
||||
if (error.details) {
|
||||
response.error.details = error.details;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
function createError(code, message, statusCode = 500, details = null) {
|
||||
return new AppError(code, message, statusCode, details);
|
||||
}
|
||||
|
||||
function sendErrorResponse(res, error, req = null) {
|
||||
logError(error, req);
|
||||
|
||||
let statusCode = 500;
|
||||
let appError = error;
|
||||
|
||||
if (!(error instanceof AppError)) {
|
||||
if (error.name === 'SequelizeValidationError') {
|
||||
statusCode = 400;
|
||||
const details = error.errors.map(err => ({
|
||||
field: err.path,
|
||||
message: err.message,
|
||||
}));
|
||||
appError = createError(
|
||||
ERROR_CODES.VALIDATION_ERROR,
|
||||
FRIENDLY_MESSAGES[ERROR_CODES.VALIDATION_ERROR],
|
||||
statusCode,
|
||||
details
|
||||
);
|
||||
} else if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
statusCode = 409;
|
||||
appError = createError(ERROR_CODES.CONFLICT, '数据已存在', statusCode);
|
||||
} else {
|
||||
statusCode = error.statusCode || 500;
|
||||
appError = createError(
|
||||
STATUS_CODE_MAP[statusCode] || ERROR_CODES.INTERNAL_ERROR,
|
||||
FRIENDLY_MESSAGES[STATUS_CODE_MAP[statusCode] || ERROR_CODES.INTERNAL_ERROR],
|
||||
statusCode,
|
||||
process.env.NODE_ENV === 'development' ? error.stack : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
statusCode = appError.statusCode || statusCode;
|
||||
res.status(statusCode).json(buildErrorResponse(appError));
|
||||
}
|
||||
|
||||
function errorHandlerMiddleware(err, req, res, next) {
|
||||
sendErrorResponse(res, err, req);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AppError,
|
||||
ERROR_CODES,
|
||||
createError,
|
||||
logError,
|
||||
buildErrorResponse,
|
||||
sendErrorResponse,
|
||||
errorHandlerMiddleware,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
prefix: '',
|
||||
timestampBase: 36,
|
||||
timestampUpperCase: true,
|
||||
randomLength: 4,
|
||||
randomBase: 36,
|
||||
randomUpperCase: true,
|
||||
};
|
||||
|
||||
function generateId(config = {}) {
|
||||
const {
|
||||
prefix,
|
||||
timestampBase,
|
||||
timestampUpperCase,
|
||||
randomLength,
|
||||
randomBase,
|
||||
randomUpperCase,
|
||||
} = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
const timestamp = Date.now().toString(timestampBase);
|
||||
const formattedTimestamp = timestampUpperCase ? timestamp.toUpperCase() : timestamp;
|
||||
|
||||
let randomStr = '';
|
||||
for (let i = 0; i < randomLength; i++) {
|
||||
randomStr += Math.floor(Math.random() * randomBase).toString(randomBase);
|
||||
}
|
||||
const formattedRandom = randomUpperCase ? randomStr.toUpperCase() : randomStr;
|
||||
|
||||
return `${prefix}${formattedTimestamp}${formattedRandom}`;
|
||||
}
|
||||
|
||||
function generateTicketId() {
|
||||
return generateId({ prefix: 'TKT', randomLength: 4 });
|
||||
}
|
||||
|
||||
function generateRoleId() {
|
||||
return generateId({
|
||||
prefix: 'role_',
|
||||
timestampUpperCase: false,
|
||||
randomLength: 9,
|
||||
randomUpperCase: false
|
||||
});
|
||||
}
|
||||
|
||||
function generatePlanId() {
|
||||
return generateId({ prefix: 'PLAN', randomLength: 4 });
|
||||
}
|
||||
|
||||
function generateTaskId() {
|
||||
return generateId({ prefix: 'TASK', randomLength: 4 });
|
||||
}
|
||||
|
||||
function generateRecordId() {
|
||||
return generateId({ prefix: 'REC', randomLength: 6 });
|
||||
}
|
||||
|
||||
function generatePendingId() {
|
||||
return generateId({ prefix: 'PEND', randomLength: 4 });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateId,
|
||||
generateTicketId,
|
||||
generateRoleId,
|
||||
generatePlanId,
|
||||
generateTaskId,
|
||||
generateRecordId,
|
||||
generatePendingId,
|
||||
DEFAULT_CONFIG,
|
||||
};
|
||||
@@ -127,9 +127,7 @@ class ImportJobManager extends EventEmitter {
|
||||
error: job.error,
|
||||
startTime: job.startTime,
|
||||
endTime: job.endTime,
|
||||
elapsedTime: job.startTime
|
||||
? Date.now() - new Date(job.startTime).getTime()
|
||||
: null,
|
||||
elapsedTime: job.startTime ? Date.now() - new Date(job.startTime).getTime() : null,
|
||||
};
|
||||
|
||||
return progress;
|
||||
@@ -164,11 +162,14 @@ class ImportJobManager extends EventEmitter {
|
||||
|
||||
const importJobManager = new ImportJobManager();
|
||||
|
||||
setInterval(() => {
|
||||
importJobManager.cleanupOldJobs();
|
||||
}, 60 * 60 * 1000);
|
||||
setInterval(
|
||||
() => {
|
||||
importJobManager.cleanupOldJobs();
|
||||
},
|
||||
60 * 60 * 1000
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
importJobManager,
|
||||
ImportJobManager,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,11 +4,11 @@ const routesConfig = require('../config/routes');
|
||||
function loadRoutes(app) {
|
||||
const routesDir = path.join(__dirname, '../routes');
|
||||
|
||||
routesConfig.forEach((routeConfig) => {
|
||||
routesConfig.forEach(routeConfig => {
|
||||
try {
|
||||
const routerPath = path.join(routesDir, routeConfig.file);
|
||||
const router = require(routerPath);
|
||||
|
||||
|
||||
if (typeof router === 'function') {
|
||||
app.use(routeConfig.path, router);
|
||||
console.log(`路由已加载: ${routeConfig.path} -> ${routeConfig.file}`);
|
||||
|
||||
Reference in New Issue
Block a user