Browse Source

去掉agv任务应用锁

master
常熟吴彦祖 3 weeks ago
parent
commit
8533f928a2
  1. 4
      src/main/java/com/gaotao/modules/autoWareHourceHaian/HAIAN_AI_CONTEXT.md
  2. 51
      src/main/java/com/gaotao/modules/autoWareHourceHaian/api/service/impl/HaiAnWmsMessageServiceImpl.java
  3. 28
      src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/HaiAnTransportTaskDetailLockParam.java
  4. 19
      src/main/java/com/gaotao/modules/autoWareHourceHaian/mapper/HaiAnAgvTaskMapper.java
  5. 70
      src/main/resources/mapper/autoWareHourceHaian/HaiAnAgvTaskMapper.xml

4
src/main/java/com/gaotao/modules/autoWareHourceHaian/HAIAN_AI_CONTEXT.md

@ -98,8 +98,8 @@
- 自动发料只按 `site + transport_task_no` 防重,每次搬运只发一次;同标签退料再入库后用新任务号允许再次发料。`transport_detail_id` 仅用于回写,不得按标签号、托盘号或RFID明细ID跨搬运拦截。查重使用事务内更新/范围锁和搬运唯一索引,存量库执行 `sql/20260920_haian_auto_issue_taskno_unique.sql`;重复回调复用已有任务并收口,不重发、不重复通知WCS。- rqrq - 自动发料只按 `site + transport_task_no` 防重,每次搬运只发一次;同标签退料再入库后用新任务号允许再次发料。`transport_detail_id` 仅用于回写,不得按标签号、托盘号或RFID明细ID跨搬运拦截。查重使用事务内更新/范围锁和搬运唯一索引,存量库执行 `sql/20260920_haian_auto_issue_taskno_unique.sql`;重复回调复用已有任务并收口,不重发、不重复通知WCS。- rqrq
- 海安非归档自动任务统一用 `scheduler.haianAutoTask.enabled` 控制:`HaianAutoTaskScheduler`(IFS移库、齐套回写、自动发料)与 `HaianWcsSchedule`(AGV回调、WCS出入库回调、栈板扫描回调)共用这一项,禁止再拆 `haianWcsSchedule.enabled`/`callback-enabled`;归档仍独立用 `scheduler.haianDataArchive.enabled`。自动发料建单另受 `scheduler.haianAutoTask.autoIssue.enabled` 控制(默认 `false`):关闭时 RFID 复核通过不调用 `createFromRfidCheck`,不挂 `待自动发料`,直接完结明细并 type=1。间隔/超时可继续用 `kit-transport-fixed-delay-ms`、`ifsPalletMoveQueue.processing-timeout-minutes`、`autoIssue.fixed-delay-ms`、`autoIssue.processing-timeout-minutes`;捞单站点写死 `HaiAnSiteStaticData.HAIAN_SITE` - rqrq - 海安非归档自动任务统一用 `scheduler.haianAutoTask.enabled` 控制:`HaianAutoTaskScheduler`(IFS移库、齐套回写、自动发料)与 `HaianWcsSchedule`(AGV回调、WCS出入库回调、栈板扫描回调)共用这一项,禁止再拆 `haianWcsSchedule.enabled`/`callback-enabled`;归档仍独立用 `scheduler.haianDataArchive.enabled`。自动发料建单另受 `scheduler.haianAutoTask.autoIssue.enabled` 控制(默认 `false`):关闭时 RFID 复核通过不调用 `createFromRfidCheck`,不挂 `待自动发料`,直接完结明细并 type=1。间隔/超时可继续用 `kit-transport-fixed-delay-ms`、`ifsPalletMoveQueue.processing-timeout-minutes`、`autoIssue.fixed-delay-ms`、`autoIssue.processing-timeout-minutes`;捞单站点写死 `HaiAnSiteStaticData.HAIAN_SITE` - rqrq
- 海安 AGV 反馈接口 `POST /api/wms54/count/agvCallback` 必填 `site/wmsId/taskId/托盘号/targetPoint/targetArea/type`;托盘号兼容 `palletId` 与 `pallet`,两者都有时优先 `palletId`。`targetPoint` 命中三个端口时按 `site+A01` 解析区域,允许在途旧区域码但不依赖旧端口区域记录;其它点位仍按 `haian_area.wcs_area_code -> area_id` 转换后写明细 `to_area`,禁止改变车间等最终目的地。解析后的托盘号在“下达任务”场景写入明细 `pallet_id`;`comment` 在“下达任务”场景写 `source_bill_no`、在RFID复核场景保存复核备注 - rqrq - 海安 AGV 反馈接口 `POST /api/wms54/count/agvCallback` 必填 `site/wmsId/taskId/托盘号/targetPoint/targetArea/type`;托盘号兼容 `palletId` 与 `pallet`,两者都有时优先 `palletId`。`targetPoint` 命中三个端口时按 `site+A01` 解析区域,允许在途旧区域码但不依赖旧端口区域记录;其它点位仍按 `haian_area.wcs_area_code -> area_id` 转换后写明细 `to_area`,禁止改变车间等最终目的地。解析后的托盘号在“下达任务”场景写入明细 `pallet_id`;`comment` 在“下达任务”场景写 `source_bill_no`、在RFID复核场景保存复核备注 - rqrq
- 海安 AGV 回调采用“两段式”:第一段接口仅写 `haian_wms_transport_task_detail(status=已录入)`;第二段由 `HaianWcsSchedule.scheduleAgvFeedback(3.1秒)` 乐观锁消费明细并驱动主任务/托盘状态(下达=calling_flag:Y并创建主任务含from_area、取货=托运中、放货完成=完成+按需移库+回写area/location_code且不改wcs_location、取消=已取消且calling_flag:N)。放货完成若按来源任务号或搬运任务号命中同site、同栈板、类型为“运输栈板”的立库任务,须在本次回调事务内完成该立库主单和明细;外部来源不联动。协议正式 type 只有 `放货完成`,历史 `送货完成` 入库/消费前归一成 `放货完成`;同步RFID复核接口与本回调共用任务级插入锁和明细查重口径。- rqrq
- 海安同步 RFID 复核 `POST /api/wms54/inbound/verifyPalletRfid` 必填 `site=54/taskNo/palletId`;`taskNo` 原样对应 `haian_wms_transport_task_detail.task_no`,但不要求运输主表存在,也不校验任务与托盘的归属。按 `site+taskNo+action_type=RFID复核` 只认首次:首次按site+palletId一致性读取标签并比较,写 `item_no=1/status=已录入/comment=通过或不通过/pallet_id`,失败再写 `error_code=复核失败/error_msg=原立库resMsg`,完整响应存 `rfid_verify_result_json`;重复请求不改首条,返回首次响应。字段及唯一索引升级脚本为 `sql/20261018_haian_rfid_verify_first_result.sql`,归档过程需同步复制响应快照。- rqrq
- 海安 AGV 回调采用“两段式”:第一段接口仅写 `haian_wms_transport_task_detail(status=已录入)`;第二段由 `HaianWcsSchedule.scheduleAgvFeedback(3.1秒)` 乐观锁消费明细并驱动主任务/托盘状态(下达=calling_flag:Y并创建主任务含from_area、取货=托运中、放货完成=完成+按需移库+回写area/location_code且不改wcs_location、取消=已取消且calling_flag:N)。放货完成若按来源任务号或搬运任务号命中同site、同栈板、类型为“运输栈板”的立库任务,须在本次回调事务内完成该立库主单和明细;外部来源不联动。协议正式 type 只有 `放货完成`,旧名称 `送货完成` 入库/消费前归一成 `放货完成`;接收阶段不申请运输明细写入应用锁,仅按请求 `site+wmsId(taskNo)+type(action_type)` 查询在线表,任意状态已有记录即幂等跳过,不查询 `_history`。- rqrq
- 海安同步 RFID 复核 `POST /api/wms54/inbound/verifyPalletRfid` 必填 `site=54/taskNo/palletId`;`taskNo` 原样对应 `haian_wms_transport_task_detail.task_no`,但不要求运输主表存在,也不校验任务与托盘的归属。仅按在线表 `site+taskNo+action_type=RFID复核` 判断幂等,不申请写入应用锁,不查询 `_history`;在线已有记录时返回首次响应。在线无记录时按site+palletId一致性读取标签并比较,写 `item_no=1/status=已录入/comment=通过或不通过/pallet_id`,失败再写 `error_code=复核失败/error_msg=原立库resMsg`,完整响应存 `rfid_verify_result_json`。字段及唯一索引升级脚本为 `sql/20261018_haian_rfid_verify_first_result.sql`,归档过程同步复制响应快照仅供追溯。- rqrq
- 海安 RFID复核消费(`action_type=RFID复核`)只用明细 `site/task_no/pallet_id/comment`,不要求运输主表。`comment` 去空格后不等于 `通过`:先把明细改 `已完成` 并保留首次复核 `error_code/error_msg`,再调 `notifyOutboundPortRfidPalletAction(type=2)` 去异常位;等于 `通过`:先查 `haian_pallet_detail`;无标签则明细改 `已完成` 后再调 `type=1`;有标签且 `scheduler.haianAutoTask.autoIssue.enabled=true` 时,收集全部 `reserve_flag=Y` 且 `reserve_order_ref1` 能关联 `haian_SOIssueNotifyHeader(site+notify_no)` 且 `order_type=shoporder` 的预定标签:只要有一张命中,就写自动发料任务并调用现有海安移库入队,RFID 明细改 `待自动发料`(不进 AGV 定时扫描、不调立库);开关关闭或未命中则明细改 `已完成` 后再调 `type=1`。立库接口返回仅供记录,托盘移动由定时消费后下发 `POST /api/wms/outbound/rfid-action`;WCS调用失败由明细事务回滚后重试。- rqrq - 海安 RFID复核消费(`action_type=RFID复核`)只用明细 `site/task_no/pallet_id/comment`,不要求运输主表。`comment` 去空格后不等于 `通过`:先把明细改 `已完成` 并保留首次复核 `error_code/error_msg`,再调 `notifyOutboundPortRfidPalletAction(type=2)` 去异常位;等于 `通过`:先查 `haian_pallet_detail`;无标签则明细改 `已完成` 后再调 `type=1`;有标签且 `scheduler.haianAutoTask.autoIssue.enabled=true` 时,收集全部 `reserve_flag=Y` 且 `reserve_order_ref1` 能关联 `haian_SOIssueNotifyHeader(site+notify_no)` 且 `order_type=shoporder` 的预定标签:只要有一张命中,就写自动发料任务并调用现有海安移库入队,RFID 明细改 `待自动发料`(不进 AGV 定时扫描、不调立库);开关关闭或未命中则明细改 `已完成` 后再调 `type=1`。立库接口返回仅供记录,托盘移动由定时消费后下发 `POST /api/wms/outbound/rfid-action`;WCS调用失败由明细事务回滚后重试。- rqrq
- 海安自动发料移库目标区域写死 `HaiAnAreaStaticData.AUTO_ISSUE_TRANSFER_AREA = "A99"`,实际仓库/库位按显式site+A99查询haian_area配置,缺少映射中断建单。`HaiAnPalletTransferService.doWareHouseForPalletForAuto` 回参改为实体 `HaiAnPalletTransferEnqueueResult`(`queueIds`+`message`),无需搬库时 `queueIds` 为空列表并视为 IFS 已完成;三个原调用方仍忽略回参,禁止改 `haian_ifs_pallet_move_queue` 表结构,禁止改老模块 `WmsMessageService.doWareHouseForPalletForAuto` - rqrq - 海安自动发料移库目标区域写死 `HaiAnAreaStaticData.AUTO_ISSUE_TRANSFER_AREA = "A99"`,实际仓库/库位按显式site+A99查询haian_area配置,缺少映射中断建单。`HaiAnPalletTransferService.doWareHouseForPalletForAuto` 回参改为实体 `HaiAnPalletTransferEnqueueResult`(`queueIds`+`message`),无需搬库时 `queueIds` 为空列表并视为 IFS 已完成;三个原调用方仍忽略回参,禁止改 `haian_ifs_pallet_move_queue` 表结构,禁止改老模块 `WmsMessageService.doWareHouseForPalletForAuto` - rqrq
- 海安自动发料定时任务只读 IFS 队列表:关联 `queueId` 存在 N/P 则继续等;全部关联队列均为Y后,任一条 `result_flag=F` 则不发料,任务与 RFID 明细都改 `已完成`,`error_code=移库失败`,覆盖写 `haian_pallet.remark` 与 RFID `error_msg`,再调立库 `type=2`。全部 `Y` 且全 `S`(或无队列)后:实时查 `handling_unit`,用 `haian_SOIssueNotifyOrderMaterialList_detail` 关联 `haian_SOIssueNotifyOrderList` 按 `site+soorder_no+release+sequence` 分组,经 `HaiAnProductionAutoIssueAdapter` 调用同事 `AutoIssueService.autoIssue`(不改同事方法,`labelCode` 空、`workOrderNo` 空、release/sequence 空填 `*`、`lineItemNo` 由 `BOM_item_no` 转 Integer 失败为 0、`issueQty=qty` 全发)。回参只有 failures,成功标签用送出清单减去失败 handlingUnitId 反推;全成功则解绑、`type=1`、不写失败码;只要有一张失败则成功的解绑、失败的不解绑、`type=2`、`error_code=发料失败`。不论成败先改本地再调立库。已 `issue_flag=S` 的标签禁止再送同事 - rqrq - 海安自动发料定时任务只读 IFS 队列表:关联 `queueId` 存在 N/P 则继续等;全部关联队列均为Y后,任一条 `result_flag=F` 则不发料,任务与 RFID 明细都改 `已完成`,`error_code=移库失败`,覆盖写 `haian_pallet.remark` 与 RFID `error_msg`,再调立库 `type=2`。全部 `Y` 且全 `S`(或无队列)后:实时查 `handling_unit`,用 `haian_SOIssueNotifyOrderMaterialList_detail` 关联 `haian_SOIssueNotifyOrderList` 按 `site+soorder_no+release+sequence` 分组,经 `HaiAnProductionAutoIssueAdapter` 调用同事 `AutoIssueService.autoIssue`(不改同事方法,`labelCode` 空、`workOrderNo` 空、release/sequence 空填 `*`、`lineItemNo` 由 `BOM_item_no` 转 Integer 失败为 0、`issueQty=qty` 全发)。回参只有 failures,成功标签用送出清单减去失败 handlingUnitId 反推;全成功则解绑、`type=1`、不写失败码;只要有一张失败则成功的解绑、失败的不解绑、`type=2`、`error_code=发料失败`。不论成败先改本地再调立库。已 `issue_flag=S` 的标签禁止再送同事 - rqrq

