1. 业务目标与边界

活动域负责“运营配置营销规则,服务站按规则购买,最终转成可支付和履约的业务单据”。代码中包含普通销售活动、赠品活动、客户活动、优惠券、预售和秒杀等能力。本篇以当前代码最完整的秒杀链路为主,说明:

  • OPS 创建、编辑、导入活动商品、配置服务站范围、提交 OA、审批和提前结束。
  • 服务站查看商品、加购物车、确认订单、提交订单和主动取消。
  • 秒杀订单怎样转换为采购单,支付回调怎样确认占用量。
  • 待支付超时、采购关闭、回调丢失时怎样释放或补偿。

本篇不展开普通销售活动、赠品和优惠券的全部字段;这些能力与秒杀共处活动域,但拥有各自的 Service、模型和状态,不能直接套用秒杀状态。

2. 参与角色与前置条件

角色/系统职责关键前置条件
OPS 运营创建活动、维护商品、名单、提交审批、提前结束有活动管理权限;商品和仓库资料有效
OA审批秒杀活动模板编码为 callback_20260526;回调能进入 OA 消费者
服务站用户浏览、加购、确认、提交、支付或取消登录上下文包含服务站 sid;活动对该站可见
DGJ2 秒杀域校验活动、价格、数量、库存,保存秒杀单活动审批通过、启用且在有效时间内
采购域接收秒杀单并创建待支付采购单秒杀订单和明细已在同一事务中生成
支付中心返回采购单支付结果回调采购单可通过来源字段关联秒杀单
定时任务刷状态、清购物车、释放超时、补偿异常CLI 环境执行;任务日志可用

3. 代码入口地图

层级文件主要职责
OPS Controllerapplication/controllers/inner/activity/FlashActivity.php活动列表、保存、导入导出、名单、提交、结束
PC 服务站 Controllerapplication/controllers/scm/FlashSale.phpPC 秒杀商品、购物车、订单接口
移动商城 Controllerapplication/controllers/inner/moveMall/FlashSale.php移动端同口径秒杀接口
OPS Serviceapplication/Services/Activity/FlashActivitySer.php活动校验、保存、OA、状态刷新
购买 Serviceapplication/Services/Activity/FlashSaleSer.php可见性、库存、购物车、订单、采购、释放和补偿
支付回调application/controllers/tasks/PayCenterNotify.php采购支付落账并通知秒杀订单
OA 回调application/controllers/tasks/OaNotify.php消费 OA 审批事件并更新活动状态
补偿任务application/controllers/tasks/FlashSaleTask.php状态刷新、超时释放、支付/关闭补偿
枚举application/KzData/Enums/FlashActivityEnums.php活动、库存范围、商品、购物车、订单状态
采购枚举application/KzData/Enums/PoOrderEnums.php秒杀采购单类型和来源类型
已确认:接口方法和参数来自当前 Controller、Request Entry 和 Service。真实 HTTP 前缀会受网关、环境和 CodeIgniter 路由影响,联调时应从浏览器 Network 或环境路由确认,不能把类路径直接当成固定公网 URL。

4. OPS 接口与请求

4.1 OPS 接口总表

接口方法核心请求核心 Service关键返回数据副作用
listFlashActivity页码、活动编号/状态/时间/申请人筛选listFlashActivity活动分页列表只读
detailFlashActivityid/activity_id/activityIddetailFlashActivity活动、商品、状态快照只读
listFlashActivityGoods活动 ID、分页、商品/仓库筛选listFlashActivityGoods活动商品分页只读
saveFlashActivity活动对象、时间、goodsListsaveFlashActivity完整活动详情新增/更新活动和商品
invalidateFlashActivityGoods活动 ID、activity_goods_id、reasoninvalidateFlashActivityGoods更新后结果商品规则置失效
importFlashActivityGoodsfile_path 上传文件、活动 IDimportGoods解析后的商品行不直接保存活动;前端需随保存接口提交
downloadFlashActivityGoodsTemplate无业务参数buildGoodsImportTemplateExcel 文件流无
exportFlashActivityGoods活动 IDbuildGoodsExportExcel 文件流无
exportFlashActivity列表筛选条件buildActivityExportExcel 文件流无
copyFlashActivity活动 IDcopyFlashActivity新草稿详情新增活动和商品快照
saveFlashActivityStationScope活动 ID、type、stationssaveStationScope名单分页新增/恢复名单记录
getFlashActivityStationScope活动 ID、type、分页getStationScope黑/白名单分页只读
deleteFlashActivityStationScope活动 ID、type、ids/sidsdeleteStationScope删除后的分页软删名单记录
submitFlashActivity活动 ID、is_oa、可选 oa_nosubmitFlashActivityOA 单号或冲突清单更新审批状态、调用 OA
endFlashActivity活动 ID、reasonendFlashActivity更新后详情活动提前结束、商品同步结束
convertFlashActivityToDraft活动 IDconvertFlashActivityToDraft草稿详情驳回/可转换活动重置草稿

4.2 保存活动请求示例

下面是 Controller 可以识别的结构示例,金额按当前秒杀活动代码以分存储,展示时再格式化为元;fixed_price 的前端具体传值需继续与当前 OPS 页面 Network 核对。

{
  "activity": {
    "id": 0,
    "name": "暑期轮胎秒杀",
    "start_time": "2026-07-20 09:00:00",
    "end_time": "2026-07-20 18:00:00",
    "activity_type": 2,
    "stock_scope": 2,
    "remark": "示例活动"
  },
  "goodsList": [
    {
      "goods_id": 10001,
      "package_group_no": "PKG-001",
      "wms_location_no": "WH001",
      "wms_location_name": "示例仓",
      "qty": 4,
      "price_mode": 1,
      "fixed_price": 19900,
      "discount_rate": 0,
      "date_calc_mode": 2,
      "backward_from": 90,
      "backward_to": 30,
      "sort": 10
    }
  ]
}

