|  | @@ -698,256 +698,52 @@ public class MiddleMeterApplyServiceImpl extends BaseServiceImpl<MiddleMeterAppl
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public String getMeterNumber(MiddleMeterApply apply) {
 | 
	
		
			
				|  |  | -        if (apply.getContractPeriodId() == -1) {
 | 
	
		
			
				|  |  | +        //如果计量期id为-1则代表不用查询
 | 
	
		
			
				|  |  | +        if (apply.getContractPeriodId() == -1){
 | 
	
		
			
				|  |  |              return "";
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        String lockKey = "meter_number_lock:" + apply.getContractId() + ":" + apply.getContractPeriodId();
 | 
	
		
			
				|  |  | -        String requestId = UUID.randomUUID().toString();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            // 获取分布式锁
 | 
	
		
			
				|  |  | -            if (!tryLock(lockKey, requestId, 10L)) {
 | 
	
		
			
				|  |  | -                return "";
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            // 生成并检查编号
 | 
	
		
			
				|  |  | -            int maxRetries = 5;
 | 
	
		
			
				|  |  | -            for (int retryCount = 0; retryCount < maxRetries; retryCount++) {
 | 
	
		
			
				|  |  | -                String number = generateMeterNumber(apply);
 | 
	
		
			
				|  |  | -                if (StringUtils.isBlank(number)) {
 | 
	
		
			
				|  |  | -                    continue;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                // 在Redis中预占编号
 | 
	
		
			
				|  |  | -                if (reserveNumber(number)) {
 | 
	
		
			
				|  |  | -                    return number;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                try {
 | 
	
		
			
				|  |  | -                    Thread.sleep(100 * (retryCount + 1));
 | 
	
		
			
				|  |  | -                } catch (InterruptedException e) {
 | 
	
		
			
				|  |  | -                    Thread.currentThread().interrupt();
 | 
	
		
			
				|  |  | -                    break;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            return "";
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        } finally {
 | 
	
		
			
				|  |  | -            releaseLock(lockKey, requestId);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 使用setEx实现分布式锁
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private boolean tryLock(String lockKey, String requestId, long expireSeconds) {
 | 
	
		
			
				|  |  | -        int maxRetry = 3;
 | 
	
		
			
				|  |  | -        for (int i = 0; i < maxRetry; i++) {
 | 
	
		
			
				|  |  | -            // 先检查是否已存在锁
 | 
	
		
			
				|  |  | -            if (!bladeRedis.exists(lockKey)) {
 | 
	
		
			
				|  |  | -                // 不存在时尝试设置锁
 | 
	
		
			
				|  |  | -                bladeRedis.setEx(lockKey, requestId, expireSeconds);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                // 再次检查确认自己是锁的持有者
 | 
	
		
			
				|  |  | -                String currentOwner = bladeRedis.get(lockKey);
 | 
	
		
			
				|  |  | -                if (requestId.equals(currentOwner)) {
 | 
	
		
			
				|  |  | -                    return true;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            if (i < maxRetry - 1) {
 | 
	
		
			
				|  |  | -                try {
 | 
	
		
			
				|  |  | -                    Thread.sleep(100 * (i + 1));
 | 
	
		
			
				|  |  | -                } catch (InterruptedException e) {
 | 
	
		
			
				|  |  | -                    Thread.currentThread().interrupt();
 | 
	
		
			
				|  |  | -                    break;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        return false;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 安全释放锁
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private void releaseLock(String lockKey, String requestId) {
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            String currentValue = bladeRedis.get(lockKey);
 | 
	
		
			
				|  |  | -            if (requestId.equals(currentValue)) {
 | 
	
		
			
				|  |  | -                bladeRedis.del(lockKey);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        } catch (Exception e) {
 | 
	
		
			
				|  |  | -            log.error("释放锁失败", e);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 预占编号 - 使用setEx + 检查机制
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private boolean reserveNumber(String number) {
 | 
	
		
			
				|  |  | -        String numberKey = "meter_number_reserve:" + number;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 先检查是否已存在
 | 
	
		
			
				|  |  | -        if (bladeRedis.exists(numberKey)) {
 | 
	
		
			
				|  |  | -            return false;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 尝试设置,如果在此期间有其他线程设置,可能会覆盖,但通过value来区分
 | 
	
		
			
				|  |  | -        String reserveValue = "reserved:" + System.currentTimeMillis() + ":" + Thread.currentThread().getId();
 | 
	
		
			
				|  |  | -        bladeRedis.setEx(numberKey, reserveValue, 300L); // 5分钟过期
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 再次检查确认我们是设置者
 | 
	
		
			
				|  |  | -        String currentValue = bladeRedis.get(numberKey);
 | 
	
		
			
				|  |  | -        return reserveValue.equals(currentValue);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private String generateMeterNumber(MiddleMeterApply apply) {
 | 
	
		
			
				|  |  |          StringBuilder str = new StringBuilder();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 获取合同信息
 | 
	
		
			
				|  |  | +        //获取合同信息
 | 
	
		
			
				|  |  |          ContractInfo info = baseMapper.getContractInfo(apply.getContractId());
 | 
	
		
			
				|  |  |          String contractNumber = info.getContractNumber();
 | 
	
		
			
				|  |  | -        if (StringUtils.isBlank(contractNumber)) {
 | 
	
		
			
				|  |  | -            return "";
 | 
	
		
			
				|  |  | +        if (StringUtils.isBlank(contractNumber)){
 | 
	
		
			
				|  |  | +            throw new ServiceException("未获取到当前合同段编号信息");
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        str.append(contractNumber).append("-");
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 获取计量期信息
 | 
	
		
			
				|  |  | +        str.append(contractNumber+"-");
 | 
	
		
			
				|  |  | +        //获取计量期信息
 | 
	
		
			
				|  |  |          ContractMeterPeriod contractMeterPeriod = contractMeterPeriodService.getById(apply.getContractPeriodId());
 | 
	
		
			
				|  |  | -        if (contractMeterPeriod == null || StringUtils.isBlank(contractMeterPeriod.getPeriodNumber())) {
 | 
	
		
			
				|  |  | -            return "";
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        str.append(contractMeterPeriod.getPeriodNumber()).append("-");
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 从Redis获取流水号
 | 
	
		
			
				|  |  | -        Integer nextSerial = getNextSerialNumber(apply);
 | 
	
		
			
				|  |  | -        if (nextSerial == null) {
 | 
	
		
			
				|  |  | -            log.error("获取流水号失败");
 | 
	
		
			
				|  |  | -            return "";
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        str.append(nextSerial);
 | 
	
		
			
				|  |  | -        return str.toString();
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 使用Redis管理可用流水号
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private Integer getNextSerialNumber(MiddleMeterApply apply) {
 | 
	
		
			
				|  |  | -        String serialKey = "meter_serial_available:" + apply.getContractId() + ":" + apply.getContractPeriodId();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            // 先尝试从Redis获取最小可用流水号
 | 
	
		
			
				|  |  | -            String minSerial = bladeRedis.lIndex(serialKey, 0);
 | 
	
		
			
				|  |  | -            if (minSerial != null) {
 | 
	
		
			
				|  |  | -                // 弹出最小的可用流水号
 | 
	
		
			
				|  |  | -                String popped = bladeRedis.lPop(serialKey);
 | 
	
		
			
				|  |  | -                if (popped != null) {
 | 
	
		
			
				|  |  | -                    return Integer.parseInt(popped);
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            // Redis中没有可用流水号,从数据库初始化
 | 
	
		
			
				|  |  | -            return initAndGetSerialFromRedis(apply);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        } catch (Exception e) {
 | 
	
		
			
				|  |  | -            log.error("Redis获取流水号异常", e);
 | 
	
		
			
				|  |  | -            return getNextSerialNumberFromDB(apply);
 | 
	
		
			
				|  |  | +        if (contractMeterPeriod == null || StringUtils.isBlank(contractMeterPeriod.getPeriodNumber())){
 | 
	
		
			
				|  |  | +            throw new ServiceException("未获取到计量期期号信息");
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 初始化Redis中的流水号池
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private Integer initAndGetSerialFromRedis(MiddleMeterApply apply) {
 | 
	
		
			
				|  |  | -        String lockKey = "serial_init_lock:" + apply.getContractId() + ":" + apply.getContractPeriodId();
 | 
	
		
			
				|  |  | -        String requestId = UUID.randomUUID().toString();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            // 加锁防止重复初始化
 | 
	
		
			
				|  |  | -            if (tryLock(lockKey, requestId, 10L)) {
 | 
	
		
			
				|  |  | -                // 再次检查,防止其他线程已经初始化完成
 | 
	
		
			
				|  |  | -                String serialKey = "meter_serial_available:" + apply.getContractId() + ":" + apply.getContractPeriodId();
 | 
	
		
			
				|  |  | -                if (bladeRedis.lLen(serialKey) > 0) {
 | 
	
		
			
				|  |  | -                    String popped = bladeRedis.lPop(serialKey);
 | 
	
		
			
				|  |  | -                    return popped != null ? Integer.parseInt(popped) : null;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                // 从数据库获取已使用的流水号
 | 
	
		
			
				|  |  | -                List<Integer> usedSerials = getUsedSerialsFromDB(apply);
 | 
	
		
			
				|  |  | -                int nextSerial = findNextAvailableSerial(usedSerials);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                // 将后续的可用流水号预先放入Redis
 | 
	
		
			
				|  |  | -                for (int i = nextSerial + 1; i <= nextSerial + 10; i++) {
 | 
	
		
			
				|  |  | -                    bladeRedis.rPush(serialKey, String.valueOf(i));
 | 
	
		
			
				|  |  | +        str.append(contractMeterPeriod.getPeriodNumber()+"-");
 | 
	
		
			
				|  |  | +        //获取流水号:当前合同段存在的当前计量期的申请单总数+1
 | 
	
		
			
				|  |  | +        List<MiddleMeterApply> allAPPly = baseMapper.getAllAPPly(apply.getContractId(), apply.getContractPeriodId());
 | 
	
		
			
				|  |  | +        if (allAPPly.size() == 0){
 | 
	
		
			
				|  |  | +            str.append(1);
 | 
	
		
			
				|  |  | +        }else {
 | 
	
		
			
				|  |  | +            List<Integer> list = allAPPly.stream().filter(l -> l.getApproveStatus() != null).map(l -> l.getApproveStatus()).sorted().collect(Collectors.toList());
 | 
	
		
			
				|  |  | +            if (list.size() == 0 || list.get(0) != 1){
 | 
	
		
			
				|  |  | +                str.append(1);
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                for (int i = 0; i < list.size() - 1; i++) {
 | 
	
		
			
				|  |  | +                    Integer num = list.get(i);
 | 
	
		
			
				|  |  | +                    if (num .equals(list.get(i+1)) ){
 | 
	
		
			
				|  |  | +                        throw new ServiceException("计量期期号出现相同,请联系管理员");
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    Integer j = ++num;
 | 
	
		
			
				|  |  | +                    String j2 = j.toString();
 | 
	
		
			
				|  |  | +                    String j3 = list.get(i + 1).toString();
 | 
	
		
			
				|  |  | +                    if (!j2.equals(j3)){
 | 
	
		
			
				|  |  | +                        str.append(num);
 | 
	
		
			
				|  |  | +                        return str.toString();
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -                bladeRedis.expire(serialKey, 86400L);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                return nextSerial;
 | 
	
		
			
				|  |  | -            } else {
 | 
	
		
			
				|  |  | -                // 等待其他线程初始化完成
 | 
	
		
			
				|  |  | -                Thread.sleep(100);
 | 
	
		
			
				|  |  | -                return getNextSerialNumber(apply); // 重试
 | 
	
		
			
				|  |  | +                str.append(list.size()+1);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -        } catch (Exception e) {
 | 
	
		
			
				|  |  | -            log.error("初始化流水号池失败", e);
 | 
	
		
			
				|  |  | -            return getNextSerialNumberFromDB(apply);
 | 
	
		
			
				|  |  | -        } finally {
 | 
	
		
			
				|  |  | -            releaseLock(lockKey, requestId);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 从数据库获取已使用的流水号
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private List<Integer> getUsedSerialsFromDB(MiddleMeterApply apply) {
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            List<MiddleMeterApply> allApply = baseMapper.getAllAPPly(apply.getContractId(), apply.getContractPeriodId());
 | 
	
		
			
				|  |  | -            return allApply.stream()
 | 
	
		
			
				|  |  | -                    .filter(l -> l.getApproveStatus() != null)
 | 
	
		
			
				|  |  | -                    .map(MiddleMeterApply::getApproveStatus)
 | 
	
		
			
				|  |  | -                    .sorted()
 | 
	
		
			
				|  |  | -                    .collect(Collectors.toList());
 | 
	
		
			
				|  |  | -        } catch (Exception e) {
 | 
	
		
			
				|  |  | -            log.error("从数据库获取已使用流水号失败", e);
 | 
	
		
			
				|  |  | -            return new ArrayList<>();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 查找下一个可用的流水号
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private int findNextAvailableSerial(List<Integer> usedSerials) {
 | 
	
		
			
				|  |  | -        if (usedSerials.isEmpty()) {
 | 
	
		
			
				|  |  | -            return 1;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 查找第一个可用的流水号
 | 
	
		
			
				|  |  | -        for (int i = 0; i < usedSerials.size(); i++) {
 | 
	
		
			
				|  |  | -            if (usedSerials.get(i) != i + 1) {
 | 
	
		
			
				|  |  | -                return i + 1;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // 如果所有序号都连续,返回下一个
 | 
	
		
			
				|  |  | -        return usedSerials.size() + 1;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 从数据库获取流水号(备用方案)
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private Integer getNextSerialNumberFromDB(MiddleMeterApply apply) {
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            List<Integer> usedSerials = getUsedSerialsFromDB(apply);
 | 
	
		
			
				|  |  | -            return findNextAvailableSerial(usedSerials);
 | 
	
		
			
				|  |  | -        } catch (Exception e) {
 | 
	
		
			
				|  |  | -            log.error("从数据库获取流水号失败", e);
 | 
	
		
			
				|  |  | -            return null;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +        return str.toString();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * 获取本期计量总金额
 | 
	
		
			
				|  |  |       */
 |