本文不是“脚本文件清单”,而是一份可执行的治理手册。它回答五个问题:这个入口到底是什么任务、会改哪些数据、能否重复执行、失败后如何续跑、怎样证明修复结果正确。

1. 文档目标

DGJ2.0 的 application/controllers/tasks 目录同时承载消息消费者、周期任务、数据补偿、历史迁移和临时修复。它们的运行方式相似,但业务语义完全不同。将所有入口都理解成“定时任务”会产生很大风险。

本文用于:

  1. 给现有任务建立统一分类和风险等级。
  2. 解释典型修复、迁移脚本的数据流和副作用。
  3. 固化生产执行前、执行中、执行后的操作流程。
  4. 给新脚本提供参数、幂等、日志、回滚和归档标准。
  5. 帮助排查重复执行、部分成功、跨系统不一致和误跑全站等问题。

本文不替代具体业务手册。涉及库存、采购、支付、权限、报价等业务口径时,应同时阅读对应专题文档。

2. 核心结论

结论说明
tasks 不是单一任务类型目录101 个控制器中混有消费者、定时任务、迁移、修复、重放和测试入口
CLI 限制不等于生产安全is_cli() 只阻止 HTTP 访问,不能阻止跑错环境、跑错 sid 或重复执行
“脚本执行成功”不等于业务修复成功必须分别验证主单、明细、流水、实时汇总、缓存、MQ、外部系统和报表
一次性迁移与长期补偿必须分开治理前者要求版本、批次、验收和归档;后者要求调度、锁、重试、监控和告警
默认空参数最危险多个脚本在 sid 为空或为 0 时会查询全部服务站、选中零号分片或扩大范围
数据库事务不能覆盖外部副作用HTTP、MQ、Redis、文件和外部系统操作需要单独的幂等键和补偿状态
修复前必须先定义正确性没有修复前后对账公式,就无法判断是修复还是二次污染

3. 代码入口地图

3.1 主要目录

