# 数据迁移脚本 老结构(厂家/出货单/授权码)→ 新结构(品牌商/生产批次/带 batchno 的授权码)的一次性迁移。 **只对应用主库(`starpivot`)执行**,按文件名日期顺序跑。两个脚本都用事务包裹、幂等可重复执行。 ## 执行顺序 ```bash # 0) 先备份(必做) docker exec postgres pg_dump -U starpivot -d starpivot --no-owner --no-privileges -f /tmp/pre.sql docker cp postgres:/tmp/pre.sql ./starpivot-premigrate-$(date +%Y%m%d-%H%M).sql # 1) 主迁移 docker cp 2026-07-31-factory-to-brand-batch.sql postgres:/tmp/m1.sql docker exec postgres psql -U starpivot -d starpivot -v ON_ERROR_STOP=1 -f /tmp/m1.sql # 2) 补 product.serialcursor(新代码 allocSerialRange 依赖,缺了生产授权码直接报错) docker cp 2026-07-31-product-serialcursor.sql postgres:/tmp/m2.sql docker exec postgres psql -U starpivot -d starpivot -v ON_ERROR_STOP=1 -f /tmp/m2.sql # 3) 方案商改手工 ID + 厂商分组,并按 factoryflag 回填品牌商归属 docker cp 2026-07-31-solution-provider-vendor.sql postgres:/tmp/m3.sql docker exec postgres psql -U starpivot -d starpivot -v ON_ERROR_STOP=1 -f /tmp/m3.sql ``` ```bash # 4) 渠道商维度(2026-08-08):授权码/批次/用户设备加 channelid, # stats_global_day 把 channel_id 并进复合主键 # ⚠️ 执行前先停 console(stats_global_day 的唯一写库方),执行后再起。 # 脚本 [A] 段跑 console 主库,[B] 段(默认注释)跑各应用业务库。 docker cp 2026-08-08-channel-dimension.sql postgres:/tmp/m4.sql docker exec postgres psql -U starpivot -d starpivot -v ON_ERROR_STOP=1 -f /tmp/m4.sql ``` ```bash # 5) 区域二值化(2026-08-11):app_registry.region 与 console_account.regions 统一成 cn/hw, # 对应后台「应用切换按应用去重、区域只留 国内/海外」的改动。只跑 console 主库。 docker cp 2026-08-11-region-cn-hw.sql postgres:/tmp/m5.sql docker exec postgres psql -U starpivot -d starpivot -v ON_ERROR_STOP=1 -f /tmp/m5.sql ``` > 想先看结果不落库:把脚本末尾的 `COMMIT;` 改成 `ROLLBACK;` 跑一遍即可。 ## 映射关系 | 老 | 新 | 说明 | |---|---|---| | `factory` | `brand` | **id 沿用**,所以各表 `factoryid` 的值无需转换 | | `factory.factoryflag` | `brand.providerid` | **它就是方案商 ID**(十六进制:`ABEF`/`ABF0`~`ABF4`/`AB21`),解析成整数即可 | | `factory.probatch` | — | 老批次计数器,新结构无对应字段,丢弃 | | `factory_deliverynote` | `production_batch` | 并上授权码里实际存在的批次(见下) | | `license_b*.factoryid` | `.brandid` | 新增列回填,老列保留作对照 | | `license_b*.probatch` | `.batchno` | 6 位短码,字符集同 `comm.GeneratePublicCode` | | `product.factoryid` | `.brandid` | 改列名 | | `factory_public_code.factoryid` | `.brandid` | 改列名 | | — | `solution_provider` | 按 `factoryflag` 的实际取值建 7 条,带 `vendor` 厂商分组(1=杰里 2=蓝汛 3=恒玄) | | 老主表 `license` | — | 不动。新代码只读分表 `license_` | 批次来源取**并集**:以各 `license_b*` 分表按 `(productid, probatch)` 聚合为主(保证每个授权码都挂得上批次), 出货单里有、授权码里没有的补进来;MAC 区间和生产时间用出货单回填。 `production_batch.remark` 里写了 `probatch=<老批次号>`,既是对账依据,也是脚本重跑时的幂等锚点。 ## 已知的语义差异 老数据的 `number` 是**批次内序号**(每批都从 1 重新计数),而新代码的 `number` 是**产品级全局序号**, 由 `product.serialcursor` 累加分配。因此: - 迁移后同一产品的多个批次,`serialstart/serialend` 会重叠(如都是 1~2),这是老数据本身的性质,无法反推出全局序号; - 脚本 2 把 `serialcursor` 初始化为该产品现有 `max(number)`,**新生产的批次序号会接在老数据之后**,不会与历史重叠; - 授权码 `code` 由 `license.GenerateV2` 生成,内部带随机种子,不依赖 `serial` 唯一,所以老数据 `number` 重复不会造成主键冲突。 ## 方案商 ID 规则(重要) `solution_provider.id` 是**手工指定的有含义编码,不自增**(列上的序列默认值已 DROP):十六进制看, `0xC101` = 杰里 01 方案、`0xC102` = 杰里 02 方案。历史 ID(`ABEF`/`ABF0`~`ABF4`/`AB21`) **已烧录进设备、不可变更**,只能保留;**新增方案商一律用 `C` 开头段**。 一个厂商可有多个方案,所以 `vendor` 分组(enum `SolutionVendor`)才是「三个方案商」那一层: `ABEF`→杰里,`ABF0`~`ABF3`→蓝汛,`ABF4`→恒玄,`AB21`→未指定(慕然科技,待确认)。 因为 id 不自增,`api_addsolutionprovider` 会强制校验 id 非 0 且不重复——否则不传 id 会插成 0,第二条直接主键冲突。 ## 2026-07-31 广州测试环境的执行结果 品牌商 25、方案商 7、生产批次 64(63 个来自授权码 + 1 个仅出货单登记)、 授权码 37,253 条全部回填 `batchno`(未填 0、孤儿 0、`brandid` 与 `factoryid` 不一致 0)。 25 个品牌商按 `factoryflag` 全部归属到方案商,未归属 0 个。