关键字段:

字段含义和规则
id/activity_id/activityId活动 ID 兼容别名;新增为 0,编辑传已有 ID
activityTime兼容 [开始时间, 结束时间],服务端归一到 start_time/end_time
activity_type1 单品售卖,2 套包售卖
stock_scope1 服务站对应仓,2 指定仓,3 全局预留
goodsList/goods_list商品规则完整快照;导入结果也要随保存接口提交
goods_id商品/物料档案 ID,后端补充 SKU、名称、品牌、分类和单位快照
qty单品可为 0 表示不以活动量限制;套包必须大于 0
price_mode1 一口价,2 固定折扣
date_calc_mode0 无,1 按月倒推,2 按天倒推
wmsAttributeCode用于判断生产日期字段是否必填,不落活动商品表

4.3 服务站范围请求示例

{
  "activity_id": 100,
  "type": 2,
  "stations": [
    {"sid": 20001, "station_name": "示例服务站"},
    {"sid": 20002, "station_name": "另一服务站"}
  ],
  "page": 1,
  "pageSize": 20
}
  • type=1 是黑名单,type=2 是白名单。
  • 保存兼容 stations、stationList、station_list、sids、单个 sid。
  • 删除兼容名单记录 ids 或服务站 sids,并限制在活动和名单类型内软删。

4.4 提交 OA 请求和分支

{
  "activity_id": 100,
  "is_oa": 1
}
  • is_oa=1:构造 OA payload,外部提交成功后把活动更新为审批中并保存 oa_no。
  • is_oa=0:必须传 oa_no,按人工确认路径直接置审批通过。
  • 提交前会检查商品明细、活动配置和跨活动冲突。
  • 有冲突时不是成功提交,返回 can_submit=0、conflict_count、conflictList 和可展示消息。

5. OPS 创建到 OA 生效流程

flowchart TD
    A[OPS 新建或编辑活动] --> B[saveFlashActivity]
    B --> C{活动/时间/商品/价格校验}
    C -- 失败 --> X[返回明确校验错误 不落有效配置]
    C -- 通过 --> D[保存活动主表和商品快照]
    D --> E[配置黑名单或白名单]
    E --> F[submitFlashActivity]
    F --> G{跨活动商品冲突}
    G -- 有冲突 --> H[返回 conflictList 调整后重提]
    G -- 无冲突 --> I{是否走 OA}
    I -- 否 --> J[校验 oa_no 并直接置通过]
    I -- 是 --> K[FlashActivityOaProvider 提交 OA]
    K --> L[活动置审批中 保存 oa_no]
    L --> M[OaNotify 消费 oa_audiat]
    M --> N{审批结果}
    N -- 通过 --> O[审批通过 按活动时间决定启用状态]
    N -- 驳回 --> P[审批驳回 记录 reject_reason]
    O --> Q[同步活动商品状态 服务站可按规则展示]

5.1 OA 系统时序

sequenceDiagram
    participant OPS as OPS 前端
    participant C as FlashActivity Controller
    participant S as FlashActivitySer
    participant DB as MySQL 活动表
    participant OA as OA Provider
    participant MQ as OA MQ
    participant T as OaNotify

    OPS->>C: submitFlashActivity(activity_id,is_oa)
    C->>S: submitFlashActivity(requestData,apiUserHeader)
    S->>DB: 读取活动和商品 校验冲突
    alt 存在冲突
        S-->>OPS: can_submit=0 + conflictList
    else 提交 OA
        S->>OA: submit(template payload)
        OA-->>S: oa_no/requestId
        S->>DB: apply_status=1, oa_status=1
        S-->>OPS: activity_id + oa_no
        OA-->>MQ: oa_audiat(templateCode,status,summaryId)
        MQ->>T: consume
        T->>S: callback20260526(messageData)
        S->>DB: 审批通过或驳回并同步商品状态
    end

6. 服务站接口与请求

PC Controller 和移动商城 Controller 最终都调用 FlashSaleSer,业务口径应保持一致;PC 额外提供 checkAccess。

接口方法核心请求关键返回主要读写
checkAccess登录服务站上下文has_access、message读活动和白名单
goodsListpage、pageSize、商品筛选list、total读活动商品、名单、库存/物料
goodsDetail活动商品 ID 或活动 ID活动、商品/套包、库存和价格只读
packageGoodsList套包活动/商品 ID、分页套包及明细只读
goodsFilters登录服务站上下文品牌、分类、活动类型等只读
getCartCount活动商品 ID本站/供给仓/在途库存、包装、可购量读购物车和库存
addCartactivity_goods_id、qty、packSpecType更新后的购物车新增或更新购物车
cartList可选筛选/排序购物车、失效原因、金额门槛读并可能重置历史勾选/刷新状态
updateCartitems 或活动商品 ID 集合更新后的购物车事务内更新/删除购物车
orderPreviewsource_type、activity_goods 或活动 ID仓库、商品、金额、额度只读实时校验
submitOrder与确认页相同,另含收货/采购上下文秒杀订单详情、采购单 ID事务新增秒杀单、锁量、创建采购单
orderDetailid 或 bill_no秒杀单、明细、关联采购单只读
cancelOrderid/bill_no、可选 reason取消后订单详情关闭采购单、释放锁量、更新状态

