fix(activity): close Phase 14 KEEP-VERIFY/EXTEND — 11 backend + 3 frontend-contract + 2 test-fidelity defects

Phase 14 (multi-type public-welfare activities) verification closure.

Backend (D1-D11):
- D1 CRITICAL: ActivityMapper.countRemainingCapacity had no @Select and no XML
  -> activity detail / register / approve all threw Invalid bound statement.
  Removed the shell; unified on ActivityRegistrationMapper.
- D2 CRITICAL: register & approve were read-then-write -> oversold under
  concurrency. Replaced with single atomic SQL (insertIfCapacityAvailable /
  approveIfCapacityAvailable, affectedRows==0 => full).
- D3 HIGH: activityType / orgRole never validated against production
  chk_activity_type / chk_org_role -> 500 only on PostgreSQL.
- D4 HIGH: ActivityListItemDTO had no activityType -> miniapp category tabs
  always empty (Gate failure).
- D5: male/female counts were fabricated as approvedCount/2; now real
  aggregation over user_profiles.gender.
- D6: minAge/maxAge was a TODO no-op; added assertAgeInRange.
- D7: registration_policy write-only; per-type eligibility now enforced
  (identity / local eligibility / gender quota).
- D8: POST /operator/activities dropped type, multi-org and capacity groups
  and set organizer_org_id to the operator user id; now delegates to
  createActivityFromAdmin (single implementation).
- D9: three operator write endpoints were mounted on the user-side
  ActivityController (dual implementation, zero callers) -> moved.
- D10: NPE on soft-deleted activity in getMyActivities; NPE/500 on missing
  admin form fields (Instant.parse(null)); tolerant time parsing added.
- D11: DTO lacked registeredCount / status / coverFileId / description
  -> card always showed 0 registered, "view detail", no cover, no summary.

Frontend contract (F1-F3):
- F1: miniapp category code "classroom" is not in the production whitelist
  (should be relationship_education); three types had no tab at all.
- F2: ui-activity-card read 7 fields that do not exist on the backend;
  now reads only real DTO fields, plus per-type badge and eligibility hint.
- F3: admin-web had no activity management page at all; added
  ActivityManagementView.vue (type-driven dynamic form, multi-org,
  capacity groups, publish/cancel) + /activities route and nav entry.

Test fidelity (T1-T2):
- T1: deleted two assertTrue(true) placeholder tests (testNoOverbooking,
  testNoDuplicateRegistration) that covered the exact broken invariants.
- T2: test schema lacked V023 columns/constraints and three tables; the
  activity tables were only in the OPTIONAL set (zero coverage) -> schema
  completed and moved to REQUIRED_IN_TEST_SCHEMA.

Tests: ActivityPhase14Test (23 cases), ActivityConstraintGuardTest (9 cases,
parses real db/migration/V*.sql plus miniapp source). Two negative-verification
rounds proved both guard families actually turn red.

