# 数据迁移计划：lingniu_prod → 新系统

## Context

老系统 `lingniu_prod`（单库 255 张表）需要全量迁移到新系统的 3 个库。新系统已做架构重构：拆分为 `ln_asset_management`（资产管理）、`ln_energy`（能源计费）、`ry-cloud`（系统核心/RuoYi 框架）。新库中现有数据为测试数据，迁移前清空。

## 源与目标

| | 源 | 目标 |
|---|---|---|
| **Host** | rm-uf65w5v2r77n674x2ko.mysql.rds.aliyuncs.com | 47.100.22.206:3306 |
| **用户** | oneos_read (只读) | root |
| **数据库** | lingniu_prod (255 表) | ln_asset_management (114), ln_energy (19), ry-cloud (29) |

## ID 策略

- `ln_asset_management` / `ln_energy`：`IdType.AUTO`（MySQL 自增），插入时不指定 ID，获取 `LAST_INSERT_ID`
- `ry-cloud`：雪花算法 ID（已有数据如 sys_user ID = 2038797628680523778）

所有迁移记录保存到 `ln_migration.id_mapping` 表，维护 `(old_table, old_id) → (new_table, new_id)` 映射。

## 通用字段转换规则

| 老字段 | 新字段 | 转换 |
|---|---|---|
| `is_deleted` (tinyint 0/1) | `del_flag` (char '0'/'1') | `str(val)` |
| `creater_id` (bigint) | `create_by` (bigint) | 查 user id_mapping |
| `updater_id` (bigint) | `update_by` (bigint) | 查 user id_mapping |
| int 枚举 (如 `truck_type`) | varchar 字典码 | 查 dict_mapping |
| `datetime` | `date` | 取日期部分 |
| `double` | `decimal` | `Decimal(str(val))` |

---

## 迁移分阶段执行

### Phase 0: 准备

1. 在目标库创建 `ln_migration` 数据库和 `id_mapping` 表
2. **清空**目标库 3 个库中所有业务表数据（保留 ry-cloud 框架种子数据如 sys_config, sys_oss_config, sys_tenant）
3. 读取老库 `tab_dic` 构建枚举映射字典 → `dict_mapping.py`

### Phase 1: 基础数据（无 FK 依赖）

| 老表 | 新表 (库) | 行数 | 说明 |
|---|---|---|---|
| `tab_dic` | `sys_dict_type` + `sys_dict_data` (ry-cloud) | 681 | 按 dic_type 分组为 type+data |
| `tab_region` | `common_district` (asset) | 3,359 | 直接映射 |
| `tab_vehicle_model` | `vehicle_model` (asset) | 39 | 字段对齐 |
| `tab_insurance_company` | `insurance_company` (asset) | 20 | 直接映射 |
| `tab_hydrogen_site` | `hydrogen_station` (asset) | 101 | int→varchar 枚举 |
| `tab_parking` | `parking_lot_info` (asset) | 65 | 直接映射 |
| `tab_maintain_site` | `repair_station` (asset) | 64 | 直接映射 |
| `tab_rescue_site` | `rescue_team` (asset) | ~14 | 直接映射 |
| `tab_annual_review_service_station` | `inspection_station` (asset) | 14 | 直接映射 |
| `tab_truck_check_item` | `vehicle_check_item` (asset) | 294 | 直接映射 |
| `tab_contract_templates` | `contract_template` (asset) | 13 | 直接映射 |
| `tab_charge_station` | `charging_station` (asset) | ~数条 | 直接映射 |

### Phase 2: 系统数据（用户/组织/角色）

| 老表 | 新表 (ry-cloud) | 行数 | 说明 |
|---|---|---|---|
| `tab_org` | `sys_dept` + `sys_organization` | 19 | 重建 ancestors 字段 |
| `tab_user` | `sys_user` | 405 | 密码需重置为 BCrypt 临时密码 |
| `tab_role` | `sys_role` | 69 | 雪花 ID |
| `tab_user_role` | `sys_user_role` | 201 | 查 user+role id_mapping |
| `tab_menu` | `sys_menu` | 464 | **评估**：新系统菜单已重新定义，可能跳过 |
| `tab_role_menu` | `sys_role_menu` | 6,236 | 依赖菜单决策 |

> **密码处理**：老系统使用自定义 salt+hash，新系统使用 BCrypt。迁移时统一设置临时密码（如 `Abc@123456` 的 BCrypt 值），首次登录强制修改。

### Phase 3: 核心业务实体

