本文不是“脚本文件清单”,而是一份可执行的治理手册。它回答五个问题:这个入口到底是什么任务、会改哪些数据、能否重复执行、失败后如何续跑、怎样证明修复结果正确。
1. 文档目标
DGJ2.0 的 application/controllers/tasks 目录同时承载消息消费者、周期任务、数据补偿、历史迁移和临时修复。它们的运行方式相似,但业务语义完全不同。将所有入口都理解成“定时任务”会产生很大风险。
本文用于:
- 给现有任务建立统一分类和风险等级。
- 解释典型修复、迁移脚本的数据流和副作用。
- 固化生产执行前、执行中、执行后的操作流程。
- 给新脚本提供参数、幂等、日志、回滚和归档标准。
- 帮助排查重复执行、部分成功、跨系统不一致和误跑全站等问题。
本文不替代具体业务手册。涉及库存、采购、支付、权限、报价等业务口径时,应同时阅读对应专题文档。
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 | 需求或版本 SQL | DDL、初始化、修正脚本,需绑定版本和执行记录 |
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/外部系统 |
| Canary | 1 条或 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 取模分片。修复脚本必须先明确:
- 分片数量常量是什么。
- 表名计算使用哪一个字段。
- 是否还需要在分片表内增加
sid条件。 sid=0是真实站点、零号分片,还是“全部”的特殊语义。- 表名表达式是否受 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 和结果汇总 |
migrateRobotFriend | AI 报价助手迁移到服务人员微信 | 空 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 入口和核心服务
| 层级 | 文件 | 责任 |
|---|---|---|
| CLI | application/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.php | VIN 回复配置迁移 |
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 菜单 | opsMenuMigrate | JSON 结构、显示状态和版本 |
| 退站 | 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 | 修复后数据摘要 |
status | success/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. 禁止事项
- 禁止在 P0/P1 脚本中把空
sid当作默认全站。 - 禁止把真实生产站点、单号、客户隐私直接写死并长期提交在脚本中。
- 禁止无 dry-run、无
maxRows、无固定截止水位运行全表循环。 - 禁止仅凭控制台“success”判断完成。
- 禁止直接删除库存或资金流水来“对平”实时结果。
- 禁止在数据库事务中假定 HTTP、MQ、Redis 会一起回滚。
- 禁止重复执行没有唯一键或幂等记录的一次性迁移。
- 禁止从未校验摘要和 schema 的
/tmp文件执行生产动作。 - 禁止同时运行范围重叠的修复批次。
- 禁止修完业务库后忽略缓存、外部系统和报表。
- 禁止把测试自动发货、自动售后入口开放到生产环境。
- 禁止未经确认按当前组织关系覆盖历史快照字段。
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-01 | DataFix::deletePuOrder | 固定历史条件、删除入库和库存影响、成功日志可能误导 | P0 |
| R40-02 | DataFix::delDb | 绕过调拨主流程直接按库存条件删除 | P0 |
| R40-03 | updatePaymentInfoStlDate | sid=0 可能扩大到零号分片全部数据 | P0 |
| R40-04 | 同上 | 默认筛选过宽且无游标推进,可能反复更新 | P0 |
| R40-05 | GeneralTasks::migrateQuoteConfig | rollback 后仍走 commit/成功输出 | P0 |
| R40-06 | GeneralTasks::initHistoryKeyword | 异常后仍输出成功,外部别名与本地批量 upsert | P1 |
| R40-07 | ActivityRuleMigrate::actPlanMigrate | offset 手工分页,重跑可能重复计划和模板 | P0 |
| R40-08 | TmpResetQuoteRange::init | 空 sid 默认全站并调用重建服务 | P0 |
| R40-09 | TmpInitAdminLever | 空 sid 全站、offset 分页、无原值审计 | P1 |
| R40-10 | TmpInitTransmit | 全 JSON 覆盖、无乐观锁、空 sid 全站 | P1 |
| R40-11 | TmpChannelOrder | 临时入口执行正式发货和售后业务动作 | P0 |
| R40-12 | FixSaHxCode | /tmp 输入无摘要/批次,参数语义敏感 | P0 |
| R40-13 | QtyAsync | 多进程回收、SQL 参数、删后重建事务风险 | P0 |
| R40-14 | ExpressNoSupplement | 跨分片循环并调用 WMS/OPS,部分成功难追踪 | P0 |
| R40-15 | 多个 *Notify 重放入口 | 原消息 ID、headers、路由和幂等语义可能丢失 | P0 |
| R40-16 | RobotV2 SQL | 多版本文件无唯一执行顺序,含索引删除 | P0 |
40. 建议治理改造路线
40.1 第一阶段:立刻止险
- 给 P0/P1 入口增加显式环境和
sid强校验。 - 默认关闭全站模式和生产临时业务自动化。
- 修正 catch/finally 后错误的成功日志。
- 为无限循环增加
endId、maxRows、超时和失败熔断。 - 为
/tmp重放增加文件摘要、schema 和批次记录。
40.2 第二阶段:统一执行框架
- 建立批次和明细审计表。
- 提供统一参数解析、任务锁、游标、dry-run 和结构化日志组件。
- 外部副作用统一进入 outbox。
- 提供可视化任务状态、失败项下载和受控续跑。
- 一次性迁移绑定版本 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 | 当前代码入口 |
category | consumer/schedule/compensation/migration/repair/replay/automation/diagnostic |
riskLevel | P0-P3 |
owner | 业务与技术 Owner |
allowedEnvironments | 允许环境 |
allowAllSid | 是否允许全站,默认 false |
idempotency | 幂等键和策略 |
checkpoint | 水位字段 |
externalEffects | DB/Redis/MQ/HTTP/文件 |
rollbackRunbook | 回滚文档链接 |
status | active/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、HTTP | 23_外部系统对接清单.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 |
| 需求 SQL | document/*.sql |
| RobotV2 迁移 SQL | application/Services/MoveMall/RobotV2/database/*.sql |
45. 待环境确认项
下列内容无法仅凭仓库代码最终确认,生产执行前必须补齐:
- 101 个 task 控制器当前实际由哪些 cron、Supervisor、容器或发布平台调度。
- 哪些一次性迁移已经在开发、预发、生产分别执行,以及执行批次和结果。
- 生产表的真实唯一索引、分片数量和历史 DDL 差异。
- MQ 的 publisher confirm、消费者重试、死信、保序和幂等配置。
- 外部 Provider 在各环境的目标地址、幂等能力和撤销接口。
- 报价迁移状态表、检查点和版本切换的生产真实数据。
- 活动计划/模板是否已有业务唯一键,重复迁移的实际行为。
- ACL 迁移各阶段是否已完成,以及新旧权限流量比例。
/tmp重放脚本是否仍由线上 SOP 使用。- 临时自动发货和自动售后入口是否仍在任何调度中。
- RobotV2 SQL 的正式执行顺序和 migration ledger。
- 数据修复审计记录目前存放位置和保留期限。
46. 最终判断标准
一个数据修复或迁移任务只有同时满足以下条件,才能称为“完成”:
flowchart LR
A["范围准确"] --> H["完成"]
B["执行幂等"] --> H
C["失败可续跑"] --> H
D["结果可对账"] --> H
E["外部一致"] --> H
F["能够安全回滚"] --> H
G["全程可审计"] --> H
“脚本跑完了”“控制台没有报错”“数据库影响了预期行数”都只是过程信号,不是完成标准。真正的完成是:目标业务恢复正确、非目标数据没有被改变、所有下游最终一致,并且后续任何人都能根据批次记录复现这次决策和验证过程。
请求-日志-数据变更追踪卡
多入口请求链路
| 场景 | 调用方与入口 | 请求载荷/上下文 | Controller/Consumer | Service/Provider | 汇合点 | 最终业务事实 |
|---|---|---|---|---|---|---|
| 一次性修复 | CLI tasks/DataFix/GeneralTasks | dry-run、环境、ID 白名单、批次、limit | Task Controller | 领域/修复 Service | repair batch + business ID | 只修目标异常记录 |
| 结构/数据迁移 | Acl/Activity/Quote/Robot migration | version、游标、批大小 | migration task | Services/Migrate/* | migration version + source ID | 旧数据转换为新模型 |
| 临时工具 | controllers/temp/tools | 明确白名单和动作参数 | Temp/Tools Controller | 领域 Service | request/batch ID | 受控执行诊断或补偿 |
| 后置同步 | 修复任务触发 MQ/cache/index | 业务 ID、routing key、版本 | Task/Service | MqSer/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-run | Task 查询方法 | 只读 | 精确 WHERE、当前主明细/流水 | 审计输出,不写业务表 | 候选集冻结;业务数据 0 变化 | SQL/条件 hash、ID 清单、快照 |
| 小批执行 | DataFix/Migrate Service | 每条/每批事务 | before、幂等标志、目标值 | 目标业务/映射/修复日志 | old -> expected;不匹配前置条件则 skip | batch+ID+before/after+影响行数 |
| 失败恢复 | checkpoint/错误清单 | 独立事务 | 成功/失败/未处理集合 | checkpoint/任务状态 | cursor/retry old -> new,已成功不重做 | 三集合计=候选总数 |
| 后置派生 | MqSer/Cache/ES | commit 后异步 | 修复后业务事实 | MQ/cache/index/report | 只同步受影响业务键 | ACK、缓存/索引/报表抽样 |
| 验收回滚 | 领域反向 Service/验证脚本 | 受控事务 | 基线、目标/非目标样本 | 必要反向单而非裸 SQL | 目标恢复,非目标 diff=0,重跑 diff=0 | 业务守恒、批次审计、负责人/时间 |
子模块追踪:datafix DataFix 定点修复
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| dry-run | 指定 BUG/业务键生成候选 | issue/batch、exact IDs/WHERE | application/controllers/tasks/DataFix.php | 主明细/流水/状态、候选范围和预期值 | 查询只读 不写;冻结 ID 清单与条件 hash | issue + batch + candidate IDs/count + snapshot | WHERE 不精确/数量超预期立即停止,不扩大范围 |
| 小批修复 | 审批后按候选执行 | batch、business ID、before hash | application/controllers/tasks/DataFix.php | 当前值仍等于 baseline、幂等标记 | 每条/批本地事务 old -> expected,前置不符 skip | batch + ID + before/after + rows | 失败保留清单;重跑成功项 0 变化,生产写需授权 |
子模块追踪:general-task GeneralTasks 通用任务
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 任务选择 | CLI 调 GeneralTasks 指定方法 | task method、args、scope/limit | application/controllers/tasks/GeneralTasks.php | 方法白名单、环境、参数和候选数 | 调度阶段不写;目标方法按自己的本地事务执行 | task + args hash + scope + scanned | 禁止无参默认全量;先读代码确认是否可重入 |
| 执行验收 | 通用任务小批运行 | batch、IDs、checkpoint | application/controllers/tasks/GeneralTasks.php | before、已处理标记和失败集合 | 每项 old -> expected 或 skip;checkpoint old -> new | batch + success/fail/skip counts | 不同方法不可共用幂等假设;按方法回归副作用 |
子模块追踪:quote-migrate 报价规则迁移
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 映射预演 | 旧报价配置迁新结构 | batch、old rule/config ID | application/controllers/tasks/QuoteMigrate.php -> application/Services/Migrate/MigrateQuoteConfig.php | 旧规则、目标唯一键、字段/范围映射和冲突 | dry-run 查询只读;输出 old->new 映射 | batch + old/new IDs + conflict count | 未知规则/多义映射进入人工确认,不默认迁移 |
| 分批迁移 | 确认映射后写目标 | batch、config IDs | application/Services/BaseData/QuoteManagerMigrate.php | baseline、目标是否存在和版本 | 每配置本地事务 upsert old rule -> new rule,写迁移标记 | batch + config + before/after + rows | 成功项重跑 0 新增;回归报价查询与人工报价 |
子模块追踪:activity-migrate 活动规则迁移
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 候选分析 | 旧活动规则转新模型 | batch、activity/rule ID | application/controllers/tasks/ActivityRuleMigrate.php | 活动态、规则、商品范围、订单引用和目标冲突 | dry-run 只读,冻结候选与映射差异 | batch + activity/rule + candidate/diff | 有进行中订单的高风险规则单列,不自动改 |
| 执行验证 | 小批迁移规则/关系 | batch、activity IDs | application/controllers/tasks/ActivityRuleMigrate.php | before、目标唯一键和已迁移标记 | 每活动本地事务 old config -> new config | batch + activity + rows + before/after | 非目标活动 diff=0;搜索、下单、退款均回归 |
子模块追踪:acl-migrate ACL 与菜单迁移
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 权限映射 | 旧菜单/角色转新 ACL | batch、menu/resource/user IDs | application/controllers/tasks/AclMigrate.php | 旧关系、目标资源、父链和重复键 | dry-run 只读;迁移本地事务 upsert old grants -> new grants | batch + resource/user + counts/diffs | 未映射资源停止;成功重跑 0 新授权 |
| 登录验收 | 最小/管理员/总部账号验证 | batch、sample users、resource codes | application/controllers/tasks/TmpInitAdminLever.php | 新旧权限、缓存和运行时鉴权 | 查询只读;必要时事务外失效 rights cache | batch + sample + allow/menu diff | 不用管理员全权掩盖子账号缺权限;保留回滚映射 |
子模块追踪:channel-temp 临时渠道业务自动化
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 候选处理 | 临时渠道订单批任务 | batch、channel/source order IDs、scope | application/controllers/tasks/TmpChannelOrder.php | 渠道订单态、来源关系、库存/资金副作用和已处理标记 | 每单本地事务 old -> target,稳定来源键防重复 | batch + channel/source IDs + rows | 必须固定渠道/时间/状态;不复用生产全量扫描 |
| 下游验收 | 处理后同步渠道/MQ | channel/local billNo、event | application/controllers/tasks/TmpInitTransmit.php | 本地已提交事实和发送记录 | commit 后事务外发消息;核心 DB 不重做 | batch + billNos + routing/result | 发送失败只补消息;任务完成后应下线/归档入口 |
子模块追踪:file-replay 临时文件重放
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 文件解析 | 读取历史失败键/业务清单 | file hash、rowNo、business key | application/controllers/tasks/GeneralTasks.php | 文件来源、表头、去重、范围和当前 DB 状态 | 解析/dry-run 不写;生成去重候选集合 | file hash + row/candidate/error counts | 不接受不明来源文件;敏感数据脱敏并限制路径 |
| 幂等重放 | 逐行调用原领域方法 | batch、row/business ID | application/controllers/tasks/DataFix.php | 当前状态、已有副作用和前置条件 | 每行本地事务只补缺失 old -> expected | batch + row + before/after + result | 已完成 skip;失败文件与成功文件分开,防二次全量 |
子模块追踪:multiprocess-task 多进程与循环任务
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 分片调度 | 多进程/循环处理大候选 | task、worker、shard/cursor、limit | application/controllers/tasks/QtyAsync.php | 总候选、分片算法、锁、水位和 worker 状态 | 调度不写业务;worker 每条本地事务 old -> new | task + worker/shard + cursor + counts | 分片不得重叠/遗漏;worker 异常保留其 cursor |
| 汇总恢复 | 子进程完成/失败 | task、worker checkpoints | application/controllers/tasks/GeneralTasks.php | success/fail/unprocessed 三集合 | checkpoint/任务态本地事务 running -> partial/success | task + worker + three counts | 三集合计=总候选;只重启失败分片,成功不重做 |
子模块追踪:express-fix 运单补偿
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 差异扫描 | 外部有运单、本地字段缺失 | batch、invoice/delivery IDs | application/controllers/tasks/ExpressNoSupplement.php | 本地 null/旧值、外部运单/箱号/ETA 和关系 | dry-run 查询只读;输出可补与冲突列表 | batch + invoice + external ID + diff | 外部多单/冲突不自动覆盖,进入人工核对 |
| 小批补值 | 确认后补本地关系字段 | batch、invoice、before hash | application/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、environment | application/Services/Migrate/MigrateRobotFriend.php、application/Services/Migrate/MigrateVinReply.php | schema/数据前置、影响行估算、索引和回滚 SQL | 预检只读 不写;明确事务能力和锁风险 | change + checksum + explain/count | 禁止无 WHERE DML;大表 DDL 需发布窗口和备份 |
| 执行验收 | 小批/正式迁移 | change/batch、checkpoint | application/Services/Migrate/MigrateRobotFriend.php | before snapshot、已迁移标记和目标唯一键 | 每批本地事务 old schema/data -> expected,记录版本 | change + batch + before/after + rows | 失败按 checkpoint 恢复;回归 RobotV2 会话/查询并保证重跑 0 差异 |