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
+31 -10
View File
@@ -40,13 +40,31 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
]
}
const [total, employees] = await Promise.all([
// 状态过滤在 DB 层完成(contractStatus 需要后处理计算,仍需内存过滤)
if (status === 'RESIGNED') {
whereBase.status = 'RESIGNED'
} else if (status === 'PRE_HIRE') {
whereBase.status = 'ACTIVE'
whereBase.hireDate = { gt: today }
} else if (status === 'ACTIVE') {
whereBase.status = 'ACTIVE'
whereBase.hireDate = { lte: today }
}
// unsigned 合同状态可以在 DB 层过滤
if (contractStatus === 'unsigned') {
whereBase.contracts = { none: {} }
}
// 当有 contractStatus(非 unsigned)筛选时,需要先查全部再过滤后分页
const needPostFilter = !!contractStatus && contractStatus !== 'unsigned'
const [dbTotal, employees] = await Promise.all([
prisma.employee.count({ where: whereBase }),
prisma.employee.findMany({
where: whereBase,
orderBy: { createdAt: 'desc' },
skip,
take: pageSize,
...(needPostFilter ? {} : { skip, take: pageSize }),
include: {
contracts: { orderBy: { createdAt: 'desc' }, take: 1 },
terminations: { orderBy: { terminationDate: 'desc' }, take: 1 },
@@ -107,22 +125,25 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
}
})
// 前端过滤:状态和合同状态(因为合同状态需要后处理,不适合放 Prisma where
if (status) {
result = result.filter((e) => e.status === status)
}
if (contractStatus) {
// 前端过滤:合同状态(非 unsigned 的需要后处理计算
if (contractStatus && contractStatus !== 'unsigned') {
result = result.filter((e) => e.contractStatus === contractStatus)
}
// 计算过滤后的总数和分页
const filteredTotal = needPostFilter ? result.length : dbTotal
if (needPostFilter) {
result = result.slice(skip, skip + pageSize)
}
res.json({
success: true,
data: result,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize),
total: filteredTotal,
totalPages: Math.ceil(filteredTotal / pageSize),
},
})
} catch (err) {