docs: p1a acceptance record
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 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**。
|
||||
Reference in New Issue
Block a user