51
src/main/java/com/gaotao/modules/autoWareHourceHaian/api/service/impl/HaiAnWmsMessageServiceImpl.java

@ -24,7 +24,6 @@ import com.gaotao.modules.autoWareHourceHaian.api.entity.HaiAnRobotErrorRequest;
import com.gaotao.modules.autoWareHourceHaian.api.entity.HaiAnWcsInboundCallbackRequest; import com.gaotao.modules.autoWareHourceHaian.api.entity.HaiAnWcsInboundCallbackRequest;
import com.gaotao.modules.autoWareHourceHaian.api.service.HaiAnWmsMessageService; import com.gaotao.modules.autoWareHourceHaian.api.service.HaiAnWmsMessageService;
import com.gaotao.modules.autoWareHourceHaian.entity.HaiAnCountResultInsertData; import com.gaotao.modules.autoWareHourceHaian.entity.HaiAnCountResultInsertData;
import com.gaotao.modules.autoWareHourceHaian.entity.HaiAnTransportTaskDetailLockParam;
import com.gaotao.modules.autoWareHourceHaian.entity.pda.HaiAnAreaOption; import com.gaotao.modules.autoWareHourceHaian.entity.pda.HaiAnAreaOption;
import com.gaotao.modules.autoWareHourceHaian.entity.pda.HaiAnHandlingUnitLabelRow; import com.gaotao.modules.autoWareHourceHaian.entity.pda.HaiAnHandlingUnitLabelRow;
import com.gaotao.modules.autoWareHourceHaian.entity.pda.HaiAnPalletDetailRow; import com.gaotao.modules.autoWareHourceHaian.entity.pda.HaiAnPalletDetailRow;
@ -184,7 +183,7 @@ public class HaiAnWmsMessageServiceImpl implements HaiAnWmsMessageService {
} }
/** /**
* @Description 立库同步RFID复核:同site+taskNo只认首次结果;首次比对后先写已录入明细,再提交并返回原立库报文。- rqrq
* @Description 立库同步RFID复核:在线表同site+taskNo+RFID复核动作已有记录时复用首次结果,否则比对并写入已录入明细后返回立库报文。- rqrq
* @param data site、搬运taskNo、palletId必填;rfidBarcodes空值按空盘比对,不要求运输主表已存在。- rqrq * @param data site、搬运taskNo、palletId必填;rfidBarcodes空值按空盘比对,不要求运输主表已存在。- rqrq
* @return 首次响应的完整字段;重复调用返回已保存的首次响应,不因再次扫描改变运输动作。- rqrq * @return 首次响应的完整字段;重复调用返回已保存的首次响应,不因再次扫描改变运输动作。- rqrq
*/ */
@ -206,9 +205,7 @@ public class HaiAnWmsMessageServiceImpl implements HaiAnWmsMessageService {
throw new RuntimeException("RFID复核taskNo或palletId超过运输明细字段长度"); throw new RuntimeException("RFID复核taskNo或palletId超过运输明细字段长度");
} }
// 与AGV回调共用事务级任务锁;锁失败中断首次复核写入,不查询或要求运输主表存在。- rqrq
acquireTransportTaskDetailInsertLock(site, taskNo);
// 锁内先读取历史及在线首条;重复请求直接返回首轮响应,禁止用本次扫描覆盖复核事实。- rqrq
// 仅按请求site+taskNo+RFID复核动作查询在线首条;已存在则返回首次响应,查询失败中断,不申请应用锁。- rqrq
HaiAnWmsTransportTaskDetail firstDetail = haiAnAgvTaskMapper.getFirstRfidVerifyDetail( HaiAnWmsTransportTaskDetail firstDetail = haiAnAgvTaskMapper.getFirstRfidVerifyDetail(
site, taskNo, HaiAnAgvCallbackTypeStaticData.RFID_CHECK); site, taskNo, HaiAnAgvCallbackTypeStaticData.RFID_CHECK);
if (firstDetail != null) { if (firstDetail != null) {
@ -265,7 +262,7 @@ public class HaiAnWmsMessageServiceImpl implements HaiAnWmsMessageService {
+ differenceType + "," + differenceDetail); + differenceType + "," + differenceDetail);
result.setActionDirective("EXIT_TO_INBOUND_PORT"); result.setActionDirective("EXIT_TO_INBOUND_PORT");
} }
// 锁内按本任务现有步骤分配序号;失败回滚复核事务,避免只返回立库结果而无待消费明细。- rqrq
// 按在线任务现有步骤分配序号;失败回滚复核事务,避免只返回立库结果而无待消费明细。- rqrq
Integer nextSeqNo = haiAnAgvTaskMapper.getNextSeqNoForTaskDetail(site, taskNo); Integer nextSeqNo = haiAnAgvTaskMapper.getNextSeqNoForTaskDetail(site, taskNo);
if (nextSeqNo == null || nextSeqNo <= 0) { if (nextSeqNo == null || nextSeqNo <= 0) {
throw new RuntimeException("RFID复核生成运输明细序号失败,taskNo=" + taskNo); throw new RuntimeException("RFID复核生成运输明细序号失败,taskNo=" + taskNo);
@ -285,6 +282,7 @@ public class HaiAnWmsMessageServiceImpl implements HaiAnWmsMessageService {
// 首次响应和RFID业务错误同事务写入海安运输明细;重复保护返回0时回读首条,绝不返回未落库的新判定。- rqrq // 首次响应和RFID业务错误同事务写入海安运输明细;重复保护返回0时回读首条,绝不返回未落库的新判定。- rqrq
int insertedRows = haiAnAgvTaskMapper.insertTransportTaskDetail(detail); int insertedRows = haiAnAgvTaskMapper.insertTransportTaskDetail(detail);
if (insertedRows <= 0) { if (insertedRows <= 0) {
// 插入前已出现相同在线业务键时回读首次响应;未找到记录则回滚,禁止返回未保存的扫描结果。- rqrq
HaiAnWmsTransportTaskDetail concurrentFirst = haiAnAgvTaskMapper.getFirstRfidVerifyDetail( HaiAnWmsTransportTaskDetail concurrentFirst = haiAnAgvTaskMapper.getFirstRfidVerifyDetail(
site, taskNo, HaiAnAgvCallbackTypeStaticData.RFID_CHECK); site, taskNo, HaiAnAgvCallbackTypeStaticData.RFID_CHECK);
if (concurrentFirst == null) { if (concurrentFirst == null) {
@ -315,7 +313,7 @@ public class HaiAnWmsMessageServiceImpl implements HaiAnWmsMessageService {
} }
return saved; return saved;
} }
// 历史AGV回调不具备首次同步响应;仅从首条comment恢复通过/失败,避免重复扫描改变定时任务既定动作。- rqrq
// 旧AGV在线明细不具备首次同步响应;仅从首条comment恢复通过/失败,避免重复扫描改变定时任务既定动作。- rqrq
boolean passed = HaiAnRfidCheckResultStaticData.PASS.equals( boolean passed = HaiAnRfidCheckResultStaticData.PASS.equals(
detail.getComment() == null ? null : detail.getComment().trim()); detail.getComment() == null ? null : detail.getComment().trim());
HaiAnInboundRfidVerifyResult result = new HaiAnInboundRfidVerifyResult(); HaiAnInboundRfidVerifyResult result = new HaiAnInboundRfidVerifyResult();
@ -331,25 +329,6 @@ public class HaiAnWmsMessageServiceImpl implements HaiAnWmsMessageService {
return result; return result;
} }
/**
* @Description 通过单次Callable取得同site+taskNo的事务级应用锁;两入口共用锁键并在锁内分配明细序号。- rqrq
* @param site 请求大site,必须已按入口规则校验,不能取登录态。- rqrq
* @param taskNo 搬运任务号,直接对应明细task_no,不要求主任务存在。- rqrq
*/
private void acquireTransportTaskDetailInsertLock(String site, String taskNo) {
if (!StringUtils.hasText(site) || !StringUtils.hasText(taskNo)) {
throw new RuntimeException("运输明细写入锁缺少site或taskNo");
}
HaiAnTransportTaskDetailLockParam lockParam = new HaiAnTransportTaskDetailLockParam(site, taskNo);
// 单次调用SQL Server sp_getapplock并读取返回码;数据库异常或负码须回滚,禁止无锁写入首次复核。- rqrq
haiAnAgvTaskMapper.lockTransportTaskDetailInsert(lockParam);
Integer lockResult = lockParam.getLockResult();
if (lockResult == null || lockResult < 0) {
throw new RuntimeException("取得运输明细写入锁失败,site=" + site + ",taskNo=" + taskNo
+ ",lockResult=" + lockResult);
}
}
/** /**
* @author rqrq * @author rqrq
* @Description 处理海安立库出入库回调:按事务类型分流落库,到达出库口(原取货通知)执行幂等校验,目标区域固定A01并映射仓库/库位;仅认新中文 - rqrq * @Description 处理海安立库出入库回调:按事务类型分流落库,到达出库口(原取货通知)执行幂等校验,目标区域固定A01并映射仓库/库位;仅认新中文 - rqrq
@ -1523,22 +1502,18 @@ public class HaiAnWmsMessageServiceImpl implements HaiAnWmsMessageService {
// 托盘号兼容对端pallet与内部palletId,两者都有时取palletId,避免协议字段名不一致导致下达任务落空托盘 - rqrq // 托盘号兼容对端pallet与内部palletId,两者都有时取palletId,避免协议字段名不一致导致下达任务落空托盘 - rqrq
String palletId = data.resolvePalletId(); String palletId = data.resolvePalletId();
String targetPoint = data.getTargetPoint().trim(); String targetPoint = data.getTargetPoint().trim();
// 按目标站点先识别立库三个口,再解析其它区域;缺少映射时保留WCS原码,仍须保存回调轨迹。- rqrq
String targetArea = convertAgvTargetAreaToBusinessArea(site, data.getTargetArea(), targetPoint);
String normalizedType = normalizeAgvCallbackType(data.getType()); String normalizedType = normalizeAgvCallbackType(data.getType());
// 2. 只写haian_wms_transport_task_detail:task_no用wmsId,状态统一“已录入”,targetArea落to_area,不校验主记录存在 - rqrq // 2. 只写haian_wms_transport_task_detail:task_no用wmsId,状态统一“已录入”,targetArea落to_area,不校验主记录存在 - rqrq
// 与同步RFID复核共用事务级任务锁;失败中断,避免两入口并发分到同一seqNo或产生两条首次复核。- rqrq
acquireTransportTaskDetailInsertLock(site, wmsId);
// 旧AGV接口的RFID动作也须遵守首次结果;历史或在线已有复核时直接幂等返回,不在归档后补写第二条。- rqrq
if (HaiAnAgvCallbackTypeStaticData.RFID_CHECK.equals(normalizedType)) {
HaiAnWmsTransportTaskDetail firstRfidDetail = haiAnAgvTaskMapper.getFirstRfidVerifyDetail(
site, wmsId, HaiAnAgvCallbackTypeStaticData.RFID_CHECK);
if (firstRfidDetail != null) {
updateSuccessLog(logId, messageId + " | RFID复核已按首次结果录入,跳过重复回调", startTime);
return messageId;
}
// 仅按请求site+wmsId(taskNo)+归一化type查询在线明细;任意状态已有记录即幂等跳过,查询失败回滚,不申请应用锁。- rqrq
Integer existingCount = haiAnAgvTaskMapper.countTaskDetailByType(site, wmsId, normalizedType);
if (existingCount != null && existingCount > 0) {
// 重复回调仅补齐本次接口成功日志,不覆盖在线明细及其首次业务结果。- rqrq
updateSuccessLog(logId, messageId + " | 同site+taskNo+type在线明细已存在,跳过重复回调", startTime);
return messageId;
} }
// 在线无同动作记录才解析目标区域;按site与站点识别立库口,其它区域缺少映射时保留WCS原码供消费重试。- rqrq
String targetArea = convertAgvTargetAreaToBusinessArea(site, data.getTargetArea(), targetPoint);
// 序号仍按当前最大步骤+1;插入语句内按动作去重,已存在则返回0。- rqrq // 序号仍按当前最大步骤+1;插入语句内按动作去重,已存在则返回0。- rqrq
Integer nextSeqNo = haiAnAgvTaskMapper.getNextSeqNoForTaskDetail(site, wmsId); Integer nextSeqNo = haiAnAgvTaskMapper.getNextSeqNoForTaskDetail(site, wmsId);
if (nextSeqNo == null || nextSeqNo <= 0) { if (nextSeqNo == null || nextSeqNo <= 0) {

28
src/main/java/com/gaotao/modules/autoWareHourceHaian/entity/HaiAnTransportTaskDetailLockParam.java

@ -1,28 +0,0 @@
package com.gaotao.modules.autoWareHourceHaian.entity;
import lombok.Data;
import java.util.Locale;
/**
* @Description 海安运输明细事务级应用锁参数;将site与搬运任务号归一为同一锁键,并承接SQL Server过程返回码。- rqrq
*/
@Data
public class HaiAnTransportTaskDetailLockParam {
/** 应用锁资源名,由请求大site和搬运taskNo组成;仅传给sp_getapplock,不落库。- rqrq */
private String resource;
/** 过程返回码,由sp_getapplock写回;非负为取得锁,负数须使调用方事务回滚。- rqrq */
private Integer lockResult;
/**
* @Description 按数据库业务键的大小写不敏感口径构造锁键,供同步复核与AGV回调共用。- rqrq
* @param site 请求大site;调用方已校验非空。- rqrq
* @param taskNo 搬运任务号;调用方已校验非空。- rqrq
*/
public HaiAnTransportTaskDetailLockParam(String site, String taskNo) {
this.resource = "HAIAN_TRANSPORT_DETAIL:" + site.toUpperCase(Locale.ROOT)
+ ":" + taskNo.toUpperCase(Locale.ROOT);
}
}

19
src/main/java/com/gaotao/modules/autoWareHourceHaian/mapper/HaiAnAgvTaskMapper.java

@ -3,7 +3,6 @@ package com.gaotao.modules.autoWareHourceHaian.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gaotao.modules.autoWareHourceHaian.entity.HaiAnWmsTransportTaskData; import com.gaotao.modules.autoWareHourceHaian.entity.HaiAnWmsTransportTaskData;
import com.gaotao.modules.autoWareHourceHaian.entity.HaiAnTransportTaskDetailLockParam;
import com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsOrderTask; import com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsOrderTask;
import com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsTransportTask; import com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsTransportTask;
import com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsTransportTaskDetail; import com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsTransportTaskDetail;
@ -65,22 +64,16 @@ public interface HaiAnAgvTaskMapper {
/** /**
* @author rqrq * @author rqrq
* @Description 按site+taskNo查询当前最大步骤序号并返回下一个序号;调用方须先持有同任务的事务级应用锁,避免并发插入序号冲突。- rqrq
* @Description 按site+taskNo查询在线明细最大步骤序号并返回下一个序号;不申请应用锁,并发序号冲突由数据库主键拒绝并回滚。- rqrq
*/ */
Integer getNextSeqNoForTaskDetail(@Param("site") String site, @Param("taskNo") String taskNo); Integer getNextSeqNoForTaskDetail(@Param("site") String site, @Param("taskNo") String taskNo);
/** /**
* @Description 通过单次Callable调用对同site、同任务号的插入取得事务级应用锁;不放宽Druid多语句限制。- rqrq
* @param param 由请求site和搬运taskNo组成的锁键;过程返回码写入lockResult,负数须回滚。- rqrq
*/
void lockTransportTaskDetailInsert(HaiAnTransportTaskDetailLockParam param);
/**
* @Description 按site、taskNo和RFID动作读取最早落库的复核明细;历史优先于在线表,供重复请求返回首次响应。- rqrq
* @Description 仅按site、taskNo和RFID动作读取在线首条复核明细,供重复请求返回首次响应;不查询历史表。- rqrq
* @param site 请求大site,隔离任务。- rqrq * @param site 请求大site,隔离任务。- rqrq
* @param taskNo 必填搬运任务号,直接对应明细task_no。- rqrq * @param taskNo 必填搬运任务号,直接对应明细task_no。- rqrq
* @param actionType 固定RFID复核,禁止匹配其它动作。- rqrq * @param actionType 固定RFID复核,禁止匹配其它动作。- rqrq
* @return 首条复核明细,未录入时为空;历史旧回调可能没有响应快照。- rqrq
* @return 在线首条复核明细,未录入时为空;旧AGV回调可能没有响应快照。- rqrq
*/ */
HaiAnWmsTransportTaskDetail getFirstRfidVerifyDetail(@Param("site") String site, HaiAnWmsTransportTaskDetail getFirstRfidVerifyDetail(@Param("site") String site,
@Param("taskNo") String taskNo, @Param("taskNo") String taskNo,
@ -88,8 +81,8 @@ public interface HaiAnAgvTaskMapper {
/** /**
* @author rqrq * @author rqrq
* @Description 按site+taskNo+actionType统计已提交明细条数,供取消回调判断是否已取货 - rqrq
* 普通读,不加UPDLOCK/HOLDLOCK:调用方事务已按id锁住当前明细,再扫任务号范围会和回调插入死锁 - rqrq
* @Description 仅按site+taskNo+actionType统计在线明细,供AGV接收幂等及取消时判断已取货;放货完成兼容旧送货完成名称。- rqrq
* 普通读,不加UPDLOCK/HOLDLOCK:消费阶段已按id锁住当前明细,再扫任务号范围会和回调插入死锁。- rqrq
* 不校验托盘号;查不到返回0,查询失败由调用方事务回滚 - rqrq * 不校验托盘号;查不到返回0,查询失败由调用方事务回滚 - rqrq
*/ */
Integer countTaskDetailByType(@Param("site") String site, Integer countTaskDetailByType(@Param("site") String site,
@ -98,7 +91,7 @@ public interface HaiAnAgvTaskMapper {
/** /**
* @author rqrq * @author rqrq
* @Description 写入haian_wms_transport_task_detail;同site+taskNo+actionType已存在则不插,放货完成同时把历史送货完成视为已存在 - rqrq
* @Description 写入haian_wms_transport_task_detail;同site+taskNo+actionType在线已存在则不插,放货完成同时把旧名称送货完成视为已存在。- rqrq
* 返回1表示新插入,返回0表示幂等跳过;主键序号冲突会抛异常并由调用方事务回滚,避免静默覆盖 - rqrq * 返回1表示新插入,返回0表示幂等跳过;主键序号冲突会抛异常并由调用方事务回滚,避免静默覆盖 - rqrq
*/ */
int insertTransportTaskDetail(HaiAnWmsTransportTaskDetail detail); int insertTransportTaskDetail(HaiAnWmsTransportTaskDetail detail);

70
src/main/resources/mapper/autoWareHourceHaian/HaiAnAgvTaskMapper.xml

@ -186,7 +186,7 @@
ORDER BY seq_no ASC ORDER BY seq_no ASC
</select> </select>
<!-- 同任务所有写入方先取得事务级应用锁,再一致性读取下一个序号;脏读会固化错误seq_no,禁止NOLOCK。- rqrq -->
<!-- 仅按site+taskNo读取在线明细的下一个序号,不申请应用锁;并发序号冲突由主键拒绝并回滚,禁止脏读固化错误序号。- rqrq -->
<select id="getNextSeqNoForTaskDetail" resultType="java.lang.Integer"> <select id="getNextSeqNoForTaskDetail" resultType="java.lang.Integer">
SELECT ISNULL(MAX(seq_no), 0) + 1 SELECT ISNULL(MAX(seq_no), 0) + 1
FROM haian_wms_transport_task_detail FROM haian_wms_transport_task_detail
@ -195,64 +195,38 @@
AND item_no = 1 AND item_no = 1
</select> </select>
<!-- 单次Callable取得同site+taskNo事务级应用锁;输出返回码由Service检查,避免Druid拦截多语句。- rqrq -->
<select id="lockTransportTaskDetailInsert"
parameterType="com.gaotao.modules.autoWareHourceHaian.entity.HaiAnTransportTaskDetailLockParam"
statementType="CALLABLE">
{#{lockResult, mode=OUT, jdbcType=INTEGER} = call sys.sp_getapplock(
#{resource, mode=IN, jdbcType=NVARCHAR},
'Exclusive',
'Transaction',
10000
)}
</select>
<!-- 重复复核优先取历史首条,再取在线首条;即使旧回调在归档后误补写在线记录,也只认最初业务结果。- rqrq -->
<!-- RFID幂等仅查询同site+taskNo+actionType的在线首条;任意状态均复用首次结果,不读取历史表,不申请应用锁。- rqrq -->
<select id="getFirstRfidVerifyDetail" resultType="com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsTransportTaskDetail"> <select id="getFirstRfidVerifyDetail" resultType="com.gaotao.modules.autoWareHourceHaian.entity.vo.HaiAnWmsTransportTaskDetail">
SELECT TOP (1) SELECT TOP (1)
first_result.id,
first_result.site,
first_result.taskNo,
first_result.actionType,
first_result.comment,
first_result.palletId,
first_result.errorCode,
first_result.errorMsg,
first_result.rfidVerifyResultJson
FROM (
SELECT 1 AS sourcePriority, d.id, d.site, d.task_no AS taskNo,
d.action_type AS actionType, d.[comment] AS comment,
d.pallet_id AS palletId, d.error_code AS errorCode,
d.error_msg AS errorMsg,
d.rfid_verify_result_json AS rfidVerifyResultJson
FROM haian_wms_transport_task_detail d
WHERE d.site = #{site}
AND d.task_no = #{taskNo}
AND d.action_type = #{actionType}
UNION ALL
SELECT 0 AS sourcePriority, h.id, h.site, h.task_no AS taskNo,
h.action_type AS actionType, h.[comment] AS comment,
h.pallet_id AS palletId, h.error_code AS errorCode,
h.error_msg AS errorMsg,
h.rfid_verify_result_json AS rfidVerifyResultJson
FROM haian_wms_transport_task_detail_history h
WHERE h.site = #{site}
AND h.task_no = #{taskNo}
AND h.action_type = #{actionType}
) first_result
ORDER BY first_result.sourcePriority ASC, first_result.id ASC
d.id,
d.site,
d.task_no AS taskNo,
d.action_type AS actionType,
d.[comment] AS comment,
d.pallet_id AS palletId,
d.error_code AS errorCode,
d.error_msg AS errorMsg,
d.rfid_verify_result_json AS rfidVerifyResultJson
FROM haian_wms_transport_task_detail d
WHERE d.site = #{site}
AND d.task_no = #{taskNo}
AND d.action_type = #{actionType}
ORDER BY d.id ASC
</select> </select>
<!-- 取消回调判断是否已有取货完成:普通读,不持有更新锁和范围锁。调用方事务已按明细id加排他锁,再UPDLOCK扫任务号会和回调插入死锁 - rqrq -->
<!-- 按site+taskNo+actionType统计在线明细,任意状态均算已存在;兼容旧送货完成名称,供AGV接收幂等及取消判断使用,不申请应用锁或范围锁。- rqrq -->
<select id="countTaskDetailByType" resultType="java.lang.Integer"> <select id="countTaskDetailByType" resultType="java.lang.Integer">
SELECT COUNT(1) SELECT COUNT(1)
FROM haian_wms_transport_task_detail d FROM haian_wms_transport_task_detail d
WHERE d.site = #{site} WHERE d.site = #{site}
AND d.task_no = #{taskNo} AND d.task_no = #{taskNo}
AND d.action_type = #{actionType}
AND (
d.action_type = #{actionType}
OR (#{actionType} = N'放货完成' AND d.action_type = N'送货完成')
)
</select> </select>
<!-- 一条语句完成查重并插入:已有相同动作则影响行数为0。放货完成把历史送货完成也视为已存在,避免重复放货后再移库。不加UPDLOCK/HOLDLOCK,只锁新插入行 - rqrq -->
<!-- 仅查在线表并插入:同site+taskNo+actionType已存在则返回0;放货完成兼容旧名称送货完成,避免重复移库。不申请应用锁,不加更新锁或范围锁提示。- rqrq -->
<insert id="insertTransportTaskDetail"> <insert id="insertTransportTaskDetail">
INSERT INTO haian_wms_transport_task_detail INSERT INTO haian_wms_transport_task_detail
( (

Loading…
Cancel
Save