fix(ops-backend/service-station): close Phase 19 KEEP-VERIFY — 25 defects (unreachable announcements + station write privilege escalation; banner 500 + soft-delete leak; content publish dead-end; program approval dead-end/draft leak/zero counts/missing withdraw; zero-coverage test schema) ; add V042 announcement chain + 34 guard tests; full regression 602 green
Backend - Service Station: expose the 「临时公告」 chain end-to-end (V042 table/service existed but had ZERO endpoints -> both admin and miniapp could never reach it): 5 operator endpoints (list/create/update/publish/archive) + 1 user-facing read endpoint. - Fix privilege escalation: create/update were guarded only by the class-level service_location.view, making V015's service_location.manage a never-used dead permission -> read-only roles could edit stations. Permissions moved to method level. - Add ServiceLocationOperatorController to OperatorEndpointPermissionCoverageTest (it was NOT scanned, which is why the escalation stayed invisible). - Add create/update validation (address NOT NULL + status CHECK only blew up on PG). - Banner: validation failures threw bare RuntimeException -> 500; now VALIDATION_ERROR -> 400. Add deleted_at IS NULL to both home queries (soft-deleted banners stayed live). - Content: operator list/detail split (admin «publish» was a dead end because it reused the published-only public API); category deactivate/reactivate; article body on edit. - Service Program: operator member list now includes pending (approval reachable); active-only public visibility (draft leak); memberCount/pendingCount aggregation; withdraw/removeMember; reuse row on re-apply (UNIQUE(program_id,user_id) crash). - MeController: add the missing GET /me/service-history route. Schema / guards - schema-h2.sql: add service_location_announcements (+ chk_announcement_status) and content_categories (+ uk_content_category_code, chk_category_status); move both into REQUIRED_IN_TEST_SCHEMA. MigrationValidationTest baseline 41 -> 42. - V11PermissionMatrixContractTest now scans ALL migrations instead of hardcoding V015+V029. - New: ServiceLocationAnnouncementPhase19IntegrationTest (24), ServiceLocationControllerPermissionContractTest (5), ServiceLocationAnnouncementConstraintParityTest (5, prod DDL <-> test schema <-> backend whitelist <-> admin labels). Frontend - admin-web: ServiceStationView gains announcement management (statuses, create/edit, publish/archive with confirm, expiry); write buttons gated by service_location.manage. - miniapp: U50 renders the 临时公告 block via new serviceStationService; page no longer talks to utils/api directly; announcement failure degrades to [] instead of erroring. - ui-tests: U37 four-group + U50 announcement contracts; /home assertion retargeted to the service layer. Verification - Negative verification: 3 injected defects (drop soft-delete filter / drop test-schema CHECK / downgrade write endpoint to view) -> 4 named tests fail precisely, no collateral failures; restored, NEG-VAL residue = 0. - Backend full regression: 602 tests, 0 failures, 0 errors, 3 skipped (baseline 420). - admin-web: vue-tsc --noEmit EXIT=0, vite build EXIT=0. Miniapp: tsc --noEmit EXIT=0. - ui-tests 14/14 PASS.
This commit is contained in:
@@ -22,7 +22,9 @@
|
||||
>
|
||||
> **2026-09-17 Phase 18 复验关闭:** EPIC-P18-V11 已完成。复验发现申诉整条真实 DB 路径因测试库缺 `appeals` 表而**零覆盖**,`AppealServiceTest` 9 例中 6 例是 `assertTrue(true)` 假绿(覆盖的恰是 Gate 四条不变量);且 `escalateAppeal` 向 `operatorId` 发用户通知会撞 `notifications.user_id → users(id)` FK(operators 与 users 不同表)→ PG 上升级 100% 失败;`appealType` 未校验生产 `chk_appeal_type` 白名单。另修复 escalated 死胡同、`processAppeal` 先读后写并发竞态、三处查询漏软删过滤、硬编码 `resolved`、证据字段契约。共修复 **8 处真实缺陷**(含 1 处必崩级测试盲区、2 处「只在 PG 上炸」),补 `appeals` 表并新增 `AppealPhase18IntegrationTest`(15 例)、删除假绿 9 例,负向验证证明 Gate 守卫有牙。全量回归 **494 tests 全绿**;admin-web `vue-tsc` EXITCODE=0。
|
||||
> 证据:`docs/Phase-18-V11-Recovery-Completion-Report-2026-09-17.md`。
|
||||
> **Phase 19 需用户确认后启动。**
|
||||
>
|
||||
> **2026-09-17 Phase 19 复验关闭:** EPIC-P19 已完成。复验不采信任何「历史 ✅」,逐条核对运行时路径与生产约束,查出并修复 **25 处真实缺陷**。高价值项:①**服务站「临时公告」整条不可达** —— `ServiceLocationAnnouncementService` 与 V042 表都已存在,但**全仓零端点**,后台与小程序都拿不到(D20);②**服务站 `create/update` 越权写** —— `@PreAuthorize("service_location.view")` 挂在**类**上,V015 下发的 `service_location.manage` 是**从未被使用的死权限**,只读角色可直接改站点(D21);③该控制器**未纳入** `OperatorEndpointPermissionCoverageTest` 扫描清单,导致 D21 长期隐形(D22);④Banner 校验失败抛裸 `RuntimeException` → **500**(应 400)+ 软删 Banner 仍上首页(D2/D3);⑤Content「发布」死路(后台复用只返回 published 的公开接口)+ 分类停用不可恢复(D5/D6);⑥ServiceProgram 审批死路 / 草稿泄露 / 成员数恒 0 / 退出未实现 / 重复申请撞唯一键(D10~D14);⑦测试库缺 `content_categories` 与 `service_location_announcements` → 对应路径**零覆盖**(D9/D23);⑧预约 IDOR(D18);⑨`MigrationValidationTest` 基线未跟进 V042(D24)。新增守卫:`ServiceLocationAnnouncementPhase19IntegrationTest`(24) / `ServiceLocationControllerPermissionContractTest`(5) / `ServiceLocationAnnouncementConstraintParityTest`(5,生产 DDL↔测试库↔后端白名单↔后台标签四方一致);`V11PermissionMatrixContractTest` 由硬编码 V015+V029 改为扫描全部迁移。负向验证:注入 3 处缺陷(移除软删过滤 / 移除测试库 CHECK / 写端点降级为 `view`)→ **4 个用例精准变红且无无关失败**,还原后 `NEG-VAL` 残留 0。后端 **602 tests 全绿**;admin-web `vue-tsc` EXIT=0 + `vite build` EXIT=0;小程序 `tsc --noEmit` EXIT=0;ui-tests 14/14 PASS。
|
||||
> 证据:`docs/Phase-19-V11-Recovery-Completion-Report-2026-09-17.md`。**Phase 20 尚未获准。**
|
||||
|
||||
> **2026-09-16 状态校正:** Phase 07 UI 已按 2026-09-13 规格重新实现,**首批 11 页于 2026-09-16 通过用户验收(结论:基本达标)**,本文件此前"视觉否决/REOPENED"口径随之关闭。
|
||||
>
|
||||
@@ -145,6 +147,7 @@
|
||||
| Phase 16 活动互选二阶段同意复验 | 已完成(2026-09-17) | 见 `Phase-16-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **7 处缺陷 + 1 处隐藏编译缺陷 + 1 处测试保真度缺陷**(**D1 双确认后根本不建 Match/会话(Gate 缺陷·INV-014/015)**、**D3 Phase 15 提交态引用未定义 `reopenEpisode` 致整个模块不编译("474 绿"为假象)**、D4 INV-006 单向意向泄露、D5 复用/新建 Match 后缺会话);统一双实现到 `RelationshipService.confirmActivityInterest`;`MatchMapper` 补 `reopenEpisode`;测试库补 `activity_mutual_connection_consents` 表并移入必选集合,`MigrationValidationTest` 基线 40→41;新建 `ActivityMutualConsentServiceTest`(7 用例);`ChatPhase09IntegrationTest:344` 断言对齐 AGENTS.md #20 复用语义;新增 V041 `expires_at`+`match_id`;负向验证 2 用例精准变红并还原;后端 **481 tests 全绿**;**Phase 17 尚未获准** |
|
||||
| Phase 17 举报与风控复验 | 已完成(2026-09-17) | 见 `Phase-17-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **6 处真实缺陷**(**D1(Critical,必崩级盲区)测试库缺 `reports`/`risk_events`/`risk_actions` 三表 → 风控/举报真实 DB 路径零覆盖,`ReportServiceTest` 全 `assertTrue(true)` 假绿**;**D3(High)敏感访问(查看他人风险画像/举报)无审计(违反 AGENTS.md #8)**;D4 审计缺 `ip`/`userAgent`;D5 `findByUserId` 未过滤软删;D2 人工标记无审计;**D6 `javax`→`jakarta` 隐藏编译缺陷 + `risk_events` 漏 `updated_at` 列**);补齐三表并移出白名单入 `REQUIRED_IN_TEST_SCHEMA`;新增 `AuditLogService.recordSensitiveAccess` 并接线三端点;新增 `ReportAndRiskPhase17IntegrationTest`(7 用例);负向验证(注释 getReportDetail 审计 → D3 守卫精准变红并还原);后端 **488 tests 全绿**;**Phase 18 尚未获准** |
|
||||
| Phase 18 申诉复验 | 已完成(2026-09-17) | 见 `Phase-18-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **8 处真实缺陷**(**D1(Critical,必崩级盲区)测试库缺 `appeals` 表 + `AppealServiceTest` 9 例中 6 例 `assertTrue(true)` 假绿 → 申诉真实 DB 路径零覆盖**;**D2(High,只在 PG 上炸)`escalateAppeal` 向 `operatorId` 发用户通知撞 `notifications.user_id → users(id)` FK → 升级事务整体回滚**;**D3(High,只在 PG 上炸)`appealType` 未校验生产 `chk_appeal_type` 白名单**;D4 escalated 死胡同(前后端均无法再决策);D5 `processAppeal` 先读后写并发竞态;D6 三处查询漏 `deleted_at` 过滤;D7 硬编码 `resolved`;D8 证据字段契约 `evidenceFileIds`);补 `appeals` 表并移出白名单入 `REQUIRED_IN_TEST_SCHEMA`;新增原子认领 `markReviewingIfProcessable`;新增 `AppealPhase18IntegrationTest`(15 用例),删除假绿 `AppealServiceTest`(9 用例);负向验证(误伤其他处置 → Gate 用例精准变红并还原);后端 **494 tests 全绿**;admin-web `vue-tsc` EXITCODE=0;**Phase 19 尚未获准** |
|
||||
| Phase 19 统一运营后台 + 用户端公益服务页面复验 | 已完成(2026-09-17) | 见 `Phase-19-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **25 处真实缺陷**(**D20 服务站公告能力整条不可达:Service + V042 表都在、全仓零端点**;**D21 服务站 `create/update` 越权写:类级 `service_location.view` 兜底,`service_location.manage` 是从未使用的死权限**;D22 该控制器未纳入权限覆盖守卫;**D2/D3 Banner 校验 500 + 软删 Banner 仍上首页**;**D5/D6 Content 发布死路 + 分类停用不可恢复**;D10~D14 ServiceProgram 审批死路/草稿泄露/成员数恒 0/退出未实现/撞唯一键;D9/D23 测试库缺 `content_categories` 与 `service_location_announcements` → 零覆盖;D18 预约 IDOR;D24 迁移基线未跟进 V042);补 V042 公告表 + 运营端五端点 + 用户端只读端点 + 后台公告管理 UI + 小程序 U50 公告区块;新增 3 组守卫(24+5+5 用例)并把服务站运营端收入权限覆盖扫描;负向验证 3 注入 → 4 用例精准变红;后端 **602 tests 全绿**;admin-web `vue-tsc`/`vite build` 双绿;小程序 `tsc` EXIT=0;ui-tests 14/14;**Phase 20 尚未获准** |
|
||||
| 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,217 @@
|
||||
# Phase 19 V1.1 Recovery Completion Report — 统一运营后台 + 用户端公益服务页面
|
||||
|
||||
> 日期:2026-09-17
|
||||
> 阶段:Phase 19(统一运营后台 ADD + 用户端公益服务页面 REFACTOR)
|
||||
> 分支:`main` | Tag:`phase19-recovery-2026-09-17`
|
||||
> 结论:**CLOSED(Gate 全项通过)**
|
||||
|
||||
---
|
||||
|
||||
## 一、计划目标与 Gate
|
||||
|
||||
官方 Phase 19 计划(`WorkBuddy首轮启动提示词 + Phase 01~Phase 26 Jira级任务清单 + 每阶段验收条件.md` 第 1734 行起)要求的是「统一运营后台」;
|
||||
`STATUS.md` 的 V1.1 待实现模块表把 **Banner 管理 / 内容管理 / 专项公益服务项目 / 善缘服务站与用户端公益服务页面** 一并映射到 Phase 19,
|
||||
用户端 U 编号页面定义见 `docs/善缘丹阳 V1.1 页面级功能清单与交互流程修订版.md`(U49~U54 / U60 / U61 / U64 / U37 / U50)。
|
||||
|
||||
| Gate | 要求 | 结论 | 证据 |
|
||||
|---|---|---|---|
|
||||
| Gate ① | 小程序页面清单无缺页且路由可达 | ✅ 通过 | `ui-tests/ui-contracts.mjs`:`app.json` 页面数 **63**;14 个 ui-tests 全绿 |
|
||||
| Gate ② | 七类后台角色只看到自己的工作 | ✅ 通过 | `admin-role-navigation-contracts.mjs` 全绿;`V11PermissionMatrixContractTest` 扫全部迁移;`OperatorEndpointPermissionCoverageTest` 扩至含服务站 |
|
||||
| Gate ③ | 后台 type-check / build 通过 | ✅ 通过 | `vue-tsc --noEmit` EXIT=0;`vite build` EXIT=0(9.17s,仅 chunk 体积警告) |
|
||||
| Gate ④ | 公告 / Banner / 内容 / 服务项目 / 预约链路真实可达 | ✅ 通过 | 后端全量回归 **602 tests, 0 failures, 0 errors** |
|
||||
|
||||
---
|
||||
|
||||
## 二、复验方法(KEEP-VERIFY)
|
||||
|
||||
本轮不采信任何「历史 ✅」。对每一项交付物按下列顺序逐条核对:
|
||||
|
||||
1. **运行时路径**:从 HTTP 端点出发,逐跳确认 Controller → Service → Mapper → DDL 无断点、无死路。
|
||||
2. **生产约束**:核对生产迁移里的 `NOT NULL` / `CHECK` / `UNIQUE` / 外键,确认代码有前置校验。
|
||||
3. **测试库保真度**:测试库 `schema-h2.sql` 是否与生产同约束(缺约束 = 该非法路径零覆盖 = 假绿)。
|
||||
4. **守卫有效性**:守卫是否解析**真实 DDL**、是否会**精准变红**。
|
||||
5. **前端落点**:后端能力是否有真实前端入口(后台 + 小程序两端)。
|
||||
|
||||
---
|
||||
|
||||
## 三、复验查出的真实缺陷与修复
|
||||
|
||||
### 3.1 死路类(能力写了但接不上 → 用户/运营 100% 用不到)
|
||||
|
||||
| 编号 | 模块 | 缺陷 | 根因 | 修复 |
|
||||
|---|---|---|---|---|
|
||||
| D1 | `/me` | `GET /me/service-history` **端点从未写出** | 仅有 Service 方法,Controller 缺路由 | `MeController` 补端点,`MeRouteContractTest` 转绿 |
|
||||
| D5 | Content | 后台「发布」是**死路**:后台复用只返回 `published` 的公开接口,草稿永远不出现 | 公开/运营未分离 | 新增 `getOperatorArticles/Categories/ArticleDetail` + `/content/operator/*` 三端点 |
|
||||
| D10 | Service Program | **审批死路**:运营端名单只返回 `approved`,`pending` 申请永不出现,后台「通过/拒绝」按钮永远点不到 | 名单查询复用用户端语义 | `findByProgram` 全状态 + `getProgramMembers` |
|
||||
| D13 | Service Program | **「退出」未实现**:生产 CHECK 早已允许 `withdrawn`,无任何代码写入 | 清单项未实现 | `withdrawFromProgram` + `removeMember` |
|
||||
| D20 | Service Station | **公告整条不可达**:`ServiceLocationAnnouncementService` 与 V042 表都存在,但**全仓零端点**,后台与小程序都拿不到 | 只写了 Service,未接 Controller | 运营端 5 端点 + 用户端只读 1 端点 |
|
||||
|
||||
### 3.2 越权 / 安全类
|
||||
|
||||
| 编号 | 模块 | 缺陷 | 修复 |
|
||||
|---|---|---|---|
|
||||
| D18 | Service Appointment | **预约 IDOR**:越权访问他人预约 | 控制器取 `principal.id()` + service 归属校验 + 运营侧 `DataScopeGuard` / `canAccessByOperator` |
|
||||
| D21 | Service Station | **越权写**:`@PreAuthorize("service_location.view")` 挂在**类**上,`create`/`update` 实际只要求"查看"权限;V015 下发的 `service_location.manage` 成了**从未被使用的死权限** → 只读角色可直接改站点 | 权限下沉到方法级:读 `view`、写 `manage` |
|
||||
| D22 | Service Station | `ServiceLocationOperatorController` **未纳入** `OperatorEndpointPermissionCoverageTest` 扫描清单 → D21 长期隐形 | 控制器加入 `CONTROLLERS` |
|
||||
|
||||
### 3.3 约束漂移类(生产有约束、测试库没有 → 只在 PG 上炸)
|
||||
|
||||
| 编号 | 缺陷 | 修复 |
|
||||
|---|---|---|
|
||||
| D2 | Banner 校验失败抛裸 `RuntimeException` → 落兜底 handler 变 **500**(应为 400) | 统一 `BusinessException(VALIDATION_ERROR)` |
|
||||
| D3 | `findActiveBanners` / `findActiveBannersByScope` 漏 `deleted_at IS NULL` → **软删 Banner 仍上首页** | 补软删过滤 |
|
||||
| D9/D23 | 测试库缺 `content_categories`、`service_location_announcements`,且缺 `chk_category_status` / `chk_announcement_status` → 对应路径零覆盖 | 补表 + 补 CHECK;移入 `REQUIRED_IN_TEST_SCHEMA` |
|
||||
| D25 | 服务站 `create/update` 无入参校验:`address NOT NULL` 与 `status` CHECK 只在 PG 炸 | 前置校验 → 400 |
|
||||
| D11 | Service Program 公开列表/详情**不限定状态** → 草稿项目泄露 | `getActivePrograms` / active 限定 |
|
||||
| D14 | 重复申请撞 `UNIQUE(program_id, user_id)` → 500 | 复用原记录(被拒/退出后重新申请) |
|
||||
|
||||
### 3.4 数据正确性类
|
||||
|
||||
| 编号 | 缺陷 | 修复 |
|
||||
|---|---|---|
|
||||
| D12 | Service Program 成员数 / 待审数**恒为 0**(DTO 无字段) | DTO 增 `memberCount` / `pendingCount` + 聚合统计 |
|
||||
| D6 | Content 分类**停用不可恢复**(公开接口只给 active) | `getOperatorCategories` + `setCategoryStatus`(不做物理删除,避免破坏文章外键) |
|
||||
| D7/D8 | 内容编辑看不到正文;前后端字段命名不一致(snake vs camel) | 详情带 `content`;前端对齐 camelCase |
|
||||
|
||||
### 3.5 前端与守卫类
|
||||
|
||||
| 编号 | 缺陷 | 修复 |
|
||||
|---|---|---|
|
||||
| D4 | Banner 后台缺 `buttonText` / 状态下拉 / 预览 / 禁外链前置校验 | `BannerManagementView` 补齐(预览用模拟小程序卡片,**禁外链**) |
|
||||
| D15 | `V11PermissionMatrixContractTest` **硬编码 V015+V029** → 后续迁移的权限变更全部隐形 | 改为扫描**全部迁移** |
|
||||
| D16 | V042 曾新增 `service_appointment.register` 权限,与 V029 的 `service.assisted_registration` 语义重复 | 删除重复迁移,收敛到既有权限(收敛权限膨胀) |
|
||||
| D17 | U37「我的」未按信息架构分四组 → 入口找不到归属 | 重构为四组 + 22 个 `bindtap` 全部有实现(ui-tests 增断言) |
|
||||
| D19 | `ServiceProgramOperatorPhase19IntegrationTest:114` 编译错误(`mapper.selectOne` 返回**实体**却赋给 **DTO**) | 修正变量类型;该错误此前让整轮回归卡在 testCompile |
|
||||
|
||||
---
|
||||
|
||||
## 四、本轮新增能力(此前完全不存在)
|
||||
|
||||
### 4.1 善缘服务站「临时公告」(V042)
|
||||
|
||||
全仓此前**无表、无实体、无 Mapper、无接口**。本轮补齐:
|
||||
|
||||
| 层 | 文件 | 说明 |
|
||||
|---|---|---|
|
||||
| DDL | `V042__add_service_location_announcements.sql` | `status` CHECK(draft/published/archived)、`expires_at` 支撑"临时"语义、软删、`(location_id, status, sort_order)` 部分索引 |
|
||||
| Entity | `ServiceLocationAnnouncement.java` | — |
|
||||
| Mapper | `ServiceLocationAnnouncementMapper.java` | `findVisibleByLocation`(published + 未过期 + 未软删)、`findByLocation`(运营全状态) |
|
||||
| Service | `ServiceLocationAnnouncementService.java` | 可见性 / 白名单 / 标题长度 / ISO-8601 解析 / 404 语义 |
|
||||
| 运营端点 | `ServiceLocationOperatorController` | 列表 / 新建 / 编辑 / 发布 / 归档(读 `view`、写 `manage`) |
|
||||
| 用户端点 | `ServiceLocationController` | `GET /service-locations/{id}/announcements`,只读 published + 未过期 |
|
||||
| 后台 UI | `ServiceStationView.vue` | 公告抽屉:全状态列表 + 新建/编辑 + 发布/下架二次确认 + 过期时间选择 |
|
||||
| 小程序 | U50 `service-station` + `serviceStationService.js` | 「临时公告」区块,点击弹窗看正文;失败降级为空数组,不阻断站点信息 |
|
||||
|
||||
**用户端语义(刻意设计,已在代码注释固化)**:站点不存在或已软删 → 返回**空列表**而非错误码,避免存在性探测;
|
||||
**不**按 `service_locations.status` 过滤 —— 站点"休息中/已关闭"时公告(如"本站在改造,请前往相邻服务站")恰恰最需要触达用户。
|
||||
|
||||
### 4.2 用户端公益服务页面(U 编号)
|
||||
|
||||
U49~U54 / U60 / U61 / U64 新增并注册;U37「我的」重构为四组结构;U50 补 `serviceLocationId` 透传与公告区块;
|
||||
U51 补只读「服务站点」字段并回传 `serviceLocationId`。统一复用 Token / 状态组件(`ui-loading` / `ui-empty` / `ui-error-state`)。
|
||||
|
||||
---
|
||||
|
||||
## 五、守卫与测试保真度(本轮新增)
|
||||
|
||||
| 守卫 | 用例数 | 作用 |
|
||||
|---|---|---|
|
||||
| `ServiceLocationAnnouncementPhase19IntegrationTest` | 24 | 公告**真实 DB** 路径:草稿不泄露 / 发布可见 / 归档隐藏 / 过期自动隐藏 / 排序 / 软删 / 状态白名单 / 标题校验 / ISO 解析 400 / 404 / 部分更新语义 |
|
||||
| `ServiceLocationControllerPermissionContractTest` | 5 | **写端点必须 `manage`**;类级不得挂粗粒度注解(否则新增写方法静默继承读权限);用户端点不得带运营权限 |
|
||||
| `ServiceLocationAnnouncementConstraintParityTest` | 5 | **生产 V042 DDL ↔ 测试库 ↔ 后端白名单 ↔ 后台标签** 四方一致;表在两侧都存在(自检) |
|
||||
| `BannerOperatorPhase19IntegrationTest` | 18 | 禁外链 / 白名单 / 软删过滤 / 约束保真度 |
|
||||
| `BannerConstraintParityTest` | 5 | Banner 四方一致性 |
|
||||
| `ContentOperatorPhase19IntegrationTest` | 16 | 发布死路 / 分类停用 / 校验 / 软删 / 约束保真度 |
|
||||
| `ServiceProgramOperatorPhase19IntegrationTest` | 29 | 审批可达 / 草稿泄露 / 退出 / 唯一键 / 统计 / 约束保真度 |
|
||||
| `ServiceProgramControllerPermissionContractTest` | 4 | 写 `manage`、读 `view`、用户端点不守护 |
|
||||
| `OperatorEndpointPermissionCoverageTest` | 2 | 扫描清单**新增服务站运营端** |
|
||||
| `MeRouteContractTest` / `ServiceAppointmentOwnerGuardTest` | — | `/me/service-history` 路由;预约归属与数据范围 |
|
||||
|
||||
`AppealServiceTest` 式的 `assertTrue(true)` 假绿在本轮被清除;`EntityMigrationParityTest` 的
|
||||
`testSchemaMissingTablesAreWhitelisted` 主动报出了 D23(生产表在测试库缺失),证明该守卫解析的是**真实迁移 DDL**。
|
||||
|
||||
---
|
||||
|
||||
## 六、负向验证(证明守卫是承重的,而非装饰)
|
||||
|
||||
一次性注入 3 处缺陷(均以 `NEG-VAL` 标记),跑定向回归 → **4 个用例精准变红,且无任何无关失败**:
|
||||
|
||||
| 注入缺陷 | 精准变红的用例 | 断言信息 |
|
||||
|---|---|---|
|
||||
| 移除 `findVisibleByLocation` 的 `deleted_at IS NULL` | `softDeletedAnnouncementIsInvisibleToBothSides` | 软删后用户端不得再看到该公告 |
|
||||
| 从测试库移除 `chk_announcement_status` | `databaseCheckRejectsIllegalStatusEvenWhenServiceValidationIsBypassed` | 期望抛异常但未抛(非法状态落库成功) |
|
||||
| 同上 | `productionAndTestSchemaDeclareIdenticalAnnouncementStatusCheck` | 未能解析出 `chk_announcement_status`(守卫显式报错而非假绿) |
|
||||
| 把 `update` 的权限降级为 `service_location.view` | `everyWriteEndpointRequiresManagePermission` | `update` 权限码不符合 V015(期望 `manage`,实为 `view`) |
|
||||
|
||||
还原后 `grep -rc "NEG-VAL"` = **0**,全量回归恢复全绿。
|
||||
|
||||
---
|
||||
|
||||
## 七、回归证据
|
||||
|
||||
```
|
||||
后端全量:Tests run: 602, Failures: 0, Errors: 0, Skipped: 3 / BUILD SUCCESS
|
||||
(复验起点 420 → 本轮 602,净 +182;本轮相对「修完编译错误」基线 568 净 +34)
|
||||
前端 admin-web:vue-tsc --noEmit EXIT=0;vite build EXIT=0(9.17s,仅 chunk 体积警告)
|
||||
小程序:tsc --noEmit EXIT=0
|
||||
契约:ui-tests 14/14 PASS(含「63 页清单」「U37 四组」「U50 公告」断言)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、明确记录的设计决策(非缺陷,但需知情)
|
||||
|
||||
1. **`service_location.manage` 仅授予 `platform_admin`**,**未**扩权给 `service_station_operator`
|
||||
(V015 该角色刻意只有 `view`,定位是"现场处理预约")。
|
||||
因此服务站信息与公告的维护入口对平台管理员开放;服务站运营人员在后台可见站点与公告列表(只读),
|
||||
写按钮按同一权限显隐,避免"点了才 403"。
|
||||
若后续要求服务站**自主**维护本站公告,需单独评审并同时引入 `service_location_operators`
|
||||
的站点级数据范围校验(不能只加权限 —— 那会让任一服务站运营改到别人的站点)。
|
||||
2. **`/service-locations/**` 不做匿名放行**:公告是登录后可见内容,鉴权走 `SecurityConfig` 的 `/** → authenticated` 兜底,
|
||||
与 `/home` 一致。
|
||||
3. **公告不做物理删除**:下架走 `archived`,保留可追溯性;软删字段仍保留但不由运营直接触发。
|
||||
4. **`/home` 聚合刻意不塞公告**:公告是"站点详情"维度内容,塞进首页聚合会让首页随公告变化而频繁失效缓存。
|
||||
|
||||
---
|
||||
|
||||
## 九、遗留与下一阶段
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| `ServiceLocationOperatorController` 支持 `service_station_operator` 站点级自维护 | ⏳ 需产品确认(见决策 1) |
|
||||
| 公告富文本 / 图片 | ⏳ 当前为纯文本 `content` |
|
||||
| 计划表中「本地资格声明与审核」(Phase 04~05)等仍有 ⏳ 项 | 后续阶段 |
|
||||
| 下一阶段 | Phase 20 起(等用户确认) |
|
||||
|
||||
---
|
||||
|
||||
## 附:本轮改动文件(节选)
|
||||
|
||||
**后端**
|
||||
`service_appointment/controller/ServiceLocationOperatorController.java`(重写)、
|
||||
`service_appointment/controller/ServiceLocationController.java`(新增)、
|
||||
`service_appointment/service/ServiceLocationAnnouncementService.java`、
|
||||
`service_appointment/mapper/ServiceLocationAnnouncementMapper.java`、
|
||||
`service_appointment/entity/ServiceLocationAnnouncement.java`、
|
||||
`db/migration/V042__add_service_location_announcements.sql`、
|
||||
`banner/controller/HomeBannerOperatorController.java`、`banner/mapper/HomeBannerMapper.java`、
|
||||
`content/service/ContentService.java`、`content/controller/ContentController.java`、
|
||||
`service_program/**`、`me/controller/MeController.java`
|
||||
|
||||
**前端**
|
||||
`admin-web/src/modules/service-station/ServiceStationView.vue`、
|
||||
`admin-web/src/modules/banner/BannerManagementView.vue`、
|
||||
`admin-web/src/modules/content/ContentManagementView.vue`、
|
||||
`admin-web/src/modules/service-program/ServiceProgramView.vue`、
|
||||
`miniapp/pages/service-station/*`、`miniapp/services/serviceStationService.js`、
|
||||
`miniapp/pages/mine/*`、`miniapp/pages/service-appointment/*`
|
||||
|
||||
**守卫 / 测试**
|
||||
`backend/src/test/resources/schema-h2.sql`、
|
||||
`ServiceLocationAnnouncementPhase19IntegrationTest`、
|
||||
`ServiceLocationControllerPermissionContractTest`、
|
||||
`ServiceLocationAnnouncementConstraintParityTest`、
|
||||
`BannerOperatorPhase19IntegrationTest`、`BannerConstraintParityTest`、
|
||||
`ContentOperatorPhase19IntegrationTest`、`ServiceProgramOperatorPhase19IntegrationTest`、
|
||||
`OperatorEndpointPermissionCoverageTest`、`EntityMigrationParityTest`、`MigrationValidationTest`、
|
||||
`ui-tests/miniapp-reference-contracts.mjs`
|
||||
+27
-9
@@ -1,8 +1,8 @@
|
||||
# 项目状态跟踪 — STATUS.md
|
||||
|
||||
> **2026-09-17 当前有效结论:`V1.1 INCREMENTAL RECOVERY PHASE 18 COMPLETE / GIT CLEAN`。**
|
||||
> **2026-09-17 当前有效结论:`V1.1 INCREMENTAL RECOVERY PHASE 19 COMPLETE / UI REDESIGN ACCEPTED BY USER / 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(婚恋咨询与婚前辅导)、Phase 14(多类型公益活动)、Phase 15(活动报名、审核与签到)、Phase 16(活动互选与双方开启沟通确认)、Phase 17(举报与风控)、Phase 18(申诉)复验已关闭**,十二轮共修复 85 处真实缺陷(含 2 处隐藏编译缺陷),并各自补齐测试保真度与守卫。**Phase 19 需用户确认后启动。**
|
||||
> 阶段 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(多类型公益活动)、Phase 15(活动报名、审核与签到)、Phase 16(活动互选与双方开启沟通确认)、Phase 17(举报与风控)、Phase 18(申诉)、Phase 19(统一运营后台 + 用户端公益服务页面)复验已关闭**,十三轮共修复 110+ 处真实缺陷(含 3 处隐藏编译缺陷),并各自补齐测试保真度与守卫。**Phase 20 起需用户确认后启动。**
|
||||
|
||||
最后更新: 2026-09-17 — 分支 `main`(`feat-ui-v11` 保留为冗余锚点)
|
||||
|
||||
@@ -455,12 +455,13 @@
|
||||
| 模块 | 对应 Phase | 状态 |
|
||||
|------|-----------|------|
|
||||
| Home Aggregate API + V1.1首页 | Phase 07 | ✅ 已完成 |
|
||||
| Banner 管理(后端+前端) | Phase 19 | ⏳ 待开发 |
|
||||
| Banner 管理(后端+前端) | Phase 19 | ✅ 已完成(2026-09-17 复验关闭) |
|
||||
| 本地资格声明与审核 | Phase 04~05 | ⏳ 待开发 |
|
||||
| 公益红娘服务转介 | Phase 13 | ✅ 已完成 |
|
||||
| 婚恋咨询 + 婚前辅导(Phase 13A) | Phase 13A | ✅ 已完成(2026-09-17 复验关闭) |
|
||||
| 内容管理(婚恋课堂/反诈) | Phase 19 | ⏳ 待开发 |
|
||||
| 专项公益服务项目 | Phase 19 | ⏳ 待开发 |
|
||||
| 内容管理(婚恋课堂/反诈) | Phase 19 | ✅ 已完成(2026-09-17 复验关闭) |
|
||||
| 专项公益服务项目 | Phase 19 | ✅ 已完成(2026-09-17 复验关闭) |
|
||||
| 善缘服务站 + 用户端公益服务页面(U37/U49~U54/U60/U61/U64) | Phase 19 | ✅ 已完成(含 V042 临时公告) |
|
||||
| V1.1新增不变量测试(INV-011~020) | Phase 23 | ⏳ 待开发 |
|
||||
|
||||
### 当前运行状态
|
||||
@@ -491,14 +492,31 @@ MinIO: localhost:9003 (凭证由本地环境变量提供)
|
||||
|
||||
**Phase 13A(公益婚恋咨询与婚前辅导)— 已完成** ✅
|
||||
|
||||
### Phase 19 — 统一运营后台(后端API核心链路)✅ 已完成
|
||||
### Phase 19 — 统一运营后台 + 用户端公益服务页面 ✅ 已完成(2026-09-17 复验关闭)
|
||||
|
||||
> 复验报告:`docs/Phase-19-V11-Recovery-Completion-Report-2026-09-17.md`
|
||||
> 后端全量回归:`Tests run: 602, Failures: 0, Errors: 0, Skipped: 3 / BUILD SUCCESS`
|
||||
> admin-web:`vue-tsc --noEmit` EXIT=0、`vite build` EXIT=0;小程序:`tsc --noEmit` EXIT=0;ui-tests 14/14 PASS
|
||||
|
||||
| 任务 | 状态 |
|
||||
|------|------|
|
||||
| V026 内容+专项项目+组织关系表 | ✅ |
|
||||
| V042 善缘服务站「临时公告」表(本轮新建) | ✅ |
|
||||
| ContentService(分类/文章 CRUD + publish/offline) | ✅ |
|
||||
| ServiceProgramService(项目CRUD + 成员审批) | ✅ |
|
||||
| V021~V026 共6个迁移文件 | ✅ |
|
||||
| Admin Web前端 | ⏳ 后续补充 |
|
||||
| Content 运营端接口分离(修「发布死路」/「分类停用不可恢复」) | ✅ |
|
||||
| ServiceProgramService(项目CRUD + 成员审批 + 退出/移除/统计) | ✅ |
|
||||
| Banner 运营端(白名单校验 400 而非 500 + 软删过滤 + 禁外链 + 预览) | ✅ |
|
||||
| 善缘服务站运营端(信息维护 + 公告五端点 + 权限下沉方法级) | ✅ |
|
||||
| 预约 IDOR 修复(principal.id() + 归属校验 + DataScopeGuard) | ✅ |
|
||||
| Admin Web 前端(Banner / 内容 / 服务项目 / 服务站公告) | ✅ |
|
||||
| 用户端 U37 四组重构 + U49~U54/U60/U61/U64 页面 | ✅ |
|
||||
|
||||
**本轮复验修复 25 处真实缺陷**,其中高价值项:服务站公告能力**整条不可达**(Service + 表都在、零端点);
|
||||
服务站 `create/update` **越权写**(写端点只受 `service_location.view` 保护,`manage` 是从未使用的死权限)
|
||||
且该控制器**未纳入**权限覆盖守卫;Banner 校验抛 500;Content 发布死路;ServiceProgram 审批死路 / 草稿泄露 / 成员数恒 0 / 退出未实现;
|
||||
测试库缺 `content_categories` 与 `service_location_announcements`(对应路径零覆盖)。
|
||||
新增守卫:`ServiceLocationAnnouncementPhase19IntegrationTest`(24) / `ServiceLocationControllerPermissionContractTest`(5) /
|
||||
`ServiceLocationAnnouncementConstraintParityTest`(5)。负向验证 3 处注入 → 4 个用例精准变红并还原(`NEG-VAL` 残留 0)。
|
||||
|
||||
### Phase 20 — Analytics数据统计扩展 ✅ 已完成
|
||||
| 任务 | 状态 |
|
||||
|
||||
@@ -479,17 +479,27 @@ D2——`RelationshipService.createMatch` 不写 episode/lastActivityAt,与 `M
|
||||
|
||||
**Files:** Admin banner/content/service-station/service-appointment/service-program/counseling/referral 模块、MainLayout/router;Miniapp 新增 U49~U54、U60、U61、U64 页面;service API 文件。
|
||||
|
||||
- [ ] 先修复 Admin 全量 type-check 和角色路由,再实现“角色第一屏知道今天做什么”的待办型工作台。
|
||||
- [ ] Banner 完成 CRUD、排期、排序、Audience、内部 target 校验和预览;禁止外链。
|
||||
- [ ] Content 完成分类、文章编辑、发布/下架;用户端只做文章列表/详情,不做 Feed、评论和点赞。
|
||||
- [ ] Service Station 完成万善公园站信息、开放时间、公告、服务项目和预约管理。
|
||||
- [ ] 修复预约 IDOR:普通用户创建、详情、列表、取消只能操作本人;代登记使用独立 operator 接口和权限。
|
||||
- [ ] Service Program 完成项目、申请、审批、退出和必要统计,禁止把项目成员身份变成用户价值标签。
|
||||
- [ ] 新增并注册 U49 公益服务中心、U50 服务站、U51 预约、U52 我的预约、U53 课堂、U54 内容详情、U60 反诈、U61 专项服务、U64 公益服务记录。
|
||||
- [ ] 重构 U37“我的”四组结构,接入上述新增页面;U64 只显示用户自己的服务时间线,不显示内部备注。
|
||||
- [ ] 所有页面复用统一 Token 和组件,覆盖 Loading、Skeleton、Empty、Error、Permission、Disabled、Responsive。
|
||||
- [x] 先修复 Admin 全量 type-check 和角色路由,再实现“角色第一屏知道今天做什么”的待办型工作台。
|
||||
- [x] Banner 完成 CRUD、排期、排序、Audience、内部 target 校验和预览;禁止外链。
|
||||
- [x] Content 完成分类、文章编辑、发布/下架;用户端只做文章列表/详情,不做 Feed、评论和点赞。
|
||||
- [x] Service Station 完成万善公园站信息、开放时间、公告、服务项目和预约管理。
|
||||
- [x] 修复预约 IDOR:普通用户创建、详情、列表、取消只能操作本人;代登记使用独立 operator 接口和权限。
|
||||
- [x] Service Program 完成项目、申请、审批、退出和必要统计,禁止把项目成员身份变成用户价值标签。
|
||||
- [x] 新增并注册 U49 公益服务中心、U50 服务站、U51 预约、U52 我的预约、U53 课堂、U54 内容详情、U60 反诈、U61 专项服务、U64 公益服务记录。
|
||||
- [x] 重构 U37“我的”四组结构,接入上述新增页面;U64 只显示用户自己的服务时间线,不显示内部备注。
|
||||
- [x] 所有页面复用统一 Token 和组件,覆盖 Loading、Skeleton、Empty、Error、Permission、Disabled、Responsive。
|
||||
|
||||
**Gate:** 小程序 U01~U64 页面清单无缺页且路由可达;七类后台角色只看到自己的工作;后台 type-check/build 通过。
|
||||
**Gate:** 小程序 U01~U64 页面清单无缺页且路由可达 ✅(`ui-tests/ui-contracts.mjs`:`app.json` 63 页);七类后台角色只看到自己的工作 ✅(`admin-role-navigation-contracts` + `V11PermissionMatrixContractTest` 扫全部迁移 + `OperatorEndpointPermissionCoverageTest`);后台 type-check/build 通过 ✅(`vue-tsc --noEmit` EXIT=0、`vite build` EXIT=0)。
|
||||
|
||||
**复验结论:** 修复 **25 处真实缺陷**。高价值项:**Service Station 公告能力整条不可达**(Service 与 V042 表都已存在但全仓零端点,后台与小程序都拿不到)→ 补运营端五端点 + 用户端只读端点 + 后台公告 UI + 小程序 U50 公告区块;
|
||||
**服务站 `create/update` 越权写**(类级 `service_location.view` 兜底,V015 的 `service_location.manage` 是从未被使用的死权限)→ 权限下沉方法级,并把该控制器纳入权限覆盖守卫(此前未纳入,正是缺陷长期隐形的原因);
|
||||
**Banner 校验失败落 500**(裸 `RuntimeException`)→ 统一 `VALIDATION_ERROR`,并补软删过滤(软删 Banner 曾仍上首页);
|
||||
**Content「发布」是死路**(后台复用只返回 `published` 的公开接口,草稿永不出现)→ 运营端接口分离;**Service Program 审批死路 / 草稿项目泄露 / 成员数恒 0 / 「退出」未实现 / 重复申请撞唯一键**;
|
||||
**测试库缺 `content_categories` 与 `service_location_announcements`** → 对应路径零覆盖(约束漂移盲区),已补表并移入必选集合。
|
||||
新增守卫:`ServiceLocationAnnouncementPhase19IntegrationTest`(24) / `ServiceLocationControllerPermissionContractTest`(5) / `ServiceLocationAnnouncementConstraintParityTest`(5,**生产 DDL ↔ 测试库 ↔ 后端白名单 ↔ 后台标签** 四方一致);`V11PermissionMatrixContractTest` 由硬编码 V015+V029 改为扫描全部迁移,消除「后续迁移权限变更隐形」。
|
||||
负向验证:注入 3 处缺陷(移除软删过滤 / 移除测试库 CHECK / 写端点降级为 `view`)→ **4 个用例精准变红且无无关失败**,还原后 `NEG-VAL` 残留 0。
|
||||
全量回归 **602 tests 全绿**(起点 420);admin-web `vue-tsc` EXIT=0 + `vite build` EXIT=0;小程序 `tsc --noEmit` EXIT=0;ui-tests 14/14。
|
||||
**报告:** `docs/Phase-19-V11-Recovery-Completion-Report-2026-09-17.md`。**Phase 20 需用户确认后启动。**
|
||||
|
||||
### Phase 20:公益服务 Analytics — EXTEND
|
||||
|
||||
|
||||
Reference in New Issue
Block a user