登录上下文由 Controller 注入或透传:JXCSID/sid 表示服务站,JXCUID 表示用户,JXCUNAME 表示用户名。业务请求不应允许前端用任意 sid 越权读取其他服务站数据。

6.1 加购请求

{
  "activity_goods_id": 30001,
  "qty": 4,
  "packSpecType": 1
}
  • qty 最小为 1。
  • packSpecType 兼容 pack_spec_type,服务端会换算成基础数量。
  • 单品允许加购;套包不进购物车,必须立即购买。
  • 同服务站、同活动商品执行 upsert,不重复生成多行购物车。

6.2 批量更新购物车

{
  "items": [
    {"activity_goods_id": 30001, "qty": 8, "selected": 1},
    {"activity_goods_id": 30002, "deleted": 1}
  ]
}

服务端在事务中逐行处理:数量小于等于 0 或 deleted=1 进入删除;改数量时重新校验商品可见性、采购限制和实时库存;只改 selected 时不重算数量。任一行抛错会回滚本次批量修改。

6.3 购物车确认和提交

{
  "source_type": 2,
  "activity_goods": [
    {"activity_goods_id": 30001, "qty": 8},
    {"activity_goods_id": 30002, "qty": 4}
  ]
}
  • source_type=1 为立即购买,2 为购物车。
  • 购物车未传 activity_goods 时,默认取当前勾选且有效的行。
  • activity_goods 可传 JSON 数组,也兼容 JSON 字符串;每行必须有活动商品 ID 和正数量。
  • 单品必须先进入购物车;套包立即购买可传 activity_id 和购买套数 qty。
  • 确认页只读计算,提交时会再次加行锁并重新校验,不能依赖确认页库存快照。

6.4 套包立即购买

{
  "source_type": 1,
  "activity_id": 100,
  "qty": 1
}

服务端加载套包全部可见明细,将“套数 × 每行配置数量”展开为采购明细数量。当前常量 PACKAGE_PURCHASE_LIMIT=1,套包购买限制和每行可售量还会结合活动商品锁定量、已使用量及供给库存判断。

7. 服务站购买主流程

flowchart TD
    A[服务站进入秒杀专区] --> B[按审批/启用/时间/名单筛选活动]
    B --> C[加载物料 价格 本站库存 在途库存 供给仓库存]
    C --> D{单品还是套包}
    D -- 单品 --> E[查询可购量和包装后加入购物车]
    E --> F[购物车实时刷新有效性和金额门槛]
    F --> G[orderPreview]
    D -- 套包 --> H[直接购买 展开套包明细]
    H --> G
    G --> I{实时校验是否通过}
    I -- 否 --> X[返回库存/状态/金额/权限错误]
    I -- 是 --> J[submitOrder 开启事务]
    J --> K[FOR UPDATE 锁活动商品行]
    K --> L[写秒杀主单和明细]
    L --> M[增加 locked_qty]
    M --> N[创建并提交秒杀来源采购单]
    N --> O{采购单创建成功}
    O -- 否 --> P[事务回滚 秒杀单/锁量/采购草稿不得残留]
    O -- 是 --> Q[删除已结算购物车并提交事务]
    Q --> R[返回秒杀单详情和 po_order_ids]

8. 下单、支付和关闭时序

sequenceDiagram
    participant U as 服务站
    participant C as FlashSale Controller
    participant S as FlashSaleSer
    participant FDB as 秒杀表
    participant PO as 采购域
    participant PDB as 采购表
    participant PAY as 支付中心
    participant CB as PayCenterNotify

    U->>C: submitOrder(source_type,activity_goods)
    C->>S: submitOrder(登录上下文+请求)
    S->>FDB: BEGIN + 锁 t_flash_activity_goods
    S->>FDB: 新增 flash_order/detail
    S->>FDB: 已锁数量增加本次订单数量
    S->>PO: savePoOrder + InvPoFactory::saveOrder
    PO->>PDB: 新增并提交待支付采购单
    alt 采购创建失败
        S->>FDB: ROLLBACK
        S-->>U: 返回提交失败
    else 创建成功
        S->>FDB: 删除已结算购物车 + COMMIT
        S-->>U: 秒杀订单 + po_order_ids
        U->>PAY: 支付采购单
        PAY-->>CB: 支付结果回调
        CB->>PDB: 写付款/明细并推进采购状态
        CB->>S: markPaidByPoOrder
        S->>FDB: 已锁数量减少,已用数量增加
        S->>FDB: flash_order=已支付
    end

关闭路径:

  • 待支付主动取消:cancelOrder 找关联采购单;待支付采购单由 PoOrderSer::cancelWaitPayOrder 关闭并触发释放。
  • 秒杀单没有关联采购单:秒杀域直接锁订单并释放 locked_qty。
  • 采购待支付超时:expireWaitPayOrders 关闭采购单或直接释放,并把秒杀单置已关闭。
  • 采购未发货关闭:PoOrderSer/InvPoService 调用 releaseByPoCloseItems,按关闭明细释放已占用数量。
  • 支付成功但状态未同步:compensatePaidOrders 根据采购状态补调 markPaidByPoOrder。

9. 状态机

9.1 活动状态

活动有三组相关状态,页面不能只看一个字段:

维度字段/枚举值
申请状态apply_status0 草稿、1 审批中、2 通过、3 驳回
OA 状态oa_status0 草稿、1 审批中、2 通过、3 驳回
启用状态enable_status0 未启用、1 启用、2 提前结束
页面状态运行时计算1 未开始、2 进行中、3 已结束

页面状态还依赖当前时间:只有审批通过、启用、start_time <= now <= end_time 才是进行中。

