Files
junhong_cmp_fiber/openspec/changes/archive/2026-09-18-close-august-iteration-gaps/rollout-and-rollback.md
break 5ed6b39deb
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
2026-09-18 15:34:29 +08:00

113 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# close-august-iteration-gaps 上线与回滚说明
对应 tasks.md §14.1 / §14.2 / §14.4。上线顺序与回滚口径依据 design.md「Migration Plan」与 proposal.md「Impact」。
## 1. 上线顺序(必须先迁移,再发二进制,再配置企微,最后开放新字段使用)
### 第一步:执行六对迁移(按编号顺序,不修改既有迁移)
| 顺序 | 迁移编号 | 内容 |
| --- | --- | --- |
| 1 | `000232_add_h5_popup_type` | `tb_h5_popup_configuration``popup_type`(枚举 `promotion`/`announcement`,非空,默认 `announcement`+ 取值 CHECKup 以默认值回填既有行 |
| 2 | `000233_extend_refund_settlement_fields` | 退款主表加 `source_payment_no``varchar(64)`)、`original_channel_trade_no`**`varchar(100)`**,与来源列 `tb_payment.third_party_trade_no` 一致)、`offline_settlement_no``varchar(128)`)、`offline_settled_at``offline_settled_by`;退款审批尝试表加 `remark`(申请人备注,逐次冻结) |
| 3 | `000234_extend_polling_priority_item` | 优先轮询事实表加 `asset_type``asset_id``device_no_snapshot``agent_shop_id_snapshot``attempt_limit`(默认 3`started_at``finished_at``next_run_at``dequeued_at``integration_log_id``priority_effective_from`;重建状态 CHECK 容纳 `closed``expired`;活动项部分唯一索引谓词保持 `status IN ('pending','processing')` 不变;为 `next_run_at` 建部分索引;既有活动项 `priority_effective_from` 回填为迁移时刻,资产列按既有设备—卡绑定回填 |
| 4 | `000235_extend_carrier_threshold_hit` | 阈值锁表加命中累计流量、阈值数值、阈值单位、判定时间、触发来源、解锁时间、复机结果;判定时间回填既有行创建时间(并置默认 `now()`),命中数值与阈值留空;新增按通道、卡与判定时间的查询索引 |
| 5 | `000236_add_phone_association_invalidator_name` | 手机号—资产关联加 `invalidator_name_snapshot`(非空默认空串) |
| 6 | `000237_extend_withdrawal_attempt_qualification` | 提现审批尝试表加资料资格版本标识、校验时间、是否通过、未通过稳定原因 |
执行方式:维护者按既有迁移脚本(`scripts/migrate.sh`)执行,六对全部落盘后再统一 `up`(本次测试环境即如此,避免先落盘的号被后落盘的低号跳过)。执行后用只读查询核对列/默认值/CHECK/索引(见 `verification.md` 第 2 节)。
### 第二步:发布 API / Worker 二进制
手工发布,步骤见 `docs/deployment/production-runbook.md`。发布前确认工作树包含本变更全部生产代码改动(`go build ./cmd/api ./cmd/worker` 通过)。
### 第三步:维护者配置企业微信控件映射(仅本变更新增字段强制映射)
- 退款审批场景:套餐已用量与总量、资产类型、设备类型与型号、原支付渠道交易流水号。
- 代理注册审批场景:业务员。
-`GET /api/admin/wecom/scenes` 返回的启用记录与 `control_mapping` 作为验收证据。既有未映射的可选控件保持既有静默跳过行为。
- 未配置前:退款/注册审批提交时对缺失的新增控件**明确失败并提示缺失控件**(不静默丢弃)。
### 第四步:开放新字段使用
新端点与新增筛选按第 3 节启用;前端按第 4 节的参数改名同步。
### 上线前置清单(两项,待维护者执行)
| 项 | 内容 | 验收证据 | 状态 |
| --- | --- | --- | --- |
| 1 | 在生产配置企业微信控件映射:退款审批的套餐用量(已用量与总量)、资产类型、设备类型与型号、原渠道流水号;代理注册审批的业务员 | `GET /api/admin/wecom/scenes` 返回的启用记录与 `control_mapping` | **待维护者执行** |
| 2 | 员工账单时间参数改名的前端同步:`created_from`/`created_to``start_time`/`end_time`(旧参数名与旧格式一律拒绝,无兼容别名) | 前端发布后员工账单列表与详情的时间筛选可用 | **待维护者执行** |
两项上线前置未完成前:新字段的企微审批材料会在提交时**明确失败并提示缺失控件**(不静默丢弃);员工账单的旧时间参数调用会被**参数非法拒绝**。
## 2. 回滚步骤
### 2.1 代码回退
代码回退到上一版本后,数据库停留在 237 形态,各新增列**可空或带默认值**,旧二进制不写这些列也不会违反约束:
| 表 | 列 | 回滚期旧代码不写时的取值 |
| --- | --- | --- |
| `tb_h5_popup_configuration` | `popup_type` | 默认 `announcement` |
| `tb_refund_request` | `source_payment_no``varchar(64)`/ `original_channel_trade_no``varchar(100)`,与来源列 `tb_payment.third_party_trade_no` 一致)/ `offline_settlement_no``varchar(128)` | 默认空串 |
| `tb_refund_request` | `offline_settled_at` | NULL可空 |
| `tb_refund_request` | `offline_settled_by` | 默认 `0` |
| `tb_refund_request_attempt` | `remark` | 默认空串 |
| `tb_polling_priority_item` | `asset_type` / `device_no_snapshot` / `integration_log_id` | 默认空串 |
| `tb_polling_priority_item` | `asset_id` / `agent_shop_id_snapshot` / `started_at` / `finished_at` / `next_run_at` / `dequeued_at` / `priority_effective_from` | NULL可空 |
| `tb_polling_priority_item` | `attempt_limit` | 默认 `3` |
| `tb_carrier_traffic_threshold_lock` | `hit_traffic_mb` / `hit_threshold_value` | NULL可空 |
| `tb_carrier_traffic_threshold_lock` | `hit_threshold_unit` / `trigger_source` / `resume_result` | 默认空串 |
| `tb_carrier_traffic_threshold_lock` | `judged_at` | **默认 `now()`** |
| `tb_carrier_traffic_threshold_lock` | `unlocked_at` | NULL可空 |
| `tb_phone_asset_association` | `invalidator_name_snapshot` | 默认空串 |
| `tb_commission_withdrawal_request_attempt` | `qualification_version_id` / `qualification_passed` | 默认 `0` |
| `tb_commission_withdrawal_request_attempt` | `qualification_checked_at` | NULL可空 |
| `tb_commission_withdrawal_request_attempt` | `qualification_failure_reason` | 默认空串 |
`judged_at` 单独说明:该列需要非空(判定时间是命中记录的查询与排序依据),但若不给默认值,回退到上一版本二进制时旧代码写入停机锁不带该列就会违反 NOT NULL使达量停机事实无法落库。因此保留 `DEFAULT now()`,让旧代码插入的命中事实以插入时刻作为判定时间,与「既有行以创建时间回填」的语义自洽。
### 2.2 读侧兼容
- 读侧对上述空值与默认值一律按「无」处理,不因新列缺失或为空阻断查询(命中读数/阈值历史行为空按「无」展示,判定时间取既有创建时间)。
- 既有优先轮询行的有效期起算时间(`priority_effective_from`)为迁移时刻,**不会**在上线后被首轮到期扫描批量判为已过期;既有行的 `next_run_at` 为空时,读侧 MUST NOT 据此判定已过期。
- 历史审批尝试不受本变更的必填与留痕影响:退款原因必填只对上线后新提交与重提生效,历史申请与补发历史审批不追溯;提现历史尝试的资格校验留痕为空按「历史尝试无资格校验留痕」处理。
### 2.3 企微控件配置回滚
代码回滚时需**同步移除**本变更新增的控件映射(退款审批的套餐用量/资产类型/设备类型与型号/原支付渠道交易流水号,代理注册审批的业务员),避免模板残留未使用控件。
### 2.4 时间参数替换回滚
员工账单列表「产生时间」参数改名属有意的破坏性收敛,**旧参数名不再被接受**;回滚需**同步前端**(见第 4 节)。
### 2.5 迁移 down
如需实际回滚 Schema按 6→1 逆序执行六对迁移的 down注意各 down 均声明不可逆事实的丢弃H5 类型、退款结算标识与备注、优先轮询留痕、命中数字证据、解绑人名称快照、资格校验留痕),其中 `000234` 的 down 在存在 `closed`/`expired` 终态行时会拒绝执行。
## 3. 新增端点与失效字段默认值
### 3.1 新增端点(方法 / 路径 / 权限主体)
| 方法 | 路径 | 权限主体 |
| --- | --- | --- |
| GET | `/api/admin/refunds/order-options` | 退款组门禁(超级管理员/平台/代理,企业账号拒绝)+ 订单数据范围 |
| POST | `/api/admin/refunds/:id/offline-settlement` | 同退款组门禁 + 订单数据范围 |
| POST | `/api/admin/polling-priority-items/:id/close` | 超级管理员与平台账号 |
| POST | `/api/admin/polling-priority-items/:id/retrigger` | 与人工入队一致(含数据范围内的代理账号) |
| GET | `/api/admin/carrier-traffic-threshold-hits` | 超级管理员与平台账号 + 卡数据范围 |
同时新增周期任务类型优先轮询有效期到期扫描与出队Worker 调度注册,`@every` 周期)。
### 3.2 失效字段默认值(新增列的上线默认值)
见 2.1 表;一句话概括:新增列一律可空或带默认值(空串 / `0` / `3` / `announcement` / `now()`),既有行零变更。
## 4. 调用方同步要求
- **员工账单列表**:既有「产生时间」参数由 `created_from`/`created_to` **改名为 `start_time`/`end_time`**,并改用统一严格解析器。旧参数名(`created_from`/`created_to`与旧格式date-only、无时区串、空格分隔、小数秒、`±hhmm`**一律以参数非法错误拒绝,不保留别名或兼容**;前端 MUST 同步改用 `start_time`/`end_time`
- 员工账单「核销通过时间」使用 `decided_start_time`/`decided_end_time`,与主字段同一解析器、同一闭区间(带时区 RFC3339 秒级、含两端)。
- 优先轮询项查询、通道阈值命中查询的筛选时间字段亦使用 `start_time`/`end_time`
- 员工账单列表与统计响应新增「未核销金额」与「剩余可核销金额」两个并列字段;详情新增操作日志与来源订单快照投影。