4. 生产报工数量异常:报工接口幂等性与重复提交排查

问题定位:报工数量翻倍的三个写入来源

同一工单出现报工数量翻倍,本质是同一笔业务被写入了两次。在食品MES中,报工接口通常由称量/包装工位终端或PDA调用,一次报工包含工单号、工序、报工数量、操作人、时间戳等字段。翻倍现象一般来自三个层面:接口幂等键缺失导致重试即重复、前端防抖失效导致连点多次提交、MES事务边界过大导致部分失败后整单重放。排查时应先确认重复记录的时间差与报工来源终端,再逐层核对。

前端提交层 包装工位终端 / PDA 按钮防抖 · 提交锁 requestId 生成 连点 → 多次请求 报工接口层 POST /report/work 幂等键:工单+工序+requestId 唯一索引 / 去重表 键缺失 → 重复落库 MES事务边界 报工主表 + 工序流水 + 库存扣减 超时重试 / 补偿重放 部分失败 → 整单重放 报工记录重复写入 同一工单数量 = 2 × 实际报工量 重复来源判读(按重复记录时间差与来源终端) 时间差 < 2s 同一终端连续请求 → 前端防抖/提交锁 → 接口幂等键校验 时间差 数秒~分钟 网关/客户端重试 → 幂等键未生效 → 唯一索引缺失 时间差 较长/整批 事务超时后补偿重放 → 事务边界过大 → 拆分事务+状态机

幂等键设计与接口改造

报工接口的幂等键应包含业务唯一维度:工单号 + 工序号 + 报工批次(或报工流水号)。前端每次进入报工页面时生成一个 requestId(UUID),随请求体提交;接口层以 requestId 作为去重主键写入 mes_report_idempotent 表,命中则直接返回首次结果,不再执行写入。这样即使网关重试或用户连点,也只会落一条报工记录。

-- 幂等去重表(MySQL 示例)
CREATE TABLE mes_report_idempotent (
  request_id   VARCHAR(64)  NOT NULL COMMENT '前端生成的请求唯一号',
  work_order   VARCHAR(32)  NOT NULL COMMENT '工单号',
  process_code VARCHAR(16)  NOT NULL COMMENT '工序编码',
  report_qty   DECIMAL(12,3) NOT NULL COMMENT '本次报工数量',
  status       TINYINT      NOT NULL DEFAULT 0 COMMENT '0处理中 1成功 2失败',
  created_at   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (request_id),
  UNIQUE KEY uk_biz (work_order, process_code, request_id)
) ENGINE=InnoDB;

接口伪代码逻辑:先 INSERT ... ON DUPLICATE KEY 抢占幂等键,插入成功才执行业务写入;插入冲突说明是重复请求,直接查询首次结果返回。业务写入与幂等状态更新放在同一本地事务内,避免“幂等键已占、业务未落”的中间态。

@Transactional
public ReportResult report(ReportCmd cmd) {
    int inserted = idempotentMapper.tryInsert(cmd.getRequestId(),
            cmd.getWorkOrder(), cmd.getProcessCode(), cmd.getQty());
    if (inserted == 0) {
        // 重复请求:返回首次处理结果,不再写业务表
        return idempotentMapper.queryResult(cmd.getRequestId());
    }
    reportMapper.insertReport(cmd);          // 报工主表
    reportMapper.insertProcessFlow(cmd);     // 工序流水
    stockMapper.deduct(cmd);                 // 库存扣减
    idempotentMapper.markSuccess(cmd.getRequestId());
    return ReportResult.ok(cmd.getRequestId());
}
前端侧同步加两道防线:提交按钮点击后立即置灰并加 submitting 标志;页面刷新或重新进入时复用同一 requestId,直到收到成功响应才重置。这样即使接口幂等键暂时未上线,也能挡住大部分连点重复。

已发生重复数据的冲销方案

对已经翻倍的报工记录,不能直接物理删除(会破坏工序流水与批次追溯)。正确做法是生成红字冲销单:以原报工记录为源,冲销数量 = 重复量,冲销单关联原单号并标记 reverse_of,同时回补库存。冲销后工单累计报工量应等于实际产出量,并在电子批记录中留下冲销原因与操作人签名,满足食品追溯审计要求。

检查项正常判据异常处置
幂等键是否包含 requestId请求体含唯一 requestId 且落库补字段、加唯一索引
重复记录时间差< 2s 属前端连点加防抖与提交锁
事务边界报工+流水+库存同一事务拆分长事务,加状态机
已重复数据冲销单数量 = 重复量红字冲销并回补库存
冲销操作必须与报工写入使用同一幂等机制:冲销单同样携带 requestId,防止冲销请求本身被重复提交,造成“冲销两次”把数量冲成负数。