|
|
@@ -2053,57 +2053,104 @@ public class MESHelperServiceImpl implements MESHelperService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 原子占用SN
|
|
|
+ * 原子占用SN(按SN维度占用,并保证同一SN只发放一次)
|
|
|
* <p>
|
|
|
- * 原实现是"先select符合条件的SN,再在循环里逐条update",两步之间没有互斥,两个请求时间接近时会读到
|
|
|
- * 同一批msl_status=0的记录,从而返回相同的SN。
|
|
|
- * 这里改成先占用再反查:
|
|
|
- * 1)占用用单条update完成,where里带nvl(msl_status,0)=0。并发请求在行锁等待结束(Oracle会阻塞后重试该行)
|
|
|
- * 会用当前数据重新评估该条件,已被占用的行会被跳过,因此同一行不会被占用两次;
|
|
|
- * 2)占用时写入唯一token,再按token反查,精确得到本次占用的SN,不再依赖时间或行号;
|
|
|
- * 3)占用不足时重新补占用,最终仍不足则归还本次已占用的行,避免占用泄漏。
|
|
|
+ * 防重码的三层保障:
|
|
|
+ * 1)占用用单条update原子完成,where里带nvl(msl_status,0)=0,并发请求在行锁等待后会用当前数据
|
|
|
+ * 重新评估该条件,已被占用的行会被跳过,不存在同一时刻两个请求拿到同一行;
|
|
|
+ * 2)按SN维度圈定占用范围(group by msl_sncode):同一SN在MakeSnList里若被维护了多条记录
|
|
|
+ * (同工单重复导入等),一次性全部占用并在返回结果里只保留一条,避免把同一个SN发出多次;
|
|
|
+ * 3)MACRELEASE发放台账里已有的SN直接不下发:占用前先用 not exists 把已发放过的SN排除在候选之外,
|
|
|
+ * 占用后再校验一次,命中则把这些记录置为已使用(msl_status=-1)并移出本次结果、从头重新圈定候选。
|
|
|
+ * 这样无论状态被谁复位(取消采集/手工改库)、SN是否重复维护,都不可能出现二次发放。
|
|
|
*
|
|
|
* @param macode 工单号
|
|
|
* @param mType SN类型,传空表示不限类型
|
|
|
* @param need 需要占用的数量
|
|
|
- * @return 本次占用的SN列表(每项含sn、id,按msl_sncode升序);数量不足时归还并返回null
|
|
|
+ * @return 本次占用的SN列表(每项含sn、id,按msl_sncode升序,SN不重复);数量不足时归还并返回null
|
|
|
*/
|
|
|
private List<Map<String,String>> claimSnCodes(String macode,String mType,int need){
|
|
|
String typeCond=(mType==null||"".equals(mType))?"1=1":"nvl(msl_type,' ')='"+mType+"'";
|
|
|
+ //候选SN:该工单下未占用、且MACRELEASE台账里没有发放记录的SN
|
|
|
+ String candidateSql="select msl_sncode from MakeSnList where msl_makecode='"+macode+"' and "+typeCond+" and nvl(msl_status,0)=0 " +
|
|
|
+ "and not exists(select 1 from MACRELEASE where mr_sncode=MakeSnList.msl_sncode) group by msl_sncode";
|
|
|
String token=UUID.randomUUID().toString().replace("-","");
|
|
|
List<Map<String,String>> list=new ArrayList<>();
|
|
|
+ int emptyRound=0;
|
|
|
while(list.size()<need){
|
|
|
+ //按SN维度占用:圈定剩余数量的SN,该工单下这些SN的所有未占用记录一起置为占用
|
|
|
int updated=baseDao.executeUpdate("update MakeSnList set msl_status=-1,msl_signtime=sysdate,msl_mainbarcode='"+token+"' " +
|
|
|
- "where nvl(msl_status,0)=0 and msl_id in (select msl_id from (select msl_id from MakeSnList " +
|
|
|
- "where msl_makecode='"+macode+"' and "+typeCond+" and nvl(msl_status,0)=0 order by msl_sncode) where rownum<="+(need-list.size())+")");
|
|
|
+ "where msl_makecode='"+macode+"' and nvl(msl_status,0)=0 and msl_sncode in " +
|
|
|
+ "(select msl_sncode from ("+candidateSql+" order by msl_sncode) where rownum<="+(need-list.size())+")");
|
|
|
if(updated<=0){
|
|
|
- break;
|
|
|
+ //候选SN可能刚好被并发请求占用完,重新圈定候选再试;确实没有可用SN才退出
|
|
|
+ if(++emptyRound>=3){
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ continue;
|
|
|
}
|
|
|
- //token唯一,反查到的就是本次请求占用的行
|
|
|
+ emptyRound=0;
|
|
|
+ //token唯一,反查到的就是本次请求占用的行;同一SN多条记录时按SN归组
|
|
|
+ Map<String,List<String>> snIds=new LinkedHashMap<>();
|
|
|
SqlRowList rs=baseDao.queryForRowSet("select msl_sncode,msl_id from MakeSnList where msl_mainbarcode='"+token+"' and msl_status=-1 order by msl_sncode");
|
|
|
- list.clear();
|
|
|
while(rs.next()){
|
|
|
+ String sn=rs.getGeneralString("msl_sncode");
|
|
|
+ List<String> ids=snIds.get(sn);
|
|
|
+ if(ids==null){
|
|
|
+ ids=new ArrayList<>();
|
|
|
+ snIds.put(sn,ids);
|
|
|
+ }
|
|
|
+ ids.add(rs.getGeneralString("msl_id"));
|
|
|
+ }
|
|
|
+ //兜底校验:MACRELEASE台账里已有的SN不再下发,记录置为已使用并重新圈定候选(并发下候选校验后又被发放的场景)
|
|
|
+ boolean issuedFound=false;
|
|
|
+ for(String sn:queryIssuedSns(snIds.keySet())){
|
|
|
+ //保持msl_status=-1(已使用),清掉token,避免被下面整批归还
|
|
|
+ baseDao.execute("update MakeSnList set msl_status=-1,msl_signtime=sysdate,msl_mainbarcode='' where msl_id in ("+String.join(",",snIds.get(sn))+")");
|
|
|
+ snIds.remove(sn);
|
|
|
+ issuedFound=true;
|
|
|
+ }
|
|
|
+ list.clear();
|
|
|
+ for(Map.Entry<String,List<String>> en:snIds.entrySet()){
|
|
|
Map<String,String> one=new HashMap<>();
|
|
|
- one.put("sn",rs.getGeneralString("msl_sncode"));
|
|
|
- one.put("id",rs.getGeneralString("msl_id"));
|
|
|
+ one.put("sn",en.getKey());
|
|
|
+ one.put("id",en.getValue().get(0));
|
|
|
list.add(one);
|
|
|
}
|
|
|
+ if(issuedFound){
|
|
|
+ continue;
|
|
|
+ }
|
|
|
}
|
|
|
if(list.size()<need){
|
|
|
- //可用数量不足,归还本次已占用的行
|
|
|
- StringBuilder ids=new StringBuilder();
|
|
|
- for(Map<String,String> one:list){
|
|
|
- ids.append(one.get("id")).append(",");
|
|
|
- }
|
|
|
- if(ids.length()>0){
|
|
|
- ids.append("0");
|
|
|
- baseDao.execute("update MakeSnList set msl_status=0,msl_signtime=null,msl_mainbarcode='' where msl_id in ("+ids+")");
|
|
|
- }
|
|
|
+ //可用数量不足,把本次token占用的行整批归还(已发放并入账的行token已清空,不会被归还)
|
|
|
+ baseDao.execute("update MakeSnList set msl_status=0,msl_signtime=null,msl_mainbarcode='' where msl_mainbarcode='"+token+"' and msl_status=-1");
|
|
|
return null;
|
|
|
}
|
|
|
+ //清掉token标记(同SN的多余行保持占用状态),主条码由调用方回写到保留的那条记录上
|
|
|
+ baseDao.execute("update MakeSnList set msl_mainbarcode='' where msl_mainbarcode='"+token+"' and msl_status=-1");
|
|
|
return list;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 查询MACRELEASE发放台账里已经发放过的SN
|
|
|
+ */
|
|
|
+ private Set<String> queryIssuedSns(Set<String> sns){
|
|
|
+ Set<String> issued=new HashSet<>();
|
|
|
+ if(sns==null||sns.isEmpty()){
|
|
|
+ return issued;
|
|
|
+ }
|
|
|
+ StringBuilder in=new StringBuilder();
|
|
|
+ for(String sn:sns){
|
|
|
+ in.append("'").append(sn.replace("'","''")).append("',");
|
|
|
+ }
|
|
|
+ in.append("' '");
|
|
|
+ SqlRowList rs=baseDao.queryForRowSet("select mr_sncode from MACRELEASE where mr_sncode in ("+in+")");
|
|
|
+ while(rs.next()){
|
|
|
+ issued.add(rs.getGeneralString("mr_sncode"));
|
|
|
+ }
|
|
|
+ return issued;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public Map<Object,Object> getMESSnCode(String data) {
|
|
|
Map<String, Object> map=null;
|