stateDiagram-v2
    [*] --> 草稿
    草稿 --> 审批中: submitFlashActivity
    审批中 --> 审批通过: OA PASS
    审批中 --> 审批驳回: OA REJECT
    审批驳回 --> 草稿: convertFlashActivityToDraft
    审批通过 --> 未开始: 当前时间早于开始时间
    审批通过 --> 进行中: 启用且进入活动时间
    未开始 --> 进行中: 到达开始时间
    进行中 --> 已结束: 到达结束时间
    未开始 --> 提前结束: endFlashActivity
    进行中 --> 提前结束: endFlashActivity
    已结束 --> [*]
    提前结束 --> [*]

9.2 秒杀订单状态

stateDiagram-v2
    [*] --> 待支付: submitOrder 创建成功
    待支付 --> 已支付: 支付回调或支付补偿
    待支付 --> 已取消: 用户主动取消
    待支付 --> 已关闭: 支付超时或采购关闭
    已支付 --> 已关闭: 采购未发货关闭并按明细释放
    已支付 --> [*]
    已取消 --> [*]
    已关闭 --> [*]

代码常量还保留 0=已创建,但 submitOrder 当前直接写入 1=待支付。维护旧数据时不能把 0 当作当前主流程必经状态。

10. 单号与数据关系

