Files
photo-live/docs/plans/progress/P2-验收记录.md
T
浅念 79eb975096 docs: p2 acceptance record
P2 Task 12: 验收记录(A09/A10/A14/A21 部分/A26/A27 仿真级 + A25 部分
本地直读证据;D07/D11 初值确认——30min 浏览窗口、2s 推送/5min 对账/
60s 回传建议维持待 P5 实测冻结;13 项偏差登记;P4a/P5 移交清单;回归
门实测统计——全仓 448 测试绿)+ 总纲 P2 行进度注记。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-18 20:17:54 +08:00

12 KiB
Raw Blame History

P2 验收记录

  • 阶段:P2 双地域仿真 + 互动(计划 docs/plans/2026-09-18-P2-双地域仿真与互动.md,Task 1–12)
  • 日期:2026-09-18
  • 基线 commits:a4b95ab..HEAD(P2 计划收尾提交之后至 Task 11 跨地域套件 452ec12,共 10 个实施提交,不含本记录提交)
  • 执行模式:子代理驱动 + 双审(每任务先写失败测试→实现→通过→提交;跨任务由子代理接力,主代理与复核代理双重审查)
  • 回归门:pnpm lint && pnpm typecheck && pnpm test && pnpm e2e && pnpm build 全绿(数字见文末统计表)
  • 基础设施:photo-live compose 以 profiles 起双地域仿真——mysql-cn(3306)/redis-cn(6379) + mysql-ov(3309,宿主 3307 被占,见偏差 #9)/redis-ov(6381) + MinIO;ov 入口与 cn 入口同一 services/api 代码库按部署角色装配(app-ov.module 显式挑选);sync 进程三循环(推送 2s / 对账 5min / 统计回传 60s)

A09 点赞计数(仿真级)

  • 后端:services/api/test/likes-realtime.e2e-spec.ts 6 用例——① 连点累计(delta 求和进权威 likeTotal)+ 同 batchId 幂等重试不加计 + outbox_stat_events 权威快照 + 参数/404 校验;② sessionId 滑窗限速超额 429;③ WS ping→pong/幽灵 token error 帧/错误后连接保持;⑥ 隐藏照片后点赞 404(公开面与互动面同一可见性口径);④⑤(房间隔离/合并广播)见下 A10/A21 条目。
  • 前端:apps/viewer/e2e/likes.spec.ts——大图真实点赞 ×3 → 权威校准 3 → 刷新持久;WS 不可达三败降级轮询校准后重连。
  • 跨境一致:cross-region 套件 ①⑥(见 A27)——cn 副本 likeCount 终值 == ov 权威 Σdelta。

A10 实时隔离(仿真级)

  • likes-realtime.e2e-spec.ts ④ 房间隔离:双相册双房间并发订阅,跨册点赞只达本册房间,另一房间零消息(帧级断言)。
  • 网关模型:房间 = album:{albumId},subscribe 逐相册校验公开性;广播不排除发送者(过滤依据是 originBatchIds,非连接身份)。

A14 浏览统计(仿真级)

  • services/api/test/visits.e2e-spec.ts 11 用例——① 首访计入/窗口内去重/跨访客计入/GET album 真实 viewCount;② requestId 幂等重试回显原结局;③ 越窗再计;④ 并发同访客两 requestId 恰一个 counted(session 唯一约束仲裁);⑤ 过期 requestId 重放拒绝;⑥ 404/400;⑦ visitorId+IP 固定窗限速(幂等快速通道不耗预算);⑧ 上报排除规则(读接口/点赞/WS 帧一律不触碰浏览数据);⑨ WS stats.updated 计入即广播、重复不广播;⑩ runCleanupOnce 超窗清理;⑪ 过期批次重放边界(登记偏差 #3 的固化用例)。
  • 前端:apps/viewer/e2e/likes.spec.ts 浏览上报组——全新上下文 +1、刷新不重复、第二上下文再 +1;visitorId 跨标签共享(localStorage+BroadcastChannel,vitest 覆盖协调写)。

A21 气泡体验(部分:后端合并 + 前端自身过滤)

  • 后端合并验证:likes-realtime.e2e-spec.ts ⑤——合并窗内两批 → 恰一条 likes.burst(delta 求和 + 双 originBatchIds,客户端过滤自身批次的依据);窗参数 LIKE_BURST_MERGE_MS 惰性 env。
  • viewer 自身过滤 e2e:likes.spec.ts 气泡组——双真实浏览器上下文,他赞出气泡、己赞(originBatchIds 含己 batchId)不出;「减少动画」设置下他赞无气泡、计数照常校准。
  • P4b 移交:页面不可见暂停动画(visibilitychange)有实现与单测,全端动画体验验收(含 App 端推送通道)随 P3/P4b。

A26 内容同步(仿真级)

  • services/sync/test/sync.e2e-spec.ts ④⑨——隐藏后旧 published 以新 eventId 重放被版本比较拒绝;乱序 v7 hidden 先应用、迟到 v6 published 不复活。
  • services/sync/test/reconcile.e2e-spec.ts——当前态清扫重放对已追平实体判重/判旧;digest 比对触发 bootstrap 重建;「投后即失」漏账追赶。
  • services/sync/test/cross-region.e2e-spec.ts ④——photo3 隐藏(v3)后伪造其旧 published 事件(v2 载荷、全新 eventId)直推 ov content-batches → applied=0 仍隐藏;全量 bootstrap 重跑后仍隐藏(当前态快照不含复活路径)。
  • 「不显示缺资源图片」:photos_ov 行缺失即缺席(列表无行、单图 404 PHOTO_NOT_PUBLIC),public-ov spec ⑤ 覆盖。

A27 统计回传(仿真级)

  • services/sync/test/stat-return.e2e-spec.ts 7 用例——① 逐次点赞回传 cn likeCount 被权威快照替换(2→5→10,绝非 2+5+10 累加);② 浏览回传 viewCountReplica + statsSyncedAt 落位(同步时间展示,cn listAlbums 已暴露);③ 重复回传幂等(清 sentAt 重发 → 同版本全判旧 accepted=0,值分毫不动);④ 乱序快照仅更高 statVersion 替换(版本权威,total 可下调);④b 发送端聚合(同相册 v5/v3 折叠只发 v5、两行全确认);⑤ cn 管理展示;⑥ 新端点守卫/校验。
  • 跨境闭环:cross-region ①(两会话点赞 + 两访客浏览 → 一轮回传 → cn 副本一致);⑥(50 并发点赞回传后 cn == ov 权威 Σdelta、30 并发浏览回传后 viewCountReplica=17)。

A25 海外直读(部分:本地直读语义)

  • 仿真级证据:cross-region.e2e-spec.ts ①——观众路径全程只打 ov 入口(公开相册/列表/单图/点赞/浏览/WS 六类请求一律指向 ovBase;cn 入口仅承载 staff 上传与内部同步动作),列表 likeCount/viewCount 均为 ov 副本本地值,无跨境读。
  • P5 移交:真实地域部署 + 全球 CDN 图片分发 + 压测容量结论(当前为 compose 仿真,图片仍走本地 MinIO 签名 URL)。

D07 / D11 初值确认(P2 验收前给初值,P5 实测冻结)

决策 初值(contracts 常量,env 可覆盖) 结论
D07 浏览统计窗口 VISIT_WINDOW_MINUTES = 30(requestId 保留 35min、请求限速 60/窗) 建议维持 30 分钟——与需求说明书建议值一致;真实流量分布 P5 复核
D11 跨境同步参数 推送 SYNC_PUSH_INTERVAL_MS = 2000(批 100,high 两段优先);对账 SYNC_RECONCILE_INTERVAL_MS = 300000;统计回传 STAT_RETURN_INTERVAL_MS = 60000;中断策略 = 幂等重试(eventId 受理锚 + entityVersion 单调,失败指数退避 60s 封顶,整批不标记下轮重推) 建议维持(2s 推送 / 5min 对账 / 60s 回传)——仿真规模下语义正确性已验,容量与真实 RTT 下的参数调优随 P5 压测冻结

偏差与发现登记

# 事项 处理/结论
1 reconcile 返回事件流 cn /internal/sync/reconcile 按 entityType 返回当前状态事件清单(合成 eventId recon:<type>:<id>:<ver>),非版本水位清单——重放走既有 content-batches 管道即幂等,无需第二条拉取通道;真实部署若需缩量改 per-entity 水位比对(P5 演进项)
2 digest 形状 每相册权威计数 {categoryCount, publishedPhotoCount..photoCount 区间, assetCount}——照片取区间口径(从未发布过的 hidden/deleted 照片 ov 合法无行,缺席即撤下),ov 行数落区间内即判一致
3 过期点赞批次接受 + 上限兜底 like_batches 按 24h 清理后,同 (photoId,sessionId,batchId) 重放会以新批次身份重新加计(永久幂等账本与清理诉求直接冲突,理论边界);缓解 = sessionId 滑窗 500/窗口 + IP 2000/窗口 + 基础限速;visits spec ⑪ 固化该行为
4 紧急停用审计 pino 字段 计划原文「写 ov 侧日志文件」落地为 pino 结构化字段 audit=emergency(action/albumId/publicToken/reason/already*)——免新增文件 transport 依赖,P5 运维按字段路由聚合到审计存储
5 visits 重试语义修订 requestId 行龄 > 35min → counted=false 无论原结局(清理后行不存在与「从未提交」数据上不可区分,「窗口外拒绝重放」以清理端为拒绝点);行龄内重试回显原结局(确认响应丢失的闭环依赖)
6 EventEmitter2 单进程 ov 实时广播为进程内事件总线(OV_LIKE_BATCH_COMMITTED 等三事件),单实例部署假设;多实例横向扩展需 Redis pub/sub 跨进程扇出(REDIS_URL_OV 已就位,事件名/载荷可原样迁移为频道/消息体,P5 部署项)
7 hot 排序偏移游标 热门序游标为点赞快照 offset(30s TTL 缓冲,窗口内冻结、photoId 决胜),与最新序 (publishedAt,photoId) 键集游标不同形——排序为快照语义,切换即重拉(对齐技术方案 §7)
8 contracts ./realtime 子路径导出 WS 帧类型/常量新增 @photo/contracts/realtime 子入口(package.json exports);主入口同时重导出,前端可深引入收敛产物
9 mysql-ov 宿主端口 3309 宿主机 3307 已被用户另一项目(yunke-mysql)占用,mysql-ov 发布端口改绑 3309(用户裁决 2026-09-18,commit a4b95ab;compose/env/.env.example 同步)
10 Prisma client 双输出目录 db-ov generator 显式 output = ../generated/client(db-cn 维持 node_modules 内默认生成)——双 PrismaClient 并存不互踩;生成产物不入库,pnpm db:generate 统一生成
11 visit requestId 跨相册不复核 requestId 为 visit_requests 全局主键,重试快速通道按行龄裁决即回显,不复核「该 requestId 原属哪本相册」(幂等键语义 = 单次上报;跨册重用属客户端契约误用,风险面为回显错误 viewCount,不产生加计)
12 statsSyncedAt 仅 views 驱动 albums.statsSyncedAt 只由 views 快照应用推进(相册级同步时间);likes 快照替换 photos.likeCount 不触碰相册时间(照片级无相册时间语义,cn 管理列表按相册展示同步时间)
13 ov 孤儿相册仅告警 digest 比对发现 ov 有而 cn 无的相册(cn 行已删)仅告警登记——无 cn 快照源不可 bootstrap 重建;清理动作随 P4a 管理页(sync_state/sync_inbox 延迟与积压口径展示)

未尽事项 / 移交

  • P4a:管理页同步状态展示(相册级 statsSyncedAt 已在 listAlbums 暴露,UI 消费随后台完备);ov 孤儿相册清理入口;批量隐藏;清理任务生产化参数复核(LIKE_BATCH_WINDOW_HOURS / VISIT_REQUEST_TTL_MINUTES / 清理节奏)。
  • P5:真实地域部署(海外 ECS 已就绪)+ 域名 + 全球 CDN 接入与失效策略;跨地域故障演练(断网/积压/恢复已在仿真覆盖,真实拓扑重演);容量压测与 D07/D11 参数冻结;trust-proxy 配置(点赞/浏览限速当前取 req.ip,经反代需正确取 X-Forwarded-For);EventEmitter2 → Redis pub/sub 多实例扇出;reconcile per-entity 水位比对缩量。

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

包 单测 suites(files)/tests e2e suites(tests)
@photo/contracts 3 / 22 –
@photo/db-cn / @photo/db-ov –(schema 校验随 migrate/生成流程) –
@photo/storage 1 / 6(真实 MinIO 集成) –
@photo/api-cn 7 / 51 16 / 179(P1 12/148 基础上 +likes-realtime 6、visits 11、emergency 7、public-ov 7 = +31)
@photo/worker 1 / 8 1 / 7(publish)
@photo/sync(P2 新包) –(passWithNoTests) 4 / 27(sync 10、reconcile 4、stat-return 7、cross-region 6)
@photo/viewer vitest 16 / 105 playwright 6 个 spec / 21 用例(album 5、feed 3、viewer 4、smoke 1、journey 1、likes 7)
@photo/admin vitest 4 / 15 playwright 2 个 spec / 7 用例(login 5、albums 2)
合计 32 / 207 29 / 241(jest 21 suites / 213 用例 + playwright 8 个 spec / 28 用例)
  • 附加门:lint 9/9 任务、typecheck 13/13 任务、build 9/9 任务全绿。
  • 总测试数(单测 + e2e):448(P1b 出口 324 → +124)。
  • 跨地域套件(Task 11):六幕串行剧本 12–16s 稳定通过(双入口进程内起真服务,真实双 MySQL/MinIO/HMAC/WS,限速滑窗键跨 spec 清场)。