From 79eb975096b7efb8490cafba31287a77f027ea2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Fri, 18 Sep 2026 20:17:54 +0800 Subject: [PATCH] docs: p2 acceptance record MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/plans/2026-09-17-实施总纲.md | 2 + docs/plans/progress/P2-验收记录.md | 103 +++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 docs/plans/progress/P2-验收记录.md diff --git a/docs/plans/2026-09-17-实施总纲.md b/docs/plans/2026-09-17-实施总纲.md index e8a3a13..2547574 100644 --- a/docs/plans/2026-09-17-实施总纲.md +++ b/docs/plans/2026-09-17-实施总纲.md @@ -74,6 +74,8 @@ Schema 所有权:国内库表(users、albums、photos、upload_sessions、ou > **进度:P1 整体完成(P1a + P1b),2026-09-18,tag `p1`**。P1a 验收记录见 docs/plans/progress/P1a-验收记录.md(195 测试全绿,A01-A03/A06/A07/A13 证据落档);P1b 验收记录见 docs/plans/progress/P1b-验收记录.md(A08 基础级 / A12 部分 / R09 / P1a 终审修缮补登,全仓 324 测试绿,含跨端 Playwright 联调冒烟)。**G1(可选关口)正式登记跳过**:真实 OSS 凭证未提供,按出口约定「无凭证显式跳过并登记」处理(本地 MinIO 已覆盖 presign/head/禁止覆盖/受控复制全语义,凭证到位后可补跑)。 | P2 双地域仿真 + 互动 | ov-app + outbox/inbox 同步(版本、墓碑、高优先级下线、补漏对账);点赞批次幂等;WebSocket 气泡(自身批次过滤、后台暂停);浏览统计窗口去重;统计快照回传;观众页切换至 ov-api 并新增最新/热门排序 | T25(仿真部分)T26 T13 T19 T27 T24,T11 完成 | A09 A10 A14 A21 A26 A27(仿真级) | 回归全绿 + 验收记录;D07/D11 初值确认 | 无 | + +> **进度:P2 完成,2026-09-18,tag `p2`**。验收记录见 docs/plans/progress/P2-验收记录.md(A09/A10/A14/A21 部分/A26/A27 仿真级 + A25 部分(本地直读)证据落档,全仓 448 测试绿,含跨地域六幕集成套件);**D07/D11 已给初值**(30min 浏览窗口;2s 推送 / 5min 对账 / 60s 统计回传),建议维持、P5 实测冻结。 | P3 Flutter App | 登录、相册配置、协作、持久化上传队列(准备阶段状态机、账号隔离、跨设备服务端历史)、连拍入队、原生后台传输桥接 | T03 T08 T09 T21 | A04 A05 A15 A19(代码 + 模拟器级) | 回归全绿 + 验收记录 | G2(强制)、G3(资源) | | P4a 后台完备 + 账号治理 | Art Design Pro 全功能后台:相册/照片/用量管理与告警、上传公开开关、批量隐藏、负责人交接、水印版本与历史重生成、下载两级控制、后台语言切换与账号管理(激活停用/重置密码/角色)、用户定期同步任务与管理页(mock 源) | T10 T20 T22 T12 T30 T04(mock 部分) | A16 A17 A18 A20 A23 A29(mock 级) | 回归全绿 + 验收记录;D06 前置 | G4(真实接口) | | P4b 传播组件 | embed-loader + 展示位管理 + 轮播 iframe:configVersion 校验、旧响应丢弃、不可见暂停、postMessage 白名单、data-lang 语言配置 | T14 T23 T15 | A11 A12 A22;A23 组件部分 | 回归全绿 + 验收记录 + 全端语言验收(T15/A12) | 无 | diff --git a/docs/plans/progress/P2-验收记录.md b/docs/plans/progress/P2-验收记录.md new file mode 100644 index 0000000..b6d7a77 --- /dev/null +++ b/docs/plans/progress/P2-验收记录.md @@ -0,0 +1,103 @@ +# 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:::`),非版本水位清单——重放走既有 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 清场)。