好的,审完了。这毕竟是个原型(prototype),代码结构还挺清晰的,插件化设计也不错,但想上生产环境……差得远呢。我按优先级给你列一下:
🔴 Critical — 不改不能上线
1. 认证形同虚设:无密码登录 = 任何人可冒充
位置:app/api/auth/login/route.ts:6-13
现状:知道 teacherId(比如 "T_wang")就能直接设置 session cookie 登录成校长。没有密码、没有 OTP、没有 OAuth。
生产化方向:
- 最短路径:接入 Clerk / Auth.js (NextAuth) / Supabase Auth,支持手机号验证码或微信扫码登录
- 自研路径:教师表增加
passwordHash(bcrypt/Argon2),登录时校验;setSession 不要存 teacherId,存随机的 session token + 服务端 session 表(或 JWT 但需考虑吊销)
- 必须加登录失败速率限制(Rate Limiting),防止暴力枚举 teacherId
2. Cookie 安全属性缺失
位置:lib/auth.ts:17-22
jar.set(SESSION_COOKIE, teacherId, {
httpOnly: true,
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 7,
});
缺少 secure: true(HTTPS 环境必须)、没有 maxAge 对应的话应设 expires。如果用 HTTPOnly cookie,还需要CSRF 防护(虽然 SameSite=Lax 能挡一部分,但敏感操作如 DELETE /reset 应该用 Double Submit Cookie 或 CSRF Token)。
3. 单文档 JSON 存储 = 数据灾难
位置:lib/persistence.ts、lib/store.ts
整个数据库就是一行 jsonb / 一个 JSON 文件。这在生产中是定时炸弹:
- 竞态条件:两个并发的
saveDB() 会互相覆盖(没有文件锁/事务)
- 无法扩展:数据量大了以后,每次加载/保存都是全量 IO,延迟爆炸
- 多实例不兼容:Vercel 是 serverless,多个函数实例的
globalThis.__tutoringDB 互相独立,写入互相覆盖
- 没有备份/回滚
生产化方向:
- 立即将核心表拆分为关系型 Schema:
teachers、students、classes、grading_tasks、behavior_records 等独立表
- 使用 Prisma / Drizzle 做 ORM 和迁移管理
- 如果坚持 jsonb 单文档(小团队快速迭代),至少加乐观锁(
updated_at 版本控制)和行级锁(SELECT FOR UPDATE)
4. API Key 明文存储
位置:lib/types.ts:116-121、lib/seed.ts:289-294、app/api/settings/ai/route.ts
AI 提供商的 API Key 以明文存在数据库/jsonb 里,然后原样返回给前端(虽然做了 mask,但 hasApiKey 和 masked 版本仍然泄露了 Key 长度和前后缀)。
生产化方向:
- Key 必须加密存储(AES-256,密钥来自环境变量
ENCRYPTION_KEY)
- 前端绝不回传完整的 apiKey,设置时只接受新 Key 的写入,读取时只返回 masked 版本
- 考虑将 AI 调用迁移到服务端专属微服务/Edge Function,前端不直接触碰 Key
🟠 High — 强烈建议改
5. 文件访问无任何鉴权
位置:app/api/files/[key]/route.ts
任何人只要知道图片 key(比如 tasks/abc123.jpg)就能直接访问,连 cookie 都不检查。学生的作业照片可能包含隐私信息。
生产化方向:
GET /api/files/[key] 必须调用 requireUser(),并检查该用户是否有权查看这张图片(通过 task 关联的 classId/studentId 做 RBAC)
- 对于公网可访问的对象存储 URL(如 public Vercel Blob / OSS),设置短时效签名 URL(presigned URL)而不是永久直链
6. 输入验证完全缺失
位置:几乎所有 API 路由
所有 req.json() 都是直接 as 类型断言,没有运行时验证:
const body = (await req.json()) as { name?: string; subject?: Subject };
这意味着我可以 POST 一个超大字符串、恶意 JSON、甚至嵌套对象来搞事情。
生产化方向:
- 引入 Zod 做请求体验证:
const schema = z.object({
name: z.string().min(1).max(50),
subject: z.enum(["math", "chinese"]),
});
- 对图片上传加大小限制(如 5MB)和MIME 白名单(
image/jpeg, image/png, image/webp)
compressImage 在客户端压缩了,但服务端不能信任客户端,上传后应二次校验
7. 异步任务没有可靠队列
位置:app/api/grading/tasks/route.ts:106-124
用 next/server 的 after() 做异步批改。这在原型里没问题,但生产隐患很大:
- 如果服务器在
after() 执行期间重启/崩溃,任务会永远卡在 processing
- 没有重试机制:AI 提供商 503 一次就直接标记
failed
- Vercel Hobby 计划的
maxDuration 限制可能导致长批改被截断
生产化方向:
- 接入任务队列:BullMQ (Redis)、Inngest、QStash、或 AWS SQS + Lambda
- 任务状态机增加超时检测(如 processing 超过 10 分钟自动标记 failed 并重试)
- 至少实现指数退避重试(3 次)
8. AI 调用缺乏防护
位置:lib/ai/client.ts、lib/ai/grading.ts
- 120 秒超时固定写死,没有断路器(Circuit Breaker)。如果 AI 服务挂了,请求会堆积拖垮服务器
- 降级策略只有 mock 模式,真实模式下如果失败直接抛错
jsonMode: false 降级重试时,如果再次失败会进入无限递归(虽然概率低,但 chatCompletion 在 400 时递归调用自身,如果一直 400 会栈溢出)
生产化方向:
- 加断路器:连续失败 N 次后快速失败一段时间
- 限制递归降级次数(最多 1 次)
- 增加请求日志(耗时、token 数、模型名),便于排查问题和成本审计
🟡 Medium — 有余力时做
9. 缺少测试与类型安全
项目没有测试套件(package.json 里没有 test 命令)。生产环境至少要有:
- API 路由的单元测试(Vitest + MSW)
- 关键业务逻辑测试(RBAC、知识点匹配、行为分析)
- E2E 测试登录流和批改流(Playwright)
另外 tsconfig.json 建议开启 strict: true(如果还没开的话)。
10. 数据隐私与合规
- 学生姓名、家长电话、作业照片都是敏感个人信息。生产环境需要:
- 数据库加密(TDE 或列级加密)
- 传输加密(TLS 1.3,强制 HSTS)
- 访问审计日志(谁看了谁的数据)
- 数据保留策略与软删除(不要
DELETE 硬删,加 deletedAt)
- 如果面向中国市场,需考虑个人信息保护法合规;如果有海外用户,需考虑 GDPR
11. 缺乏可观测性
- 没有健康检查端点(
/health)
- 没有结构化日志(建议用 Pino / Winston,输出 JSON 格式便于接入 ELK/Loki)
- 没有性能监控(Vercel Analytics 可以补一部分,但 API latency 需要自定义 trace)
12. 前端一些小问题
client-api.ts 的 fetch 没有设置超时,网络异常时会一直挂起
compressImage 使用 canvas 压缩,但没有处理图片方向(EXIF Orientation),手机上传的照片可能会旋转 90 度
📋 优先级执行路线图(建议)
| 阶段 |
目标 |
主要工作 |
| Phase 1 (1-2 周) |
安全加固 |
接入 Auth.js/Clerk、密码/验证码、Cookie Secure、CSRF、Zod 输入验证、文件接口加鉴权 |
| Phase 2 (2-3 周) |
数据层重构 |
Prisma + 关系型 Schema、迁移脚本、API Key 加密存储、软删除 |
| Phase 3 (1-2 周) |
异步任务可靠化 |
接入 BullMQ / Inngest、任务超时与重试、断路器 |
| Phase 4 (持续) |
可观测性与合规 |
结构化日志、审计日志、健康检查、测试套件、GDPR/个保法合规 |
总结:这个原型的架构设计思路很好(插件化存储、AI 提供商抽象、RBAC 数据可见性),但它在认证、数据持久化、输入验证、异步可靠性这四个维度上完全是 demo 级别。现在直接部署给真实学校用的话……学生的作业照片可能会被爬虫抓走,校长的账号可能被人猜出来,数据库可能在并发写入时损坏。
先 Phase 1 吧,安全没做好之前别碰生产环境。要我帮你写其中哪个模块的改造代码?
好的,审完了。这毕竟是个原型(prototype),代码结构还挺清晰的,插件化设计也不错,但想上生产环境……差得远呢。我按优先级给你列一下:
🔴 Critical — 不改不能上线
1. 认证形同虚设:无密码登录 = 任何人可冒充
位置:
app/api/auth/login/route.ts:6-13现状:知道
teacherId(比如"T_wang")就能直接设置 session cookie 登录成校长。没有密码、没有 OTP、没有 OAuth。生产化方向:
passwordHash(bcrypt/Argon2),登录时校验;setSession不要存 teacherId,存随机的 session token + 服务端 session 表(或 JWT 但需考虑吊销)2. Cookie 安全属性缺失
位置:
lib/auth.ts:17-22缺少
secure: true(HTTPS 环境必须)、没有maxAge对应的话应设expires。如果用 HTTPOnly cookie,还需要CSRF 防护(虽然 SameSite=Lax 能挡一部分,但敏感操作如 DELETE /reset 应该用 Double Submit Cookie 或 CSRF Token)。3. 单文档 JSON 存储 = 数据灾难
位置:
lib/persistence.ts、lib/store.ts整个数据库就是一行 jsonb / 一个 JSON 文件。这在生产中是定时炸弹:
saveDB()会互相覆盖(没有文件锁/事务)globalThis.__tutoringDB互相独立,写入互相覆盖生产化方向:
teachers、students、classes、grading_tasks、behavior_records等独立表updated_at版本控制)和行级锁(SELECT FOR UPDATE)4. API Key 明文存储
位置:
lib/types.ts:116-121、lib/seed.ts:289-294、app/api/settings/ai/route.tsAI 提供商的 API Key 以明文存在数据库/jsonb 里,然后原样返回给前端(虽然做了 mask,但
hasApiKey和 masked 版本仍然泄露了 Key 长度和前后缀)。生产化方向:
ENCRYPTION_KEY)🟠 High — 强烈建议改
5. 文件访问无任何鉴权
位置:
app/api/files/[key]/route.ts任何人只要知道图片 key(比如
tasks/abc123.jpg)就能直接访问,连 cookie 都不检查。学生的作业照片可能包含隐私信息。生产化方向:
GET /api/files/[key]必须调用requireUser(),并检查该用户是否有权查看这张图片(通过 task 关联的 classId/studentId 做 RBAC)6. 输入验证完全缺失
位置:几乎所有 API 路由
所有
req.json()都是直接as类型断言,没有运行时验证:这意味着我可以 POST 一个超大字符串、恶意 JSON、甚至嵌套对象来搞事情。
生产化方向:
image/jpeg,image/png,image/webp)compressImage在客户端压缩了,但服务端不能信任客户端,上传后应二次校验7. 异步任务没有可靠队列
位置:
app/api/grading/tasks/route.ts:106-124用
next/server的after()做异步批改。这在原型里没问题,但生产隐患很大:after()执行期间重启/崩溃,任务会永远卡在processingfailedmaxDuration限制可能导致长批改被截断生产化方向:
8. AI 调用缺乏防护
位置:
lib/ai/client.ts、lib/ai/grading.tsjsonMode: false降级重试时,如果再次失败会进入无限递归(虽然概率低,但chatCompletion在 400 时递归调用自身,如果一直 400 会栈溢出)生产化方向:
🟡 Medium — 有余力时做
9. 缺少测试与类型安全
项目没有测试套件(
package.json里没有 test 命令)。生产环境至少要有:另外
tsconfig.json建议开启strict: true(如果还没开的话)。10. 数据隐私与合规
DELETE硬删,加deletedAt)11. 缺乏可观测性
/health)12. 前端一些小问题
client-api.ts的fetch没有设置超时,网络异常时会一直挂起compressImage使用 canvas 压缩,但没有处理图片方向(EXIF Orientation),手机上传的照片可能会旋转 90 度📋 优先级执行路线图(建议)
总结:这个原型的架构设计思路很好(插件化存储、AI 提供商抽象、RBAC 数据可见性),但它在认证、数据持久化、输入验证、异步可靠性这四个维度上完全是 demo 级别。现在直接部署给真实学校用的话……学生的作业照片可能会被爬虫抓走,校长的账号可能被人猜出来,数据库可能在并发写入时损坏。
先 Phase 1 吧,安全没做好之前别碰生产环境。要我帮你写其中哪个模块的改造代码?