Files
danyang-match/docs/superpowers/plans/2026-10-14-global-public-id-implementation.md
WorkBuddy 3c2b967274
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Canceled after 0s
CI/CD Pipeline / E2E (live stack) (push) Canceled after 0s
CI/CD Pipeline / Backend Build & Test (push) Canceled after 0s
CI/CD Pipeline / Admin Web Build (push) Canceled after 0s
CI/CD Pipeline / Mini Program Compile Check (push) Canceled after 0s
CI/CD Pipeline / Migration Validation (push) Canceled after 0s
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Canceled after 0s
release: freeze 2026-10-15 validated release baseline
2026-10-15 09:47:31 +08:00

16 KiB

全平台业务展示编号统一实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 让所有面向用户或跨角色展示的人员、任务、活动和业务记录 ID 使用 13 位稳定业务编号,并确保 Web、小程序和运营 API 的展示一致。

Architecture: 保留 PostgreSQL UUID 主键、外键、权限与路由寻址;为公开展示实体持久化业务编号。以 PostgreSQL 原子计数器按前缀和上海日期分配编号,在实体创建事务中写入;后端 DTO 同时提供内部 ID 与公开展示字段,前端只渲染公开字段。

Tech Stack: Java 21、Spring Boot、MyBatis-Plus、PostgreSQL、Flyway、Vue 3、TypeScript、微信原生小程序、Node.js 行为测试、Docker Compose。

Spec: docs/superpowers/specs/2026-10-14-global-public-id-design.md

Global Constraints

  • 公开编号严格使用 2 位大写字母前缀 + Asia/Shanghai YYYYMMDD + 3 位序号,共 13 位。
  • 用户 YH 按注册日和性别分别排名,男性序号奇数、女性序号偶数;红娘 HN,其他工作人员 GL,任务 RW,活动 HD。
  • UUID 主键、外键、JWT subject、数据范围、权限校验、幂等语义和既有 UUID API 路由保持不变。
  • 推荐对象、被推荐人、被举报人和活动参与者统一展示用户 YH 编号。
  • 任意用户可见页面、弹窗、导出和小程序不得渲染原始 UUID;不可用中文占位掩盖接口漏字段,必须修 DTO/查询。
  • 不覆盖或回滚开始任务前工作区的任何修改;只提交本计划涉及的新改动。
  • 不添加第三方依赖;后端唯一编号由 PostgreSQL 并发安全地产生。

文件结构与责任

  • backend/src/main/resources/db/migration/V060__add_global_public_display_ids.sql:为业务实体增加公开编号列/计数器,并为现存数据确定性回填和建立唯一索引。
  • backend/src/main/java/com/danyang/match/platform/shared/id/:编号前缀、长度、日期与原子分配服务;只新增小型领域工具,不重构 UUID 基础设施。
  • 各领域 Entity、Mapper、Service、DTO:在创建事务中写入公开编号;查询以 JOIN 取关联用户/操作员的展示编号。
  • backend/src/test/java/com/danyang/match/platform/shared/id/ 与领域测试:格式、奇偶、并发、迁移、DTO 白名单和越权行为测试。
  • admin-web/src/modules/:替换直接渲染 UUID 的列、详情、表单和导出;交互继续传内部 ID。
  • miniapp/pages/ 与 miniapp/services/:只显示公开编号,动作请求继续用内部 ID。
  • docs/DEMO-VALIDATION-2026-10-13.md:追加编号回填与双端展示验收证据,不改写历史记录。

Task 1: 建立全仓公开 ID 展示清单与锁定测试

2026-10-14 执行结果:模板/关键动态文案盘点与检查器已完成,清单见 ../specs/2026-10-14-global-public-id-display-inventory.md。检查器 7 项自检 PASS;114 个模板发现 31 个审查候选,全局展示检查仍 FAIL。Java 响应行为覆盖测试移至 Task 3,避免仅断言源码字符串。预约 YY 前缀待用户确认后进入迁移实现。

Files:

  • Create: backend/src/test/java/com/danyang/match/platform/shared/id/PublicDisplayIdCoverageTest.java

  • Create: tests/public-id-display-coverage.cjs

  • Inspect: admin-web/src/modules/、miniapp/pages/、各模块 Controller/DTO/Mapper、Flyway 迁移

  • Step 1: 建立清单和失败测试

