Files
photo-live/docs/plans/progress/P1a-验收记录.md
T
浅念 8a09001525 docs: p1a acceptance record
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-18 00:06:44 +08:00

7.8 KiB
Raw Blame History

P1a 验收记录

  • 阶段:P1a 后端核心链路(计划 docs/plans/2026-09-17-P1a-后端核心链路.md,Task 1–26)
  • 日期:2026-09-18
  • 基线 commits:d973c71..HEAD(chore: init repository skeleton 起,共 28 个提交,不含本记录提交)
  • 执行模式:子代理驱动 + 双审(每任务先写失败测试→实现→通过→提交;跨任务由子代理接力,主代理与复核代理双重审查)
  • 回归门:pnpm lint && pnpm typecheck && pnpm test && pnpm e2e && pnpm build 全绿(数字见文末统计表)
  • 基础设施:photo-live MySQL/Redis/MinIO compose(infra/),测试全部跑在真实容器上(MySQL TRUNCATE 隔离 + MinIO 前缀清理 + Redis/BullMQ 真队列)

A01 认证与会话(API 级)

  • 证据:services/api/test/auth.e2e-spec.ts 14 用例全绿——正确登录(tokenHash=sha256、库内不存明文、audit auth.login)、错误密码/不存在账号同码 401(防枚举)、未激活(credentialStatus=pending)/停用(sourceStatus=disabled)/本地锁定(localLocked)正确密码也不放行、限速锁定与解锁窗口、会话滑动续期(旧令牌不轮换)、登出即撤销(refresh 401)、seed-admin 幂等(重跑不改写密码,seed.spec.ts)。
  • 全链路佐证:e2e.full-chain.e2e-spec.ts admin/staffB 均走真实 POST /auth/password 登录。
  • App 端(微信登录等)留 P3:P1a 仅密码会话体系。

A02 权限逐次校验(防中间人/越权)

  • 证据:services/api/test/permissions.spec.ts 11 用例——canViewAlbum/canManageAlbum/canUploadToAlbum/canManagePhoto 对 owner/collaborator/admin/非成员/被移除成员的全组合,且每次调用直查 DB 不缓存。
  • e2e 全程逐次校验:uploads.e2e(非成员/他册成员 404、被移除成员 renew/complete 403)、photos.e2e(协作者对他人上传 403)、full-chain(owner 移除 staffB 后,同一有效会话的 init → 404 ALBUM_NOT_FOUND、管理历史上传 → 404 PHOTO_NOT_FOUND,权限链即刻失效)。

A03 上传幂等与并发安全

  • 证据:services/api/test/uploads.e2e-spec.ts(init 幂等复用同 uploadId+photoId、同 clientTaskId 异参 409、同名文件不同 clientTaskId 两会话两照片不覆盖、complete 幂等不重复入队/不重复 audit、字节/类型不符 400 后会话可重试、staging 删后 re-complete 走 copyIfAbsent created:false)。
  • full-chain 断言:同名 show.jpg 两传(900px 真 JPEG 双 presign PUT 实写)→ complete 其一调两次 → photo_jobs 各恰 1 条 → Worker 发布后 outbox 全库恰 2 条 photo.published,无第三条;重复 processPhoto → already 不再发自带事件。

A06 上传即公开(发布事务)

  • 证据:services/worker/test/publish.e2e-spec.ts 7 用例——发布事务(变体对象 + photo_variants×2 + photos ready/published/entityVersion+1 + outbox payload 契约全集,单事务一致)、重复处理幂等、不可处理态跳过、sweepOnce 认领/僵尸重排、真实 BullMQ Worker happy 路径、损坏文件 3 次退避终败且 visibility 保持 unpublished(失败不公开)、pending_upload 跳过。
  • full-chain:API complete 入队 → 真实 Worker 消费 → 双照片 ready+published、MinIO 变体对象(head 复核 jpeg 非零字节)、photo_jobs done(attempt=1)。
  • Worker 进程冒烟(Task 21 关注点):pnpm dev:worker 实机启动,photo worker started(concurrency=2、sweep 30s)后首轮 sweep claimed=0 正常完成,无崩溃退出。

