Files
2026-02-02 11:22:35 +08:00

218 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DB Design & Migrations数据库设计与迁移Plan
> 对应规范:`spec_kit/Personalized Reco/modules/db-design/spec.md`
>
> 前置确认(已对齐):
>
> - `content_id`MySQL **自增主键**;文案微调时 **content_id 不变**(更新同一条记录)。
> - `need_suitability/context_suitability`**JSON** 存储。
> - `risk_flags`:选择更强扩展性的方案(本计划采用 **关联表**,利于索引与过滤)。
> - 安全池L3**方式 A**`is_safe_pool`)。
> - 迁移:使用 **Alembic**,目录放 `server/alembic/`dev/pro 两套库均可运行同一套迁移。
> - 字符集:统一 **utf8mb4**。
---
## 1. 目标与交付物
### 1.1 目标
- 在“库为空”的前提下,落地推荐系统最小可用的数据模型。
- 保证后续推荐查询可实现:按画像条件召回、按 ID 批量查、按风险标记过滤、支持安全池兜底。
### 1.2 交付物
- `server/alembic/`Alembic 初始化目录、`alembic.ini`(或等价配置)、迁移脚本。
- SQLAlchemy ORM 模型(建议放 `server/app/db/models/`)。
- 初始迁移:创建 `contents``content_profiles``content_risk_flags`(以及必要索引)。
---
## 2. 技术决策V1
### 2.1 表设计原则
- **分离主体与画像**`contents` 存文本与来源字段;`content_profiles` 存画像字段(便于未来画像重算/回填)。
- **JSON 存 suitability**`context_suitability``need_suitability` 用 JSON保持结构与规则文档一致。
- **risk_flags 关联表**:用 `content_risk_flags(content_id, flag)`,便于:
- 快速 Hard Filter`block_health_medical` 等)
- 索引与统计(按 flag 计数)
- 兼容旧 flag 映射(在写入/读取层)
- **emotion_score 的 general 表示**:用 `NULL` 表示 general与规则文档“可为 general”语义等价
- **personalization_power**:存为 `TINYINT`0/5/10`DECIMAL(2,1)`0/0.5/1。本计划推荐 `TINYINT`(更易索引/更省空间),应用层做映射:
- 0 → 0.0
- 5 → 0.5
- 10 → 1.0
### 2.2 字符集与排序规则
- 数据库与表:`utf8mb4`
- collation建议 `utf8mb4_0900_ai_ci`MySQL 8 默认更常见;若环境不同以实际为准,但必须 utf8mb4
---
## 3. 表结构V1 方案)
> 以下为“建议 schema”。实际字段名可调整但语义必须严格对齐 `句子文案打分规则`。
### 3.1 `contents`(文案主体)
- `content_id` BIGINT UNSIGNED PK AUTO_INCREMENT
- `text` TEXT NOT NULL
- `author_id` VARCHAR(64) NULL
- `template_id` VARCHAR(64) NULL
- `created_at` DATETIME NOT NULL
- `updated_at` DATETIME NOT NULL
索引建议:
- `idx_contents_author_id(author_id)`
- `idx_contents_template_id(template_id)`
### 3.2 `content_profiles`(内容画像)
- `content_id` BIGINT UNSIGNED PKFK → contents.content_idON DELETE CASCADE
- `stage` ENUM('general','expecting','parenting','unknown') NOT NULL DEFAULT 'general'
- `emotion_score` DECIMAL(3,2) NULL
- 约定NULL 表示 general
- `context_suitability_json` JSON NOT NULL
- `need_suitability_json` JSON NOT NULL
- `personalization_power` TINYINT UNSIGNED NOT NULL DEFAULT 0
- 约定:只允许 0/5/10
- `review_confidence` DECIMAL(3,2) NULL
- 约定NULL 由推荐模块按 0.7 兜底(对齐规则文档)
- `is_safe_pool` BOOLEAN NOT NULL DEFAULT FALSE
- `updated_at` DATETIME NOT NULL
索引建议:
- `idx_profiles_stage(stage)`
- `idx_profiles_personalization_power(personalization_power)`
- `idx_profiles_is_safe_pool(is_safe_pool)`
> 说明suitability 放 JSON 后V1 可以先不做 JSON 路径索引;当候选量上来后再加“生成列/函数索引”做加速(见 6.2)。
### 3.3 `content_risk_flags`(风险标记,关联表)
- `id` BIGINT UNSIGNED PK AUTO_INCREMENT
- `content_id` BIGINT UNSIGNED NOT NULLFK → contents.content_idON DELETE CASCADE
- `flag` VARCHAR(64) NOT NULL
- `created_at` DATETIME NOT NULL
约束与索引:
- UNIQUE`uniq_content_flag(content_id, flag)`(同一 content 不重复插同 flag
- 索引:`idx_flag(flag)`(用于 Hard Filter 与统计)
- 索引:`idx_content_id(content_id)`(用于按内容批量取 flags
命名约束应用层强制DB 可选):
- flag 必须以 `unsafe_for_` / `block_` / `soft_` 开头(严格对齐规则文档)
---
## 4. Alembic 迁移落地步骤
### 4.1 依赖与目录
- 后端依赖:`alembic`(加入 `server/requirements.txt`,版本随项目统一管理)
- 目录:`server/alembic/`(包含 `env.py``versions/`
- 连接串:复用现有 `DATABASE_URL``mysql+aiomysql://...`
### 4.2 初始化与生成迁移(一次性)
- `alembic init alembic`(在 `server/` 下)
- 配置 `env.py`
-`app/core/config.py` 读取 `DATABASE_URL`
- 引入 ORM Base 与 models启用 autogenerate
- 创建初始迁移:
- `alembic revision --autogenerate -m "init content tables"`
- `alembic upgrade head`
### 4.3 dev/pro 一致性
- 迁移脚本保持同一套;通过不同环境的 `DATABASE_URL` 指向 `mindfulness_dev``mindfulness`
---
## 5. 入库流程(写入契约)
### 5.1 一条文案最小入库数据
必须字段V1 最小可用):
- `text`
- `content_profiles.stage`
- `content_profiles.emotion_score`(可为 NULL 表示 general
- `content_profiles.context_suitability_json`(必须包含 5 个 keyfamily/work/relationship/friends/health值为 0/0.5/1
- `content_profiles.need_suitability_json`(必须包含 5 个 keyemotional_support/parenting_pressure/self_worth/anxiety_relief/rest_balance值为 0/0.5/1
- `content_profiles.personalization_power`0/5/10
- `content_risk_flags`(可为空集合,但若存在必须按命名规范)
强烈建议字段:
- `author_id``template_id`
- `review_confidence`
- `is_safe_pool`(若要参与 L3 安全池)
### 5.2 写入策略
- 创建文案时:
- 先写 `contents` 得到 `content_id`(自增)
- 再写 `content_profiles`(同 content_id
- 再批量写 `content_risk_flags`
- 文案微调时content_id 不变):
- 更新 `contents.text``updated_at`
- 同步更新 `content_profiles`(若画像变更)
- risk_flags 做“全量覆盖”或“差量更新”plan 实现阶段定)
---
## 6. 查询与性能规划
### 6.1 V1 查询策略(先可用)
- 候选召回:
- 先按 `stage``personalization_power``is_safe_pool` 等可索引字段进行粗过滤
- 再在应用层结合 suitability JSON 与 risk_flags 做精过滤/打分
- Hard Filter
- 通过 `content_risk_flags` join 或子查询排除指定 flags`block_health_medical`
- 批量查:
- `content_id IN (...)` join `content_profiles` + left join `content_risk_flags`
### 6.2 V1.1 性能增强(候选量上来后再做)
当候选池变大、应用层过滤成本上升时,优先做两类增强:
- **生成列/函数索引**:为常用召回维度(例如 need/context 的某些 key创建 generated columns从 JSON_EXTRACT 取值并映射到 TINYINT再加索引。
- **风险 flag 位图/派生列**:对 `block_health_medical` 等强规则增加派生布尔列(或维护冗余表),降低 join 成本。
---
## 7. 测试与验收DB 子模块)
### 7.1 迁移验收
- 在全新库执行 `alembic upgrade head` 成功。
- 执行 `downgrade`(若实现)可回滚(至少在开发环境可用)。
### 7.2 数据契约验收
插入一条最小文案记录后,能够查询并组装出推荐模块所需的 `ContentProfile` 字段集合:
- `content_id/text/stage/emotion_score/context_suitability/need_suitability/personalization_power/risk_flags`
### 7.3 规则口径验收(写入侧)
- 写入 `risk_flags` 时,若出现旧 flag`block_stage_unknown`
- 写入层需在入库前映射为新命名(或拒绝写入并提示)
- 推荐侧读取层不得再出现旧 flag 名称
---
## 8. 与其他子模块的接口约定
- `Content Repository` 只依赖本模块提供的表与字段语义,不依赖具体迁移实现细节。
- 推荐引擎/打分模块对 `review_confidence` 的缺省值假设0.7)在 DB 缺失时依然成立。