4. 生产报工数量异常:报工接口幂等性与重复提交排查
问题定位:报工数量翻倍的三个写入来源
同一工单出现报工数量翻倍,本质是同一笔业务被写入了两次。在食品MES中,报工接口通常由称量/包装工位终端或PDA调用,一次报工包含工单号、工序、报工数量、操作人、时间戳等字段。翻倍现象一般来自三个层面:接口幂等键缺失导致重试即重复、前端防抖失效导致连点多次提交、MES事务边界过大导致部分失败后整单重放。排查时应先确认重复记录的时间差与报工来源终端,再逐层核对。
幂等键设计与接口改造
报工接口的幂等键应包含业务唯一维度:工单号 + 工序号 + 报工批次(或报工流水号)。前端每次进入报工页面时生成一个 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,防止冲销请求本身被重复提交,造成“冲销两次”把数量冲成负数。