A07 公开读性能/稳定性(后端级)

  • 证据:services/api/test/public.e2e-spec.ts——35 张已发布照片跨页稳定(缺省 30 + 次页 5,并集恰 35 无重复;(publishedAt,id) 键集游标从不回看已发射行)、同 publishedAt 批次 id DESC 决胜、hidden/deleted/unpublished 永不出现、无变体行的发布照片防御性跳过不炸公开页、签名 URL 可取回真实字节。压测级指标留 G1。

A13 凭证安全(设计级)

  • presign 短期凭证:上传 PUT 与读取 GET 均为预签名 URL,TTL 由 SIGNED_URL_TTL_SECONDS=900(env)控制,会话过期滑动续期走 renew。
  • 无长期客户端密钥:客户端仅持有会话 token(32B 随机、库存 sha256 哈希)。
  • DB 不存签名 URL:库中只有对象键(objectKey),URL 每次请求现签(公开单图即契约规定的过期刷新入口)。
  • OSS 生命周期核验(staging 过期清理等)随 G1/P5 落实。

HEIC 实测结论(D12 复核项)

  • HEVC 压缩 HEIF:预编译 libvips 无 HEVC 编码器,编码用例自跳过(pipeline.spec.ts 捕获后 skip 并说明);iPhone 真机样本端到端验证留 G2。
  • AV1 压缩 HEIF:走同一 libheif 解码路径与 heif 格式桶,解码→JPEG 变体用例通过。

偏差与发现登记

# 事项 处理/结论
1 quay.io 镜像 MinIO 镜像改用 quay.io/minio/*(Docker Hub 官方仓库已删除);P2 ov 侧同样
2 Prisma 校对 migrate 产物与 schema 校对存在手工修正流程(生成后人工核对差异再提交)
3 pnpm -- 语法 透传参数给包脚本必须 pnpm <cmd> -- <args>,曾因缺 -- 踩坑,已记入执行注意
4 presigner 不签 content-type S3 presign PUT 不覆盖 Content-Type 头 → complete 时以 head().contentType 与申明值比对兜底(uploads.e2e 有专例)
5 P2002 matcher 存量 bug isUniqueViolationOn 对复合索引匹配存在存量缺陷,已修复并回归
6 maxWorkers/turbo concurrency 串行化 api jest maxWorkers: 1 + turbo concurrency: 1:DB 型 spec 共库并行会互相 TRUNCATE 脏数据
7 turbo e2e 并发竞态 test:e2e 同样串行化(依赖同 MySQL/Redis/MinIO 实例),避免跨包 e2e 并发互扰
8 collaborator 重新公开 403 hidden→published 恢复 P1a 从严仅 owner/admin(待需求确认,文件头已登记;photos.e2e 覆盖该 403)
9 nest-pino → nestjs-pino 包名勘误:正确包为 nestjs-pino(曾误引 nest-pino)
10 ESM 方案 (a) @photo/contracts 为 ESM 包,运行时经 require(esm) 消费 dist(Node ≥ 22.12 前提;jest 侧 moduleNameMapper 映射 TS 源)
11 request-id UUID 请求 id 采用 UUID(错误信封/日志/响应头三处一致,x-request-id)

未尽事项 / 移交

  • P1b:viewer 基础页;admin 最小页;宣传图/封面/Logo 上传链路(album_assets presign+确认,复用照片上传机制,R09 依赖);GET /albums/{albumId}/photos 管理照片列表(含未发布/隐藏态);用户账号查询接口(邀请协作者按 account 搜索);Playwright 冒烟。
  • P2 关注:outbox 已落库但暂无消费者(副本/通知接入时按 eventId 去重、entityVersion 单调消费);公开端点无限速(点赞限流引入时统一评估公开面配额)。
  • G1/P5:OSS 生命周期核验;G2:HEVC-HEIF 真机样本端到端验证。

测试统计汇总(2026-09-18 回归门实测)

包 单测 suites/tests e2e suites/tests
@photo/contracts 2 / 8 –
@photo/db-cn –(schema 校验随 migrate/生成流程) –
@photo/storage 1 / 6(真实 MinIO 集成) –
@photo/api-cn 6 / 46 9 / 120(auth 14、albums 20、members-categories 15、uploads 20、photos 14、public 17、health 4、db.semantics 6、full-chain 10)
@photo/worker 1 / 8 1 / 7(publish)
合计 10 / 68 10 / 127
  • 附加门:lint 5/5 包、typecheck 8 任务、build 5 任务全绿;pnpm dev:worker 启动冒烟通过。
  • 总测试数(单测 + e2e):195。