P4a Task 10 收口:回归门全绿(web lint/typecheck/build 9/9 包任务, 单测 271 + e2e 318 = 589;mobile analyze 0 issue + 280/280 零漂移)。 验收记录含 A16/A17/A18/A20/A23/A29 证据、D06 工作默认登记、契约 增补汇总(36→51 operations + DOWNLOAD_DISABLED)、偏差 14 项、 G4 待真实接口清单(UserSourcePort 替换点 + D08 字段确认)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
14 KiB
P4a 后台完备 + 账号治理 实施计划
✅ 阶段已完成(2026-09-20,tag
p4a,基线 45d4651..HEAD)。 完成状态的权威记录是 docs/plans/progress/P4a-验收记录.md;Task 1-10 全部交付、勾选已回填。偏差(用量实时 SUM、SVG 文字水印 + G5 字体、mock 源 department 忽略、下载 window.open 策略、/admin/users 双模式等 14 项)与 G4 待真实接口清单(UserSourcePort 替换点 + D08 字段确认)见验收记录。
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 管理后台达到运营完备:照片批量管理(逐项结果)、负责人交接、水印版本化与历史重生成、下载两级控制(含 viewer 下载 UI 与微信引导)、用量统计与阈值告警、账号管理(激活/停用/重置密码/角色)、用户定期同步任务(mock 源先行)与同步管理页。
Architecture: 全部在既有 cn-api / admin / viewer 上增量;水印重生成走 BullMQ 新队列(cn 侧,worker 消费);用户同步为独立调度任务(services/api 内 @Cron 风格 setInterval + 互斥锁)+ mock provider(D08 到位后换适配器);用量统计以 photos/photos_ov 行数据 + 可配置计数表为基础(真实流量计费数据 P5 接 OSS 用量 API,登记偏差)。
Tech Stack: 沿用既有(NestJS/Prisma/BullMQ/Art Design Pro/Vue3 viewer);唯一新依赖 = admin 端 qrcode 渲染库(Task 9 分享卡)。
对应验收: A16 A17 A18(下载开关组合)A20(水印重生成不破坏)A23(告警+组件暂停已有 P2,本阶段补后台展示)A29(mock 级用户同步)。
依赖基线: tag p3(45d4651)。D06 前置:下载/水印默认值——沿用 P1 默认(downloadEnabled=true、originalDownloadEnabled=false、watermark 关闭),水印开启为相册级显式操作,登记为工作默认(用户可随时改)。
G4 边界:用户同步以 mock provider 落地全语义(锁/水位/幂等/失败不停用/补拉);真实接口契约到位后仅替换 provider(P5/G4)。
Task 1: 契约扩展(P4a 批次)
Files: Modify packages/contracts/openapi.yaml(+gen)
- 新增路径(遵循既有风格):
POST /albums/{albumId}/photos/hide-batch:{photoIds: string[] (1..200)} → 200 {results: [{photoId, ok, code?}]}(逐项结果,A16);403/404 同管理列表POST /admin/albums/{albumId}/transfer:{targetUserId, keepAsCollaborator: boolean} → 200 {album}(A17:owner 转移、历史上传人不变)POST /albums/{albumId}/watermark-jobs:{config: {enabled, position?, opacity?, scalePercent?, text?{content?, fontSize?}}} → 202 {jobId, configVersion};GET /albums/{albumId}/watermark-jobs→ {current: {configVersion, config}, jobs: [{jobId, configVersion, state, progress, total, failed, errorCode?, createdAt}]}(A18/A20)GET /public/albums/{token}/photos/{photoId}/download?variant=browse|original→ 200 {url, expiresAt, variant}(匿名;按相册两级开关校验,A18;404 PHOTO_NOT_PUBLIC/403 ALBUM_NOT_PUBLIC…下载关闭=404 NOT_FOUND 语义?→ 用 403 DOWNLOAD_DISABLED 新码)——新增错误码 DOWNLOAD_DISABLED- 账号管理(tag admin):
GET /admin/users?query=&status=&cursor=(管理列表:id/account/displayName/role/sourceStatus/localLocked/credentialStatus/locale/createdAt);PATCH /admin/users/{id}{role?, localLocked?, displayName?};POST /admin/users/{id}/reset-password{newPassword} → 200 {initialPassword?}(管理员重置:直接设新密码返回给管理员,或生成随机——决策:body 可选 newPassword,缺省服务端生成随机返回,e2e 两种都测);POST /admin/users/{id}/activate/deactivate(deactivate=localLocked=true+撤销该用户全部会话) - 用户同步管理(tag admin):
GET /admin/user-sync/status→ {lastRunAt, lastSuccessAt, state, sourceVersion, counts: {added,updated,disabled,failed}};GET /admin/user-sync/runs?cursor=(历史批次);GET /admin/user-sync/errors?runId=(失败明细);POST /admin/user-sync/runs(手动补拉触发 → 202 {runId}) - 用量(tag admin):
GET /admin/usage/summary→ {albums: [{albumId, title, photoCount, originalBytes, variantBytes, totalBytes}], totals, thresholds: {storageBytes?, perAlbumBytes?}, alerts: [{id, kind, albumId?, message, createdAt, resolvedAt?}]};PUT /admin/usage/thresholds{storageBytes?, perAlbumBytes?}(告警接收人 P5 通知渠道,本阶段落库+列表,A23 后台侧)
- gen + lint + 全回归绿;⏸ 快速确认点(要点摘要给用户)
- Commit
feat(contracts): p4a admin governance, watermark, download and usage endpoints
Task 2: 批量隐藏 + 负责人交接(后端)
- e2e(先行):批量隐藏 3 张(2 成功 1 已删→ok:false code PHOTO_NOT_PUBLIC)逐项结果+audit(photo.hide×2);outbox photo.hidden×2 高优先级;ov 端同步不可见(复用 sync 测试基建)。交接:admin 转移给 staffB、keepAsCollaborator=true/false 两例→owner 变更+成员角色正确+历史上传人不变(photos.uploaderId 不动)+audit album.transfer;非 admin 403;目标无效账号 400
- 实现(album 域事务 + PermissionsService:仅 admin 可交接)
- Commit
feat(api): batch hide with per-item results and owner transfer
Task 3: 水印版本化 + 历史重生成(后端+worker)
- cn 迁移(校对手决):albums 已有 watermarkVersion;新增
watermark_configs(albumId+configVersion 唯一, config Json, createdAt)与watermark_jobs(jobId, albumId, configVersion, state pending/running/done/failed, progress/total/failed 计数, errorCode, timestamps) - worker 新消费者(QUEUE_WATERMARK,cn Redis):逐张:读原图→按 config 合成新水印图(sharp composite:position/opacity/scale/text 基本实现,文字水印用 SVG render——sharp 无原生 text,登记实现方式)→ 写 variants 新键
{version}/watermark.jpg→upsert photo_variants(variant=watermark, version=新)→原子:全部成功才切换(job 级聚合,失败照片保留旧浏览图,A20)→ 完成时 outbox album.updated(新 watermarkVersion)→ 失败可重试(job 重入按 photo 粒度幂等) - 设置 API:POST watermark-jobs(创建 config 版本+job 入队,仅创建者/admin);GET 状态
- e2e:开启水印→传 1 新照(发布图含水印变体)→发起历史重生成(3 张)→进度推进→done→GET 列表照片 browse URL 指向水印版(bytes 变化/内容校验)→原图 objectKey 不变(A20 核心);中途 1 张注入失败→job failed→重试→done;关闭水印→新照片无水印
- Commit
feat(worker): watermark versioning with atomic history regeneration
Task 4: 下载端点 + viewer 下载 UI(T12 收口)
- 后端:GET download(ov 侧:读 photos_ov + albums_ov 开关副本;签名 URL 短期;variant 校验:original 需 originalDownloadEnabled,browse 需 downloadEnabled;关闭→403 DOWNLOAD_DISABLED;日志不记完整签名)——注意此端点在 ov 域(观众用),契约放 public tag
- viewer:大图工具栏下载按钮(按 album 开关显示:两级都关→无按钮;browse→下载当前浏览图;original 也开→下拉两项);微信内置浏览器检测(UA)→引导浮层(保存图片/复制链接文案,l10n);下载失败(403/过期)重取一次
- e2e(ov):组合矩阵(开+开/开+关/关+关)×variant;过期 URL 刷新;Playwright:viewer 大图下载按钮出现性随相册配置、微信 UA mock 引导浮层
- Commit
feat(ov,viewer): two-tier download with wechat guidance
Task 5: 用量统计与阈值告警(后端 + A23 后台侧)
- 统计查询:albums 聚合(photos bytes + photo_variants bytes per album)实时 SUM(P1 规模够用;P5 换物化/OSS 用量,登记);thresholds 表(单行配置)+ alerts 表(超阈写入一次直到回落,重复告警去重:kind+albumId 活跃告警唯一)
- 检查点:照片发布/删除时、阈值修改时评估(worker 发布路径与 photos PATCH 钩子);A23 断言:超阈产生 alert→后台 summary 可见→阈值上调→旧 alert resolvedAt 标记
- e2e:造假 bytes(直改 DB)→ 告警生成/去重/解除全链
- Commit
feat(api): usage summary with threshold alerts
Task 6: 账号管理(后端)
- 管理列表(游标+过滤 query/status)、PATCH(role/localLocked/displayName;不越权改 sourceStatus——只读展示)、reset-password(指定或生成随机;撤销其他会话;audit)、deactivate(localLocked=true + 撤销全部会话 + audit)/activate(§10 语义:credentialStatus pending→active + 经 reset-password 机制出初始凭据——闭环 Task 7 同步创建的待激活用户)
- e2e:矩阵(停用→该用户登录 401+已有 token 401;重置→旧密码失效新密码可登;role 改变生效;非 admin 全 403;audit 全程;Task 7 联动:同步创建的 pending 用户登录 401 → activate+重置 → 可登录)
- Commit
feat(api): admin account governance endpoints
Task 7: 用户定期同步(mock 源,T30 语义全量)
- cn 迁移:user_sync_runs(id, trigger cron|manual, state running|success|partial|failed, cursor 水位, counts Json, startedAt/finishedAt, error)、user_sync_errors(runId, externalId, code, detail)、external_user_mappings(已有?P1 无——建:userId+sourceSystem+externalUserId 唯一, sourceVersion)、users 增 sourceSystem?(用 mappings 表即可,不改 users)
- MockUserSource provider:可配置 fixtures(分页/增量版本/改名/停用/新增/空页/超时失败模式),接口:listPage(cursor), 与真实 D08 对齐的方法签名(适配器接口 UserSourcePort)
- 同步引擎(api 内定时 SYNC_USERS_INTERVAL 默认 5min + 手动触发共用):互斥锁(Redis SETNX cn)→ 读水位→分页拉取→逐条:externalId 映射存在→幂等更新(displayName,sourceVersion 比较);不存在→建待激活用户(credentialStatus=pending + 随机初始密码不入库不可登,等管理员激活)→ 显式停用字段→localLocked? NO:sourceStatus=disabled + 撤会话;空响应/分页中断→不动任何用户(记录 failed run)→ 提交水位
- 状态/历史/失败明细/手动补拉 API(Task 1 契约)
- e2e(mock 全场景):首轮全量(3 用户)→改名幂等→停用撤会话→新增待激活→手动补拉与定时互斥→空响应不误伤→失败批次水位不进→部分无效记录(state=partial,坏行入 errors 其余照常)→增量模式下周期全量对账→上游恢复(disabled→active)不解除 localLocked(技术方案 3.1A:本地锁定独立)
- Commit
feat(api): scheduled user sync with mock source (T30 semantics)
Task 8: admin 账号治理 + 用户同步页
- 账号管理页:列表(过滤/分页)、行操作(激活/停用/重置密码弹窗显示生成密码一次性/角色编辑);同步管理页:状态卡(上次成功/水位/计数)、运行历史、失败明细抽屉、手动补拉按钮(防抖+运行中禁用)
- Playwright(真实后端+mock 源注入:admin e2e 已有 harness 模式):账号全流程(停用→列表状态→重置→用新密码登录 viewer 无关/admin 登录验证);同步页(触发补拉→状态刷新→失败明细可见)
- Commit
feat(admin): account governance and user sync pages
Task 9: admin 相册治理增量(批量隐藏/交接/水印/下载设置/用量页/分享码)
- 照片管理页:多选+批量隐藏(逐项结果 toast 汇总 成功x/失败y+失败列表)、交接对话框(选目标账号+是否保留协作者)、行内展示 P2 回传的统计副本与同步时间(statsSyncedAt/likeCount 副本列);相册详情:水印卡(开关+参数表单+版本历史+发起重生成+进度条轮询)、下载开关组已有(P1b 编辑表单)——补"测试水印"预览(生成 1 张水印示例展示)、分享链接与二维码卡(评审缺口:viewer URL(VITE_VIEWER_BASE)+ qrcode 库渲染 + 复制——需新增 qrcode 依赖,修正"无新依赖"表述)
- 用量页:汇总表(相册容量/照片数)+阈值设置表单+告警列表(未解决/已解决 tab)
- Playwright:批量隐藏旅程(3 选 2 隐藏→逐项结果→viewer 侧 2 消失)、交接旅程、水印重生成进度到 done、用量阈值触发告警可见、分享二维码渲染(canvas/dataURL 断言)
- Commit
feat(admin): batch ops, transfer, watermark, usage and qr share pages
Task 10: 回归门 + P4a 验收记录
- 全仓回归绿(web+mobile analyze/test 不降级)+ 记录数字
docs/plans/progress/P4a-验收记录.md(A16/A17/A18/A20/A23/A29 mock 级证据、D06 工作默认登记、偏差表[用量实时 SUM、文字水印 SVG 实现、下载端点在 ov 域、mock 源方法签名、A23 接收人 P5]、G4 待真实接口清单、移交 P4b/P5)- 总纲 P4a 进度注记;
git tag p4a;Commitdocs: p4a acceptance record
执行注意
- 沿用全部既有约定(校对手决/契约先行 gen/reset 隔离/turbo 串行/maxWorkers/偏差登记/Co-Authored-By)。
- 水印涉及的文化字体问题:SVG 文字用系统 sans-serif(Noto Sans CJK 于 sharp 内置渲染链可用性在 e2e 验证,不可用则英文水印先行+中文登记 G5 项)。
- 批量上限 200/次(契约 minItems/maxItems);管理列表过滤不做全文搜索(query=前缀)。
- 用户同步绝不写 password_hash/role/album_members——映射表隔离(技术方案 3.1A 红线)。
- A23 的"组件不可见暂停"已在 P2 viewer 落地,本阶段只补后台证据链;Tasks 8-9 全部新页面文案走模板 i18n(zh/en 词条,P4b T15/A12 统一查漏译)。