|
@@ -0,0 +1,466 @@
|
|
|
|
|
+# 统一维度体系 — 增量架构设计(Unified Dimension System Design)
|
|
|
|
|
+
|
|
|
|
|
+> 文档类型:增量架构设计(基于 PRD `2026-11-03-unified-dimension-system-prd.md`)
|
|
|
|
|
+> 作者:高见远(架构师) · 日期:2026-11-04
|
|
|
|
|
+> 关联决策:主理人(廖总/齐活林)已拍板的 4 项决策 + Q5 双写过渡期策略
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 0. 文档信息 / 与 PRD 的关系
|
|
|
|
|
+
|
|
|
|
|
+本设计**仅产出架构与任务拆分,不写业务代码**。它是 PRD 的落地蓝图,严格遵循主理人 4 项决策与 5 条红线(见 PRD §5)。
|
|
|
|
|
+
|
|
|
|
|
+**架构终态一句话**:CFC 只有一棵维度树——根为 5 个权威 code(`body`/`mind`/`wisdom`/`action`/`wealth`),`product_dimension_config` 升级为**唯一维度注册中心**(含 `parent_id` 多级树 + 指标混排 + `legacy_alias` 历史别名映射),所有子系统只读引用注册中心,4 套旧编码仅作历史别名,经映射层翻译,五行规则逻辑不变仅维度来源切换。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 1. 主理人 4 项决策落地结论(决策映射)
|
|
|
|
|
+
|
|
|
|
|
+| # | 决策 | 本设计落地方式 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 1 | 注册中心 = 升级现有表 | `product_dimension_config` 加 `legacy_alias`/`source_system`/`wuxing_rule_ref`/`node_kind` 列;复用 `/api/admin/dimension-config/*` 与 `cfc-web/src/views/admin/dimension/index.vue`;**不新建** `dimension_registry` 表 |
|
|
|
|
|
+| 2 | 指标并入维度树混排 | `indicator_definitions` 加 `parent_id`(自引用),允许指标节点作为维度树子节点;`id` 主键不变 → 5 张外键表仍 `FK→indicator_definitions.id`,**红线 1 满足** |
|
|
|
|
|
+| 3 | 7 维纳入统一树 | **7 维作为「维度节点」挂在 `body` 根下**(非指标节点):新增 `body.growth`/`body.sleep`/…/`body.exercise` 7 行 `product_dimension_config`,`parent_id=body.id`,`legacy_alias=['growth',…]`;指标(如体脂率)后续在 P2 作为指标节点挂到 `body.exercise` 下 |
|
|
|
|
|
+| 4 | 历史数据全量迁移 | 存量 `health_dimension_scores.dimension` 全量改写为 `body.*`;`indicator_definitions.dimension_code` 校验收敛至注册中心 code 集合;提供迁移脚本 + 回滚预案(含备份表) |
|
|
|
|
|
+| Q5 | 双写过渡期 | 注册中心上线后**先双写**(新写入同时落注册中心 + 旧表,由 `DimensionSyncService` 经 feature flag 控制);网关/调用日志确认旧接口零调用后再退役旧体系;提供回滚 |
|
|
|
|
|
+
|
|
|
|
|
+**关键事实支撑**:经核对 schema.sql,`energy_dimension.code` 与 `dimension_weights.dimension` **本就使用** `body/mind/wisdom/action/wealth`,与统一 5 根完全一致。故旧能量体系与注册中心 5 根是 **1:1 code 对齐**,双写同步是低成本映射,五行规则逻辑零改动。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 2. 实现方案 + 框架选型
|
|
|
|
|
+
|
|
|
|
|
+### 2.1 注册中心物理落地
|
|
|
|
|
+- **物理表**:`product_dimension_config`(升级,不新建表)。
|
|
|
|
|
+- **树结构**:沿用现有 `parent_id` 自引用多级树;新增 `node_kind` 区分 `root` / `sub`;新增 `source_system` 标记来源(health/energy/indicator/knowledge);新增 `legacy_alias`(JSON 数组)承载 4 套旧编码;新增 `wuxing_rule_ref`(VARCHAR(30))指向五行规则(沿用 `EnergyDimension.element` 的 木/火/金/水/土 取值)。
|
|
|
|
|
+- **混排**:统一树 = `product_dimension_config` 维度节点 ∪ `indicator_definitions` 指标节点(其 `parent_id` 指向某维度节点 `id`)。由 `DimensionRegistryService.buildTree()` 合并。
|
|
|
|
|
+
|
|
|
|
|
+### 2.2 指标并入维度树(混排)
|
|
|
|
|
+- `indicator_definitions` 增加 `parent_id BIGINT`(自引用 + 可指向维度节点)、`legacy_alias VARCHAR(255)`。
|
|
|
|
|
+- **不改 `id` 主键、不改外键目标** → 5 张外键表(`indicator_values`/`indicator_mapping_rules`/`indicator_correlations(definition_id_a/b)`/`indicator_favorite`/归一化中间表)与 19 个后端引用文件**一律不动**(红线 1)。
|
|
|
|
|
+
|
|
|
|
|
+### 2.3 7 维落点决策(决策 3 落地)
|
|
|
|
|
+- 7 维是「维度」不是「指标」,归属 `health_dimension_scores.dimension`,故作为**维度节点**挂在 `body` 下,code 为 `body.growth` 等。
|
|
|
|
|
+- `legacy_alias` 绑定旧名 `growth`,实现双向可查(旧 health 代码 `growth` ↔ 新 `body.growth`)。
|
|
|
|
|
+- 指标(如体脂率、慢性病)归属 `body.exercise`/`body.nutrition` 等,在 P2 作为指标节点挂接(混排),不抢占 7 维维度节点位置。
|
|
|
|
|
+
|
|
|
|
|
+### 2.4 框架选型
|
|
|
|
|
+- **后端**:沿用现有 `Spring Boot + MyBatis-Plus + Jackson`,注册中心服务/映射/同步均为新增 Spring `@Service`,不引入新框架。
|
|
|
|
|
+- **前端**:沿用 `Vue 2(cfc-web)/ uni-app(cfc-frontend)`,新增统一取数 composable `useDimensionRegistry`(DimensionProvider)。
|
|
|
|
|
+- **数据库迁移**:严格走 `DatabaseInitializer.runMigrations()` + `ensureColumn()`(幂等),同步 `schema.sql`。
|
|
|
|
|
+- **预计新增第三方依赖:无。**
|
|
|
|
|
+
|
|
|
|
|
+### 2.5 接口策略
|
|
|
|
|
+- **新增只读查询接口**:`/api/dimension-registry/*`(统一取数入口)。
|
|
|
|
|
+- **升级现有 admin 写入接口**:`/api/admin/dimension-config/*` 内部改调注册中心(`DimensionRegistryService`),保留路径兼容;写入时经 `DimensionSyncService` 双写到旧表(flag 控制)。
|
|
|
|
|
+- **标记废弃**:`/api/dimension/overview`、`/api/dimension/radar-data` 标 `@Deprecated`,保留至调用日志确认零调用(红线 4)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 3. 数据模型变更清单(增量 DDL)
|
|
|
|
|
+
|
|
|
|
|
+### 3.1 `product_dimension_config` 增量列
|
|
|
|
|
+```sql
|
|
|
|
|
+-- 通过 DatabaseInitializer.ensureColumn 幂等新增(不修改既有列)
|
|
|
|
|
+ALTER TABLE product_dimension_config
|
|
|
|
|
+ ADD COLUMN legacy_alias VARCHAR(512) DEFAULT NULL COMMENT '历史别名(JSON数组): 承载现存4套编码, 如 ["growth"]',
|
|
|
|
|
+ ADD COLUMN source_system VARCHAR(32) DEFAULT NULL COMMENT '归属子系统: health/energy/indicator/knowledge',
|
|
|
|
|
+ ADD COLUMN wuxing_rule_ref VARCHAR(30) DEFAULT NULL COMMENT '五行规则引用(木/火/金/水/土), 关联EnergyDimension.element',
|
|
|
|
|
+ ADD COLUMN node_kind VARCHAR(16) DEFAULT 'root' COMMENT '节点种类: root(五维根)/sub(子维度)';
|
|
|
|
|
+```
|
|
|
|
|
+> 现有 `dimension_code VARCHAR(50) NOT NULL UNIQUE` 复用为**统一 code**(`body`/`body.growth` 等,均为唯一字符串,无冲突)。
|
|
|
|
|
+
|
|
|
|
|
+### 3.2 `indicator_definitions` 增量列
|
|
|
|
|
+```sql
|
|
|
|
|
+ALTER TABLE indicator_definitions
|
|
|
|
|
+ ADD COLUMN parent_id BIGINT DEFAULT NULL COMMENT '父节点ID(可指向product_dimension_config.id或本表id, 混排进维度树)',
|
|
|
|
|
+ ADD COLUMN legacy_alias VARCHAR(512) DEFAULT NULL COMMENT '历史别名(JSON数组)';
|
|
|
|
|
+-- 既有 dimension_code VARCHAR(16) 保留(向后兼容), 取值收敛至注册中心code集合(P1校验)
|
|
|
|
|
+```
|
|
|
|
|
+> 不改变 `id` 主键、不改变任何外键目标(红线 1)。`dimension_code` 维持 `VARCHAR(16)`(统一子 code 如 `body.growth`=11 字符,`mind.perception`=14 字符,均 <16,足够)。
|
|
|
|
|
+
|
|
|
|
|
+### 3.3 统一 code 命名规范
|
|
|
|
|
+- **根维度**:`body` / `mind` / `wisdom` / `action` / `wealth`(5 个唯一权威,PRD 红线 2)。
|
|
|
|
|
+- **子维度**:`{root}.{sub}`,全小写点分。固化示例:
|
|
|
|
|
+ - `body.growth` `body.sleep` `body.vision` `body.immunity` `body.nutrition` `body.gut` `body.exercise`
|
|
|
|
|
+ - (P2 扩展)`mind.memory`/`mind.attention`…、`wisdom.openness`…、`action.skill`…、`wealth.income`…
|
|
|
|
|
+- **指标节点 code**:沿用 `indicator_definitions.code`(如 `tongue_color`),通过 `parent_id` 挂到维度节点,不占用维度 code 命名空间。
|
|
|
|
|
+- **legacy_alias 格式**:JSON 数组字符串,如 `["growth","physical"]`;解析用 Jackson。
|
|
|
|
|
+
|
|
|
|
|
+### 3.4 schema.sql 同步纪律(AGENTS.md 约束)
|
|
|
|
|
+- 每次 `ensureColumn` 后,**必须同步** `cfc-backend/src/main/resources/schema.sql` 中对应 `CREATE TABLE` 的字段定义(将新增 4 列补入 `product_dimension_config`、`parent_id`/`legacy_alias` 补入 `indicator_definitions`)。
|
|
|
|
|
+- 迁移编号:搜索 `// 迁移` 取最新号递增,保证幂等。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 4. 映射层设计(legacy_alias ↔ 统一 code)
|
|
|
|
|
+
|
|
|
|
|
+**双向可查**:旧编码 → 统一 code(`resolveToUnified`);统一 code → 旧编码(`resolveLegacy`)。
|
|
|
|
|
+
|
|
|
|
|
+- **存储**:每节点 `legacy_alias` 列(JSON 数组)。注册中心加载后构建内存索引 `Map<legacyCode, unifiedCode>` 与 `Map<unifiedCode, List<legacyCode>>`。
|
|
|
|
|
+- **读取 API**:`GET /api/dimension-registry/map?legacy=growth` → `body.growth`;`GET /api/dimension-registry/code/{code}` → 节点详情(含 legacy_alias)。
|
|
|
|
|
+- **服务类**:`DimensionCodeMapper`(见 §6 类图)。
|
|
|
|
|
+- **示例映射**(P0 固化):
|
|
|
|
|
+
|
|
|
|
|
+| 统一 code | legacy_alias(旧体系来源) | source_system |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `body` | `["body"]` | energy |
|
|
|
|
|
+| `body.growth` | `["growth"]` | health |
|
|
|
|
|
+| `body.sleep` | `["sleep"]` | health |
|
|
|
|
|
+| `body.vision` | `["vision"]` | health |
|
|
|
|
|
+| `body.immunity` | `["immunity"]` | health |
|
|
|
|
|
+| `body.nutrition` | `["nutrition"]` | health |
|
|
|
|
|
+| `body.gut` | `["gut"]` | health |
|
|
|
|
|
+| `body.exercise` | `["exercise"]` | health |
|
|
|
|
|
+| `mind`/`wisdom`/`action`/`wealth` | 各自同名 | energy |
|
|
|
|
|
+
|
|
|
|
|
+> 旧能量体系(`energy_dimension.code`)与统一 5 根同码,无需 alias 翻译,双写同步即 1:1。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 5. 接口设计(新增 / 改造 / 废弃)
|
|
|
|
|
+
|
|
|
|
|
+### 5.1 新增(只读查询,统一取数入口)
|
|
|
|
|
+| 方法 | 路径 | 入参 | 出参 | 说明 |
|
|
|
|
|
+|---|---|---|---|---|
|
|
|
|
|
+| GET | `/api/dimension-registry/tree` | `?sourceSystem=`(可选) | `Result<List<DimensionNode>>` | 合并维度节点+指标节点的统一树 |
|
|
|
|
|
+| GET | `/api/dimension-registry/roots` | — | `Result<List<DimensionNode>>` | 5 根 |
|
|
|
|
|
+| GET | `/api/dimension-registry/code/{code}` | path `code` | `Result<DimensionNode>` | 按统一 code 取节点(含 legacy_alias) |
|
|
|
|
|
+| GET | `/api/dimension-registry/map` | `?legacy=` | `Result<String>` | legacy→统一 code 解析 |
|
|
|
|
|
+
|
|
|
|
|
+`DimensionNode` 结构:`{id, code, name, parentId, nodeKind, sourceSystem, legacyAlias, wuxingRuleRef, children?}`。
|
|
|
|
|
+
|
|
|
|
|
+### 5.2 改造(兼容保留)
|
|
|
|
|
+| 方法 | 路径 | 变更 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| POST | `/api/admin/dimension-config/tree` | 内部改调 `DimensionRegistryService.getTree()`,返回含 7 body 子节点;路径/出参不变 |
|
|
|
|
|
+| POST | `/api/admin/dimension-config/save` | 改调注册中心写入;若 `dimension.registry.dualWrite=true`,触发 `DimensionSyncService.syncToLegacy()` |
|
|
|
|
|
+| POST | `/api/admin/indicator/save` | 新增 `parentId` 入参;校验 `dimensionCode` ∈ 注册中心 code 集合 |
|
|
|
|
|
+
|
|
|
|
|
+### 5.3 标记废弃(保留至零调用)
|
|
|
|
|
+- `GET /api/dimension/overview`、`GET /api/dimension/radar-data`:标 `@Deprecated`,前端改走 `/api/dimension-registry/*` 后下线。
|
|
|
|
|
+- `DimensionSelector`/`ContentDimensionThresholdEditor`/`KnowledgeBase` 直读 `product_dimension_config` 的代码(P1-4 改为经 `DimensionRegistryService`)。
|
|
|
|
|
+
|
|
|
|
|
+### 5.4 旧体系退役范围(P2-T3)
|
|
|
|
|
+`energy_dimension` / `energy_dimension_config` / `dimension_weights` / `energy_rule` 等配置表,在 `EnergyService` 完成来源切换(P1-T2)且调用日志确认零引用后 DROP(无外键指向 indicator 表,可安全删除)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 6. 类图 / 关键实体关系(Mermaid classDiagram)
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+classDiagram
|
|
|
|
|
+ class ProductDimensionConfig {
|
|
|
|
|
+ +Long id
|
|
|
|
|
+ +String dimensionCode // 统一 code
|
|
|
|
|
+ +String dimensionName
|
|
|
|
|
+ +String dimensionType
|
|
|
|
|
+ +Long parentId // 自引用树
|
|
|
|
|
+ +Integer sort
|
|
|
|
|
+ +Integer enabled
|
|
|
|
|
+ +String legacyAlias // JSON数组, 历史别名
|
|
|
|
|
+ +String sourceSystem // health/energy/indicator/knowledge
|
|
|
|
|
+ +String wuxingRuleRef // 五行规则引用
|
|
|
|
|
+ +String nodeKind // root/sub
|
|
|
|
|
+ +Date createdAt
|
|
|
|
|
+ +Date updatedAt
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ class IndicatorDefinition {
|
|
|
|
|
+ +Long id // 主键不变(红线1)
|
|
|
|
|
+ +String domain
|
|
|
|
|
+ +String code
|
|
|
|
|
+ +String name
|
|
|
|
|
+ +String dimensionCode // 收敛至注册中心code
|
|
|
|
|
+ +Long parentId // 新增: 混排挂到维度节点
|
|
|
|
|
+ +String legacyAlias // 新增
|
|
|
|
|
+ +Date createdAt
|
|
|
|
|
+ +Date updatedAt
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ class HealthDimensionScore {
|
|
|
|
|
+ +Long id
|
|
|
|
|
+ +Long memberId
|
|
|
|
|
+ +String dimension // 迁移后=body.growth等
|
|
|
|
|
+ +Integer score
|
|
|
|
|
+ +Integer percentile
|
|
|
|
|
+ +Date assessDate
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ class EnergyDimension {
|
|
|
|
|
+ +Long id
|
|
|
|
|
+ +String code // 已=body/mind/...
|
|
|
|
|
+ +String name
|
|
|
|
|
+ +String element // 木/火/金/水/土
|
|
|
|
|
+ }
|
|
|
|
|
+ class EnergyDimensionConfig
|
|
|
|
|
+ class DimensionWeight {
|
|
|
|
|
+ +Long id
|
|
|
|
|
+ +String targetType
|
|
|
|
|
+ +Long targetId
|
|
|
|
|
+ +String dimension // 已=body/mind/...
|
|
|
|
|
+ +Integer weight
|
|
|
|
|
+ }
|
|
|
|
|
+ class EnergyRule
|
|
|
|
|
+
|
|
|
|
|
+ class DimensionCodeConstants
|
|
|
|
|
+ class DimensionCodeMapper {
|
|
|
|
|
+ +String resolveToUnified(String legacy)
|
|
|
|
|
+ +List~String~ resolveLegacy(String unified)
|
|
|
|
|
+ }
|
|
|
|
|
+ class DimensionRegistryService {
|
|
|
|
|
+ +List~DimensionNode~ getTree()
|
|
|
|
|
+ +DimensionNode getNodeByCode(String code)
|
|
|
|
|
+ +List~DimensionNode~ getRoots()
|
|
|
|
|
+ +void saveConfig(ProductDimensionConfig node)
|
|
|
|
|
+ }
|
|
|
|
|
+ class DimensionSyncService {
|
|
|
|
|
+ +void syncToLegacy(ProductDimensionConfig node)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ProductDimensionConfig "1" --> "0..* " ProductDimensionConfig : parent_id self-ref
|
|
|
|
|
+ IndicatorDefinition "0..*" --> "1" ProductDimensionConfig : parent_id 挂到维度节点
|
|
|
|
|
+ IndicatorDefinition "1" --> "0..* " IndicatorDefinition : parent_id 自引用(指标子树)
|
|
|
|
|
+
|
|
|
|
|
+ HealthDimensionScore ..> ProductDimensionConfig : dimension → dimensionCode(经映射层)
|
|
|
|
|
+ DimensionRegistryService --> DimensionCodeMapper
|
|
|
|
|
+ DimensionRegistryService --> ProductDimensionConfig
|
|
|
|
|
+ DimensionSyncService --> ProductDimensionConfig
|
|
|
|
|
+ DimensionSyncService ..> EnergyDimension : 双写同步(flag控制)
|
|
|
|
|
+ DimensionSyncService ..> DimensionWeight : 双写同步
|
|
|
|
|
+
|
|
|
|
|
+ EnergyService ..> EnergyDimensionConfig : 过渡期读旧表
|
|
|
|
|
+ EnergyService ..> DimensionRegistryService : P1后读注册中心
|
|
|
|
|
+ DimensionScoreServiceImpl ..> DimensionRegistryService : P1后读7维
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **红线 1 满足说明**:`IndicatorDefinition.id` 主键未改,`indicator_values` 等 5 张表外键仍指向 `indicator_definitions.id`,本设计仅对 `indicator_definitions` 新增 `parent_id`/`legacy_alias` 列,不动外键目标。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 7. 调用流程图(Mermaid sequenceDiagram)
|
|
|
|
|
+
|
|
|
|
|
+### 7.1 前端雷达图/沙盘从注册中心取维度(读路径)
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor User
|
|
|
|
|
+ participant FE as 小程序/管理端 DimensionProvider
|
|
|
|
|
+ participant API as DimensionRegistryController
|
|
|
|
|
+ participant SVC as DimensionRegistryService
|
|
|
|
|
+ participant CFG as ProductDimensionConfigMapper
|
|
|
|
|
+ participant IND as IndicatorDefinitionMapper
|
|
|
|
|
+
|
|
|
|
|
+ FE->>API: GET /api/dimension-registry/tree
|
|
|
|
|
+ API->>SVC: getTree()
|
|
|
|
|
+ SVC->>CFG: listAll() // 维度节点(5根+7body+...)
|
|
|
|
|
+ SVC->>IND: listAll() // 指标节点(带parentId)
|
|
|
|
|
+ CFG-->>SVC: 维度节点列表
|
|
|
|
|
+ IND-->>SVC: 指标节点列表
|
|
|
|
|
+ SVC->>SVC: mergeByParentId() 统一树
|
|
|
|
|
+ SVC-->>API: List<DimensionNode> tree
|
|
|
|
|
+ API-->>FE: Result.success(tree)
|
|
|
|
|
+ FE->>FE: 取 body 子节点渲染 RadarChart
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 7.2 指标挂维度树节点(写入路径)
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor Admin
|
|
|
|
|
+ participant UI as 指标管理页
|
|
|
|
|
+ participant IC as IndicatorAdminController
|
|
|
|
|
+ participant ISVC as IndicatorService
|
|
|
|
|
+ participant M as DimensionCodeMapper
|
|
|
|
|
+ participant CFG as ProductDimensionConfigMapper
|
|
|
|
|
+ participant DB as indicator_definitions
|
|
|
|
|
+
|
|
|
|
|
+ Admin->>UI: 选父维度节点(body.exercise)+填指标
|
|
|
|
|
+ UI->>IC: POST /api/admin/indicator/save {code,parentId,dimensionCode}
|
|
|
|
|
+ IC->>ISVC: save(indicator)
|
|
|
|
|
+ ISVC->>M: validate(dimensionCode) ∈ 注册中心codes
|
|
|
|
|
+ M->>CFG: getNodeByCode(dimensionCode)
|
|
|
|
|
+ CFG-->>M: node / null
|
|
|
|
|
+ M-->>ISVC: ok / fail(400)
|
|
|
|
|
+ ISVC->>DB: INSERT/UPDATE (parent_id 设置)
|
|
|
|
|
+ DB-->>ISVC: id
|
|
|
|
|
+ ISVC-->>IC: IndicatorDefinition
|
|
|
|
|
+ IC-->>UI: Result.success(node)
|
|
|
|
|
+ Note over SVC: 后续 getTree() 把该指标挂到 body.exercise 下(混排)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 7.3 双写过渡期:admin 写入 → 注册中心 + 旧表同步
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ actor Admin
|
|
|
|
|
+ participant AC as DimensionConfigController
|
|
|
|
|
+ participant RS as DimensionRegistryService
|
|
|
|
|
+ participant CFG as product_dimension_config
|
|
|
|
|
+ participant SYNC as DimensionSyncService
|
|
|
|
|
+ participant LEG as energy_dimension_config / dimension_weights
|
|
|
|
|
+
|
|
|
|
|
+ Admin->>AC: POST /api/admin/dimension-config/save {code,parentId,...}
|
|
|
|
|
+ AC->>RS: saveConfig(node)
|
|
|
|
|
+ RS->>CFG: upsert product_dimension_config // 注册中心权威
|
|
|
|
|
+ alt dimension.registry.dualWrite=true
|
|
|
|
|
+ RS->>SYNC: syncToLegacy(node)
|
|
|
|
|
+ SYNC->>LEG: upsert 对应 legacy 行(按code 1:1)
|
|
|
|
|
+ end
|
|
|
|
|
+ RS-->>AC: node
|
|
|
|
|
+ AC-->>Admin: success
|
|
|
|
|
+ Note over SYNC: 旧 EnergyService 仍读 legacy 表(行为不变); 新消费者读注册中心
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 8. 历史数据全量迁移 + 双写过渡 + 退役 + 回滚预案
|
|
|
|
|
+
|
|
|
|
|
+### 8.1 迁移脚本(`cfc-backend/src/main/resources/db/migrate_dimension_unified.sql`)
|
|
|
|
|
+```sql
|
|
|
|
|
+-- 0) 备份(回滚用)
|
|
|
|
|
+CREATE TABLE IF NOT EXISTS health_dimension_scores_bak_20261104 LIKE health_dimension_scores;
|
|
|
|
|
+INSERT INTO health_dimension_scores_bak_20261104 SELECT * FROM health_dimension_scores;
|
|
|
|
|
+
|
|
|
|
|
+-- 1) 7维历史评分全量改写为统一code
|
|
|
|
|
+UPDATE health_dimension_scores
|
|
|
|
|
+ SET dimension = CONCAT('body.', dimension)
|
|
|
|
|
+ WHERE dimension IN ('growth','sleep','vision','immunity','nutrition','gut','exercise');
|
|
|
|
|
+
|
|
|
|
|
+-- 2) indicator_definitions.dimension_code 校验/收敛(既有值多为body/mind/..., 仅修正越界值)
|
|
|
|
|
+-- 依据 DimensionCodeConstants 根集合; 越界值置 NULL 并记日志(由迁移程序扫描, 见 P0-T5)
|
|
|
|
|
+-- 3) 固化 5 根 + 7 维节点(seed)由 DatabaseInitializer 幂等插入(见 P0-T3)
|
|
|
|
|
+```
|
|
|
|
|
+- **幂等**:备份表 `IF NOT EXISTS`;UPDATE 仅命中 7 维旧值,重复执行无副作用。
|
|
|
|
|
+
|
|
|
|
|
+### 8.2 双写过渡期实现
|
|
|
|
|
+- **Feature flag** `dimension.registry.enabled` / `dimension.registry.dualWrite`(application.yml),默认 `enabled=false → true` 分阶段开启。
|
|
|
|
|
+- **写**:admin 写入注册中心后,`DimensionSyncService.syncToLegacy()` 按 `code` 1:1 upsert `energy_dimension` / `energy_dimension_config` / `dimension_weights`,旧 `EnergyService` 行为不变。
|
|
|
|
|
+- **读**:新消费者(雷达/沙盘/指标管理/知识库)走 `/api/dimension-registry/*`;旧消费者保留旧路径(标记废弃)。
|
|
|
|
|
+- **验证**:对比注册中心与旧表逐节点一致性(双写校验任务)。
|
|
|
|
|
+
|
|
|
|
|
+### 8.3 退役触发条件(P2-T3)
|
|
|
|
|
+满足全部方可退役:
|
|
|
|
|
+1. `EnergyService`/`DimensionScoreServiceImpl`/前端均已切换读注册中心(P1 完成);
|
|
|
|
|
+2. 网关/调用日志统计旧接口(`/api/dimension/overview`、`/api/dimension/radar-data`、`energy_*` 直读)**连续 14 天零调用**;
|
|
|
|
|
+3. 双写一致性校验通过(差异率 0)。
|
|
|
|
|
+
|
|
|
|
|
+退役动作:关闭 `dualWrite` → 删除 `DimensionSyncService` → DROP 旧配置表(`energy_dimension`/`energy_dimension_config`/`dimension_weights`/`energy_rule`)。
|
|
|
|
|
+
|
|
|
|
|
+### 8.4 回滚预案
|
|
|
|
|
+| 风险点 | 回滚动作 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 注册中心 schema 增量列 | 仅新增列,回滚=停止读取新列;如需还原 `ALTER TABLE ... DROP COLUMN`(另写 `revert` 迁移) |
|
|
|
|
|
+| `health_dimension_scores.dimension` 改写 | 由 `health_dimension_scores_bak_20261104` 全量还原:`INSERT ... SELECT` 覆盖;并反向 `UPDATE ... SET dimension=SUBSTRING(dimension,6)` |
|
|
|
|
|
+| 双写异常 | feature flag 关 `dualWrite`,旧表仍权威,瞬间回退 |
|
|
|
|
|
+| 新消费者读注册中心异常 | flag 关 `dimension.registry.enabled`,前端回落旧接口/兜底 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 9. 任务列表(P0 → P1 → P2,含文件/依赖/验收)
|
|
|
|
|
+
|
|
|
|
|
+> 估算文件数见各任务。依赖关系自底向上:P0 地基 → P1 子系统取数 → P2 混排+退役。
|
|
|
|
|
+
|
|
|
|
|
+### P0 — 地基(先建权威表 + 映射层,不碰旧表旧接口)
|
|
|
|
|
+**P0-T1 Schema 增量迁移 + 实体字段(约 4 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`、`E:/cfc/cfc-backend/src/main/resources/schema.sql`、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/entity/ProductDimensionConfig.java`、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/entity/IndicatorDefinition.java`
|
|
|
|
|
+- 依赖:无
|
|
|
|
|
+- 内容:`ensureColumn` 加 `product_dimension_config`(legacy_alias/source_system/wuxing_rule_ref/node_kind) 与 `indicator_definitions`(parent_id/legacy_alias);同步 schema.sql CREATE TABLE;实体加对应字段
|
|
|
|
|
+- 验收:启动无 `Unknown column`;schema.sql 与库结构一致;两表新列存在;`mvn compile` 通过
|
|
|
|
|
+
|
|
|
|
|
+**P0-T2 统一 code 常量 + 映射层 DimensionCodeMapper(约 3 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/common/constant/DimensionCodeConstants.java`(NEW)、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/DimensionCodeMapper.java`(NEW)+ impl
|
|
|
|
|
+- 依赖:P0-T1
|
|
|
|
|
+- 内容:常量类固化 5 根 + 7 body code;Mapper 提供 `resolveToUnified`/`resolveLegacy`,加载节点 `legacy_alias` 建内存索引
|
|
|
|
|
+- 验收:`resolveToUnified("growth")=="body.growth"`;`resolveLegacy("body.growth")` 含 `"growth"`;单测通过
|
|
|
|
|
+
|
|
|
|
|
+**P0-T3 注册中心服务层 + 种子固化 5 根 7 维(约 3 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/DimensionRegistryService.java`(NEW)、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductDimensionConfigMapper.java`(按需)、DatabaseInitializer seed 块
|
|
|
|
|
+- 依赖:P0-T1、P0-T2
|
|
|
|
|
+- 内容:`getTree()`(合并维度+指标节点)、`getNodeByCode()`、`getRoots()`;seed 幂等插入 `body.growth..body.exercise`(parent_id=body、legacy_alias、source_system='health');5 根补 `source_system/wuxing_rule_ref`
|
|
|
|
|
+- 验收:`getRoots()` 返回 5;`getTree()` 中 body 有 7 子节点;legacy_alias 已填充;重复启动 seed 幂等
|
|
|
|
|
+
|
|
|
|
|
+**P0-T4 统一维度树只读接口 + admin 写入升级(约 4 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/controller/DimensionRegistryController.java`(NEW)、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/controller/admin/DimensionConfigController.java`、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/ProductDimensionConfigService.java`、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/DimensionSyncService.java`(NEW,flag 门控骨架)
|
|
|
|
|
+- 依赖:P0-T3
|
|
|
|
|
+- 内容:新增 `/api/dimension-registry/tree|roots|code/{code}|map`;升级 `/admin/dimension-config/tree`、`/save` 改调注册中心 + 触发 `syncToLegacy`(flag)
|
|
|
|
|
+- 验收:GET `/api/dimension-registry/tree` 返回合并树;admin save 落 `product_dimension_config`;双写仅 flag=true 时触发
|
|
|
|
|
+
|
|
|
|
|
+**P0-T5 历史数据全量迁移脚本 + 回滚预案(约 2 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/resources/db/migrate_dimension_unified.sql`(NEW)、`E:/cfc/cfc-backend/src/main/resources/db/migrate_dimension_unified_rollback.sql`(NEW)
|
|
|
|
|
+- 依赖:P0-T3(注册中心 code 已存在)
|
|
|
|
|
+- 内容:备份表 + `health_dimension_scores.dimension` 改写 `body.*` + `indicator_definitions.dimension_code` 越界校验;回滚 SQL(备份还原 + 反向 SUBSTRING)
|
|
|
|
|
+- 验收:迁移幂等;执行后 `health_dimension_scores.dimension` 全 `LIKE 'body.%'`;回滚脚本可恢复;提供越界值扫描报告
|
|
|
|
|
+
|
|
|
|
|
+### P1 — 子系统取数 / 去硬编码
|
|
|
|
|
+**P1-T1 健康维度评分子系统改造(约 4 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/impl/DimensionScoreServiceImpl.java`、`E:/cfc/cfc-frontend/composables/useDimensionRegistry.js`(NEW)、`E:/cfc/cfc-frontend/pages/health-main/index.vue`、`E:/cfc/cfc-frontend/components/RadarChart.vue`
|
|
|
|
|
+- 依赖:P0-T4、P0-T5
|
|
|
|
|
+- 内容:`DimensionScoreServiceImpl` 经 `DimensionCodeMapper` 读 7 维(替换 `DIMENSIONS` 常量,key 改用 `body.growth` 等);前端 `DimensionProvider` 取代硬编码 `demoBodyDims` 与 demo 兜底分
|
|
|
|
|
+- 验收:雷达图从注册中心渲染;无 7 维字面硬编码;demo 兜底分移除或经 provider;`DimensionScoreServiceImpl` 单测通过
|
|
|
|
|
+
|
|
|
|
|
+**P1-T2 能量沙盘改造(约 2 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java`、`E:/cfc/cfc-frontend/components/wuxing-sandbox.vue`
|
|
|
|
|
+- 依赖:P0-T4
|
|
|
|
|
+- 内容:`EnergyService` 经 `wuxing_rule_ref`/注册中心读 5 根与权重;**`applyAllRestraints`/`applyAllBoosts` 逻辑零改动**;前端沙盘从注册中心取五维
|
|
|
|
|
+- 验收:沙盘读数同注册中心;五行计算与旧版一致(回归测试);红线 3 满足
|
|
|
|
|
+
|
|
|
|
|
+**P1-T3 指标管理关联收敛 + 树挂接(约 4 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/controller/admin/IndicatorAdminController.java`、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/mapper/IndicatorDefinitionMapper.java`、`E:/cfc/cfc-web/src/views/admin/indicators/index.vue`、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/IndicatorService.java`
|
|
|
|
|
+- 依赖:P0-T3、P0-T4
|
|
|
|
|
+- 内容:`/api/admin/indicator/save` 加 `parentId`;`dimensionCode` 校验 ∈ 注册中心 code 集合;管理端维度归属由五维单选改为从注册中心树选择
|
|
|
|
|
+- 验收:指标 `parent_id` 落库;越界 `dimensionCode` 被拒;统一树含指标节点
|
|
|
|
|
+
|
|
|
|
|
+**P1-T4 其他消费方改造(约 3 文件)**
|
|
|
|
|
+- 文件:`E:/cfc/cfc-web/src/components/DimensionSelector.vue`、`E:/cfc/cfc-web/src/views/admin/.../ContentDimensionThresholdEditor.vue`、`E:/cfc/cfc-backend/src/main/java/com/etotem/cfc/service/.../KnowledgeBaseService.java`(直读改注册中心)
|
|
|
|
|
+- 依赖:P0-T4
|
|
|
|
|
+- 内容:移除对 `product_dimension_config` 直读,统一经 `DimensionRegistryService`
|
|
|
|
|
+- 验收:代码扫描无直读残留;功能等价
|
|
|
|
|
+
|
|
|
|
|
+### P2 — 指标并入树 + 退役旧体系
|
|
|
|
|
+**P2-T1 指标多级细化 / 混排完整化(约 3 文件)**
|
|
|
|
|
+- 文件:`DimensionRegistryService.java`(扩展)、`IndicatorAdminController.java`(挂接 UI)、`cfc-web` 指标树编辑页
|
|
|
|
|
+- 依赖:P1-T3
|
|
|
|
|
+- 内容:指标子树自引用 + 混排展示;运营可将指标挂到任意维度节点
|
|
|
|
|
+- 验收:统一树含指标子节点;UI 可挂接
|
|
|
|
|
+
|
|
|
|
|
+**P2-T2 评分口径文档化(约 2 文件)**
|
|
|
|
|
+- 文件:`docs/superpowers/specs/...scoring-convention.md`(NEW)、`DimensionCodeConstants.java`(扩充)
|
|
|
|
|
+- 依赖:P1-T1
|
|
|
|
|
+- 内容:percentile/tier/归一化在雷达/沙盘/指标间共享并文档化
|
|
|
|
|
+- 验收:文档评审通过;共享常量无歧义
|
|
|
|
|
+
|
|
|
|
|
+**P2-T3 旧体系退役(约 5 文件)**
|
|
|
|
|
+- 文件:`DimensionSyncService.java`(删除)、`EnergyService.java`(移除旧表引用后保留注册中心)、`energy_*` 表 DROP 迁移、调用日志统计脚本、相关 Controller 清理
|
|
|
|
|
+- 依赖:P1-T1、P1-T2(全部切换完成)
|
|
|
|
|
+- 内容:确认零调用 → 关 `dualWrite` → 删 `DimensionSyncService` → DROP 旧配置表
|
|
|
|
|
+- 验收:连续 14 天零旧调用;旧表已删;全量测试绿;红线 4 满足
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 10. 依赖包列表
|
|
|
|
|
+**本次不引入任何新第三方依赖。**
|
|
|
|
|
+- 后端:Spring Boot、MyBatis-Plus、Jackson(均已存在,分别用于服务/ORM/JSON 解析 `legacy_alias`)。
|
|
|
|
|
+- 前端:Vue 2 / uni-app 内置能力,无新增 npm 包。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 11. 共享知识 / 跨文件约定
|
|
|
|
|
+- **统一 code 常量类**:`com.etotem.cfc.common.constant.DimensionCodeConstants` — 固化 `BODY="body"`、`MIND`…`WEALTH` 及 `BODY_GROWTH="body.growth"` 等;全系统引用,禁止散落字面量。
|
|
|
|
|
+- **Mapper 命名**:`DimensionCodeMapper.resolveToUnified(legacy)` / `resolveLegacy(unified)`;统一放 `com.etotem.cfc.service` 包。
|
|
|
|
|
+- **注册中心服务**:`DimensionRegistryService`(组合 `ProductDimensionConfigService` + `IndicatorDefinitionService`),统一树与取数入口。
|
|
|
|
|
+- **前端 DimensionProvider**:`cfc-frontend/composables/useDimensionRegistry.js`(缓存 `/api/dimension-registry/tree`,暴露 `roots`、`bodyDims`、`resolveAlias`);管理端 `cfc-web` 复用同一接口(可抽 `src/utils/dimensionRegistry.js`)。
|
|
|
|
|
+- **Feature flag**:`dimension.registry.enabled`、`dimension.registry.dualWrite`(application.yml),控制读切换与双写。
|
|
|
|
|
+- **legacy_alias 存储**:JSON 数组字符串,Jackson 解析;非空判断防 NPE。
|
|
|
|
|
+- **统一 code 格式**:`{root}` 或 `{root}.{sub}`,全小写点分;新增子维须入 `DimensionCodeConstants` 并 seed。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 12. 待明确事项(需主理人拍板)
|
|
|
|
|
+
|
|
|
|
|
+1. **7 维节点类型**:本设计选「维度节点(product_dimension_config)」而非指标节点。请确认是否同意(决策 3 允许两种,需拍板)。
|
|
|
|
|
+2. **health_dimension_scores.dimension 是否真改写列值**:决策 4 要求全量改写为 `body.*`,但会连带要求 `DimensionScoreServiceImpl` 的 map key 同步改(P1-T1 工作量)。若主理人倾向**最小爆炸半径**,可改为「列值保留 `growth` + 仅经 `legacy_alias` 逻辑映射」,请二选一。
|
|
|
|
|
+3. **认知 5 维 / 心 10 维 / 关系 5 维 / 富 4 维范围**:P0 仅固化 5 根 + 7 body 维;其余是否纳入统一树、何时纳入(建议 P2),请确认范围与优先级。
|
|
|
|
|
+4. **退役确认手段与是否真删表**:用「网关日志统计」还是「代码引用扫描」判定零调用?退役是 DROP 旧表还是仅逻辑禁用(`status=0`)?请定调(影响 P2-T3 与红线 4)。
|
|
|
|
|
+5. **五行 element 归属口径**:`wuxing_rule_ref` 是否直接复用现有 `EnergyDimension.element`(木/火/金/水/土)映射 body→?,或有新口径?请提供映射表。
|
|
|
|
|
+6. **双写过渡期时长 / 退役阈值**:「连续 14 天零调用」是否合适?阈值与过渡期是否需可配置(建议入 feature flag / 配置表)?
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+*附录:类图与序列图已提取至同级 `2026-11-03-unified-dimension-system-class.mermaid` / `2026-11-03-unified-dimension-system-sequence.mermaid`。*
|