| 老表 | 新表 (asset) | 行数 | 复杂度 |
|---|---|---|---|
| `tab_truck` | `vehicle_info` | 1,203 | 高：47→29 列，大量字段重组 |
| `tab_truck_status_info` | `vehicle_status` | 1,385 | 中：int→varchar 枚举 |
| `tab_driver` | `driver_info` | 212 | 低 |
| `tab_customer` | `customer_info` | 310 | 中 |
| `tab_truck_licence` | `vehicle_license` | 1,712 | 中：truck_id→vehicle_id FK |
| `tab_truck_insure` | `insurance_procurement` | 740 | 中：15→32 列扩展 |
| `tab_equipment_info` | `aftermarket_device` | 1,562 | 中 |
| `tab_violation_management` | `traffic_violation` | 1,285 | 低 |
| `tab_accident` + `tab_accident_cost_bearing` | `accident_info` + `accident_expense` | 293+823 | 中 |
| `tab_failure` | `vehicle_fault_manage` | 5,140 | 低 |
| `tab_vehicle_annual_inspection` | `vehicle_annual_inspection` | 353 | 低 |
| `tab_vehicle_preparation` | `prepare_car` | 1,736 | 低 |
| `tab_customer_invoice` | `invoice_info` | 219 | 低 |
| `tab_truck_device_info` | `aftermarket_device` 或 `vehicle_realtime_location` | 1,227 | 评估 |
| `tab_training_materials` | `training_material` (asset) | 3 | 低 |

**vehicle_info 字段映射（核心）：**
```
tab_truck.plate_number     → vehicle_info.plate_number
tab_truck.vin              → vehicle_info.vin
tab_truck.truck_num        → vehicle_info.vehicle_code
tab_truck.model (int)      → vehicle_info.vehicle_model_id (FK, 查 vehicle_model id_mapping)
tab_truck.color            → vehicle_info.body_color
tab_truck.buy_time         → vehicle_info.purchase_date (datetime→date)
tab_truck.stock_area (int) → vehicle_info.packing_lot_id (FK, 查 parking_lot_info id_mapping)
tab_truck.mandatory_retirement_period → vehicle_info.mandatory_scrap_date
tab_truck.remarks          → vehicle_info.remark
tab_truck.address          → vehicle_info.province (提取省份)
```

### Phase 4: 合同域

| 老表 | 新表 (asset) | 行数 | 说明 |
|---|---|---|---|
| `tab_contract` | `vehicle_lease_contract_info` | 681 | 38→67 列，大量新字段 NULL |
| `tab_contract_rent_order` | `vehicle_lease_order` | 679 | 关联合同 |
| `tab_contract_rent_truck` | `vehicle_lease_order_detail` | 3,865 | 关联 order + vehicle |
| `tab_contract_rent_truck_service_cost` | `vehicle_lease_order_service_item` | 1,645 | 关联 detail |
| `tab_contract_thirty_party` | `contract_authorized_person` | 682 | 直接映射 |
| `tab_contract_authorizer_information` | `contract_authorized_person` | 666 | 合并 |
| `tab_contract_costs` | `template_*` 系列表 | 15,059 | 按费用类型拆分 |
| `tab_contract_hydrogen_fees` | `template_hydrogen_fee` | 13 | 直接映射 |

### Phase 5: 交付/退车/换车

| 老表 | 新表 (asset) | 行数 | 说明 |
|---|---|---|---|
| `tab_truck_rent_task` | `delivery_task_subject` | 3,104 | 任务容器 |
| `tab_truck_rent_take` | `delivery_order` + `delivery_vehicle` | 1,956 | 一拆多 |
| `tab_truck_rent_return` | `return_vehicle_task` | 1,079 | 重构 |
| `tab_truck_rent_return_cost` | `return_fees` | 73 | 直接映射 |
| `tab_truck_rent_return_dep_cost` | `return_settlement_*` 子表 | 4,634 | 按类型拆分 |
| `tab_truck_rent_replace` | `vehicle_replacement` | 247 | 直接映射 |
| `tab_standby_vehicle_main/detail` | `vehicle_abnormal_move` | 377+2,171 | 评估映射 |

### Phase 6: 账单/财务

| 老表 | 新表 (asset) | 行数 | 说明 |
|---|---|---|---|
| `tab_rent_contract_bill` | `bills` | 25,625 | 合同账单 |
| `tab_rent_contract_bill_truck` | `vehicle_bills` + `vehicle_bill_service_items` | **270,283** | **大表，批量处理** |
| `tab_rent_contract_bill_other_cost` | 合并到 `vehicle_bills` | ~少量 | |
| `tab_finance_receivable` | `receivable_subject` + `receivable_vehicle` | 10,308 | 拆分 |
| `tab_finance_deposit_receive` | `customer_payment_receipt` | 2,078 | 映射 |
| `tab_finance_deposit_deduction` | `customer_payment_item` | 52 | 映射 |

### Phase 7: 能源域 → ln_energy

| 老表 | 新表 (ln_energy) | 行数 | 说明 |
|---|---|---|---|
| `tab_energy_account` | `energy_account` | 201 | 结构重组 |
| `tab_energy_project_account` | `energy_account_project` | 307 | 直接映射 |
| `tab_energy_account_recharge` | `energy_recharge_order` | 942 | 字段扩展 |
| `tab_import_hydrogen_order` | `hydrogen_station_order` | 58,642 | 加氢原始订单 |
| `tab_energy_hydrogen_bill` | `energy_hydrogen_detail` | **58,554** | **大表** |
| `tab_import_ele_charge_order` | `electricity_charge_record` | 4,405 | 充电原始订单 |
| `tab_energy_electricity_bill` | `energy_bill_detail` | 4,355 | fee_type=electricity |

