Browse Source

优化代码

callm 1 week ago
parent
commit
6942caa1e5
1 changed files with 72 additions and 25 deletions
  1. 72 25
      src/main/java/com/uas/eis/serviceImpl/MESHelperServiceImpl.java

+ 72 - 25
src/main/java/com/uas/eis/serviceImpl/MESHelperServiceImpl.java

@@ -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;