这篇文章是对公司仓储项目中箱号或SN采集校验时并发分布式锁的解析
建立锁key
// 建立锁的名字:根据本次需要进行采集的出库单和物料类型创建。这个锁的粒度在这个项目中可以分为三级:
// 一级锁单据,二级锁物料编码,三级锁对应SN。粒度选择为物料编码的原因是:锁单据会造成等待,耗时较长,锁SN会导致同一物料的不同SN同时进行校验并通过,造成锁失效。相比之下选择物料编码是最好的。(lua脚本另说)
String cacheQuantityKey = "outbound:pick:" + dto.getOutboundId() + ":" + dto.getMisMatCode();
String lockKey = "lock:outbound:pick:" + dto.getOutboundId() + ":" + dto.getMisMatCode();
try {
//分布式锁
// 向redis设置锁,返回结果如果为真,代表设置成功,redis中没有这种物料在进行校验,取反后不会抛出异常。
if (!redisUtils.getValueOperations().setIfAbsent(lockKey, userEntity.getUserId().toString(), 120L, TimeUnit.SECONDS)) {
Assert.is(true, "单据正在检验中,请稍后再试!");
}
//查询出需要进行采集的SN并校验 -- 开始
SysAntennaSnEntity snEntity = sysAntennaSnService.getOne(new LambdaQueryWrapper<SysAntennaSnEntity>()
.eq(SysAntennaSnEntity::getSupplierSn, dto.getSupplierSn())
.select(SysAntennaSnEntity::getSupplierSn, SysAntennaSnEntity::getMaterialDetailId
,SysAntennaSnEntity::getMisMatCode,SysAntennaSnEntity::getPickCheckDate)
);
Assert.isNull(snEntity, "该SN未入库");
Assert.is(!snEntity.getMisMatCode().equals(dto.getMisMatCode()), "该SN不属于物料["+dto.getMisMatCode()+"]");
Assert.is(ObjectUtil.isNotNull(snEntity.getPickCheckDate()), "该SN已检验");
//得到供应商id
SysInStockDetailEntity inStockDetail = sysInStockDetailService.getOne(new LambdaQueryWrapper<SysInStockDetailEntity>()
.eq(SysInStockDetailEntity::getMisMatCode, dto.getMisMatCode())
.eq(SysInStockDetailEntity::getNum, dto.getSupplierSn())
.select(SysInStockDetailEntity::getSuprCode)
);
Assert.isNull(inStockDetail , "该SN未上架!");
//查询出需要进行采集的SN并校验 -- 结束
// 该SN符合采集需求,判断下还需不需这个SN(总数满了没)
String materialDetailId = this.loadCacheAntennaSN(cacheQuantityKey, dto.getMisMatCode(), dto.getOutboundId(),Boolean.FALSE);
Assert.is("".equals(materialDetailId), "SN数量已足够!");
// 还需要,给这个单据详情更新数量
sysOutboundNoticeDetailService.update(new LambdaUpdateWrapper<SysOutboundNoticeDetailEntity>()
.eq(SysOutboundNoticeDetailEntity::getMaterialDetailId, Long.parseLong(materialDetailId))
.set(SysOutboundNoticeDetailEntity::getSuprCode,inStockDetail.getSuprCode())
.set(SysOutboundNoticeDetailEntity::getPickedQuantity, redisUtils.hIncry(cacheQuantityKey, materialDetailId, -1L))
);
// 更新这个SN,加上拣货人以及出库单据等
sysAntennaSnService.update(new LambdaUpdateWrapper<SysAntennaSnEntity>()
.eq(SysAntennaSnEntity::getMaterialDetailId, snEntity.getMaterialDetailId())
.set(SysAntennaSnEntity::getPickCheckDate, LocalDateTime.now())
.set(SysAntennaSnEntity::getPickCheckBy, userEntity.getUserId())
.set(SysAntennaSnEntity::getMaterialStatus, MaterialStatusEnum.OUTBONUDING.getCode())
.set(SysAntennaSnEntity::getOutboundId, dto.getOutboundId())
.set(SysAntennaSnEntity::getOutboundDetailId, Long.parseLong(materialDetailId))
.last(" limit 1")
);
return Boolean.TRUE;
} finally {
redisUtils.delete(lockKey);
}