清单至少逐项记录:实体、UUID 列/关系、创建入口、API DTO、管理端页面、用户小程序页面、编号前缀、是否包含既有用户隐私权限。覆盖 users、operators、activities、review_tasks、reports、matchmaker_services、matchmaker_recommendations、matchmaker_contact_authorizations、matchmaker_assignments、matchmaker_followups、转介实体,以及搜索中发现的任何其他实际展示 UUID 的实体。

守卫测试扫描已登记的 Web Vue 与小程序 WXML/TS/JS 文件,拒绝把 operatorId、userId、targetUserId、activityId、serviceId、recommendationId 等内部 UUID 直接插值到可见模板;允许它们用于事件、路由和 API 参数。测试对明确的 display 字段白名单断言。

  • Step 2: 运行测试确认暴露当前差距

Run: node tests/public-id-display-coverage.cjs Expected: FAIL,报告仍直接显示 UUID 的具体文件与模板位置。

  • Step 3: 固化清单与覆盖测试

将前置检查清单保存到 docs/superpowers/specs/2026-10-14-global-public-id-display-inventory.md;确认每个展示 ID 均映射到规格中已有前缀。若发现需新增前缀或改变既有 API 路由参数,暂停后续实现并修订规格等产品确认。

  • Step 4: 运行测试

Run: node tests/public-id-display-coverage.cjs Expected: 检查器自检 PASS;展示扫描在遗留问题修复前保持 FAIL,不将已知问题豁免为全局 PASS。

Task 2: 编号分配器、字段迁移和历史回填

Files:

  • Create: backend/src/main/resources/db/migration/V060__add_global_public_display_ids.sql

  • Create: backend/src/main/java/com/danyang/match/platform/shared/id/PublicDisplayId.java

  • Create: backend/src/main/java/com/danyang/match/platform/shared/id/PublicDisplayIdAllocator.java

  • Create: backend/src/main/java/com/danyang/match/platform/shared/id/PublicDisplayIdMapper.java

  • Modify: backend/src/main/java/com/danyang/match/platform/user/UserDisplayNumber.java

  • Modify: 相关 entity,至少 User.java、Operator.java、Activity.java、审核/举报/红娘相关 entity

  • Create: backend/src/test/java/com/danyang/match/platform/shared/id/PublicDisplayIdTest.java

  • Create: backend/src/test/java/com/danyang/match/platform/shared/id/PublicDisplayIdAllocatorTest.java

  • Modify: backend/src/test/java/com/danyang/match/platform/deployment/MigrationValidationTest.java

  • Modify: backend/src/test/resources/schema-h2.sql

  • Step 1: 编写编号格式和分配器失败测试

测试 YH20261014001、YH20261014002 的用户奇偶规则,普通记录格式 RW20261014001;非法前缀、日期、序号、用户性别、序号越过 999 必须拒绝。PostgreSQL mapper 测试以并发任务申请同一前缀同一天的编号,断言数量一致且全部唯一;禁止使用 COUNT(*) + 1。

  • Step 2: 运行定向测试确认失败

Run: cd backend; mvn -Dtest=PublicDisplayIdTest,PublicDisplayIdAllocatorTest test Expected: FAIL,因编号工具和 mapper 尚未实现。

  • Step 3: 添加持久化编号字段与回填

新增迁移为清单内公开实体添加 display_id(用户沿用 display_number,工作人员沿用 display_id),建立 public_display_id_counters(prefix, local_date, sequence_group, last_sequence) 计数器表。迁移按 Asia/Shanghai 注册/创建日期及稳定次序回填:用户按实名性别分别排名并写入男奇女偶尾号;工作人员按角色决定 HN/GL;任务 RW;活动 HD;其余业务表按规格与覆盖清单中的前缀分表排名。回填完成后设置非空和部分唯一约束;软删除记录也保留其编号唯一性,不得复用。初始化计数器到每组已使用的最大序号。

  • Step 4: 实现原子分配器

Mapper 使用 PostgreSQL 原子 UPSERT:普通编号按 sequence_group='' 每次加 1;用户编号按 sequence_group=gender 分别加 2,男性首值 1、女性首值 2。更新前检查生成值不超过 999;无返回值时抛明确容量异常。日期由 Clock 注入并按 Asia/Shanghai 转换。UserDisplayNumber 调整为接受已分配奇偶序号且支持三位尾号,避免自行计数。

  • Step 5: 运行后端定向测试与迁移验证

