Skip to content

demo 审查 #6

Description

@pilirec

好的,审完了。这毕竟是个原型(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.tslib/store.ts

整个数据库就是一行 jsonb / 一个 JSON 文件。这在生产中是定时炸弹:

  • 竞态条件:两个并发的 saveDB() 会互相覆盖(没有文件锁/事务)
  • 无法扩展:数据量大了以后,每次加载/保存都是全量 IO,延迟爆炸
  • 多实例不兼容:Vercel 是 serverless,多个函数实例的 globalThis.__tutoringDB 互相独立,写入互相覆盖
  • 没有备份/回滚

生产化方向

  • 立即将核心表拆分为关系型 Schema:teachersstudentsclassesgrading_tasksbehavior_records 等独立表
  • 使用 Prisma / Drizzle 做 ORM 和迁移管理
  • 如果坚持 jsonb 单文档(小团队快速迭代),至少加乐观锁updated_at 版本控制)和行级锁SELECT FOR UPDATE

4. API Key 明文存储

位置lib/types.ts:116-121lib/seed.ts:289-294app/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/serverafter() 做异步批改。这在原型里没问题,但生产隐患很大:

  • 如果服务器在 after() 执行期间重启/崩溃,任务会永远卡在 processing
  • 没有重试机制:AI 提供商 503 一次就直接标记 failed
  • Vercel Hobby 计划的 maxDuration 限制可能导致长批改被截断

生产化方向

  • 接入任务队列:BullMQ (Redis)、Inngest、QStash、或 AWS SQS + Lambda
  • 任务状态机增加超时检测(如 processing 超过 10 分钟自动标记 failed 并重试)
  • 至少实现指数退避重试(3 次)

8. AI 调用缺乏防护

位置lib/ai/client.tslib/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.tsfetch 没有设置超时,网络异常时会一直挂起
  • 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 吧,安全没做好之前别碰生产环境。要我帮你写其中哪个模块的改造代码?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions