2968484d2d
- Money.tsx (2260行→54行): 拆分为 money/ 子目录4个组件, React.lazy二级分割 - AIAssistant.tsx (2038行→63行): 拆分为 ai-assistant/ 子目录6个组件, React.lazy二级分割 - xlsx改为动态导入, OvertimeTab从345KB降至12.7KB - api-services.ts: 请求参数 any→Record<string,unknown> - 移除前端3处console.log残留 - 后端console替换为pino logger - 前后端未使用import/变量清理 - Zod schema验证: termination/platform/special-status/work-process - 新增 leave.routes.ts, acceptance-test.routes.ts - UI组件: PageGuide, QueryError, Stepper
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { Request, Response, NextFunction } from 'express'
|
|
import { ZodError } from 'zod'
|
|
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
|
|
import { AppError } from '../lib/AppError'
|
|
import logger from '../lib/logger'
|
|
|
|
export function errorHandler(err: unknown, _req: Request, res: Response, _next: NextFunction) {
|
|
if (err instanceof AppError) {
|
|
return res.status(err.statusCode).json({
|
|
success: false,
|
|
error: { code: err.code, message: err.message },
|
|
})
|
|
}
|
|
|
|
if (err instanceof ZodError) {
|
|
return res.status(422).json({
|
|
success: false,
|
|
error: {
|
|
code: 'VALIDATION_ERROR',
|
|
message: '输入校验失败',
|
|
details: err.errors.map((e) => ({ path: e.path.join('.'), message: e.message })),
|
|
},
|
|
})
|
|
}
|
|
|
|
if (err instanceof PrismaClientKnownRequestError) {
|
|
if (err.code === 'P2002') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: { code: 'DUPLICATE', message: '数据已存在,请勿重复操作' },
|
|
})
|
|
}
|
|
if (err.code === 'P2025') {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: { code: 'NOT_FOUND', message: '记录不存在' },
|
|
})
|
|
}
|
|
}
|
|
|
|
if (typeof err === 'object' && err !== null && 'code' in err && 'message' in err) {
|
|
const e = err as { code: string; message: string }
|
|
const statusMap: Record<string, number> = {
|
|
NOT_FOUND: 404,
|
|
CONFLICT: 409,
|
|
VALIDATION_ERROR: 422,
|
|
PLAN_LIMIT: 403,
|
|
AUTH_FAILED: 401,
|
|
TOKEN_INVALID: 401,
|
|
ACCOUNT_DISABLED: 403,
|
|
DUPLICATE: 400,
|
|
}
|
|
return res.status(statusMap[e.code] || 400).json({
|
|
success: false,
|
|
error: { code: e.code, message: e.message },
|
|
})
|
|
}
|
|
|
|
logger.error({ err }, 'Unhandled error')
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' },
|
|
})
|
|
}
|