Run: cd backend; mvn -Dtest=PublicDisplayIdTest,PublicDisplayIdAllocatorTest,MigrationValidationTest test Expected: PASS;并发下无重复编号,迁移在空 H2 测试结构与 PostgreSQL 演示数据库验证通过。

Task 3: 后端创建写入与展示 DTO 收口

Files:

  • Modify: 各展示实体的 Mapper/Service/Controller/DTO,按 Task 1 清单逐项列出

  • Modify: backend/src/main/java/com/danyang/match/platform/matchmaker/mapper/MatchmakerContactAuthorizationMapper.java

  • Modify: backend/src/main/java/com/danyang/match/platform/matchmaker/service/ContactAuthorizationService.java

  • Modify: backend/src/main/java/com/danyang/match/platform/matchmaker/service/MatchmakerManagementService.java

  • Modify: 活动、审核、举报、审计、转介等清单中 Mapper 查询和响应 DTO

  • Create: backend/src/test/java/com/danyang/match/platform/shared/id/PublicDisplayIdResponseTest.java

  • Modify: 对应领域测试与 backend/src/test/resources/schema-h2.sql

  • Step 1: 为创建流程写回归测试

对每类清单实体调用真实 Service 创建入口,断言同事务持久化 UUID 主键和正确前缀的公开编号;模拟编号分配失败,断言领域实体未写入。用户和工作人员 API 输出同时断言 id 仍为既有 UUID、displayNumber/displayId 是业务编号。

  • Step 2: 运行测试确认失败

Run: cd backend; mvn -Dtest=PublicDisplayIdResponseTest,MatchmakerContactAuthorizationTest,MatchmakerManagementReadTest test Expected: 新增编号断言失败,既有权限测试保持可执行。

  • Step 3: 接入创建事务与关联编号查询

对每个实体创建 Service,在已有 @Transactional 中先分配编号再 insert;操作员编号依实际角色用 HN/GL。Mapper 查询只额外 JOIN 公开编号字段;DTO 增加语义明确的 displayId/displayNumber 和关联对象 operatorDisplayId、userDisplayNumber、targetUserDisplayNumber。contact authorization 不再把底层 operatorId/userId 作为展示字段使用。

推荐对象、被举报人、活动参与者和审核对象关联到 user profile 时,复用 users.display_number;查询必须保留现有数据范围、用户隐私白名单和防探测条件。列表只补展示字段,不扩大查询可见范围。

  • Step 4: 检查未覆盖 API 和 UUID 响应泄漏

运行后端静态覆盖测试和针对 Controller JSON 的响应断言;确认审计记录实体引用只在有权限且可映射时返回业务编号,否则返回实体类型脱敏说明。搜索所有列表 DTO 对 UUID 的输出使用,逐项按清单修复,不修改内部写请求。

  • Step 5: 运行相关后端回归

Run: cd backend; mvn -Dtest=PublicDisplayId*,MatchmakerContactAuthorizationTest,MatchmakerAssignmentA1Test,MatchmakerManagementReadTest,Activity*,Review*,Report*,MigrationValidationTest test Expected: PASS;既有权限、事务、隐藏/软删除与错误语义不变。

Task 4: 管理端只展示业务编号

Files:

  • Modify: admin-web/src/modules/matchmaker/MatchmakerManagementView.vue

  • Modify: admin-web/src/modules/activity/ActivityInterestView.vue

  • Modify: admin-web/src/modules/activity/ActivityCheckinView.vue

  • Modify: admin-web/src/modules/operator/OperatorManagementView.vue

  • Modify: admin-web/src/modules/audit/AuditLogView.vue

  • Modify: Task 1 清单识别的用户、举报、安全、审核、报名、活动、联系授权、转介等视图

  • Modify: 对应 API 类型定义与 service adapter

  • Modify: tests/public-id-display-coverage.cjs

  • Step 1: 为目标页面写展示行为测试

使用虚拟 API fixture 分别提供 UUID 与展示编号;渲染断言应见 HN...、YH...、HD...、RW... 等展示编号且不见 UUID,点击详情/审批/筛选后请求仍携带原内部 UUID。

  • Step 2: 修复模板和交互控件