### Phase 8: 附件

| 老表 | 新表 (asset) | 行数 | 说明 |
|---|---|---|---|
| `tab_data_attachment` | `tab_data_attachment` | **247,447** | **最大表之一**，data_id 需 FK 重映射 |
| `tab_image_attachment` | 合并到 `tab_data_attachment` 或保留 | 51,516 | 评估 |

### Phase 9: 其他

| 老表 | 新表 | 行数 | 处理 |
|---|---|---|---|
| `tab_maintain_maintenance_project` | 评估 | 243,474 | 新系统无直接对应，可能跳过 |
| `tab_truck_rent_form_data` | 评估 | 456,061 | 表单数据，新系统结构不同 |
| `tab_preparation_form_data` | 评估 | 171,497 | 同上 |
| `tab_standby_vehicle_form_data` | 评估 | 143,034 | 同上 |

---

## 暂不迁移的表（无新系统对应）

- 审批流：`tab_approve_instance*`, `tab_approve_template_node`
- 工作流：`tab_flow_task*`, `tab_flow_template*`
- G7 车联网：`tab_g7s_org`, `tab_g7s_truck_mileage` (289K), `tab_g7s_in_out_event` (159K)
- 培训考试：`tab_train_*`, `tab_safety_training`
- 系统日志：`tab_api_access_log` (4.6M), `tab_user_log`, `tab_user_message`, `tab_short_message`
- 调度记录：`tab_schedule_execute_result` (347K), `tab_data_sync_task_record`
- 应用版本：`tab_app_version`, `tab_version_user_check`, `tab_release_version_log`
- 临时表：所有 `tab_aa_temp_*`, `*_copy*`, `temp_*`
- 视图：所有 `view_*`, `v_*`
- 汇总表：`truck_info`, `truck_equipment_info`, `truck_hydrogen_info`, `truck_mileage`, `truck_parking`

---

## 实现方案：Python 脚本

### 目录结构

```
/Users/kkfluous/Projects/lingniu/oneos-corp/migration/
  config.py              # 数据库连接配置
  id_mapping.py          # ID 映射表 CRUD
  transform.py           # 通用字段转换函数
  dict_mapping.py        # 老 int 枚举 → 新 varchar 编码映射
  migrator.py            # 基础迁移器（批量读写、mapping、日志）

  phase0_prepare.py      # 建 mapping 表、清空目标库
  phase1_reference.py    # 字典、区域、车型等基础数据
  phase2_system.py       # 组织、用户、角色
  phase3_core.py         # 车辆、司机、客户、证照、保险
  phase4_contract.py     # 合同、租赁订单
  phase5_delivery.py     # 交付、退车、换车
  phase6_billing.py      # 账单、财务
  phase7_energy.py       # 能源账户、加氢、充电
  phase8_attachment.py   # 附件
  phase9_misc.py         # 其他

  verify.py              # 行数校验、FK 完整性、抽样比对
  run_all.py             # 按顺序执行所有 phase
```

### 关键技术点

1. **批量处理**：大表（27 万+ 行）使用 `SSCursor` 服务端游标 + `executemany` 批量写入（每批 1000 行）
2. **ID 映射**：`ln_migration.id_mapping(source_table, source_id, target_db, target_table, target_id)`，FK 字段通过查映射表解析
3. **枚举映射**：从 `tab_dic` 读取所有字典项，预构建 `{(dic_type, int_value): new_dict_code}` 映射
4. **密码处理**：所有用户密码统一设为 BCrypt(`Abc@123456`)，首次登录强制修改
5. **事务**：每个 Phase 按表粒度 commit，失败可单表重试

## 验证策略

1. **行数校验**：每张表迁移后对比源/目标行数
2. **抽样比对**：每张核心表随机取 10 条，比对关键业务字段
3. **FK 完整性**：检查所有外键列无孤儿记录
4. **业务逻辑**：能源账户余额一致、合同-车辆关联完整
5. **登录测试**：使用临时密码验证 sys_user 登录

## 回滚方案

1. 迁移前对目标 3 库做 `mysqldump` 备份
2. 回滚 = truncate 所有目标表 + 从备份恢复 + drop `ln_migration`
3. 单 Phase 回滚 = 根据 `id_mapping` 删除该 Phase 写入的记录

## 关键文件

- `ln-asset-management/.../BaseEntity.java` — IdType.AUTO, del_flag char(1)
- `ln-asset-management/.../VehicleInfo.java` — 新车辆实体 29 字段
- `ln-cloud/ruoyi-common/.../BaseEntity.java` — RuoYi 基础实体
- 老后端代码 `/Users/kkfluous/Projects/lingniu/ln_asset/lingniu_asset_server/lingniu-manager/src/main/java/org/lingniu/manager/model/` — 所有老实体类
