# 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 PK(FK → contents.content_id,ON 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 NULL(FK → contents.content_id,ON 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 个 key:family/work/relationship/friends/health,值为 0/0.5/1) - `content_profiles.need_suitability_json`(必须包含 5 个 key:emotional_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 缺失时依然成立。