目录或文件责任典型内容
application/controllers/tasks/CLI 任务入口消费者、周期任务、修复、迁移、临时入口
application/Services/Migrate/一次性迁移服务报价范围、机器人好友、VIN 回复配置迁移
application/Services/BaseData/QuoteManagerMigrate.php报价体系迁移核心分站迁移、检查点、版本切换、清理和重算
application/controllers/temp/历史临时控制器可能绕过标准业务入口,执行前必须审计
application/controllers/tools/运维或开发工具数据检查、辅助处理,不能默认认为只读
document/*.sql需求或版本 SQLDDL、初始化、修正脚本,需绑定版本和执行记录
application/Services/MoveMall/RobotV2/database/RobotV2 数据迁移 SQL建表、字段、索引、状态修正和历史会话迁移

3.2 典型入口

入口类型主要影响初始风险
tasks/DataFix/deletePuOrder定点数据修复采购入库、明细、库存流水和实时库存P0
tasks/DataFix/updatePaymentInfoStlDate批量字段修复付款明细分表的源单日期P0
tasks/DataFix/realtimeFix批量字段修复实时库存 SKU 标识P1
tasks/DataFix/fixZhanQu外部数据回填OPS 订单日志、运营省/战区P1
tasks/GeneralTasks/migrateQuoteConfig一次性迁移报价范围和明细P0
tasks/QuoteMigrate/migrateDaemon分站迁移报价规则、客户组、商品规则、版本P0
tasks/ActivityRuleMigrate/*一次性迁移活动规则、计划、模板和关联数据P0
tasks/AclMigrate/*一次性迁移资源、菜单、用户授权、应用开通P0
tasks/TmpChannelOrder/autoDelivery临时业务自动化渠道订单发货、库存和履约P0
tasks/TmpChannelOrder/autoRefundConfirm临时业务自动化渠道售后通过/拒绝、库存和退款链P0
tasks/TmpInitAdminLever/tmpInitAdminLever权限回填用户旧权限字符串P1
tasks/TmpInitTransmit/tmpInitTransmit菜单 JSON 回填服务站 APP 菜单配置P1
tasks/TmpResetQuoteRange/init配置重建报价范围配置明细P0
tasks/FixSaHxCode/index文件驱动修复销售核销状态P0
tasks/QtyAsync/fix多进程数量账重建采购订单数量扩展表P0
tasks/ExpressNoSupplement/*补偿任务运单号、签收、WMS/OPS 同步P0/P1

4. 先分类,再决定运行方式

4.1 八类任务

类别识别特征是否长期运行是否允许人工直接执行
MQ 消费者*Notify、注册 destination/routing key、持续消费是只按部署手册启停
周期任务按时间扫描有效数据并更新是可手工补跑,但要有任务锁和水位
补偿任务查找超时、缺失或状态不一致数据是允许受控补跑,必须幂等
一次性迁移旧结构转新结构、版本切换否仅按发布批次执行一次
定点修复针对已确认异常数据修正否必须绑定工单、范围和备份
文件重放从 /tmp 或导入文件读取 ID/消息否必须校验文件摘要和内容
临时业务自动化自动发货、自动售后等真实业务动作临时只允许指定环境和白名单数据
诊断任务查询、比对、打印结果可重复应保持严格只读
flowchart TD
  A["发现 tasks 入口"] --> B{"是否持续监听 MQ"}
  B -->|是| C["MQ 消费者:按进程治理"]
  B -->|否| D{"是否由固定周期调度"}
  D -->|是| E{"是在推进正常业务还是修复异常"}
  E -->|正常推进| F["周期任务"]
  E -->|异常兜底| G["补偿任务"]
  D -->|否| H{"是否改变数据结构或版本"}
  H -->|是| I["一次性迁移"]
  H -->|否| J{"是否产生真实业务动作"}
  J -->|是| K["临时业务自动化"]
  J -->|否| L{"是否读取外部文件"}
  L -->|是| M["文件重放"]
  L -->|否| N["定点修复或诊断任务"]

4.2 为什么不能只看文件名

TmpChannelOrder 虽然叫 Tmp,但会调用正式的渠道发货和售后服务;GeneralTasks 虽然叫通用任务,当前主要入口却是历史迁移;ExpressNoSupplement 既有扫描补运单,也有向 OPS 补签收的外部副作用。因此分类必须落到“方法级”,不能只按控制器名判断。

5. 风险分级

5.1 分级标准

等级判定条件审批和执行要求
P0删除/反冲业务数据、改库存或资金、切版本、全站批量、调用不可逆外部动作双人复核、备份、测试演练、单站灰度、明确回滚、全程监控
P1批量更新权限/配置/状态、重建缓存或索引、可影响用户使用范围复核、抽样备份、灰度、业务验收
P2幂等补偿、可重建衍生数据、影响有限参数校验、执行日志、前后计数
P3严格只读检查可直接运行,但禁止在诊断代码中夹带写操作

5.2 风险评分维度

建议对每次执行按以下六项打分,每项 0~2 分:

维度0 分1 分2 分
数据范围单条且唯一单站/小批量全站/全表
数据性质衍生缓存业务状态/配置库存/资金/不可逆单据
幂等能力有唯一键和执行记录可重复但有额外成本重跑会重复或破坏数据
外部副作用无Redis/MQ 可补偿HTTP 业务动作或外部落账
回滚能力自动回滚可用备份恢复无法自动回滚
可观测性有批次、指标和对账只有日志无明确结果记录

总分 0~3 可按 P2/P3 管理,4~7 至少 P1,8~12 按 P0 管理。资金、库存、删除和外部不可逆动作即使总分较低,也直接按 P0。

flowchart LR
  A["任务入口"] --> B["范围"]
  A --> C["业务数据性质"]
  A --> D["幂等"]
  A --> E["外部副作用"]
  A --> F["回滚"]
  A --> G["可观测性"]
  B --> H["风险等级 P0-P3"]
  C --> H
  D --> H
  E --> H
  F --> H
  G --> H

6. CLI 边界不是安全边界

多数任务使用以下守卫:

if (!defined('BASEPATH') || !is_cli()) {
    exit('No direct script access allowed');
}

它只能证明请求来自 CLI,不能证明:

  • 当前连接的是本地、预发还是生产数据库。
  • 操作者输入了正确的 sid、单号和 ID 范围。
  • 当前代码版本与目标数据结构一致。
  • 脚本没有执行过。
  • 外部系统处于可调用状态。
  • 任务不会扩大到全部服务站。

6.1 推荐的四层保护

flowchart TD
  A["CLI 守卫"] --> B["环境显式确认"]
  B --> C["任务批次和审批号"]
  C --> D["sid / ID / 时间范围强校验"]
  D --> E["dry-run 预览"]
  E --> F["人工输入确认摘要"]
  F --> G["获取任务锁"]
  G --> H["执行"]

推荐要求命令显式携带:

environment=preprod
ticket=INC-xxxx
operator=employee-id
sid=single-station-id
dryRun=1
startId=...
endId=...
limit=100
maxRows=1000

文档示例中的值均为占位符,执行时必须替换,不能照抄。

7. 标准任务参数契约

7.1 必选参数

参数作用必须校验
environment防止跑错环境只能是允许值,并与数据库 host 指纹匹配
ticket关联变更/故障记录非空,写入执行审计
operator操作者非空,不记录密码或 token
sid服务站边界P0/P1 默认禁止空值和 0
dryRun只读预览首次必须为 1
maxRows熔断阈值预计影响行数超限立即终止

7.2 批量和续跑参数

参数作用推荐语义
startId起始游标包含该 ID
endId结束边界包含该 ID,防止执行期间新数据进入
lastId已完成水位每批成功后持久化
limit单批数量小批量,不能等同总量上限
maxRows本次总量上限达到后正常停止并等待复核
sleepMs批间隔控制数据库、Redis、MQ 和外部接口压力
retry单条重试次数仅对可重试错误生效
force强制覆盖默认关闭,开启必须二次审批

7.3 参数决策图

flowchart TD
  A["准备运行"] --> B{"sid 是否为空或 0"}
  B -->|是| C{"是否明确设计为全站任务"}
  C -->|否| X["拒绝执行"]
  C -->|是| D["要求 allowAll=1 和二次确认"]
  B -->|否| E["校验站点存在且环境正确"]
  D --> F{"是否有 endId / 截止时间"}
  E --> F
  F -->|否| X
  F -->|是| G["执行 dry-run 并输出预计影响"]
  G --> H{"预计影响 <= maxRows"}
  H -->|否| X
  H -->|是| I["进入灰度执行"]

8. 标准执行生命周期

stateDiagram-v2
  [*] --> Draft: 编写或选定脚本
  Draft --> Reviewed: 代码与业务双审
  Reviewed --> DryRun: 只读预演
  DryRun --> Rejected: 范围或口径不对
  DryRun --> Approved: 影响范围确认
  Approved --> Canary: 单条/单站灰度
  Canary --> Paused: 结果异常
  Canary --> Running: 灰度通过
  Running --> Paused: 失败率或差异超阈值
  Paused --> Running: 修正后续跑
  Running --> Reconciling: 执行完成
  Reconciling --> RolledBack: 验收失败
  Reconciling --> Accepted: 验收通过
  Accepted --> Archived: 记录与代码归档
  RolledBack --> Archived
  Rejected --> Archived
阶段输入输出禁止跳过的检查
Draft问题单、正确口径脚本和影响说明不能边猜口径边改数据
Reviewed代码、SQL、调用链风险等级和回滚方案业务 Owner 与研发至少各一人
DryRun固定参数命中 ID、数量、差异摘要不得写 DB/Redis/MQ/外部系统
Canary1 条或 1 个站点灰度前后快照必须走完整业务验收
Running审批后的范围批次日志和检查点超过阈值自动停止
Reconciling执行结果多层对账报告不能只看 affected rows
Archived验收结论可追踪执行记录一次性脚本应禁止再次误跑

9. 正确性模型:修复什么就对账什么

9.1 八层数据模型

flowchart LR
  A["业务主单"] --> B["业务明细"]
  B --> C["过程/状态日志"]
  B --> D["库存或资金流水"]
  D --> E["实时汇总"]
  E --> F["Redis / ES / Mongo 缓存索引"]
  C --> G["MQ / 外部系统"]
  D --> H["报表 / 数仓 / 对账快照"]
修复对象最低验收范围
订单状态主单、明细数量、状态日志、下游单据、MQ
库存业务单据、128 分片流水、实时库存、货位、缓存、报表
支付支付单、明细、账户流水、源单状态、外部支付状态、对账
权限资源、关系、用户授权、OPS 菜单 JSON、缓存、端上可见性
报价规则、范围、客户组、商品关系、版本、缓存/索引、真实询价
运单本地关系、外部运单、订单履约状态、签收、对账

10. 幂等设计

10.1 幂等不是“第二次没报错”

真正的幂等要求相同批次重复执行后:

  • 业务结果不重复增加或减少。
  • 不重复发送 MQ、短信、推送或外部业务请求。
  • 不产生重复日志、计划、模板和关系行。
  • 检查点不会后退。
  • 已失败项可以单独重试。

10.2 常用策略

场景推荐幂等键实现
单行字段回填taskCode + table + primaryKey条件更新:仅更新旧值仍符合预期的行
关系迁移业务唯一键数据库唯一索引 + upsert
外部请求taskBatch + sourceBillNo + action本地 outbox 记录请求和结果
MQ 重放原消息 ID + 事件类型消费侧去重,保留原 correlationId
全站迁移taskCode + version + sid每站状态机和检查点
文件重放文件 SHA-256 + 行号审计表记录每行状态
sequenceDiagram
  participant Runner as 任务执行器
  participant Ledger as 执行审计表
  participant DB as 业务数据库
  participant Outbox as 外部动作表
  participant External as MQ/外部系统
  Runner->>Ledger: 创建 batch,记录参数摘要
  loop 每一条数据
    Runner->>Ledger: 查询 item 幂等键
    alt 已成功
      Ledger-->>Runner: 跳过
    else 未执行
      Runner->>DB: 条件更新/事务写入
      DB-->>Runner: 影响行数
      Runner->>Outbox: 写待发送外部动作
      Runner->>Ledger: 标记本地成功
    end
  end
  Outbox->>External: 异步发送
  External-->>Outbox: 成功/失败
  Outbox->>Ledger: 汇总最终结果

11. 事务和外部副作用

11.1 单库事务能保护什么

trans_begin() / trans_commit() 只能保护当前数据库连接内的写入。以下操作通常不在同一个事务中:

  • Redis 写入。
  • MQ 发布。
  • 调拨中心、订单中心、支付中心、OPS、TMS 等 HTTP 调用。
  • 分库或其他连接上的数据库写入。
  • 文件生成和 OSS 上传。

11.2 错误时序

sequenceDiagram
  participant Script as 修复脚本
  participant DB as 本地数据库
  participant External as 外部系统
  Script->>DB: begin
  Script->>DB: 更新本地状态
  Script->>External: 发起不可逆业务动作
  External-->>Script: 成功
  Script->>DB: commit
  DB-->>Script: 提交失败
  Note over Script,External: 外部已成功,本地却失败,数据库回滚无法撤销外部动作

推荐把外部动作拆成 outbox:先在本地事务内记录“待发送”,事务提交后再发送;发送结果独立落账,失败可重试。

12. 分表安全

DGJ2.0 大量表按 sid 取模分片。修复脚本必须先明确:

  1. 分片数量常量是什么。
  2. 表名计算使用哪一个字段。
  3. 是否还需要在分片表内增加 sid 条件。
  4. sid=0 是真实站点、零号分片,还是“全部”的特殊语义。
  5. 表名表达式是否受 PHP 运算符优先级影响。
flowchart TD
  A["输入 sid"] --> B["读取表常量和分片数"]
  B --> C["计算 shard = sid % shardCount"]
  C --> D["生成物理表名"]
  D --> E["仍然增加 where sid = ?"]
  E --> F["先 count/min/max 校验"]
  F --> G["限制 startId/endId 执行"]

12.1 禁止的写法

$table = SCM_PAYMENT_INFO . '_' . ($sid % SCM_PAYMENT_SUB_NUM);
// sid=0 时既选择 _0 分片,又不加 sid 条件,可能命中该分片全部服务站。
$sid && $query->where('sid', $sid);

正确做法是 P0/P1 入口拒绝 sid <= 0,并无条件增加 where sid = ?。如果确实要全站执行,应由外层逐站枚举,每站独立批次、独立检查点和独立失败状态。

13. DataFix 逐项审计

入口:application/controllers/tasks/DataFix.php

13.1 deletePuOrder

该方法用于处理重复采购入库,代码内写死了历史站点和单号。执行链路是:

sequenceDiagram
  participant CLI as DataFix
  participant Main as 采购入库主表分片
  participant Detail as 采购入库明细分片
  participant Inv as InventorySer
  participant Flow as 库存流水/实时库存
  CLI->>Main: 按 sid + billNo 查主单
  CLI->>Main: isDelete = 1
  CLI->>Detail: isDelete = 1
  CLI->>Inv: delete(orderId, billType)
  Inv->>Flow: 删除/反冲库存影响
  CLI->>CLI: commit 或 rollback
风险具体表现
参数写死复制脚本改值时容易误保留历史条件
业务删除语义复杂软删入库单并调用库存删除,必须确认是否还影响采购数量账和报表
成功日志误导finally 中输出成功文字,即使异常路径也可能执行 finally
只按当前代码回滚外部同步、历史报表或异步消息不会随数据库事务自动恢复
无重复执行记录第二次运行只会看到主单不存在,但无法解释第一次由谁执行

执行前必须形成以下快照:

  • 入库主单和全部明细。
  • 对应采购订单、发货明细、数量扩展表。
  • 同 iid + billType 的库存流水。
  • 对应 sid + invId + storage + locationArea 的实时库存。
  • 相关报表和外部入库状态。

更安全的改造是:删除代码内固定值,要求命令参数;先只读输出完整影响链;使用业务唯一键写执行审计;把日志改成“事务提交成功”或“事务回滚”,不能在 finally 无条件打印成功。

13.2 delDb

该方法按固定条件调用 InventorySer::deleteByWhere() 删除调拨库存影响。它没有从业务主单反查条件,而是直接使用库存层条件,风险在于:

  • 可能只移除库存事实,没有同步调拨业务状态。
  • 条件中的时间精度、单据类型或内部 ID 不唯一时可能扩大范围。
  • 修复后调拨报表、在途数量和对方站点库存可能仍不一致。

最低对账公式:

调出站库存变化 + 调入站库存变化 + 在途差异 = 业务定义允许的差额

不能只验证“目标流水被删除”。

13.3 updatePaymentInfoStlDate

用途:根据 stlNo、billDate 和 transType 推导付款明细的源单日期 stlDate。

flowchart TD
  A["输入 sid、ID 范围、类型和异常筛选"] --> B["计算 payment_info 分片"]
  B --> C["查询一批明细"]
  C --> D{"stlNo 是否为空"}
  D -->|是| E["使用 billDate"]
  D -->|否| F{"transType 和单号格式"}
  F --> G["按 TZD/HKD/YD/销售单等规则截取日期"]
  G --> H["update_batch 写 stlDate"]
  E --> H
  H --> I{"下一批是否仍命中"}
  I -->|是| C
  I -->|否| J["完成"]

已识别风险

风险原因后果
sid=0选中 _0 分片且不增加 sid 条件可能更新零号分片内全部服务站
默认筛选过宽isEmpty=0、isError=0 时不只查异常行正常日期也会被重新推导
无游标推进每批始终按 id asc limit 查询,未使用上一批最大 ID更新后仍符合查询条件时可能重复处理同一批
单号解析脆弱用固定位置 substr 推导日期历史单号格式变化会写入错误日期
缺少日期合法性检查未统一验证年月日和业务时间范围可能产生无效或未来日期
无源单回查主要依赖单号字符串结果可能与真实源单创建日期不一致

推荐修复算法

flowchart TD
  A["按 sid + 异常条件统计"] --> B["固定 endId"]
  B --> C["where id > lastId and id <= endId"]
  C --> D["按 transType 批量回查真实源单日期"]
  D --> E{"源单存在且日期合法"}
  E -->|否| F["写失败清单,不修改"]
  E -->|是| G["条件更新:stlDate 仍是旧异常值"]
  G --> H["记录 before/after"]
  H --> I["推进 lastId"]
  F --> I
  I --> J{"到达 endId"}
  J -->|否| C
  J -->|是| K["按 transType 对账"]

只读预检 SQL 模板

-- 表名必须先根据目标 sid 和分片常量人工确认。
SELECT sid,
       transType,
       COUNT(*) AS row_count,
       MIN(id) AS min_id,
       MAX(id) AS max_id
FROM <payment_info_shard>
WHERE sid = :sid
  AND (stlDate IS NULL OR stlDate = '0000-00-00')
GROUP BY sid, transType;
SELECT id, sid, stlNo, billDate, stlDate, transType
FROM <payment_info_shard>
WHERE sid = :sid
  AND id BETWEEN :start_id AND :end_id
  AND (stlDate IS NULL OR stlDate = '0000-00-00')
ORDER BY id
LIMIT 100;

13.4 realtimeFix / realtimeFixK

两个方法都修复实时库存表的 sku_id,区别是分别筛选 NULL 和空字符串。它们从实时库存的 inv_id 关联 BS_GOODS.id,但商品侧限定快准站点,实时库存侧限定目标服务站。

flowchart LR
  A["目标站实时库存 inv_id"] --> B["关联快准商品 BS_GOODS.id"]
  B --> C["读取商品 skuId"]
  C --> D["按实时库存主键批量更新 sku_id"]
  D --> E["下一批仍为 NULL/空值的数据"]
检查项原因
inv_id 是否确实指向快准基础商品若站点商品 ID 与快准商品 ID 不是同一语义,关联会错误
一个 inv_id 是否只对应一个 SKU避免关联重复导致批量更新值不稳定
NULL 与空字符串是否有不同业务含义两个入口分别处理,不能默认等价
实时表与流水中的 SKU 是否一致只修实时表可能掩盖历史流水问题
修复后搜索/缓存是否需要刷新实时库存可能被下游缓存或索引消费

这两个方法使用“修复后不再命中”的方式推进批次,具有弱幂等性;但没有固定 endId 和总量熔断,新产生的异常行也可能被本次任务继续处理。

13.5 fixZhanQu

该方法扫描 SCM_OPS_ORDER_OPS_LOG 中战区名称为空的记录,汇总 sid 后调用 OPS Center 查询运营省/部门,再批量回填 dept_name 和 dept_id。

sequenceDiagram
  participant Task as DataFix
  participant Log as OPS订单日志
  participant OPS as OPS Center
  Task->>Log: 查询 dept_name 为空的全部记录
  Task->>Task: sid 去重
  Task->>OPS: 批量查询运营省/部门
  OPS-->>Task: stationCode -> departCode/departName
  Task->>Log: update_batch 回填

风险点:没有 sid、时间和最大行数参数;外部未返回时会把空字符串写回;部门关系可能随时间变化,当前部门不一定等于历史订单发生时部门。因此它应明确是“按当前组织关系回填”还是“按历史快照修复”。

14. GeneralTasks 审计

入口:application/controllers/tasks/GeneralTasks.php

方法数据流主要风险
run当前无有效任务调度仍存在时会空跑,需确认是否应下线
migrateQuoteConfig事务包裹报价范围迁移catch 回滚后仍继续 commit 并输出成功,成功语义不可靠
migrateVinReply历史 VIN 回复配置迁移无批次、dry-run 和结果汇总
migrateRobotFriendAI 报价助手迁移到服务人员微信空 sid 数组的范围语义必须确认
initHistoryKeyword取 VIN 分类别名,兼容旧字符串,upsert JSON 同义词外部别名和本地存量批量合并,异常被捕获后仍输出成功文字

14.1 报价范围迁移事务问题

当前结构近似:

$this->db->trans_begin();
try {
    migrate();
} catch (Exception $e) {
    $this->db->trans_rollback();
    echo 'error rollback';
}
$this->db->trans_commit();
echo 'over success';

异常后仍会进入后续 commit 和成功输出。治理要求:catch 后立即返回非零退出码;仅当事务状态有效且 commit 成功时输出成功;记录迁移批次和统计。

flowchart TD
  A["begin"] --> B["执行迁移"]
  B -->|异常| C["rollback"]
  C --> D["记录失败并非零退出"]
  B -->|成功| E{"trans_status 是否正常"}
  E -->|否| C
  E -->|是| F["commit"]
  F --> G["记录成功和行数"]

15. 报价迁移体系

15.1 入口和核心服务

层级文件责任
CLIapplication/controllers/tasks/QuoteMigrate.php启动迁移、重算状态、清理、版本切换和测试
核心application/Services/BaseData/QuoteManagerMigrate.php逐站迁移、映射、日志、检查点、版本切换
一次性配置application/Services/Migrate/MigrateQuoteConfig.php旧报价范围转新范围和明细
机器人好友application/Services/Migrate/MigrateRobotFriend.php历史 AI 助手好友关系迁移
VIN 配置application/Services/Migrate/MigrateVinReply.phpVIN 回复配置迁移

15.2 QuoteMigrate 方法地图

方法用途运行建议
migrateDaemon后台持续拉取待迁移站点作为受监控进程运行,必须有站点级状态
migrate($sid)迁移指定站点或选择范围强制要求单站参数,先检查再迁移
deleteSidMigrateData($sid)删除站点迁移结果P0,仅用于明确回滚,先备份
switchStationVersion切换报价版本P0,迁移验收后执行,需回切方案
recalculateStationRuleStatus重算规则状态P1,明确 debug 是否写入
cleanupEmptyContactGroupPriceRule清理空客户组价格规则P0/P1,删除前导出命中规则
initStationVersion初始化站点报价版本P1,全站影响需分批
clearContactGroupFixPrice清理客户组固定价P0,价格结果会直接变化
test*联调/验证禁止直接假定只读,逐个审计实现

15.3 站点迁移状态机

stateDiagram-v2
  [*] --> Pending
  Pending --> Migrating: 获取站点任务
  Migrating --> CustomerCategoryDone: 客户分类迁移
  CustomerCategoryDone --> ContactDone: 客户关系迁移
  ContactDone --> PriceRuleDone: 加价/固定价规则迁移
  PriceRuleDone --> GoodsRuleDone: 商品关系生成
  GoodsRuleDone --> Verified: 数量与抽样验证
  Verified --> Switched: 切换新版本
  Migrating --> Failed: 任一步骤异常
  CustomerCategoryDone --> Failed
  ContactDone --> Failed
  PriceRuleDone --> Failed
  GoodsRuleDone --> Failed
  Failed --> Migrating: 从检查点续跑
  Switched --> RolledBack: 回切旧版本

15.4 关键数据流

flowchart TD
  A["旧客户分类/联系人"] --> B["迁移客户分类和联系人映射"]
  C["旧报价/加价/固定价规则"] --> D["建立新规则及 ruleIdMap"]
  B --> E["绑定新客户组范围"]
  D --> E
  F["基础商品/近期销售价"] --> G["生成规则商品关系"]
  E --> G
  G --> H["重算规则有效状态"]
  H --> I["检查迁移日志和数量"]
  I --> J["站点版本切换"]

15.5 必须对账的数量

旧数据新数据允许差异
旧客户分类数新客户分类和映射数仅允许明确合并/过滤项
旧联系人归属数新客户组关系数无归属联系人需输出清单
有效旧价格规则数新规则数每个差异必须有原因码
旧规则商品范围新规则商品关系不能静默丢失商品
有固定价客户组新固定价关系清理动作必须单独审批
待迁移站点成功+失败+跳过站点总数守恒

15.6 版本切换门槛

flowchart TD
  A["站点迁移完成"] --> B{"迁移错误数 = 0"}
  B -->|否| X["禁止切换"]
  B -->|是| C{"规则/客户/商品数量对账通过"}
  C -->|否| X
  C -->|是| D{"真实询价抽样一致"}
  D -->|否| X
  D -->|是| E{"缓存和索引已准备"}
  E -->|否| X
  E -->|是| F["小流量切换新版本"]
  F --> G["观察报价结果和错误率"]
  G -->|异常| H["回切旧版本"]
  G -->|正常| I["确认切换"]

16. 活动规则迁移

入口:application/controllers/tasks/ActivityRuleMigrate.php

16.1 方法地图

方法主要动作风险
migrateRule将旧活动列转换为 JSON rule旧字段语义映射错误会影响活动命中
actPlanMigrate($b)分页生成默认计划/模板并更新商品、赠品关系重跑可能重复生成计划和模板
poActivityOrderMigrate迁移采购活动订单关系订单关联错位影响返利或履约
poOrderMigrate迁移采购单活动字段历史单据口径改变
updatePackageDetail更新套包明细数量和金额可能变化
actSidMigrate回填活动站点范围扩大会影响活动可见性
btTemplateRelMigrate迁移白条模板关系影响授信支付范围
checkActPackageAmount检查套包金额应保持只读并输出差异

16.2 actPlanMigrate 数据流

sequenceDiagram
  participant Task as ActivityRuleMigrate
  participant Act as 活动主表
  participant Plan as 活动计划
  participant Template as 活动模板
  participant Goods as 活动商品/赠品
  Task->>Act: offset + limit 查询一批活动
  loop 每个活动
    Task->>Plan: 插入默认计划
    Task->>Template: 插入默认模板
    Task->>Goods: 更新为新计划/模板 ID
  end

原实现使用调用参数控制 offset,并逐活动插入计划和模板。若没有业务唯一键或“已迁移”条件,重复运行同一页可能重复插入。推荐唯一键至少包含 activityId + migrationVersion + planType,并把计划、模板、关系更新放在单活动事务内。

16.3 迁移前后对账

旧活动数 = 成功迁移数 + 明确跳过数 + 失败数
每个活动的旧商品/赠品关系数 = 新计划下关系数 + 有原因的过滤数
旧规则可表达条件 = 新 JSON rule 中等价条件

活动迁移后必须回归:活动列表、详情、编辑、商品命中、赠品计算、采购下单、支付、关闭和报表,不能只验证 JSON 写入成功。

17. ACL 权限迁移

入口:application/controllers/tasks/AclMigrate.php

该文件超过两千行,包含资源、菜单、关系、用户旧 lever、应用、OPS 菜单和退站处理。它不是一个可任意挑方法执行的工具箱,各方法存在顺序依赖。

17.1 逻辑阶段

flowchart TD
  A["旧菜单和权限数据检查"] --> B["资源主数据迁移"]
  B --> C["父子/接口/页面元素关系迁移"]
  C --> D["旧 lever 转用户资源授权"]
  D --> E["应用和应用资源建立"]
  E --> F["OPS 菜单配置迁移"]
  F --> G["关系完整性检查"]
  G --> H["缓存失效和端上验收"]

17.2 阶段与典型方法

阶段方法示例检查重点
基础检查oldMenuCheck、relationCheck旧菜单 ID、孤儿关系、重复路径
资源迁移resourceMigrate、insertMenuList资源唯一键、父链、类型
路径/文档/接口updatePath、addDocument、interfaceMigrate前端路由与后端接口对应
用户授权leverMigrate、leverMigrateBySid用户权限不能扩大或丢失
应用createApplication、createApplicationResource应用与资源关系完整
开通迁移appOpenMigrate、open8服务站开通状态
OPS 菜单opsMenuMigrateJSON 结构、显示状态和版本
退站outStation权限撤销范围与业务恢复

17.3 权限迁移验收

用户类型必测内容
无角色用户不应获得默认业务权限
普通员工旧菜单与新资源可见性一致
管理员管理能力完整,但不能越站
总部/分店账号组织范围和门店切换正确
财务锁定站点锁站限制仍优先于资源授权
APP/PC/PDA各端菜单和接口权限一致

权限迁移必须同时验证“页面看不见”和“接口调不通”。只隐藏菜单不是授权安全。

18. 临时菜单和权限回填

18.1 TmpInitAdminLever

用途:为旧权限字符串中包含出库单权限的用户追加配送权限。

flowchart TD
  A["查询有效且有角色的 sys_admin"] --> B{"lever 是否包含旧出库权限"}
  B -->|否| C["跳过"]
  B -->|是| D{"是否已有配送权限"}
  D -->|是| C
  D -->|否| E["追加、去重并更新 lever"]
已有保护不足
支持可选 sid空 sid 会处理全部站点
已存在权限时跳过无 dry-run 和总量阈值
追加后去重使用 offset 分页,执行期间数据变化可能造成跳过/重复
只更新目标用户没有 before/after 审计和回滚文件

建议改为主键游标分页,并要求显式 sid。回滚不能简单删除权限 ID,因为用户可能在迁移后被正常授权;应仅回滚“本批次追加且当前值未被后续修改”的记录。

18.2 TmpInitTransmit

用途:修改 t_sys_ops_menu_rule.app_rule_json,在满足旧菜单条件的站点中新增或启用配送菜单。

flowchart TD
  A["读取站点 APP 菜单 JSON"] --> B{"JSON 是否有效"}
  B -->|否| C["记录并跳过"]
  B -->|是| D{"存在可见首页 code=1"}
  D -->|否| C
  D -->|是| E{"首页 children 有旧配送能力"}
  E -->|否| C
  E -->|是| F{"顶层已存在 DELIVERY"}
  F -->|是| G["将 display 更新为 1"]
  F -->|否| H["在首页后插入 DELIVERY"]
  G --> I["写回完整 JSON"]
  H --> I

风险:空 sid 查询全表;写回整个 JSON 会覆盖并发配置修改;菜单项 ID、code、sort 是硬编码;没有 JSON schema 校验;没有记录原始 JSON。执行时应先按 sid 导出原 JSON,使用乐观锁(updated_at 或原 JSON hash)条件更新。

19. TmpResetQuoteRange

该入口从 t_scm_quote_range_config 读取每站报价范围和逗号分隔的分类/品牌,再调用 WechatSer::batchQuoteRangeConfig() 重建明细。

sequenceDiagram
  participant Task as TmpResetQuoteRange
  participant Config as 报价范围配置
  participant Wechat as WechatSer
  participant Detail as 报价范围明细
  Task->>Config: 获取 sid 列表
  Note over Task,Config: sid 为空时枚举全部站点
  loop 每个 sid
    Task->>Config: 查询 quote_range_id/category_ids/brand_ids
    Task->>Task: 逗号字符串转数组
    Task->>Wechat: batchQuoteRangeConfig
    Wechat->>Detail: 重建/覆盖明细
  end

执行前必须确认 batchQuoteRangeConfig 的真实语义:是差量 upsert、先删后建,还是全量覆盖。还要确认多个配置的处理顺序是否会互相覆盖。空 sid 的全站模式必须默认禁用。

最低验收:

  • 每个 quote_range_id 的分类、品牌集合前后一致。
  • 明细无重复组合。
  • 空范围语义不被误改成“全部范围”。
  • 真实询价在目标客户、分类、品牌组合下结果正确。

20. 临时渠道业务自动化

入口:application/controllers/tasks/TmpChannelOrder.php

20.1 autoDelivery

该方法只处理预设测试站点、当天、待发货、特定备注的渠道订单;为每行查询仓库和货位,再调用正式 BaseOrder::delivery()。

sequenceDiagram
  participant Task as TmpChannelOrder
  participant Order as CHANNEL_ORDER
  participant Info as CHANNEL_ORDER_INFO
  participant Inv as InventoryService
  participant Delivery as BaseOrder.delivery
  Task->>Order: 查当天待发货且备注命中的订单
  loop 每个订单
    Task->>Info: 查订单明细
    Task->>Inv: 按商品和客户查仓库/货位
    Inv-->>Task: storage/location area
    Task->>Delivery: 提交发货和全部明细
    Delivery-->>Task: 业务结果
  end

20.2 autoRefundConfirm

该方法扫描当天已审核售后,根据申请备注决定调用 refund_pass() 或 refund_reject(),并附带仓库、货位信息。

flowchart TD
  A["查询当天已审核售后"] --> B["申请备注属于自动通过/自动拒绝"]
  B --> C["查询售后明细和仓位"]
  C --> D{"备注类型"}
  D -->|自动通过| E["BaseAftersale.refund_pass"]
  D -->|自动拒绝| F["BaseAftersale.refund_reject"]

20.3 为什么它们是 P0

  • 调用正式业务 Service,不是简单改测试标志。
  • 发货会影响订单状态、库存、履约和下游消息。
  • 售后通过/拒绝会改变退款、退货和库存链路。
  • 配送员、登录上下文和备注值写在代码中。
  • catch 后仍可能输出“成功”日志,日志文字不能作为业务成功依据。
  • 当天扫描可能在定时重复执行时再次命中,是否幂等取决于正式 Service 的状态校验。

这类脚本应绑定明确环境开关,生产环境启动时直接拒绝;长期压测自动化应迁到测试工具或独立测试服务,不应与生产任务代码一同部署。

21. /tmp 文件重放治理

21.1 已发现模式

入口文件模式动作
FixSaHxCode/tmp/saIds.txt按销售单 ID 重算核销状态
ExpressNoSyncNotify/tmp/*.json重放运单/订单通知
OaNotify/tmp/*.json重放 OA/订单中心消息
OrderCenterNotify/tmp/*.json重放订单事件
CostPrice/tmp/*Ids.txt批量重算或更新成本
Import/tmp/*.txt文件驱动批量导入

21.2 主要风险

风险说明
文件来源不明/tmp 中同名文件可能由其他用户或旧任务留下
内容无 schema缺字段、类型错、重复行可能直到业务执行才发现
无摘要无法证明实际执行文件与审批附件相同
无批次失败后不清楚从哪一行续跑
原消息语义丢失重放时可能没有原 messageId、routing key、时间和 headers
敏感数据泄露/tmp 权限和生命周期不稳定

21.3 标准文件包

repair-batch-<batchId>/
  manifest.json       # taskCode、环境、sid、行数、SHA-256、创建人
  input.ndjson        # 一行一个结构化输入
  result.ndjson       # 每行执行结果,不覆盖输入
  errors.ndjson       # 可重试/不可重试错误
  summary.json        # 成功、失败、跳过、耗时和对账结果
flowchart TD
  A["接收文件"] --> B["计算 SHA-256"]
  B --> C["校验 manifest 和环境"]
  C --> D["逐行 schema 校验"]
  D --> E["去重并输出 dry-run 摘要"]
  E --> F["人工确认摘要和 hash"]
  F --> G["按行号执行"]
  G --> H["记录成功/失败/跳过"]
  H --> I["对账并归档"]

21.4 FixSaHxCode 特别说明

该入口读取销售单 ID 列表后调用 SaOrderSer::updateHxStateCode($saId, 0)。代码注释提醒第二个参数很重要,说明参数语义可能改变核销更新行为。执行前必须:

  • 通过销售单反查 sid,拒绝跨环境或不存在 ID。
  • 去重、限制总行数。
  • 记录每个销售单修复前后的核销状态。
  • 明确第二个参数的业务语义。
  • 核对收款、核销明细、销售单和报表,而非只看方法返回值。

22. 多进程和无限循环任务

22.1 QtyAsync

该任务按服务站分组 fork 子进程,重建采购订单数量扩展数据。已识别风险:

  • 依赖 pcntl,只能在支持的 CLI 环境运行。
  • 父进程使用非阻塞等待,未形成完整的子进程收敛和失败汇总。
  • 查询条件中的日期拼接需要确认是否正确加引号和参数化。
  • fixId() 先删除目标订单数量行再重建,未见完整事务保护。
  • 多进程可能同时处理重叠站点或同一订单。
  • 依赖“插入后不再命中”推进,没有显式任务水位。
flowchart TD
  A["读取站点列表"] --> B["按 sidNum 分组"]
  B --> C["fork 子进程"]
  C --> D["每个子进程扫描缺失数量账订单"]
  D --> E["删除/重建订单数量明细"]
  E --> F["继续扫描直到无命中"]
  C --> G["父进程回收子进程"]
  G --> H["汇总退出码和失败站点"]

推荐:任务批次先生成不重叠的站点分片;子进程领取租约;每站单独状态;父进程阻塞回收全部 PID;任何子进程非零退出都使总任务失败;fixId() 使用单订单事务并校验删除数和重建数。

22.2 while(1) / while(true)

无限循环本身不是问题,缺少退出条件和熔断才是问题。每个循环必须具备:

能力要求
数据退出条件本批查询为空或到达固定 endId
总量熔断达到 maxRows 停止
时间熔断达到最大运行时长停止
连续失败熔断连续 N 批失败停止并告警
信号退出响应 SIGTERM,保存检查点
压力控制批间 sleep,限制并发和外部 QPS
新数据隔离启动时固定截止水位,避免追逐持续新增数据

23. 运单补偿任务

入口:application/controllers/tasks/ExpressNoSupplement.php

23.1 责任拆分

方法类别行为外部副作用
空运单扫描跨分片查缺失运单,调用 WMS 查询并更新WMS 查询、本地批量更新
延迟同步从 Redis 延迟结构取待同步数据可能更新订单/运单关系
签收修复扫描签收异常并向 OPS 补发签收OPS 状态推进,可能不可逆
WMS 单号修复回查和修正外部单号本地与外部关系变化
sequenceDiagram
  participant Task as ExpressNoSupplement
  participant Local as 本地出库/运单分表
  participant Redis as 延迟补偿数据
  participant WMS as WMS/调拨中心
  participant OPS as OPS
  Task->>Local: 扫描空运单或异常签收
  Task->>Redis: 读取延迟映射
  Task->>WMS: 查询真实运单/状态
  WMS-->>Task: 运单号和履约状态
  Task->>Local: 条件更新
  Task->>OPS: 必要时补发签收

23.2 对账维度

  • 本地业务单号与外部 WMS 单号一一对应。
  • 箱号、运单号、采购发货明细关系完整。
  • 已签收订单不会因旧事件回退。
  • 重复补发签收在 OPS 侧幂等。
  • Redis 延迟项成功后删除,失败后保留并增加重试信息。
  • 跨分片扫描的总数等于成功、跳过、失败之和。

24. SQL 迁移文件治理

仓库中的 SQL 包括需求 SQL、活动建表、RobotV2 会话表和状态修正。文件存在于代码仓库,不代表它已在所有环境执行。

24.1 每个 SQL 文件需要的元数据

-- migration_id: DGJ2-YYYYMMDD-001
-- requirement: Pxxxx
-- owner: <team-or-role>
-- target_env: preprod,production
-- prerequisite: <previous migration ids>
-- estimated_rows: <count>
-- lock_risk: low/medium/high
-- rollback: <rollback file or explanation>
-- verification: <SQL ids or checklist>

24.2 DDL 执行流程

flowchart TD
  A["解析 SQL"] --> B["确认目标库和现有 DDL"]
  B --> C["检查重复字段/索引/表"]
  C --> D["评估锁表和磁盘"]
  D --> E["预发执行并记录耗时"]
  E --> F["应用代码兼容旧/新结构"]
  F --> G["生产低峰执行"]
  G --> H["校验 information_schema"]
  H --> I["写 migration ledger"]

24.3 RobotV2 SQL 顺序风险

RobotV2 目录同时存在会话表 v1/v2、字段补丁、状态修正、历史数据迁移和删除唯一索引等文件。执行前必须形成唯一顺序,不能按文件名猜测。特别是删除唯一索引会改变并发幂等能力,应在代码已适配且有替代约束后执行。

25. 标准预检 SOP

25.1 第一步:确认业务正确值

写出一句可验证的修复定义:

对于满足 <异常条件> 的 <目标对象>,其 <字段/关系/状态> 应根据 <权威数据源> 修正为 <目标值>;其他对象不变。

若无法写清权威数据源和“不应变化的数据”,先停止脚本设计。

25.2 第二步:建立影响清单

rg -n "class DataFix|function updatePaymentInfoStlDate|function realtimeFix" \
  application/controllers/tasks/DataFix.php

rg -n "class .*Migrate|function migrate|switchStationVersion|deleteSidMigrateData" \
  application/controllers/tasks application/Services/Migrate application/Services/BaseData

rg -n "->delete\(|deleteByWhere|update_batch|trans_begin|send.*Mq|Provider::make" \
  application/controllers/tasks application/Services/Migrate

25.3 第三步:检查环境指纹

至少确认:

  • 应用环境名。
  • 数据库 host、库名和只显示后几位的实例指纹。
  • Redis namespace/DB。
  • MQ vhost 和目标 exchange(不记录密码)。
  • 外部 Provider 指向测试还是生产。
  • 当前 Git commit 和发布版本。

25.4 第四步:只读预演

预演输出必须包含:

字段示例语义
batchId本次执行唯一编号
environment环境
gitCommit代码版本
taskCode任务稳定标识
sid目标站点
startId/endId固定范围
matched命中数量
willUpdate将更新数量
willSkip已正确或不符合条件数量
externalActions预计 MQ/HTTP/Redis 数量
sampleBeforeAfter脱敏抽样差异

26. 灰度执行 SOP

flowchart TD
  A["dry-run 结果审批"] --> B["备份灰度对象"]
  B --> C["执行单条"]
  C --> D["业务页面/接口验收"]
  D --> E["数据库多层对账"]
  E --> F["外部系统和报表验收"]
  F --> G{"全部通过"}
  G -->|否| H["停止并回滚灰度"]
  G -->|是| I["执行单站小批量"]
  I --> J["观察错误率、耗时、锁等待"]
  J --> K{"仍通过"}
  K -->|否| H
  K -->|是| L["按批次扩大"]

26.1 运行中监控

指标停止条件示例
批次失败率高于审批阈值
数据库锁等待明显影响在线请求
主从延迟超过业务允许值
外部接口错误率连续多批失败
MQ 堆积发布或消费异常增长
对账差异出现未解释差异
实际影响数超出 dry-run 或 maxRows

27. 检查点和续跑

27.1 推荐状态模型

stateDiagram-v2
  [*] --> Pending
  Pending --> Running
  Running --> Succeeded
  Running --> RetryableFailed
  Running --> FatalFailed
  RetryableFailed --> Running: 继续同一 batch
  FatalFailed --> Cancelled: 人工终止
  FatalFailed --> Running: 修复脚本后从检查点恢复
  Succeeded --> Verified
  Verified --> [*]

27.2 执行审计表建议

CREATE TABLE task_execution_batch (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  batch_id VARCHAR(64) NOT NULL,
  task_code VARCHAR(128) NOT NULL,
  task_version VARCHAR(32) NOT NULL,
  environment VARCHAR(32) NOT NULL,
  ticket_no VARCHAR(64) NOT NULL,
  operator_id VARCHAR(64) NOT NULL,
  scope_json TEXT NOT NULL,
  input_sha256 VARCHAR(64) NOT NULL DEFAULT '',
  status VARCHAR(32) NOT NULL,
  last_cursor VARCHAR(128) NOT NULL DEFAULT '',
  matched_count INT NOT NULL DEFAULT 0,
  success_count INT NOT NULL DEFAULT 0,
  failed_count INT NOT NULL DEFAULT 0,
  skipped_count INT NOT NULL DEFAULT 0,
  started_at DATETIME NULL,
  finished_at DATETIME NULL,
  created_at DATETIME NOT NULL,
  UNIQUE KEY uk_batch_id (batch_id),
  KEY idx_task_status (task_code, status)
);

生产建表需按公司规范评审,此处用于表达字段语义,不是可直接执行的发布 SQL。

27.3 明细审计表建议

字段说明
batch_id所属批次
item_key表主键、单号或消息 ID
idempotency_key唯一执行键
before_digest修复前数据摘要
after_digest修复后数据摘要
statussuccess/failed/skipped
error_code稳定错误码
error_message脱敏错误摘要
retry_count已重试次数
external_action_id外部动作关联 ID

28. 回滚设计

28.1 四种回滚方式

类型适用场景注意事项
数据快照恢复字段批量更新、JSON 配置使用主键和原值条件更新,防止覆盖后续正常修改
反向业务动作库存、资金、订单必须走正式反向单据,不直接删流水
版本回切报价、搜索、功能开关确认新版本期间新增数据如何兼容
补偿消息/请求外部系统已成功需要外部提供撤销或幂等补偿能力

28.2 为什么不能简单“再执行一条 UPDATE”

sequenceDiagram
  participant Repair as 修复批次
  participant DB as 业务数据
  participant User as 正常业务
  Repair->>DB: old -> fixed
  User->>DB: fixed -> newer
  Repair->>DB: 回滚 fixed -> old
  Note over Repair,User: 简单回滚覆盖了修复后的正常业务修改

安全回滚应使用 compare-and-set:只有当前值仍等于本批次写入值、版本号未变化时才恢复原值;否则进入人工冲突清单。

29. 修复结果验收

29.1 通用对账公式

预演命中数 = 成功数 + 失败数 + 跳过数
实际更新数 <= 审批 maxRows
目标异常数(修复后) = 0 或等于明确保留数
非目标数据摘要(修复前) = 非目标数据摘要(修复后)
外部动作数 = 外部成功数 + 可重试失败数 + 永久失败数

29.2 验收层次

层次问题
数据库字段、关系、数量和状态是否正确
领域守恒库存、资金、订单数量是否守恒
接口真实查询接口是否返回正确结果
页面用户是否看到正确状态和操作能力
缓存索引Redis、Mongo、ES 是否与数据库一致
消息外部MQ 和外部系统是否一致、无重复
报表业务报表和数仓是否在 SLA 后一致
审计能否回答谁、何时、为何、改了什么

30. 日志规范

30.1 推荐结构化字段

{
  "taskCode": "payment-stl-date-repair",
  "taskVersion": "v2",
  "batchId": "<batch-id>",
  "environment": "preprod",
  "sid": "<station-id>",
  "cursor": "<last-id>",
  "matched": 100,
  "success": 98,
  "failed": 1,
  "skipped": 1,
  "elapsedMs": 1234,
  "dryRun": true
}

禁止记录:密码、token、银行卡、完整手机号、客户隐私、完整支付报文。单号和用户标识按日志规范脱敏。

30.2 成功语义

日志文字必须代表
batch_started参数和环境已校验,已拿到任务锁
item_succeeded单条本地动作已提交;外部动作若未完成要单独标记
batch_local_completed本地数据处理完,不代表外部一致
reconcile_passed所有指定对账通过
batch_accepted业务 Owner 验收完成
rolled_back回滚与回滚后对账均完成

不能用 catch 后继续打印 success,也不能把“循环结束”称为“业务成功”。

31. 新修复脚本模板

final class ExampleRepair extends CI_Controller
{
    public function run(
        string $environment = '',
        string $ticket = '',
        int $sid = 0,
        int $startId = 0,
        int $endId = 0,
        int $limit = 100,
        int $maxRows = 1000,
        int $dryRun = 1
    ): void {
        // 1. 强校验环境、工单、sid、范围和阈值。
        // 2. 创建批次并获取 taskCode + sid 互斥锁。
        // 3. 固定 endId,按 id 游标查询。
        // 4. dry-run 只输出脱敏 before/after,不执行任何副作用。
        // 5. 正式执行使用条件更新和业务幂等键。
        // 6. 每批提交后记录 lastId;失败保留同一 batch 续跑。
        // 7. 外部动作写 outbox,不能夹在本地事务中直接调用。
        // 8. 达到 maxRows、超时、连续失败或收到 SIGTERM 时安全停止。
    }
}

模板重点不是代码形式,而是八项能力必须存在。

32. 命令运行模板

以下命令仅表示参数顺序,禁止直接复制到生产:

# 第一次:只读预演
php index.php tasks/ExampleRepair/run \
  preprod INC-xxxx <sid> <startId> <endId> 100 1000 1

# 第二次:审批后执行同一固定范围
php index.php tasks/ExampleRepair/run \
  preprod INC-xxxx <sid> <startId> <endId> 100 1000 0

运行前先保存:

git rev-parse HEAD
php -v
date '+%F %T %z'

不要把数据库密码、服务 token 或完整连接串写入 shell history 和知识库。

33. 常用代码检索

33.1 找所有任务入口

find application/controllers/tasks -maxdepth 1 -type f -name '*.php' | sort

rg -n '^\s*public function' application/controllers/tasks

33.2 找写操作和外部副作用

rg -n 'insert|update|delete|update_batch|insert_batch|trans_begin' \
  application/controllers/tasks application/Services/Migrate

rg -n 'MqSer|send.*Mq|publish|Provider::make|->request|KzCache|Redis' \
  application/controllers/tasks application/Services/Migrate

33.3 找危险范围

rg -n 'sid\s*=\s*["'"'][0-9]+|billNo\s*=|where_in|limit\(|offset\(' \
  application/controllers/tasks

rg -n '/tmp/|file_get_contents|fopen|json_decode' application/controllers/tasks

rg -n 'while\s*\(\s*1\s*\)|while\s*\(\s*true\s*\)|pcntl_' \
  application/controllers/tasks

33.4 找无 CLI 守卫入口

for file in application/controllers/tasks/*.php; do
  rg -q 'is_cli\(' "$file" || echo "$file"
done

检索结果只是审计线索,注释代码和间接调用都需要人工确认。

34. 常用只读 SQL 模板

34.1 固定批次边界

SELECT COUNT(*) AS matched,
       MIN(id) AS min_id,
       MAX(id) AS max_id
FROM <target_table>
WHERE sid = :sid
  AND <exact_abnormal_condition>;

34.2 检查重复关系

SELECT <business_key_columns>, COUNT(*) AS duplicate_count
FROM <relation_table>
WHERE sid = :sid
GROUP BY <business_key_columns>
HAVING COUNT(*) > 1;

34.3 修复前后分布

SELECT <status_or_type>, COUNT(*) AS row_count
FROM <target_table>
WHERE sid = :sid
  AND id BETWEEN :start_id AND :end_id
GROUP BY <status_or_type>;

34.4 孤儿关系

SELECT r.id, r.<foreign_key>
FROM <relation_table> r
LEFT JOIN <main_table> m ON m.id = r.<foreign_key>
WHERE r.sid = :sid
  AND m.id IS NULL
LIMIT 100;

所有表名、分片和字段必须根据当前环境 DDL 核对后再使用。

35. 故障树

flowchart TD
  A["脚本结果异常"] --> B{"命中范围是否正确"}
  B -->|否| B1["sid 空值/分片错误/条件过宽/游标未推进"]
  B -->|是| C{"本地写入是否完整"}
  C -->|否| C1["事务回滚/部分批失败/条件更新未命中"]
  C -->|是| D{"缓存索引是否一致"}
  D -->|否| D1["缓存未失效/索引任务失败/水位未推进"]
  D -->|是| E{"外部系统是否一致"}
  E -->|否| E1["事务外调用失败/MQ 丢失/重复/乱序"]
  E -->|是| F{"报表是否一致"}
  F -->|否| F1["ETL 延迟/历史快照未重算/报表口径不同"]
  F -->|是| G["检查业务口径本身是否定义错误"]

36. 常见异常与处理

现象优先检查处理原则
一直处理同一批查询条件是否在更新后仍命中、是否缺 lastId停止任务,增加游标后从审计水位续跑
影响行数远超预估sid=0、分片、空条件、全站默认值立即熔断,保存现场,按批次回滚
日志显示成功但数据没变catch/finally 成功文字、affected rows 未校验以 DB 和业务对账为准,修正日志语义
本地成功外部失败事务内/后直接调用 HTTP 或 MQ建 outbox,单独补偿外部动作
重跑生成重复关系无唯一键、先插入再判断去重前先确认业务真重复,补唯一约束和幂等键
回滚覆盖正常修改使用无条件 UPDATE 恢复compare-and-set,冲突转人工
任务退出但子进程仍跑pcntl 回收不完整记录 PID,发送停止信号并等待安全检查点
报表仍错误数仓/快照未刷新或口径不同先确认 SLA,再触发受控重算

37. 禁止事项

  1. 禁止在 P0/P1 脚本中把空 sid 当作默认全站。
  2. 禁止把真实生产站点、单号、客户隐私直接写死并长期提交在脚本中。
  3. 禁止无 dry-run、无 maxRows、无固定截止水位运行全表循环。
  4. 禁止仅凭控制台“success”判断完成。
  5. 禁止直接删除库存或资金流水来“对平”实时结果。
  6. 禁止在数据库事务中假定 HTTP、MQ、Redis 会一起回滚。
  7. 禁止重复执行没有唯一键或幂等记录的一次性迁移。
  8. 禁止从未校验摘要和 schema 的 /tmp 文件执行生产动作。
  9. 禁止同时运行范围重叠的修复批次。
  10. 禁止修完业务库后忽略缓存、外部系统和报表。
  11. 禁止把测试自动发货、自动售后入口开放到生产环境。
  12. 禁止未经确认按当前组织关系覆盖历史快照字段。

38. 发布和回归清单

38.1 脚本代码回归

  • [ ] CLI 守卫存在且生产环境有额外限制。
  • [ ] sid、环境、工单、ID 范围是必填。
  • [ ] dry-run 不产生任何 DB/Redis/MQ/HTTP 写操作。
  • [ ] 固定 endId 或截止时间。
  • [ ] 主键游标能够单调推进。
  • [ ] limit 和 maxRows 含义不同且都生效。
  • [ ] 每批有事务,事务范围不包含不可控长耗时外部调用。
  • [ ] 外部动作有幂等键和补偿记录。
  • [ ] 退出码准确,异常后不会打印成功。
  • [ ] 日志脱敏并带 batchId/taskCode/sid/cursor。
  • [ ] SIGTERM 能保存检查点并停止。
  • [ ] 一次性脚本完成后有禁用或归档机制。

38.2 业务回归

  • [ ] 主单、明细和状态日志一致。
  • [ ] 数量、金额、库存或资金满足守恒公式。
  • [ ] 分片表选择和 sid 条件正确。
  • [ ] Redis/Mongo/ES 与数据库一致。
  • [ ] MQ 无重复、丢失和乱序回退。
  • [ ] 外部系统状态与本地一致。
  • [ ] PC、APP、PDA 或开放接口结果正确。
  • [ ] 报表在约定 SLA 后正确。
  • [ ] 非目标数据抽样和摘要未变化。
  • [ ] 回滚脚本已在测试环境演练。

38.3 运行验收

  • [ ] 实际命中数不超过审批范围。
  • [ ] 成功、失败、跳过之和等于命中数。
  • [ ] 每个失败项有稳定错误码和后续动作。
  • [ ] 业务 Owner 已验收灰度和全量结果。
  • [ ] 执行命令、Git commit、时间、操作者和结果已归档。

39. 已识别的具体高风险点

编号位置风险建议优先级
R40-01DataFix::deletePuOrder固定历史条件、删除入库和库存影响、成功日志可能误导P0
R40-02DataFix::delDb绕过调拨主流程直接按库存条件删除P0
R40-03updatePaymentInfoStlDatesid=0 可能扩大到零号分片全部数据P0
R40-04同上默认筛选过宽且无游标推进,可能反复更新P0
R40-05GeneralTasks::migrateQuoteConfigrollback 后仍走 commit/成功输出P0
R40-06GeneralTasks::initHistoryKeyword异常后仍输出成功,外部别名与本地批量 upsertP1
R40-07ActivityRuleMigrate::actPlanMigrateoffset 手工分页,重跑可能重复计划和模板P0
R40-08TmpResetQuoteRange::init空 sid 默认全站并调用重建服务P0
R40-09TmpInitAdminLever空 sid 全站、offset 分页、无原值审计P1
R40-10TmpInitTransmit全 JSON 覆盖、无乐观锁、空 sid 全站P1
R40-11TmpChannelOrder临时入口执行正式发货和售后业务动作P0
R40-12FixSaHxCode/tmp 输入无摘要/批次,参数语义敏感P0
R40-13QtyAsync多进程回收、SQL 参数、删后重建事务风险P0
R40-14ExpressNoSupplement跨分片循环并调用 WMS/OPS,部分成功难追踪P0
R40-15多个 *Notify 重放入口原消息 ID、headers、路由和幂等语义可能丢失P0
R40-16RobotV2 SQL多版本文件无唯一执行顺序,含索引删除P0

40. 建议治理改造路线

40.1 第一阶段:立刻止险

  1. 给 P0/P1 入口增加显式环境和 sid 强校验。
  2. 默认关闭全站模式和生产临时业务自动化。
  3. 修正 catch/finally 后错误的成功日志。
  4. 为无限循环增加 endId、maxRows、超时和失败熔断。
  5. 为 /tmp 重放增加文件摘要、schema 和批次记录。

40.2 第二阶段:统一执行框架

  1. 建立批次和明细审计表。
  2. 提供统一参数解析、任务锁、游标、dry-run 和结构化日志组件。
  3. 外部副作用统一进入 outbox。
  4. 提供可视化任务状态、失败项下载和受控续跑。
  5. 一次性迁移绑定版本 ledger。

40.3 第三阶段:目录和生命周期治理

建议按责任拆分:

application/controllers/tasks/consumers/      # 长期 MQ 消费者
application/controllers/tasks/schedules/      # 周期任务
application/controllers/tasks/compensations/  # 长期补偿
application/controllers/tasks/migrations/     # 有版本的一次性迁移
application/controllers/tasks/repairs/        # 绑定工单的数据修复
application/controllers/tasks/diagnostics/    # 严格只读
tools/test-automation/                         # 非生产测试自动化

旧 CodeIgniter 路由若不适合立即移动文件,可先通过任务注册表完成逻辑分类,再逐步迁移。

41. 任务注册表建议

字段说明
taskCode稳定唯一标识
class/method当前代码入口
categoryconsumer/schedule/compensation/migration/repair/replay/automation/diagnostic
riskLevelP0-P3
owner业务与技术 Owner
allowedEnvironments允许环境
allowAllSid是否允许全站,默认 false
idempotency幂等键和策略
checkpoint水位字段
externalEffectsDB/Redis/MQ/HTTP/文件
rollbackRunbook回滚文档链接
statusactive/deprecated/archived
expiresAt临时任务到期时间

有了注册表,运维不再靠文件名猜测任务性质,临时任务也可以到期自动告警。

42. 责任边界

角色责任
业务 Owner定义正确口径、影响范围和验收标准
开发审计调用链、实现幂等/检查点/回滚、提供预演结果
DBA审核 SQL、索引、锁表、分片、备份和数据库负载
运维/SRE环境确认、执行权限、进程、监控和审计留痕
测试验证目标链路与非目标回归
外部系统 Owner确认幂等键、重试、撤销和最终状态

P0 修复不能由单一角色从口径定义到生产执行全程独立完成。

43. 与其他业务文档的关联

修复类型配套阅读
采购入库、采购数量账14_采购完整排查手册.md、25_高风险公共文件影响面.md
销售、核销、退货15_销售完整排查手册.md、29_售后申请与销售退货申请.md
库存流水和实时库存16_库存一致性排查手册.md
秒杀活动迁移和补偿17_秒杀活动专项手册.md
支付、付款日期、账户18_财务和支付排查手册.md
ACL、菜单、用户权限19_权限菜单和白名单.md
搜索、缓存、索引重建21_搜索缓存与索引.md
报表修复和数仓延迟22_报表口径专题.md
外部系统、MQ、HTTP23_外部系统对接清单.md
本地运行与日志24_本地开发联调和日志定位.md
报价迁移41_报价_智能询价与报价规则.md

44. 代码证据索引

主题代码路径
定点修复application/controllers/tasks/DataFix.php
通用迁移入口application/controllers/tasks/GeneralTasks.php
报价迁移入口application/controllers/tasks/QuoteMigrate.php
报价迁移核心application/Services/BaseData/QuoteManagerMigrate.php
报价范围迁移application/Services/Migrate/MigrateQuoteConfig.php
活动规则迁移application/controllers/tasks/ActivityRuleMigrate.php
权限迁移application/controllers/tasks/AclMigrate.php
渠道临时自动化application/controllers/tasks/TmpChannelOrder.php
用户旧权限回填application/controllers/tasks/TmpInitAdminLever.php
APP 菜单 JSON 回填application/controllers/tasks/TmpInitTransmit.php
报价范围重建application/controllers/tasks/TmpResetQuoteRange.php
销售核销文件修复application/controllers/tasks/FixSaHxCode.php
采购数量多进程重建application/controllers/tasks/QtyAsync.php
运单补偿application/controllers/tasks/ExpressNoSupplement.php
需求 SQLdocument/*.sql
RobotV2 迁移 SQLapplication/Services/MoveMall/RobotV2/database/*.sql

45. 待环境确认项

下列内容无法仅凭仓库代码最终确认,生产执行前必须补齐:

  1. 101 个 task 控制器当前实际由哪些 cron、Supervisor、容器或发布平台调度。
  2. 哪些一次性迁移已经在开发、预发、生产分别执行,以及执行批次和结果。
  3. 生产表的真实唯一索引、分片数量和历史 DDL 差异。
  4. MQ 的 publisher confirm、消费者重试、死信、保序和幂等配置。
  5. 外部 Provider 在各环境的目标地址、幂等能力和撤销接口。
  6. 报价迁移状态表、检查点和版本切换的生产真实数据。
  7. 活动计划/模板是否已有业务唯一键,重复迁移的实际行为。
  8. ACL 迁移各阶段是否已完成,以及新旧权限流量比例。
  9. /tmp 重放脚本是否仍由线上 SOP 使用。
  10. 临时自动发货和自动售后入口是否仍在任何调度中。
  11. RobotV2 SQL 的正式执行顺序和 migration ledger。
  12. 数据修复审计记录目前存放位置和保留期限。

46. 最终判断标准

一个数据修复或迁移任务只有同时满足以下条件,才能称为“完成”:

flowchart LR
  A["范围准确"] --> H["完成"]
  B["执行幂等"] --> H
  C["失败可续跑"] --> H
  D["结果可对账"] --> H
  E["外部一致"] --> H
  F["能够安全回滚"] --> H
  G["全程可审计"] --> H

“脚本跑完了”“控制台没有报错”“数据库影响了预期行数”都只是过程信号,不是完成标准。真正的完成是:目标业务恢复正确、非目标数据没有被改变、所有下游最终一致,并且后续任何人都能根据批次记录复现这次决策和验证过程。

请求-日志-数据变更追踪卡

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
一次性修复CLI tasks/DataFix/GeneralTasksdry-run、环境、ID 白名单、批次、limitTask Controller领域/修复 Servicerepair batch + business ID只修目标异常记录
结构/数据迁移Acl/Activity/Quote/Robot migrationversion、游标、批大小migration taskServices/Migrate/*migration version + source ID旧数据转换为新模型
临时工具controllers/temp/tools明确白名单和动作参数Temp/Tools Controller领域 Servicerequest/batch ID受控执行诊断或补偿
后置同步修复任务触发 MQ/cache/index业务 ID、routing key、版本Task/ServiceMqSer/Cache/Search原业务键派生层追平修复后的主事实

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | dry-run | Task 输出/审计文件 | task、环境、batch、筛选条件、候选数 | 候选 ID 可人工复核且零写入 | 无环境锁/范围过宽/无基线 | candidate ID 进入执行清单 | | 执行 | Task/Service 日志 | batch、业务 ID、before/after 摘要、影响行数 | 每条 success/skip/fail 可对账 | 中断、部分 commit、无幂等键 | business ID 回查业务表 | | 迁移 | Migrate Service | version、cursor、source/target ID | source/target 数量和映射闭合 | 重复 insert、漏页、转换错误 | source ID 映射 target ID | | 后置同步 | MQ/Cache/Search 日志 | batch、业务键、message ID/doc key | 下游追平且重跑 0 变化 | 主库正确但缓存/MQ/报表旧 | 同一业务键跨层对照 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
建基线/dry-runTask 查询方法只读精确 WHERE、当前主明细/流水审计输出,不写业务表候选集冻结;业务数据 0 变化SQL/条件 hash、ID 清单、快照
小批执行DataFix/Migrate Service每条/每批事务before、幂等标志、目标值目标业务/映射/修复日志old -> expected;不匹配前置条件则 skipbatch+ID+before/after+影响行数
失败恢复checkpoint/错误清单独立事务成功/失败/未处理集合checkpoint/任务状态cursor/retry old -> new,已成功不重做三集合计=候选总数
后置派生MqSer/Cache/EScommit 后异步修复后业务事实MQ/cache/index/report只同步受影响业务键ACK、缓存/索引/报表抽样
验收回滚领域反向 Service/验证脚本受控事务基线、目标/非目标样本必要反向单而非裸 SQL目标恢复,非目标 diff=0,重跑 diff=0业务守恒、批次审计、负责人/时间

子模块追踪:datafix DataFix 定点修复

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
dry-run指定 BUG/业务键生成候选issue/batch、exact IDs/WHEREapplication/controllers/tasks/DataFix.php主明细/流水/状态、候选范围和预期值查询只读 不写;冻结 ID 清单与条件 hashissue + batch + candidate IDs/count + snapshotWHERE 不精确/数量超预期立即停止,不扩大范围
小批修复审批后按候选执行batch、business ID、before hashapplication/controllers/tasks/DataFix.php当前值仍等于 baseline、幂等标记每条/批本地事务 old -> expected,前置不符 skipbatch + ID + before/after + rows失败保留清单;重跑成功项 0 变化,生产写需授权

子模块追踪:general-task GeneralTasks 通用任务

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
任务选择CLI 调 GeneralTasks 指定方法task method、args、scope/limitapplication/controllers/tasks/GeneralTasks.php方法白名单、环境、参数和候选数调度阶段不写;目标方法按自己的本地事务执行task + args hash + scope + scanned禁止无参默认全量;先读代码确认是否可重入
执行验收通用任务小批运行batch、IDs、checkpointapplication/controllers/tasks/GeneralTasks.phpbefore、已处理标记和失败集合每项 old -> expected 或 skip;checkpoint old -> newbatch + success/fail/skip counts不同方法不可共用幂等假设;按方法回归副作用

子模块追踪:quote-migrate 报价规则迁移

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
映射预演旧报价配置迁新结构batch、old rule/config IDapplication/controllers/tasks/QuoteMigrate.php -> application/Services/Migrate/MigrateQuoteConfig.php旧规则、目标唯一键、字段/范围映射和冲突dry-run 查询只读;输出 old->new 映射batch + old/new IDs + conflict count未知规则/多义映射进入人工确认,不默认迁移
分批迁移确认映射后写目标batch、config IDsapplication/Services/BaseData/QuoteManagerMigrate.phpbaseline、目标是否存在和版本每配置本地事务 upsert old rule -> new rule,写迁移标记batch + config + before/after + rows成功项重跑 0 新增;回归报价查询与人工报价

子模块追踪:activity-migrate 活动规则迁移

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
候选分析旧活动规则转新模型batch、activity/rule IDapplication/controllers/tasks/ActivityRuleMigrate.php活动态、规则、商品范围、订单引用和目标冲突dry-run 只读,冻结候选与映射差异batch + activity/rule + candidate/diff有进行中订单的高风险规则单列,不自动改
执行验证小批迁移规则/关系batch、activity IDsapplication/controllers/tasks/ActivityRuleMigrate.phpbefore、目标唯一键和已迁移标记每活动本地事务 old config -> new configbatch + activity + rows + before/after非目标活动 diff=0;搜索、下单、退款均回归

子模块追踪:acl-migrate ACL 与菜单迁移

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
权限映射旧菜单/角色转新 ACLbatch、menu/resource/user IDsapplication/controllers/tasks/AclMigrate.php旧关系、目标资源、父链和重复键dry-run 只读;迁移本地事务 upsert old grants -> new grantsbatch + resource/user + counts/diffs未映射资源停止;成功重跑 0 新授权
登录验收最小/管理员/总部账号验证batch、sample users、resource codesapplication/controllers/tasks/TmpInitAdminLever.php新旧权限、缓存和运行时鉴权查询只读;必要时事务外失效 rights cachebatch + sample + allow/menu diff不用管理员全权掩盖子账号缺权限;保留回滚映射

子模块追踪:channel-temp 临时渠道业务自动化

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
候选处理临时渠道订单批任务batch、channel/source order IDs、scopeapplication/controllers/tasks/TmpChannelOrder.php渠道订单态、来源关系、库存/资金副作用和已处理标记每单本地事务 old -> target,稳定来源键防重复batch + channel/source IDs + rows必须固定渠道/时间/状态;不复用生产全量扫描
下游验收处理后同步渠道/MQchannel/local billNo、eventapplication/controllers/tasks/TmpInitTransmit.php本地已提交事实和发送记录commit 后事务外发消息;核心 DB 不重做batch + billNos + routing/result发送失败只补消息;任务完成后应下线/归档入口

子模块追踪:file-replay 临时文件重放

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
文件解析读取历史失败键/业务清单file hash、rowNo、business keyapplication/controllers/tasks/GeneralTasks.php文件来源、表头、去重、范围和当前 DB 状态解析/dry-run 不写;生成去重候选集合file hash + row/candidate/error counts不接受不明来源文件;敏感数据脱敏并限制路径
幂等重放逐行调用原领域方法batch、row/business IDapplication/controllers/tasks/DataFix.php当前状态、已有副作用和前置条件每行本地事务只补缺失 old -> expectedbatch + row + before/after + result已完成 skip;失败文件与成功文件分开,防二次全量

子模块追踪:multiprocess-task 多进程与循环任务

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
分片调度多进程/循环处理大候选task、worker、shard/cursor、limitapplication/controllers/tasks/QtyAsync.php总候选、分片算法、锁、水位和 worker 状态调度不写业务;worker 每条本地事务 old -> newtask + worker/shard + cursor + counts分片不得重叠/遗漏;worker 异常保留其 cursor
汇总恢复子进程完成/失败task、worker checkpointsapplication/controllers/tasks/GeneralTasks.phpsuccess/fail/unprocessed 三集合checkpoint/任务态本地事务 running -> partial/successtask + worker + three counts三集合计=总候选;只重启失败分片,成功不重做

子模块追踪:express-fix 运单补偿

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
差异扫描外部有运单、本地字段缺失batch、invoice/delivery IDsapplication/controllers/tasks/ExpressNoSupplement.php本地 null/旧值、外部运单/箱号/ETA 和关系dry-run 查询只读;输出可补与冲突列表batch + invoice + external ID + diff外部多单/冲突不自动覆盖,进入人工核对
小批补值确认后补本地关系字段batch、invoice、before hashapplication/controllers/tasks/ExpressNoSupplement.php当前仍为空、外部最终值和幂等键每单本地事务 null -> external value,已有值不覆盖batch + invoice + before/after + rows重跑 0 变化;不重做配送/出库主流程

子模块追踪:sql-migrate SQL 与 RobotV2 迁移文件

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
SQL 审核DDL/DML/Robot 迁移执行前change ID、file checksum、environmentapplication/Services/Migrate/MigrateRobotFriend.php、application/Services/Migrate/MigrateVinReply.phpschema/数据前置、影响行估算、索引和回滚 SQL预检只读 不写;明确事务能力和锁风险change + checksum + explain/count禁止无 WHERE DML;大表 DDL 需发布窗口和备份
执行验收小批/正式迁移change/batch、checkpointapplication/Services/Migrate/MigrateRobotFriend.phpbefore snapshot、已迁移标记和目标唯一键每批本地事务 old schema/data -> expected,记录版本change + batch + before/after + rows失败按 checkpoint 恢复;回归 RobotV2 会话/查询并保证重跑 0 差异