表格/详情使用 DTO 的公开编号字段;推荐对象输入改为可搜索用户选择器,按公开用户编号和允许公开的基础资料搜索,提交时仍传选中对象内部 UUID。若缺展示编号,展示“编号待生成”并在接口测试中判为缺陷,不允许回退 UUID。

  • Step 3: 更新覆盖守卫并运行管理端检查

Run: cd admin-web; pnpm run type-check; pnpm run build; cd ..; node tests/public-id-display-coverage.cjs Expected: 类型检查、构建和 ID 展示守卫 PASS。

Task 5: 小程序只展示业务编号

Files:

  • Modify: Task 1 清单中用户卡片、活动详情/报名/签到、红娘工作台/服务详情/推荐进展、举报与安全、个人资料/认证等页面

  • Modify: 必要的 miniapp/services/*.js response adapter

  • Modify: miniapp/types/index.ts 与 miniapp/types/index.d.ts 的展示字段

  • Create: tests/miniapp/public_id_display_behavior_test.cjs

  • Modify: tests/public-id-display-coverage.cjs

  • Step 1: 写小程序字段展示失败测试

使用接口响应 fixture 注入 id/userId/targetUserId UUID 和对应 displayId/displayNumber,断言 WXML 页面绑定展示字段、服务跳转和 Like/Pass/报名/举报动作仍提交 UUID;无展示编号时不把 UUID 作为空态回退值。

  • Step 2: 修正响应类型和页面绑定

更新 TS 声明和各页面 WXML/JS,让列表、详情、确认弹窗、活动页面和举报确认中的公开文本只用业务编号;保留内部 ID 用于请求和路由。

  • Step 3: 运行小程序行为与结构检查

Run: node tests/miniapp/public_id_display_behavior_test.cjs; node tests/public-id-display-coverage.cjs; pnpm --dir miniapp run type-check Expected: PASS;另运行项目现有 WXML/WXSS 结构守卫和 matchmaker/活动相关测试。

Task 6: 全量迁移、运行验收和文档证据

Files:

  • Modify: docs/DEMO-VALIDATION-2026-10-13.md

  • Modify: docs/STATUS.md

  • Inspect: infrastructure/docker-compose.yml、运行中的 danyang Docker 服务

  • Step 1: 执行完整后端测试及迁移检查

Run: cd backend; mvn test Expected: 记录总数、失败、错误、跳过;全部失败/错误为 0。PostgreSQL 演示库迁移完成,检查每表编号无空值、无重复、长度正确,用户奇偶规则正确,计数器不小于已分配最大尾号。

  • Step 2: 执行 Web、小程序回归

Run: cd admin-web; pnpm run type-check; pnpm run build; cd ..; pnpm --dir miniapp run type-check; node tests/public-id-display-coverage.cjs Expected: 全部 PASS;执行现有小程序结构与相关行为测试。

  • Step 3: 演示环境 HTTP 和页面检查

以演示账号读取联系授权、红娘服务/推荐、用户、活动、审核、举报接口,逐类抽查 UUID 与业务编号关系;确认权限拒绝样本仍拒绝。检查浏览器常用视图及微信开发者工具小程序核心页,页面无 UUID 可见文本,动作正常。

  • Step 4: 记录证据并完成阶段报告

在 docs/DEMO-VALIDATION-2026-10-13.md 追加编号格式、各类数量、重复检测、旧号映射、测试输出摘要和目视验收结果;在 docs/STATUS.md 仅追加当前结论,不覆盖历史状态。运行 git diff --check,最终报告逐项 PASS/PARTIAL/FAIL/NOT_VERIFIED。

完成门禁

  • 六个任务均有实现与独立测试证据;不得因前端隐藏列而遗漏后端公开 DTO。
  • 演示 PostgreSQL 中公开编号检查 SQL 返回 0 空值(仅规格明确允许的未实名用户可空)、0 重复、0 格式错误。
  • 联系授权截图展示 HN... 与 YH...;跨模块推荐、举报和活动同一用户编号相同。
  • 所有 UUID 保留作内部键但不出现在面向用户的可见文本、导出或通知。
  • 若既有客户端依赖公开 UUID 字段作为用户输入,应提供公开编号解析适配并做兼容测试;权限判断仍基于解析后的 UUID。
  • 最终说明未执行的微信真机验收或不可执行测试,不将历史测试结果写成本轮证据。