erDiagram
    FLASH_ACTIVITY ||--o{ FLASH_ACTIVITY_GOODS : activity_id
    FLASH_ACTIVITY ||--o{ FLASH_ACTIVITY_STATION_SCOPE : activity_id
    FLASH_ACTIVITY ||--o{ FLASH_CART : activity_id
    FLASH_ACTIVITY ||--o{ FLASH_ORDER : activity_id
    FLASH_ORDER ||--|{ FLASH_ORDER_DETAIL : order_id
    FLASH_ACTIVITY_GOODS ||--o{ FLASH_CART : activity_goods_id
    FLASH_ACTIVITY_GOODS ||--o{ FLASH_ORDER_DETAIL : activity_goods_id
    FLASH_ORDER ||--o{ SCM_PO_ORDER : srcOrderId_srcOrderNo
    SCM_PO_ORDER ||--|{ SCM_PO_ORDER_INFO : orderId

秒杀单到采购单的定位关系:

采购字段秒杀来源值
srcOrderTypePoOrderEnums::SRC_ORDER_TYPE_FLASH_ACTIVITY = 13
srcOrderIdt_flash_order.id
srcOrderNot_flash_order.bill_no
orderTypePoOrderEnums::ORDERTYPE_FLASH_ORDER
sid秒杀订单服务站 ID,关联时必须同时校验

不能只用 srcOrderId 跨服务站查单,排查和业务更新至少同时带 sid,有条件时再带 srcOrderNo。

11. 核心表读写矩阵

物理表关键字段读取时机写入/更新时机一致性说明
t_flash_activityid、activity_no、apply_status、enable_status、oa_status、时间OPS 列表详情、服务站可见性、OA 回调保存、提交、审批、结束、状态刷新与商品规则共同决定是否可售
t_flash_activity_goodsid、activity_id、qty、locked_qty、used_qty、价格、仓库、状态商品列表、购物车、预览、提交、补偿保存规则;下单锁量;支付转已用;关闭释放提交订单时 FOR UPDATE,支付用条件更新防止锁量不足
t_flash_activity_station_scopeactivity_id、type、sid、is_deleted判断服务站可见性名单保存/恢复/软删白名单和黑名单规则需结合活动类型理解
t_flash_cartid、sid、activity_goods_id、qty、selected、status、reason加购、购物车、确认页upsert、改量、勾选、失效刷新、结算删除购物车是临时态,提交时仍重新查活动和库存
t_flash_orderid、bill_no、sid、source_type、order_status、金额、expired_at详情、取消、支付/关闭关联、补偿扫描提交新增;支付/取消/关闭更新bill_no 是业务查询键,生命周期更新需锁行
t_flash_order_detailorder_id、activity_goods_id、qty、金额快照、closed_qty详情、支付转量、关闭释放提交新增;采购关闭时累计释放订单展示和历史金额以快照为准
t_scm_po_orderid、billNo、sid、billStatus、来源三字段支付、取消、补偿、详情关联秒杀提交创建;支付/关闭推进最终支付和履约单据
t_scm_po_order_infoorderId、商品、数量、来源明细采购详情、关闭数量计算秒杀提交生成采购明细套包会展开为多行

12. 数量、库存和金额口径

12.1 活动数量

  • locked_qty:待支付秒杀订单已经锁住、尚未确认支付的基础数量。
  • used_qty:支付成功后确认占用的基础数量。
  • 支付成功:同一事务语义内执行 locked_qty -= qty、used_qty += qty。
  • 待支付取消/超时:释放 locked_qty,不得增加 used_qty。
  • 已支付采购关闭:按关闭明细和 closed_qty 释放,避免重复关闭多次扣减。

当前 availableQty() 的代码口径:

  • 单品活动返回不受活动数量上限限制的极大值,最终仍受实时供给库存、采购限制等校验。
  • 套包单行可售基础数量为 max(0, 行配置数量 × 套包购买限制 - locked_qty - used_qty)。
  • 套包整体可买套数还要取所有明细按配比折算后的最小值,并受供给仓库存约束。

因此排查“页面有库存但不能下单”时,不能只查 qty-locked_qty-used_qty;必须同时看活动类型、套包配比、仓库、供给库存、本站库存、在途、最小采购量和金额门槛。

12.2 金额

  • 秒杀 Service 内部通过 amountToCent() 和 formatCent() 在元展示值与分存储值之间转换。
  • t_flash_order.total_amount/origin_amount/discount_amount 是提交时的订单快照。
  • 一口价直接取活动价格;折扣价按服务站采购原价和折扣计算。
  • 确认页和提交都会计算金额,提交结果以加锁后重新计算为准。
  • 前端传入金额不能作为落库依据;价格、优惠和金额限制由服务端重算。

13. 事务、并发与幂等

场景保护方式结论
批量改购物车数据库事务任一行失败回滚整批修改
提交秒杀订单数据库事务 + 活动商品 FOR UPDATE主单、明细、锁量、采购创建必须整体成功
支付转已用锁秒杀订单 + 条件更新 locked_qty >= qty重复支付已是支付态直接成功;锁量不足抛错
取消/超时锁秒杀订单,已取消/已关闭直接返回避免重复释放待支付锁量
采购关闭释放明细记录 closed_qty应按差量释放,避免同一关闭明细重复处理
OA 回调通过 oa_no 找活动并按结果更新外部回调是否保证严格去重仍需联调验证

高风险边界:submitOrder 在同一数据库连接事务内调用采购保存流程。若后续把采购拆成远程服务,现有数据库事务不能覆盖远程成功/失败,必须重新设计幂等键、状态中间态和补偿。

14. 自动任务和补偿

CLI 入口都在 application/controllers/tasks/FlashSaleTask.php:

方法扫描对象修复动作验证结果
refreshGoodsStatus(limit)活动商品按活动时间/状态刷新商品状态返回扫描和更新统计
refreshInvalidCarts(limit)秒杀购物车重新计算有效状态和原因scanned/updated/valid/invalid
expireWaitPayOrders(limit)已过 expired_at 的待支付秒杀单关闭待支付采购或直接释放锁量success/fail/errors
compensatePaidOrders(limit)采购已进入支付后状态但秒杀仍待支付调 markPaidByPoOrder秒杀已支付,锁量转已用
compensateClosedOrders(limit)存在采购关闭明细且秒杀仍待支付/已支付调 releaseByPoCloseItems关闭数量已释放
runCompensate(limit)上述所有常规对象顺序执行状态、购物车、超时、支付、关闭补偿分模块结果汇总

执行原则:

  1. 先用只读 SQL 确定异常集合和影响数量。
  2. 小 limit 执行单项任务,保留任务日志和返回 JSON。
  3. 再查秒杀单、采购单、locked_qty/used_qty/closed_qty 是否符合预期。
  4. 不要在生产直接手改三个数量字段;手改会绕过订单明细和幂等保护。

15. 按单号排查 SOP

15.1 从秒杀单查采购单

以下 SQL 使用占位符,默认只读;分表或字段大小写以当前环境为准。

SELECT id, bill_no, activity_id, sid, source_type, order_status,
       total_amount, origin_amount, discount_amount, expired_at,
       cancel_reason, create_time, modify_time
FROM t_flash_order
WHERE sid = :sid AND bill_no = :flash_bill_no;

SELECT id, order_id, activity_goods_id, sku_id, inv_id, qty,
       total_amount, closed_qty
FROM t_flash_order_detail
WHERE order_id = :flash_order_id
ORDER BY id;

SELECT id, billNo, sid, billStatus, orderType,
       srcOrderType, srcOrderId, srcOrderNo, paymentTime, modifyTime
FROM t_scm_po_order
WHERE sid = :sid
  AND srcOrderType = 13
  AND srcOrderId = :flash_order_id
  AND srcOrderNo = :flash_bill_no;

判断顺序:

  1. 查不到秒杀单:确认提交请求是否真正成功,按服务站、时间和日志继续找。
  2. 有秒杀单无采购单:重点查 submitOrder 事务异常和采购创建返回;正常情况下应整体回滚。
  3. 秒杀待支付、采购已支付后状态:检查支付回调日志,必要时评估 compensatePaidOrders。
  4. 秒杀取消/关闭但 locked_qty 未下降:查采购关闭路径和释放异常。

15.2 核对锁定量和已用量

SELECT g.id, g.activity_id, g.sku_id, g.qty,
       g.locked_qty, g.used_qty, g.status, g.version,
       d.order_id, d.qty AS order_qty, d.closed_qty
FROM t_flash_activity_goods g
LEFT JOIN t_flash_order_detail d ON d.activity_goods_id = g.id
WHERE g.id IN (:activity_goods_ids)
ORDER BY g.id, d.order_id;

核对规则:

  • 待支付未取消的订单明细应贡献 locked_qty。
  • 已支付且未关闭的订单明细应贡献 used_qty。
  • 已取消/已关闭数量不应继续完整占用。
  • 汇总差异时要考虑历史数据、部分关闭和同一活动商品的其他订单,不能只拿一个订单明细与总字段直接相等比较。

15.3 代码和日志定位

rg -n "function submitOrder|function cancelOrder|function markPaidByPoOrder" \
  application/Services/Activity application/controllers

rg -n "SRC_ORDER_TYPE_FLASH_ACTIVITY|ORDERTYPE_FLASH_ORDER" application

rg -n "秒杀订单提交失败|秒杀商品锁定数量不足|秒杀待支付超时释放|支付消息通知失败" \
  application storage logs

常用日志分类:FlashSaleTask 的任务方法名、PayCenterNotify 的支付处理日志、OA 消费的 OaNotify/oaAudit。实际日志目录和容器路径见本知识库的本地联调与日志专题。

16. 常见故障决策表

现象第一检查点第二检查点典型原因
OPS 提交失败返回是否有 conflictList活动/商品时间和仓库跨活动冲突或配置校验失败
OA 已通过页面仍审批中oa_no 是否匹配OaNotify ACK/NACK 和异常模板编码、编号或回调消费失败
服务站看不到活动审批/启用/时间黑白名单和服务站 sid活动未运行或范围不匹配
列表有库存但加购失败商品类型和 can_add_cart包装换算、限购、实时供给库存套包不允许加购或库存维度不同
购物车有效但确认失败购物车实时状态金额门槛和仓库解析确认页重新计算发现变化
确认成功提交失败提交时行锁后的库存采购单创建返回并发抢占或采购校验失败
支付成功秒杀仍待支付采购来源三字段PayCenterNotify 调用和事务回调失败或历史漏同步
取消后数量未释放采购单是否仍待支付秒杀释放日志/数量字段采购已进入后续状态或释放异常
采购关闭后仍占量po_close_info 和明细closed_qty、关闭补偿关闭回调/服务调用遗漏

17. 改动风险与回归清单

17.1 高风险改动

  • 修改 availableQty、套包数量换算、供给库存选择:可能造成超卖或错误拦截。
  • 修改 submitOrder 事务顺序:可能产生无采购单秒杀单、锁量泄漏或重复采购单。
  • 修改采购来源字段:会让支付、取消、详情和补偿无法关联秒杀单。
  • 修改支付回调事务:可能出现采购已支付但秒杀未转已用。
  • 修改关闭释放:可能重复释放、负数或已支付数量长期占用。
  • 修改活动审批和启用状态:可能让未审批活动提前对服务站可见。

17.2 最小回归集

  • OPS 新增单品草稿、编辑、导入后保存、提交 OA、OA 通过、驳回转草稿。
  • OPS 新增套包、指定仓、生产日期规则、白名单、提前结束。
  • 跨活动同商品冲突能返回完整冲突清单。
  • 无权限/不在名单/未开始/已结束服务站不能看到或购买。
  • 单品加购、包装换算、改量、勾选、批量删除、历史购物车失效。
  • 套包不能加购物车,只能立即购买;套数展开数量正确。
  • 确认页金额、仓库、商品明细和提交结果一致。
  • 两个并发请求抢最后库存,不产生超卖或重复采购单。
  • 支付成功、重复支付回调、支付超时、主动取消均保持状态和数量一致。
  • 采购部分/全部关闭后 closed_qty、locked_qty、used_qty 正确。
  • 三项单独补偿和 runCompensate 可重复运行且不重复扣减。
  • PC 和移动商城对同一活动返回的业务口径一致。

18. 证据索引与待验证项

已由代码确认

  • OPS 请求归一:application/Entries/Activity/FlashActivitySaveRequestEntry.php、FlashActivityActionRequestEntry.php、FlashActivityStationScopeRequestEntry.php。
  • OPS/OA:application/Services/Activity/FlashActivitySer.php::submitFlashActivity()、callback20260526()。
  • 服务站接口:application/Services/Activity/FlashSaleSer.php::goodsList() 至 cancelOrder()。
  • 提交和采购关联:FlashSaleSer::submitOrder()、createPoOrdersForFlashOrder()。
  • 支付和释放:markPaidByPoOrder()、releaseByPoOrder()、releaseByPoCloseItems()。
  • 支付回调:application/controllers/tasks/PayCenterNotify.php。
  • OA 消费:application/controllers/tasks/OaNotify.php。
  • 补偿:application/controllers/tasks/FlashSaleTask.php。
  • 状态:application/KzData/Enums/FlashActivityEnums.php、PoOrderEnums.php。
  • 物理表名:application/config/tables.php。

待环境联调确认

  • 各环境真实 HTTP URL 前缀、网关鉴权头和 OPS 用户头格式。
  • OPS 页面传入 fixed_price/discount_rate 的最终单位与页面转换位置。
  • OA Provider 的超时、重试和外部幂等保证。
  • 定时任务在各环境的 crontab/调度频率和告警接收人。
  • 真实供给库存服务的降级策略、缓存时效和超时行为。

这些待验证项不影响理解当前代码主链路,但上线联调前必须逐项确认并回填本文。

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

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
OPS 创建/提交活动运营后台活动时间、价格规则、商品、服务站范围、库存口径活动 OPS Controller + EntryFlashActivitySerFLASH_ACTIVITY.id活动、商品和范围保存并发起 OA
OA 审批回调OA MQ DEST_OA_NOTIFY模板码、业务 ID、审批结果、回调业务键tasks/OaNotify.phpFlashActivitySer::callback20260526活动 ID + OA 单号活动审批状态更新,允许后续启用
服务站加购/提交E站秒杀页sid、活动商品、数量、收货信息秒杀站端 ControllerFlashSaleSer::goodsList/addCart/submitOrder活动 + sid + SKU购物车锁量并生成秒杀订单、采购单
支付回调支付中心 MQ采购单号、支付单号、成功状态PayCenterNotifymarkPaidByPoOrder采购单与秒杀订单关系订单已支付,锁定量转已用量
超时/关闭补偿定时调度、采购关闭事件超时订单/购物车、采购关闭明细FlashSaleTask.php 或采购关闭调用方releaseByPoOrder、releaseByPoCloseItems、runCompensate秒杀订单/采购单释放锁量、关闭订单,修复遗漏状态

日志证据矩阵

链路段日志来源可检索锚点成功信号失败信号与下一段关联方式
OPS 保存/提交Controller、Entry、FlashActivitySer活动 ID、操作方法、OA 业务号活动及明细 commit,OA 请求返回受理参数单位错误、商品/范围重复、OA 调用失败活动 ID 查三类活动表和 OA 回调
OA 消费OaNotify + callback20260526模板码、活动 ID、审批结果、消息 IDACK 且审批状态正确推进未知模板、活动不存在、重复/乱序活动 ID 回查 FLASH_ACTIVITY.status
加购锁量FlashSaleSer::addCartsid、活动 ID、SKU、购物车 IDlocked_qty 增加且购物车有效库存不足、超限、活动非进行中活动商品 + 购物车数量对账
下单支付submitOrder、PayCenterNotify秒杀单号、采购单号、支付单号订单/采购关系完整,支付后转已用量重复提交、采购创建失败、回调遗漏关系表串联三种单号
定时补偿FlashSaleTask 方法日志task 方法、批次时间、订单/购物车 ID过期记录关闭且锁量释放批次失败、部分释放、反复扫描任务批次 + 订单状态 + 数量守恒

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
保存活动FlashActivitySer活动事务活动参数、商品、站点范围FLASH_ACTIVITY、FLASH_ACTIVITY_GOODS、FLASH_ACTIVITY_STATION_SCOPEinsert/update;活动状态进入草稿/待审批;商品总量和价格落库活动 ID、SKU、sid 范围、更新时间
OA 审批submitFlashActivity、callback20260526本地提交与 OA 非同一事务当前活动状态、OA 结果活动主表、OA MQ/结果提交后待审批;同意后进入可启用状态;拒绝进入拒绝态OA 单号、模板码、状态迁移
加入购物车FlashSaleSer::addCart锁量事务活动进行状态、限购、available = total-locked-usedFLASH_CART、FLASH_ACTIVITY_GOODS购物车 insert/update;locked_qty: old -> old+n活动商品 total >= locked+used
提交订单submitOrder、createPoOrdersForFlashOrder秒杀订单与采购创建边界需联调确认有效购物车、锁量、供给关系FLASH_ORDER、FLASH_ORDER_DETAIL、采购主明细、关系表购物车有效 -> 已使用;秒杀单待支付;锁量暂保留秒杀单号、采购单号、明细数量一致
支付成功markPaidByPoOrder回调事务采购支付成功、订单未处理秒杀订单/活动商品订单 wait_pay -> paid;locked_qty: old -> old-n;used_qty: old -> old+n支付单、关系表、数量守恒
关闭/超时释放releaseByPoOrder、FlashSaleTask幂等释放事务未支付/关闭状态、尚未释放量订单、购物车、活动商品状态 -> closed/invalid;locked_qty: old -> old-n;已用量仅按已支付保留locked >= 0,重复执行数量不再变化

子模块级独立追踪

子模块追踪:flash-config 秒杀活动配置

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
保存OPS 活动保存接口request ID、activity ID、商品、价格、总量、起止时间application/controllers/inner/activity/FlashActivity.php -> Entry -> FlashActivitySer当前活动状态、效期冲突、商品和价格单位活动事务 insert/update FLASH_ACTIVITY/GOODS;配置 old -> new,草稿态保持可编辑request ID + activity ID + SKU;查主表、商品表和操作时间校验失败零写入;更新只允许草稿等代码状态,事务 rollback 后按 activity ID 重试
启停OPS action 接口request ID、activity ID、action、操作人application/controllers/scm/FlashSale.php -> FlashActivitySerOA 结果、当前状态和活动时间事务内 enabled/status old -> new;不直接改变 locked/used 数量activity ID + action + 状态更新时间非法迁移拒绝;已开始活动修改核心配置需按代码限制,不直接 SQL 覆盖

子模块追踪:flash-oa OA 提交与审批回调

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
提交 OAOPS 提交活动request ID、activity ID、模板码、申请人application/Services/Activity/FlashActivitySer.php::submitFlashActivity -> OA Provider草稿配置完整性、当前审批态本地事务 status draft -> pending;OA HTTP 在外部边界创建申请并回传 OA 单号request ID + activity ID + OA request/form IDOA 超时先查外部申请;本地 pending 但外部无单时按 activity ID 幂等补提交
审批回调DEST_OA_NOTIFYmessage ID、模板码、activity ID、审批结果application/controllers/tasks/OaNotify.php -> FlashActivitySer::callback20260526当前活动状态、回调模板和是否已处理单消息事务 pending -> approved/rejected;重复同结果 new -> newtemplate code + activity ID + OA 单号 + message ID未知模板/旧回调告警;外部已批本地失败按 OA 单号重放,不新建审批单

子模块追踪:flash-scope 服务站范围与商品可见性

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
配置范围OPS 保存黑/白名单request ID、activity ID、scope type、sid 集合application/Entries/Activity/FlashActivityStationScopeRequestEntry.php -> FlashActivitySer活动可编辑状态、站点合法性、重复 sid配置事务增删 FLASH_ACTIVITY_STATION_SCOPE;scope set old -> newactivity ID + sid + scope type;比较保存前后集合重复/冲突范围 rollback;不清空后分批写造成半套范围
站端查询E站秒杀商品列表request ID、sid、activity ID、分页application/controllers/inner/moveMall/FlashSale.php -> FlashSaleSer::goodsList活动态、时间、范围、商品状态、供给库存只读事务/无业务表写入;返回 visible true/false 和可购量快照request ID + sid + activity/SKU;对照范围表和服务时间列表不可见先查范围和时间;缓存/供给失败不得伪装为已售罄,按接口降级合同处理

子模块追踪:flash-cart 购物车与原子锁量

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
加购E站 addCartrequest ID、sid、activity ID、SKU、qty、cart IDapplication/Services/Activity/FlashSaleSer.php::addCart活动进行态、限购、available=total-locked-used、旧购物车量锁量事务 insert/update FLASH_CART;locked_qty: old -> old+deltarequest ID + activity+sid+SKU+cart ID;查 cart 与 goods 数量条件更新 0 行返回库存不足;事务失败两表均 rollback,禁止只补 cart 不补 locked
改删购物车更新数量、删除或失效任务cart ID、旧 qty、新 qty、任务批次application/Services/Activity/FlashSaleSer.php / FlashSaleTaskcart 有效态、已锁量和活动时间幂等事务 cart qty/status old -> new;locked_qty: old -> old-deltacart ID + activity/SKU + task batch重复失效必须 0 变化;locked 不得小于 0,异常先按 cart 有效量重算差额

子模块追踪:flash-preview 订单预览与价格库存复核

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
预览E站 preview 接口request ID、sid、cart IDs、地址application/controllers/inner/moveMall/FlashSale.php -> FlashSaleSer 预览方法有效购物车、活动价、供给关系、地址和配送规则只读事务/不写订单表;返回价格、数量和供给拆分快照request ID + sid + cart/activity/SKUcart 失效、价格/范围变化时拒绝;预览成功不表示库存已再次锁定
提交前复核submitOrder 进入事务前request ID、预览 token/购物车、活动版本application/Services/Activity/FlashSaleSer.php::submitOrder 前置校验当前时间、价格、locked、限购和 cart 所有人校验阶段 DB 不变;差异时返回新事实,只有通过才进入下单事务request ID + cart ID + activity update time客户端旧预览不得直接下单;失败保留/释放锁量按当前代码路径,不人工改价格

子模块追踪:flash-submit 秒杀下单与转采购

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
建秒杀单E站 submitOrderrequest ID、sid、cart IDs、收货信息、flash order noapplication/Services/Activity/FlashSaleSer.php::submitOrder有效 cart、锁量、价格、供给拆分和来源唯一性秒杀事务 insert FLASH_ORDER/DETAIL;订单由无 -> wait_pay,cart 有效 -> 已提交request ID + flash order/cart/activity IDs重复提交按稳定请求/购物车关系返回原单;事务失败不得消费 cart 或改变 locked
转采购秒杀单创建后flash order ID/no、供给项、SKU、qtyFlashSaleSer::createPoOrdersForFlashOrder -> application/Services/PoOrders/*秒杀明细、供给仓和地址跨域边界 insert 采购主明细,orderType=30-Cxx-77、srcOrderType=13,来源关系由无 -> 有flash order + PO billNo + SKU部分 PO 创建失败按来源关系识别成功项;只补缺失 PO,不重建秒杀单/已存在采购单

子模块追踪:flash-pay 秒杀支付与锁量转已用

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
支付回调PayCenter 成功事件message ID、payOrderNo、PO billNo、flash order IDapplication/controllers/tasks/PayCenterNotify.php -> FlashSaleSer::markPaidByPoOrder采购支付成功、秒杀单待支付、尚未转换数量回调事务 order wait_pay -> paid;goods locked_qty: old -> old-n、used_qty: old -> old+npayOrderNo + PO + flash order + activity/SKU重复回调 0 增量;支付成功但关系缺失先补关系再按原 PO 处理,不直接改 locked/used
支付查询E站轮询/订单详情request ID、flash/PO/pay orderapplication/controllers/inner/moveMall/FlashSale.php -> FlashSale/支付查询 Service本地订单、采购支付关系和支付中心状态只读,不写业务表;返回当前最终态request ID + 三种单号对照支付回调时间本地待支付但中心成功按回调补偿,不以查询接口直接重复记账

子模块追踪:flash-release 取消、关闭与数量释放

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
主动取消E站 cancelOrder 或采购待支付取消request ID、flash/PO order、取消原因application/Services/Activity/FlashSaleSer.php::cancelOrder/releaseByPoOrder未支付状态、尚未释放 qty、采购关闭结果幂等事务 order wait_pay -> canceled/closed;locked_qty: old -> old-nrequest ID + flash/PO + SKU;比较释放前后数量已支付禁止走未支付释放;重复取消为 0 变化,后续采购关闭失败单独补偿
履约关闭采购关闭明细回传message ID、PO、SKU、close qtyFlashSaleSer::releaseByPoCloseItemspaid/used 数量、已关闭和已释放明细单消息事务按代码从 used 或 locked old -> old-n,closed/released old -> old+nmessage ID + PO + flash detail + SKU关闭数量不得超过关联明细;只补差额,禁止整单重复释放

子模块追踪:flash-compensate 超时与补偿任务

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
超时扫描CLI 定时任务task batch、截止时间、limit、order/cart IDapplication/controllers/tasks/FlashSaleTask.php::expireWaitPayOrders/refreshInvalidCarts超时待支付订单、无效 cart、活动时间每条幂等事务 status old -> expired/invalid,locked old -> old-ntask 方法 + batch + order/cart ID;记录扫描/成功/跳过/失败数批次中断可按游标续跑;已完成记录跳过,不扩大时间窗整表更新
状态补偿compensatePaid/Closed/runCompensatetask batch、PO/flash order、支付/关闭最终态application/controllers/tasks/FlashSaleTask.php -> mark/release Service支付中心、采购和本地秒杀三方事实只补目标差异:wait_pay -> paid 或未释放量 old -> expected;重复运行 DB 不变batch + 三种单号 + before/after补偿前以支付/采购最终态为仲裁;不能只因本地状态旧就猜测改为成功/关闭