# 主键 ID 保留策略(全局) > **决策日期**:2026-07-12 > **适用范围**:`docs/data-migration/` 下全部生产数据同步(CRM、报销、地磅) > **原则**:原系统业务主键 **原样迁入** 门户库,仅做类型适配(`bigint`/`int` → 同值 `VARCHAR`),**不重新生成 UUID**。 --- ## 1. 核心规则 | 项 | 规则 | |----|------| | 业务表主键 `id` | `target.id = str(source.id)`(十进制字符串,与源数值一致) | | 业务外键 `*_id` | 指向业务实体时:`str(source_*_id)`;指向用户时:见 §3 | | `parent_id` 等树字段 | `0` → `'0'`;非 0 → `str(source_parent_id)` | | 映射文件 | 仍生成 `*_id_mapping.csv`,多为 **恒等映射**(`source_id` = `new_id`),用于审计与二次移植对账 | | 脚本实现 | 统一使用 `legacy_id_to_str()`(参考 `migrate_dibang_data.py`、`migrate_crm_product_category.py`) | **示例** ``` 源 crm_product.id = 42 → 目标 "42" 源 crm_product.category_id = 1975766458781167617 → 目标 "1975766458781167617" 源地磅 plan.id = 12 → 目标 "12" ``` --- ## 2. 好处 1. **下游表免查 mapping**:产品、客户、报价、合同等子表外键可直接 `str(源值)` 写入。 2. **二次移植简单**:按 `id` 幂等 SKIP/UPDATE,mapping 即对账表。 3. **与地磅策略一致**:三系统同一套心智模型。 4. **问题排查**:门户与旧库对照时 ID 一一对应。 --- ## 3. 例外(不保留源 id 或需额外映射) | 场景 | 处理方式 | 说明 | |------|----------|------| | **`sys_user`(P0 已执行)** | 已生成门户 UUID | 以 `mappings/user_mapping_result.csv` 为准;`create_by` / `owner_user_id` / `approver_id` 等 **必须**经用户映射转换 | | **字典 `sys_dict_data.id`** | 按门户既有主键 | 字典项以 `dict_type` + `dict_value` 对齐,不按 sx 的 `dict_code` 保留 id(见 step-01-dict) | | **门户已手工录入且 id 冲突** | 步骤文档写明 | 测试库可用 `--truncate`;生产需人工合并,禁止静默覆盖 | | **源 id 非纯数字** | 原样字符串写入 | 若超长则按目标列宽截断并记 WARNING | --- ## 4. 各阶段适用表(摘要) | 阶段 | 表 / 实体 | 主键策略 | |------|-----------|----------| | P0 | `sys_user` | ❌ 例外:门户 UUID + user_mapping | | P0 | `sys_dept`、`sys_role` | ✅ 若从 sx 导入,优先保留源 id 字符串 | | P1 CRM | `crm_product_category` | ✅ 已按本策略实现脚本 | | P1 CRM | `crm_product`、`crm_customer`、`crm_clue`、商机/报价/合同等 | ✅ 全部 `str(source.id)` | | P1 CRM | `sys_oss`(附件) | ✅ `str(oss_id)` | | P2 报销 | `bx_*` 业务表 | ✅ `str(source.id)` | | P3 地磅 | `db_*` | ✅ 已有脚本,int → 同值字符串 | --- ## 5. 脚本约定 每个 `migrate_*.py` 应: 1. 支持 `--dry-run` 2. 默认:目标已存在同 `id` → **SKIP**;`--update` 可覆盖非主键字段 3. `--mapping-out` 写出恒等或例外映射 CSV 4. 日志与 `phase-*/logs/` 记录「ID 策略:原样保留」 共享工具函数(可复制到各脚本或后续抽到 `scripts/migration_utils.py`): ```python def legacy_id_to_str(legacy_id) -> str: if legacy_id is None: return "0" return str(int(legacy_id)) ``` --- ## 6. 与旧文档的关系 - **代码移植 spec** `docs/superpowers/specs/2026-05-30-crm-migration-design.md` §7.1 曾写「bigint → UUID」;**生产数据同步以本文档为准**。 - **OpenSpec / 公共字段** 仍要求目标列类型为 `VARCHAR(64)`;保留源 id 字符串满足类型要求,不要求必须是 UUID 格式。 --- ## 7. 相关文档 - [00-master-plan.md](./00-master-plan.md) §2 已确认决策 - [03-process-logging.md](./03-process-logging.md) 映射产物说明 - [phase-1-crm/step-01-product-category.md](./phase-1-crm/step-01-product-category.md) 首版落地示例