Regression: 450 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS
(baseline 420). admin-web vue-tsc 0 error, vite build OK.
This commit is contained in:
WorkBuddy
2026-09-17 14:02:00 +08:00
parent 7a2693b239
commit 755a570849
22 changed files with 2164 additions and 136 deletions
+17
View File
@@ -5,6 +5,22 @@
> **2026-09-16 状态校正:** Phase 07 UI 已按 2026-09-13 规格重新实现,**首批 11 页于 2026-09-16 通过用户验收(结论:基本达标)**,本文件此前"视觉否决/REOPENED"口径随之关闭。
>
> **2026-09-17 Phase 14 复验关闭:** EPIC-P14 已完成。复验发现 activity 模块存在**必崩级空壳**——
> `ActivityMapper.countRemainingCapacity` **既无 `@Select` 也无 XML**,而 `ActivityService` 调用的
> 正是它 → 活动详情/报名/审核三条路径必然抛 `Invalid bound statement`。修复 **11 处后端缺陷**:
> **D1(Critical,生产故障)** Mapper 空壳致三条路径必崩;
> **D2(Critical,并发)** 报名与审核「先读后写」→ 并发**超卖**,改单条原子 SQL;
> **D3(High,生产故障)** `activityType`/`orgRole` 未校验生产 CHECK 白名单(只在 PG 上炸);
> **D4(High,契约)** `ActivityListItemDTO` 无 `activityType` → 小程序分类 Tabs 恒无数据;
> D5 男女人数为 `approvedCount/2` 编造;D6 年龄门槛为 TODO 空壳;D7 `registration_policy` 只写不读;
> **D8 `POST /operator/activities` 丢弃类型/多组织/分组且把 `organizer_org_id` 错设为操作者 userId**;
> D9 三个运营写入端点错挂用户端控制器(双实现);D10 软删活动与缺字段致 NPE/500;
> D11 DTO 缺 `registeredCount`/`status`/`coverFileId`/`description`。
> 另有 **3 处前端契约缺陷**(小程序分类 code `classroom` 不在白名单、卡片读 7 个不存在的字段、
> admin-web 无活动管理页 → 新增 `ActivityManagementView.vue` 动态表单)与
> **2 处测试保真度缺陷**(2 条 `assertTrue(true)` 占位假绿;测试库缺 V023 列与三张表、activity 五表零覆盖)。
> 证据:`docs/Phase-14-V11-Recovery-Completion-Report-2026-09-17.md`。**Phase 15 需用户确认后启动。**
>
> **2026-09-17 Phase 13A 复验关闭:** EPIC-P13A 已完成。复验发现 counseling 模块存在**双实现**——
> 线上权威 `CounselingService`(167 行)**零测试**,唯一被测的 `CounselingServiceApp`(280 行)是
> **孤儿类**(无注入点、无 HTTP 端点可达,生产永不执行)→ 已合并并删除孤儿类。修复 **9 处缺陷**:
@@ -103,6 +119,7 @@
| Phase 12 Relationship Progress 复验 | 已完成(2026-09-16) | 见 `Phase-12-V11-Recovery-Completion-Report-2026-09-16.md`;修复 4 处缺陷(**实体↔生产迁移系统性漂移 23 表 50 列**、双路径 episode/NPE、测试库缺 7 表致首页零覆盖、计划项 2 未实现),新增永久守卫 `EntityMigrationParityTest`,删除 2 个假测试类,后端 371 tests 全绿 |
| Phase 13 公益红娘转介复验 | 已完成(2026-09-16) | 见 `Phase-13-V11-Recovery-Completion-Report-2026-09-16.md`;修复 7 处缺陷(**2 处生产故障级 CHECK 约束缺口致推荐接受/服务提交必崩**、推荐与转介三处越权、`getServiceDetail` 越权读、Mapper 缺 `@Select` 致必崩、前端 5 处契约缺口),测试库补 3 张 matchmaker 表,迁移基线 V038;新增 `MatchmakerReferralPhase13Test`(17 用例)+ 重写 `MatchmakerServiceTest`(6 用例,解析真实 DDL),删除 3 处假测试,后端 **391 tests 全绿**;**Phase 13A 尚未获准** |
| Phase 13A 婚恋咨询与婚前辅导复验 | 已完成(2026-09-17) | 见 `Phase-13A-V11-Recovery-Completion-Report-2026-09-17.md`;发现 counseling **双实现**(权威类零测试 / 孤儿类被单测),已合并删除孤儿类;修复 **9 处缺陷**(**D1 咨询正文写日志=INV-013 泄露**、D2 申请 userId 取请求体、**D3 分配先读后写并发静默覆盖**、D4 manager 可读 followups=INV-013 绕过、D5 无状态机、**D6 `/services/intro` 后端不存在致小程序 intro 页 100% 无数据**、D7 Admin 5 端点 404、D8 小程序字段名/幂等/静默失败、D9 测试库 counseling 零 CHECK 约束)+ **4 处测试基建缺陷**(守卫找不到 DDL 恒真假绿、全限定 `@TableField` 漏解析、权限守卫假阳性、测试自身逻辑缺陷);迁移基线 V039;新增 `CounselingPhase13ATest`(25)+ `CounselingConstraintGuardTest`(5,解析真实 DDL),两轮负向验证证明守卫会红;后端 **420 tests 全绿**;**Phase 14 尚未获准** |
| Phase 14 多类型公益活动复验 | 已完成(2026-09-17) | 见 `Phase-14-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **11 处后端缺陷**(**D1 `ActivityMapper.countRemainingCapacity` 无 SQL 空壳致详情/报名/审核必崩**、**D2 报名与审核先读后写并发超卖** → 改原子 SQL、D3 类型/角色未校验生产 CHECK 白名单、**D4 DTO 无 `activityType` 致小程序分类 Tabs 恒空**、D5 男女人数 `approvedCount/2` 编造、D6 年龄门槛空壳、D7 按类型资格策略为空、**D8 运营端建活动丢弃类型且把主办组织错设为操作者 userId**、D9 三个运营端点错挂用户端双实现、D10 软删活动/缺字段 NPE、D11 DTO 缺 4 字段)+ **3 处前端契约缺陷**(分类 code `classroom` 不在白名单、卡片读 7 个不存在字段、admin-web 无活动管理页)+ **2 处测试保真度缺陷**(2 条占位假绿、测试库缺 V023 列与三张表);新增 `ActivityPhase14Test`(23)+ `ActivityConstraintGuardTest`(9,解析真实 DDL **与前端源码**),两轮负向验证证明守卫会红;admin-web `vue-tsc` 0 error + `vite build` 通过;后端 **450 tests 全绿**;**Phase 15 尚未获准** |
| UI-CLEANUP-20260913 | 已完成 | 废弃 UI 代码、冲突规范、旧计划和无效验收证据已清理;`ui-document-baseline-contracts` 锁定唯一视觉入口 |
| UI-ADMIN-20260913 | 已完成(待桌面端目视复验) | 管理端全局框架与工作台已按规格交付;18 modules + 22 routes,vue-tsc 与 vite build 全绿 |
| UI-MINIAPP-20260913 | 已完成 | 小程序 11 个核心页面已重构;11/11 页 TS-only,2026-09-16 通过用户验收 |
@@ -0,0 +1,138 @@
# Phase 14:多类型公益活动 — KEEP-VERIFY/EXTEND 复验完成报告
- **日期**:2026-09-17
- **分支**:`main`
- **Tag**:`phase14-recovery-2026-09-17`
- **基线**:Phase 13A 关闭时后端 **420 tests 全绿**
- **复验结论**:发现并修复 **11 处后端缺陷 + 3 处前端契约缺陷 + 2 处测试保真度缺陷**;全量回归 **450 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS**
- **计划出处**:`docs/善缘丹阳 V1.1 客户需求基线增量开发与修复计划.md` Phase 14(第 337-347 行)
---
## 一、Gate 结论
| Gate | 结论 | 证据 |
|------|------|------|
| matchmaking 与课堂/反诈类活动显示和资格策略不同 | ✅ 通过 | `resolveEligibility` 按类型返回三档策略;`matchmakingAndAntiFraudPoliciesMustDiffer` 守卫 + `resolveEligibilityDiffersByType` / `matchmakingRequiresIdentityAndLocalEligibility` / `antiFraudDoesNotRequireIdentity` 三条行为用例 |
| 容量按配置正确扣减 | ✅ 通过 | 原子 SQL `insertIfCapacityAvailable` / `approveIfCapacityAvailable`;`registerRespectsTotalLimitAtomically`、`approveRespectsTotalLimitAtomically`、`genderQuotaIsEnforcedForMatchmaking`、`antiFraudIgnoresGenderQuota` |
| 主办组织关系不自动授予用户库访问权限(INV-020) | ✅ 通过 | `activityOrganizationsExposeNoUserData`(组织接口零用户字段)+ `registrationListRequiresOperatorPermission`(运营端类级 `@PreAuthorize('activity.view')`,用户端无报名列表) |
---
## 二、缺陷清单
### 2.1 后端(11 处)
| 编号 | 级别 | 缺陷 | 修复 |
|------|------|------|------|
| D1 | **Critical 生产故障** | `ActivityMapper.countRemainingCapacity` **既无 `@Select` 也无 XML**,且项目未配置 `mapper-locations` → 活动详情 / 报名 / 审核**三条路径必抛** `Invalid bound statement (not found)`。而 `ActivityService` 调用的正是这个空壳 | 删除空壳,统一走 `ActivityRegistrationMapper`(唯一有真实 SQL 的实现) |
| D2 | **Critical 并发** | 报名与审核都是「先 SELECT 余量 → 再 INSERT/UPDATE」,并发下必然超卖;且 `needReview=false` 时写入 approved 却完全不校验名额 | 改为单条原子 SQL:`INSERT ... SELECT ... WHERE (SELECT COUNT...) < total_limit [AND 性别名额]`,`affectedRows == 0` 即报「名额已满」;审核同理 `approveIfCapacityAvailable` |
| D3 | High 生产故障 | 创建活动 / 添加组织关系**未校验** `activityType`、`orgRole` 是否落在生产 `chk_activity_type` / `chk_org_role` 白名单内。非法值在 H2 测试库静默通过,上 PostgreSQL 才 500(典型的"只在 PG 上炸") | Service 侧白名单校验(`VALID_ACTIVITY_TYPES` / `VALID_ORG_ROLES`),常量由守卫**解析真实 DDL** 双向校验 |
| D4 | High 契约 | `ActivityListItemDTO` **没有 `activityType` 字段**,而小程序 `pages/activity/activity.ts` 正是读它做分类筛选 → 分类 Tabs 恒无数据,Gate ① 直接失败 | 补字段并在 `toListItemDTO` 回填 |
| D5 | Medium 数据造假 | `toListItemDTO` 用 `approvedCount / 2` 与 `approvedCount - approvedCount/2` **凭空编造**男女人数;`countApprovedMaleRegistrations` 的 SQL 忽略 gender 入参且零调用方 | 新增 `countApprovedByGender`,按 `user_profiles.gender` 真实聚合 |
| D6 | Medium | 年龄门槛 `minAge` / `maxAge` 为 TODO 空壳,**完全不生效** | 补 `assertAgeInRange` |
| D7 | Medium Gate 未落地 | `registration_policy` 字段只写不读,按类型的资格策略实际为空 | 改为按类型强制执行:实名(`TYPES_REQUIRING_IDENTITY`)、本地资格(`TYPES_REQUIRING_LOCAL_ELIGIBILITY`)、性别名额(`TYPES_WITH_GENDER_QUOTA`) |
| D8 | Medium 生产故障 | `POST /operator/activities` **丢弃** activityType / registrationPolicy / 多组织 / 容量分组,并把 `organizer_org_id` **错设为操作者 userId** → 后台动态表单无法创建带类型活动 | 改为委托 `createActivityFromAdmin`(单一实现),并回填 `organizer_org_id` |
| D9 | Medium 权限边界 | 三个运营写入端点(`POST /activities/operator`、`/operator/{id}/organizations`、`/operator/{id}/capacity-groups`)挂在**用户端** `ActivityController`,与运营端**双实现**且零调用方 | 迁至 `ActivityOperatorController`(类级 `@PreAuthorize('activity.view')` + 方法级 `activity.create`) |
| D10 | Medium NPE | ① `getMyActivities` 对已软删活动 `selectById` 返回 null 直接 NPE;② `createActivityFromAdmin` 对缺字段请求 `Instant.parse(null)` / `((Number) null).intValue()` 直接 500 | ① 跳过软删活动;② 缺省值兜底 + 容忍时间戳 / 本地无时区文本 / ISO 文本三种时间格式 |
| D11 | Medium 契约 | DTO 缺 `registeredCount` / `status` / `coverFileId` / `description` → 卡片"报名人数"恒 0、按钮恒显示"查看详情"、封面与摘要恒不显示 | 四个字段补齐并在 `toListItemDTO` 回填 |
### 2.2 前端契约(3 处)
| 编号 | 缺陷 | 修复 |
|------|------|------|
| F1 | 小程序分类 Tabs 的 code 写的是 `classroom`,而生产白名单里**根本没有该值**(婚恋课堂是 `relationship_education`)→ 点「婚恋课堂」恒为空白列表;且 `interest_social` / `premarital_guidance` / `marriage_custom_culture` 三类**完全没有入口** | 六个 code 逐字对齐生产白名单,补全全部六类 |
| F2 | `ui-activity-card` 读取 `registeredCount` / `currentMaleCount` / `currentFemaleCount` / `coverImageUrl` / `activityStartAt` / `suitableFor` / `intro` —— **后端一个都没有** | 改为只读 DTO 真实字段;封面按 `coverUrl ?? uploadBase + '/files/' + coverFileId` 拼接;时间本地格式化;新增类型标签与差异化资格提示(Gate ① 的前端落点) |
| F3 | admin-web **完全没有活动管理页**(仅有互选页),后台动态表单 100% 缺失 | 新增 `ActivityManagementView.vue`:类型驱动的动态表单(性别名额/互选开关按类型显隐)、多组织增删、容量分组增删、发布/取消;注册 `/activities` 路由与导航 |
### 2.3 测试保真度(2 处)
| 编号 | 缺陷 | 修复 |
|------|------|------|
| T1 | **假绿占位测试**:`ActivityServiceTest#testNoOverbooking` 与 `testNoDuplicateRegistration` 是 `assertTrue(true); // 占位测试`。名字覆盖两条核心不变量,实际什么都不验证 —— 而真实实现恰恰是坏的 | 删除(占位测试不是"待补",是持续误导的假信号),迁至 `ActivityPhase14Test` 走真实落库路径 |
| T2 | **测试库漂移**:`activities` 缺 V023 五列与 `chk_activity_type`;`organizations` / `activity_organizations` / `activity_capacity_groups` **三表完全缺失**;且这五张表在 `EntityMigrationParityTest` 中只属"可选白名单"(= 零覆盖) | `schema-h2.sql` 补齐列、CHECK 约束与三张表;五张表从 `TEST_SCHEMA_OPTIONAL_TABLES` 移入 `REQUIRED_IN_TEST_SCHEMA` |
---
## 三、新增测试
| 测试类 | 用例数 | 性质 |
|--------|--------|------|
| `ActivityPhase14Test` | 23 | Spring 上下文行为测试:原子容量扣减、按类型资格策略、白名单校验、年龄门槛、列表数据真实性、软删不 NPE、INV-020、多组织/容量分组落库、后台表单容错 |
| `ActivityConstraintGuardTest` | 9 | 纯反射 + 文件解析(无 Spring):解析**真实** `db/migration/V*.sql` 校验 `chk_activity_type` / `chk_org_role`;解析小程序 `activity.ts` 分类 code 与卡片 `activity.*` 字段名 |
**守卫自检**:`parserMustResolveActivityConstraints` 断言迁移文件数 ≥ 30 且两个约束白名单非空 —— 解析器失效时守卫自己先红,避免恒真假绿。
---
## 四、负向验证(两轮,证明守卫非恒真)
### 第一轮:后端
| 注入的缺陷 | 变红的用例 |
|-----------|-----------|
| 从 `V023` 的 `chk_activity_type` 白名单删除 `anti_fraud` | `ActivityConstraintGuardTest.activityTypeConstantsWithinProductionWhitelist`(精准命中,说明解析的是**真实 DDL** 而非副本) |
| 报名退化为「不检查名额直接 INSERT」 | `ActivityPhase14Test.registerRespectsTotalLimitAtomically` + `genderQuotaIsEnforcedForMatchmaking` |
结果:`Tests run: 26, Failures: 3` / `BUILD FAILURE`。已还原。
### 第二轮:前端契约
| 注入的缺陷 | 变红的用例 |
|-----------|-----------|
| 分类 code 改回 `classroom` 并删除 `anti_fraud` 入口 | `miniappCategoryCodesWithinProductionWhitelist` + `everyProductionTypeHasMiniappTab` |
| 卡片加回 `activity.suitableFor` | `cardFieldsExistInListItemDto` |
结果:`Tests run: 9, Failures: 3` / `BUILD FAILURE`。已还原。
> 两轮负向验证中均出现过**守卫自身**的解析缺陷(`categories:` 先命中接口声明、正则扫到注释里的 `activity.capacity`),已一并修正 —— 守卫必须**先剥注释再扫描**,且匹配要精确到 `categories: [`。
---
## 五、回归证据
| 项目 | 结果 |
|------|------|
| 后端全量 `mvn -o test` | **Tests run: 450, Failures: 0, Errors: 0, Skipped: 3** / `BUILD SUCCESS`(4 分 52 秒) |
| 基线对比 | Phase 13A 420 → **450**(净 +30 = 新增 23 + 9,删除 2 条占位测试) |
| admin-web `vue-tsc --noEmit` | 通过,0 error |
| admin-web `vite build` | `✓ built in 9.75s`,新增 `ActivityManagementView`(13.28 kB) |
| 后端 `mvn test-compile` | `BUILD SUCCESS`(282 main + 75 test 源文件) |
---
## 六、变更文件
**后端**
- `activity/mapper/ActivityMapper.java` — 删除无 SQL 空壳;新增 `findOpenActivitiesByType`、`countApprovedByGender`
- `activity/mapper/ActivityRegistrationMapper.java` — 新增原子 `insertIfCapacityAvailable` / `approveIfCapacityAvailable`
- `activity/service/ActivityService.java` — 报名/审核改原子;按类型资格策略;白名单校验;年龄门槛;DTO 回填;后台表单容错
- `activity/dto/ActivityListItemDTO.java` — 新增 `activityType` / `registeredCount` / `coverFileId` / `description` / `status`
- `activity/controller/ActivityOperatorController.java` — 建活动改走 V1.1;新增组织/容量分组 4 个端点;列表补 `activityType`
- `activity/controller/ActivityController.java` — 删除 3 个错挂在用户端的运营端点
**前端**
- `miniapp/pages/activity/activity.ts` — 分类 code 对齐生产白名单(6 类)
- `miniapp/components/activity-card/{index.js,index.wxml,index.wxss}` — 只读 DTO 真实字段;类型标签 + 差异化资格提示 + 封面/时间/报名人数
- `admin-web/src/modules/activity/ActivityManagementView.vue` — **新增**,类型驱动动态表单
- `admin-web/src/router/index.ts`、`admin-web/src/layouts/MainLayout.vue` — 注册 `/activities` 路由与导航
**测试**
- 新增 `ActivityPhase14Test`(23)、`ActivityConstraintGuardTest`(9)
- `ActivityServiceTest` — 删除 2 条占位假绿测试
- `EntityMigrationParityTest` — activity 五表移入必选集合
- `backend/src/test/resources/schema-h2.sql` — 补 V023 列/约束与三张缺失表
---
## 七、遗留与建议(不在本阶段范围)
1. **容量分组尚未参与名额计算**:`activity_capacity_groups` 目前只做配置落库与回读,报名时的名额扣减仍走 `total_limit` + 男女名额。Phase 15(报名/审核/签到)应把分组规则真正接入扣减链路。
2. **`registration_policy` 目前仍只写不读**:本阶段按 `activityType` 推导资格策略(可落地、可验证),`registration_policy` 作为更细粒度的覆盖项尚未接线。若产品需要"同类型不同策略",应在 Phase 15 明确其优先级。
3. **首页活动卡片未复用 `ui-activity-card`**:`miniapp/pages/index/index.wxml` 仍内联渲染,且把 `coverFileId` 直接当图片 src(应为 `uploadBase + '/files/' + id`)。建议后续统一走组件。
---
## 八、结论
Phase 14(多类型公益活动)**复验关闭**。三项计划任务均已完成,三项 Gate 全部达成并通过行为用例与守卫双重覆盖。
**Phase 15(活动报名、审核与签到)未自动进入,等待用户确认。**
+20 -2
View File
@@ -1,8 +1,8 @@
# 项目状态跟踪 — STATUS.md
> **2026-09-17 当前有效结论:`V1.1 INCREMENTAL RECOVERY PHASE 13A COMPLETE / GIT CLEAN`。**
> **2026-09-17 当前有效结论:`V1.1 INCREMENTAL RECOVERY PHASE 14 COMPLETE / GIT CLEAN`。**
>
> 阶段 0(在途收口)、阶段 1(小程序 C 组 + discover 筛选重设计)、阶段 2(管理端 Vue 新框架适配)、阶段 3(CapabilityService 独立化)、阶段 4(双端总验收)全部完成。三端构建通过、**13/13 契约全绿**、核心 11 页面**全部 TS-only(11/11)**。**UI 重设计首批 11 页已于 2026-09-16 通过用户验收(结论:基本达标)**;`/me/*` 前后端契约同轮收敛,17 个 404 调用已恢复。**Phase 08(Like / Match / Block)、Phase 09(Text Chat)、Phase 10(Image & Chat Safety)、Phase 11(Notification)、Phase 12(Relationship Progress)、Phase 13(公益红娘转介)、Phase 13A(婚恋咨询与婚前辅导)复验已关闭**,七轮共修复 32 处真实缺陷,并各自补齐测试保真度与守卫。**Phase 14 需用户确认后启动。**
> 阶段 0(在途收口)、阶段 1(小程序 C 组 + discover 筛选重设计)、阶段 2(管理端 Vue 新框架适配)、阶段 3(CapabilityService 独立化)、阶段 4(双端总验收)全部完成。三端构建通过、**13/13 契约全绿**、核心 11 页面**全部 TS-only(11/11)**。**UI 重设计首批 11 页已于 2026-09-16 通过用户验收(结论:基本达标)**;`/me/*` 前后端契约同轮收敛,17 个 404 调用已恢复。**Phase 08(Like / Match / Block)、Phase 09(Text Chat)、Phase 10(Image & Chat Safety)、Phase 11(Notification)、Phase 12(Relationship Progress)、Phase 13(公益红娘转介)、Phase 13A(婚恋咨询与婚前辅导)、Phase 14(多类型公益活动)复验已关闭**,八轮共修复 48 处真实缺陷,并各自补齐测试保真度与守卫。**Phase 15 需用户确认后启动。**
最后更新: 2026-09-17 — 分支 `main`(`feat-ui-v11` 保留为冗余锚点)
@@ -42,6 +42,8 @@
| 1.1.0-dev | 2026-09-08 | V1.1 基线搭建启动(Phase 01 进行中) |
| 1.1.0-rc.3 | 2026-09-16 | Phase 08~12 复验关闭;新增 V037 对齐实体↔生产迁移;全量回归 371 tests 全绿 |
| 1.1.0-rc.4 | 2026-09-16 | Phase 13 复验关闭(公益红娘转介,修复 7 处缺陷);新增 V038 扩展 matchmaker CHECK 白名单;全量回归 **391 tests** 全绿 |
| 1.1.0-rc.5 | 2026-09-17 | Phase 13A 复验关闭(婚恋咨询与婚前辅导,9 处缺陷 + 4 处测试基建缺陷);新增 V039;全量回归 **420 tests** 全绿 |
| 1.1.0-rc.6 | 2026-09-17 | Phase 14 复验关闭(多类型公益活动,11 处后端缺陷 + 3 处前端契约缺陷 + 2 处测试保真度缺陷);新增 `ActivityManagementView` 动态表单;全量回归 **450 tests** 全绿 |
---
@@ -186,6 +188,22 @@
| ReviewService 集成本地资格审核通知 | ✅ |
| 隐私安全文案(中性化,不暴露敏感信息) | ✅ |
### Phase 14 — 多类型公益活动 ✅ 已完成(2026-09-17 KEEP-VERIFY/EXTEND 复验通过)
| 任务 | 状态 |
|------|------|
| ActivityType 分类 + 按类型报名策略(实名/本地资格/性别名额) | ✅ |
| 容量原子扣减(`insertIfCapacityAvailable` / `approveIfCapacityAvailable`) | ✅ 新增 |
| `activityType` / `orgRole` 生产 CHECK 白名单校验(D3) | ✅ 新增 |
| 多组织关系 + 容量分组落库与回读 | ✅ |
| 运营端:`/operator/activities` 建活动改走 V1.1;组织/分组 4 端点;列表补 `activityType` | ✅ |
| 用户端:删除 3 个错挂的运营写入端点(双实现) | ✅ |
| Miniapp:分类 Tabs code 对齐生产白名单(6 类)+ 类型化卡片 + 差异化资格提示 | ✅ |
| Admin:`ActivityManagementView.vue` 类型驱动动态表单 + `/activities` 路由与导航 | ✅ 新增 |
| INV-020:组织接口零用户字段;报名列表仅运营端且要求 `activity.view` | ✅ |
| 测试库补齐 V023 列/约束 + 三张缺失表;activity 五表移入必选集合 | ✅ |
| 测试:`ActivityPhase14Test`(23)+ `ActivityConstraintGuardTest`(9,解析真实 DDL 与前端源码) | ✅ 新增 |
| 全量回归 **450 tests 全绿** | ✅ |
### Phase 13A — 公益婚恋咨询与婚前辅导(核心链路)✅ 已完成(2026-09-17 KEEP-VERIFY/EXTEND 复验通过)
| 任务 | 状态 |
|------|------|
@@ -351,12 +351,51 @@ D2——`RelationshipService.createMatch` 不写 episode/lastActivityAt,与 `M
**Files:** activity 模块、Miniapp activity 页面、Admin activity 页面。
- [ ] 保留活动基础,完成 ActivityType 分类、按类型报名策略、capacity groups 和多组织关系。
- [ ] 完成小程序分类 Tabs、类型化卡片、动态资格提示和后台动态表单。
- [ ] 验证主办组织关系不自动授予用户库访问权限。
- [x] 保留活动基础,完成 ActivityType 分类、按类型报名策略、capacity groups 和多组织关系。
- [x] 完成小程序分类 Tabs、类型化卡片、动态资格提示和后台动态表单。
- [x] 验证主办组织关系不自动授予用户库访问权限。
**Gate:** matchmaking 与课堂/反诈类活动显示和资格策略不同;容量按配置正确扣减。
> **复验完成(2026-09-17)** — Tag:`phase14-recovery-2026-09-17`
>
> **11 处后端缺陷(D1~D11)**:
> - D1 **Critical 生产故障**:`ActivityMapper.countRemainingCapacity` 无 `@Select` 无 XML
> → 活动详情/报名/审核三条路径必抛 `Invalid bound statement`。
> - D2 **Critical 并发**:报名与审核均为「先读后写」→ 并发超卖;改
> `insertIfCapacityAvailable` / `approveIfCapacityAvailable` 单条原子 SQL。
> - D3 High 生产故障:`activityType` / `orgRole` 未校验生产 `chk_activity_type` /
> `chk_org_role` 白名单("只在 PG 上炸")。
> - D4 High 契约:`ActivityListItemDTO` 无 `activityType` → 小程序分类 Tabs 恒无数据。
> - D5 Medium:男女人数为 `approvedCount/2` 编造 → 改按 `user_profiles.gender` 真实统计。
> - D6 Medium:年龄门槛为 TODO 空壳,完全不生效。
> - D7 Medium:`registration_policy` 只写不读,按类型资格策略为空(Gate 未落地)。
> - D8 Medium:`POST /operator/activities` 丢弃类型/多组织/容量分组,且把
> `organizer_org_id` 错设为操作者 userId → 后台动态表单无法创建带类型活动。
> - D9 Medium:三个运营写入端点错挂在**用户端** `ActivityController`(双实现、零调用方)。
> - D10 Medium NPE:软删活动致 `getMyActivities` NPE;后台漏填字段致创建活动 500。
> - D11 Medium 契约:DTO 缺 `registeredCount`/`status`/`coverFileId`/`description`
> → 卡片报名人数恒 0、按钮恒"查看详情"、封面与摘要恒不显示。
>
> **3 处前端契约缺陷(F1~F3)**:小程序分类 code `classroom` 不在生产白名单
> (应为 `relationship_education`)且缺三类入口;卡片读取 7 个后端根本不存在的字段;
> admin-web 完全没有活动管理页(已新增 `ActivityManagementView.vue` 类型驱动动态表单)。
>
> **2 处测试保真度缺陷(T1~T2)**:`testNoOverbooking` /
> `testNoDuplicateRegistration` 为 `assertTrue(true)` 占位假绿(已删除);
> 测试库缺 V023 列与三张表,且 activity 五表在 `EntityMigrationParityTest`
> 只属"可选白名单"(零覆盖)→ 已补齐并移入必选集合。
>
> **负向验证两轮**:① 后端 —— 从 V023 删 `anti_fraud` + 报名退化为无容量检查
> → 3 条用例精准变红;② 前端契约 —— 分类 code 改回 `classroom` + 卡片加回
> `activity.suitableFor` → 3 条守卫精准变红。均已还原。
>
> **全量回归实测**:`[WARNING] Tests run: 450, Failures: 0, Errors: 0, Skipped: 3` /
> `BUILD SUCCESS`(4 分 52 秒)。Phase 13A 基线 420 → 450(净 +30)。
> admin-web `vue-tsc --noEmit` 0 error、`vite build` 通过。
> **Phase 14 已关闭。Phase 15 未自动进入,等待用户确认。**
> 报告:`docs/Phase-14-V11-Recovery-Completion-Report-2026-09-17.md`
### Phase 15:活动报名、审核与签到 — KEEP-VERIFY/EXTEND
**Files:** activity registration、checkin 服务和 U29~U32 页面。