fix: 全面优化安全、性能和代码质量

P0: 环境变量校验/事务保护/RBAC权限
P1: 花名册分页/密码重置验证码/ErrorBoundary/N+1查询优化
P2: 导出限制/自定义错误类/body大小限制/代码去重
P3: 自定义确认弹窗替换window.confirm
This commit is contained in:
freedakgmail
2026-07-24 22:28:58 +08:00
parent 9ec21fedea
commit 5c12f28ac7
23 changed files with 634 additions and 366 deletions
+26
View File
@@ -1,8 +1,16 @@
import { Request, Response, NextFunction } from 'express'
import { ZodError } from 'zod'
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import { AppError } from '../lib/AppError'
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,
@@ -29,6 +37,24 @@ export function errorHandler(err: unknown, _req: Request, res: Response, _next:
}
}
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 },
})
}
console.error('Unhandled error:', err)
return res.status(500).json({
success: false,
+16
View File
@@ -0,0 +1,16 @@
import { Response, NextFunction } from 'express'
import { AuthRequest } from './auth'
export function requireRole(...roles: string[]) {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: '未认证' } })
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '无权限执行此操作' } })
}
next()
}
}
export const requireAdmin = requireRole('ADMIN')