|
@@ -28,6 +28,7 @@ import org.springblade.common.utils.SnowFlakeUtil;
|
|
|
import org.springblade.core.log.exception.ServiceException;
|
|
|
import org.springblade.core.mp.base.BaseServiceImpl;
|
|
|
import org.springblade.core.mp.support.Query;
|
|
|
+import org.springblade.core.redis.cache.BladeRedis;
|
|
|
import org.springblade.core.tool.utils.Func;
|
|
|
import org.springblade.core.tool.utils.ObjectUtil;
|
|
|
import org.springblade.manager.entity.ContractInfo;
|
|
@@ -83,6 +84,7 @@ public class MiddleMeterApplyServiceImpl extends BaseServiceImpl<MiddleMeterAppl
|
|
|
|
|
|
private final IAttachmentFormServiceTask attachmentFormServiceTask;
|
|
|
|
|
|
+ private final BladeRedis bladeRedis;
|
|
|
|
|
|
|
|
|
/**
|
|
@@ -694,58 +696,258 @@ public class MiddleMeterApplyServiceImpl extends BaseServiceImpl<MiddleMeterAppl
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取计量单编号
|
|
|
- */
|
|
|
@Override
|
|
|
public String getMeterNumber(MiddleMeterApply apply) {
|
|
|
- //如果计量期id为-1则代表不用查询
|
|
|
- if (apply.getContractPeriodId() == -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)){
|
|
|
- throw new ServiceException("未获取到当前合同段编号信息");
|
|
|
+ if (StringUtils.isBlank(contractNumber)) {
|
|
|
+ return "";
|
|
|
}
|
|
|
- str.append(contractNumber+"-");
|
|
|
- //获取计量期信息
|
|
|
+ str.append(contractNumber).append("-");
|
|
|
+
|
|
|
+ // 获取计量期信息
|
|
|
ContractMeterPeriod contractMeterPeriod = contractMeterPeriodService.getById(apply.getContractPeriodId());
|
|
|
- if (contractMeterPeriod == null || StringUtils.isBlank(contractMeterPeriod.getPeriodNumber())){
|
|
|
- throw new ServiceException("未获取到计量期期号信息");
|
|
|
+ if (contractMeterPeriod == null || StringUtils.isBlank(contractMeterPeriod.getPeriodNumber())) {
|
|
|
+ return "";
|
|
|
}
|
|
|
- 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();
|
|
|
- }
|
|
|
+ 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);
|
|
|
}
|
|
|
- str.append(list.size()+1);
|
|
|
}
|
|
|
|
|
|
+ // Redis中没有可用流水号,从数据库初始化
|
|
|
+ return initAndGetSerialFromRedis(apply);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("Redis获取流水号异常", e);
|
|
|
+ return getNextSerialNumberFromDB(apply);
|
|
|
}
|
|
|
- return str.toString();
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 初始化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));
|
|
|
+ }
|
|
|
+ bladeRedis.expire(serialKey, 86400L);
|
|
|
+
|
|
|
+ return nextSerial;
|
|
|
+ } else {
|
|
|
+ // 等待其他线程初始化完成
|
|
|
+ Thread.sleep(100);
|
|
|
+ return getNextSerialNumber(apply); // 重试
|
|
|
+ }
|
|
|
+ } 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* 获取本期计量总金额
|
|
|
*/
|