2026-09-18 11:00:08 +08:00
|
|
|
|
# 善缘丹阳 V1.1 — Phase 22 部署运维配置扩展(Feature Flag)KEEP-VERIFY 复验 Completion Report
|
|
|
|
|
|
|
|
|
|
|
|
> 日期:2026-09-18 | 复验纪律:历史 ✅ 不可信;逐条核对运行时路径与生产约束;发现真实缺陷即修;补测试保真度;守卫解析真实 DDL/真实 SQL;单段分支提交 + tag + bundle + 回读。
|
|
|
|
|
|
> 范围说明:计划文件 `docs/善缘丹阳 V1.1 客户需求基线增量开发与修复计划.md` 的 Phase 22 实为「Deployment / Ops 基础设施」(Docker/CI/备份/密钥),与 STATUS.md 遗留区「Phase 22 部署运维配置扩展」所指的 **Feature Flag 配置扩展** 是两份不同工作。本 Phase 复验的是后者(用户确认的「部署运维配置扩展」标签),即 7 个 V1.1 Feature Flag + `FeatureFlagController` + `application.yml` 默认值。计划的 Deployment/Ops 基础设施 Phase 22 为另一块更大的运维任务,需另行确认。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 一、复验起点:STATUS 声明 vs 实际运行时路径
|
|
|
|
|
|
|
|
|
|
|
|
STATUS 遗留区「Phase 22 部署运维配置扩展 ✅ 已完成」逐条声明(P22-T01~T07、FeatureFlagController API、application.yml 默认配置)。KEEP-VERIFY 逐项核对真实代码路径后,结论如下:
|
|
|
|
|
|
|
|
|
|
|
|
| STATUS 声明 | 复验实测 | 结论 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| T01 counselingEnabled flag ✅ | `isCounselingEnabled()` 全仓无业务调用点 | ❌ 死配置(暴露但未 gate) |
|
|
|
|
|
|
| T02 premaritalEnabled flag ✅ | `isPremaritalEnabled()` 全仓无业务调用点 | ❌ 死配置 |
|
|
|
|
|
|
| T03 contentEnabled flag ✅ | `isContentEnabled()` 全仓无业务调用点 | ❌ 死配置 |
|
|
|
|
|
|
| T04 serviceLocationEnabled flag ✅ | `isServiceLocationEnabled()` 全仓无业务调用点 | ❌ 死配置 |
|
|
|
|
|
|
| T05 localEligibilityEnabled flag ✅ | `isLocalEligibilityEnabled()` 全仓无调用点(模块开关未接) | ❌ 死配置(但上传策略已接,见下) |
|
|
|
|
|
|
| T06 local evidence upload policy ✅ | `LocalEligibilityService` 强制 `isLocalEvidenceUploadEnabled` + `getLocalEvidenceMaxFiles`;`PhotoService` 强制 `getLocalEvidenceMaxFileSizeMB` + `getLocalEvidenceAllowedTypes` | ✅ 已真强制 |
|
|
|
|
|
|
| T07 private storage for counseling attachments ✅ | `counselingAttachmentsPrivate` / `counselingAttachmentsBucket` 全仓只在 `FeatureFlagConfig` 定义与 `getAllFlags()` 暴露,业务零引用(counseling 模块无附件存储代码引用) | ❌ 死配置 |
|
|
|
|
|
|
| FeatureFlagController API ✅ (GET/PUT) | GET 正常;**PUT 仅支持布尔 flag**(反射 `getMethod("set"+cap, boolean.class)`),7 个非布尔 flag 更新必失败 | ❌→✅ 已修复(D-22-A) |
|
|
|
|
|
|
| application.yml 默认配置 ✅ (15 个 V1.1 flag) | `backend/src/main/resources/*.yml` 无 `features:` 块,仅 Java 字段默认值生效 | ⚠️ 夸大声明(功能正常,文档纠正) |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 二、真实缺陷(修复)
|
|
|
|
|
|
|
|
|
|
|
|
| 编号 | 严重度 | 缺陷 | 修复 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| D-22-A | High | `FeatureFlagController.updateFlag` 用反射 `getMethod("set"+cap, boolean.class)` 只支持布尔 flag。7 个非布尔 flag(`livenessProvider`、`localEvidenceAllowedTypes`、`counselingAttachmentsBucket`、`localEvidenceMaxFiles`、`localEvidenceMaxFileSizeMB`、`localEvidenceRetentionDays`)经 API 更新必抛 `NoSuchMethodException` → 返回错误,运维无法经接口修改这些配置。 | 改为按字段类型分派:`boolean` → `setXxx(boolean)`、`int` → `setXxx(int)`、`String` → `setXxx(String)`;未知类型返回明确错误。 |
|
|
|
|
|
|
| D-22-B | Medium | `updateFlag` 用 `body.getOrDefault("value", false)`:缺 `value` 时静默置 `false`(可能误关功能);非布尔值转型 `((Boolean) value)` 直接崩。 | 改为:缺 `value` → 400;`value` 按字段类型校验后转型;类型不匹配返回 400 且**不**改变原值。 |
|
|
|
|
|
|
| D-22-C | Doc/Accuracy | STATUS 将 T01–T05、T07 标 ✅,但对应 flag 从未被业务读取(死配置)。 | 不静默扩权改写各模块;在报告中如实降级为「配置暴露但未强制」,列为遗留项交各模块 Phase 处理。 |
|
2026-09-18 21:58:58 +08:00
|
|
|
|
| D-22-D | Doc/Accuracy | STATUS 称「application.yml 默认配置(15 个 flag)」,**来源**表述错误:features 不在基础 `application.yml`。⚠️ **本行原判据「`*.yml` 无 `features:` 块」于 2026-09-18 二次修正中被证伪** —— `application-dev.yml` 与 `application-docker.yml` 均含该块且实际生效,原「默认值仅来自 Java 字段默认」的纠正本身不成立。 | 二次纠正为:features 由 **profile yml(dev / docker)**提供并实际生效;基础 `application.yml` 不提供。功能不受影响。 |
|
|
|
|
|
|
| D-22-E | Doc/Accuracy(新发现,2026-09-18 二次修正) | `AppController#getConfig`(`GET /app/config`)返回一份**硬编码**的 `features` map(`activityEnabled` / `matchmakerEnabled` / `imageChatEnabled` / `appealEnabled` / `checkinEnabled` 恒为 `true`),与 `FeatureFlagConfig` **构成双源**;且键名 `imageChatEnabled` 与后端 `chatImageEnabled` **不一致**,值也可能与实际配置相反(如 `appealEnabled` 实际默认 `false`)。 | 不静默改契约(小程序端依赖此响应)。如实登记为遗留,交小程序契约/PWA 模块统一为单一来源(建议改读 `FeatureFlagConfig#getAllFlags()`)。 |
|
2026-09-18 11:00:08 +08:00
|
|
|
|
|
|
|
|
|
|
> 注:T06 上传策略经实测已真强制(`LocalEligibilityService` + `PhotoService`),保留 ✅,无需修复。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 三、未静默扩权的遗留项(文档化,交后续)
|
|
|
|
|
|
|
|
|
|
|
|
1. **模块开关死配置(T01–T05)**:`counselingEnabled` / `premaritalEnabled` / `contentEnabled` / `serviceLocationEnabled` / `localEligibilityEnabled`(模块级)在后端无调用点 → 运维无法通过 flag 关闭对应模块。属「配置暴露但未 gate」,需在各模块自身 Phase 接线(建议加控制器入口 `@PreAuthorize`/服务层 `featureFlags.isXxxEnabled()` 判断),不在本 Phase 静默铺开。
|
|
|
|
|
|
2. **T07 私有存储桶死配置**:`counselingAttachmentsPrivate` / `counselingAttachmentsBucket` 无业务引用;咨询附件存储本就走 `object-storage.bucket-private`(已私有)。当前 flag 为「专用桶覆盖」旋钮,未接线,属可选增强。
|
|
|
|
|
|
3. **PUT 变更仅内存生效**:`FeatureFlagConfig` 为单例 `@Component`,运行时修改不持久化,重启后回到默认值。计划/STATUS 未要求持久化,记为已知限制(如需跨重启保留,需接 DB/可刷新配置,属范围外)。
|
|
|
|
|
|
4. **计划 Phase 22(Deployment/Ops 基础设施)**:Docker / CI / 备份恢复 / 密钥注入 / 回滚 等尚未纳入本 Phase,需另行确认后单独复验。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 四、守卫测试(新增)
|
|
|
|
|
|
|
|
|
|
|
|
`backend/src/test/java/com/danyang/match/platform/shared/controller/FeatureFlagControllerTest.java`(7 用例,直接驱动控制器方法,使用隔离的 `FeatureFlagConfig` 实例避免污染共享单例):
|
|
|
|
|
|
|
|
|
|
|
|
1. `getAllFlagsReturnsEveryDeclaredFlag` — 21 个 flag 全部暴露(size=21 + 逐项 containsKey)。
|
|
|
|
|
|
2. `updateBooleanFlagIsReflected` — 布尔 flag 更新后反映到配置。
|
|
|
|
|
|
3. `updateStringFlagIsReflected` — **字符串 flag(D-22-A 修复点)** 更新生效。
|
|
|
|
|
|
4. `updateIntFlagIsReflected` — **整数 flag(D-22-A 修复点)** 更新生效。
|
|
|
|
|
|
5. `updateUnknownFlagReturnsBadRequest` — 未知 flag → 400。
|
|
|
|
|
|
6. `updateMissingValueReturnsBadRequest` — 缺 value → 400(D-22-B 修复点)。
|
|
|
|
|
|
7. `updateBooleanFlagWithWrongTypeReturnsBadRequestAndDoesNotSilentlyDisable` — 类型不匹配 → 400 且原值不变(D-22-B 修复点)。
|
|
|
|
|
|
|
|
|
|
|
|
既有 `ConfigTest.testFeatureFlagDefaults` 保留(校验 Java 默认值)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 五、负向验证(守卫非假绿)
|
|
|
|
|
|
|
|
|
|
|
|
将控制器分发逻辑改回「仅布尔」(模拟 D-22-A 修复前缺陷,保留 `Map<String,Object>` 签名以保证可编译),重跑 `FeatureFlagControllerTest`:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
[ERROR] Tests run: 7, Failures: 2, Errors: 0, Skipped: 0
|
|
|
|
|
|
[ERROR] FeatureFlagControllerTest.updateIntFlagIsReflected <<< FAILURE! expected: <true> but was: <false>
|
|
|
|
|
|
[ERROR] FeatureFlagControllerTest.updateStringFlagIsReflected <<< FAILURE! expected: <true> but was: <false>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
→ 恰好 2 个关于非布尔更新的用例精准变红,其余 5 个通过;还原修复版后全绿。**证明守卫真实有效,非假绿。**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 六、全量回归
|
|
|
|
|
|
|
|
|
|
|
|
离线容器( `danyang-match-backend-test:latest`,挂载 `/proj`,命名卷 `danyang-m2-cache`,`--memory=3g -e MAVEN_OPTS=-Xmx256m`):
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Tests run: 657, Failures: 0, Errors: 0, Skipped: 3 — BUILD SUCCESS
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
(起点 650,净 +7 = 新增 `FeatureFlagControllerTest` 7 用例)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 七、Gate 评估
|
|
|
|
|
|
|
|
|
|
|
|
| Gate | 结论 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| 配置基础设施可经 API 读写(含非布尔) | ✅ 修复 D-22-A/B 后 GET/PUT 覆盖 21 个 flag(布尔/整数/字符串) |
|
|
|
|
|
|
| 未知 flag / 缺 value / 类型错 → 明确拒绝,不静默误关 | ✅ D-22-B |
|
|
|
|
|
|
| 上传策略真强制 | ✅ T06 已在 `LocalEligibilityService` + `PhotoService` 生效 |
|
|
|
|
|
|
| 模块开关真 gate 功能 | ❌ 遗留(T01–T05 死配置,交各模块 Phase) |
|
|
|
|
|
|
| 私有存储桶真生效 | ❌ 遗留(T07 死配置) |
|
|
|
|
|
|
|
|
|
|
|
|
**结论:KEEP-VERIFY 复验关闭。修复 2 处真实代码缺陷(D-22-A High、D-22-B Medium)+ 纠正 2 处文档夸大(D-22-C/D);补 7 守卫用例;全量 657 绿;负向验证精准。4 项遗留已文档化,未静默扩权。**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 八、Git 物证
|
|
|
|
|
|
|
|
|
|
|
|
- 提交:`(见 `git log` 最新一条)`
|
|
|
|
|
|
- Tag:`phase22-recovery-2026-09-18`
|
|
|
|
|
|
- Bundle:`.workbuddy/backup/phase22-recovery-2026-09-18.bundle`
|
|
|
|
|
|
- 改动文件:
|
|
|
|
|
|
- `backend/src/main/java/com/danyang/match/platform/shared/controller/FeatureFlagController.java`(D-22-A/B)
|
|
|
|
|
|
- `backend/src/test/java/com/danyang/match/platform/shared/controller/FeatureFlagControllerTest.java`(新增守卫)
|
|
|
|
|
|
- `docs/STATUS.md`、`docs/JIRA_BACKLOG.md`(同步复验结论)
|