|
@@ -696,85 +696,112 @@ public class MiddleMeterApplyServiceImpl extends BaseServiceImpl<MiddleMeterAppl
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取计量单编号
|
|
|
- */
|
|
|
@Override
|
|
|
public String getMeterNumber(MiddleMeterApply apply) {
|
|
|
if (apply.getContractPeriodId() == -1) {
|
|
|
return "";
|
|
|
}
|
|
|
|
|
|
- String lockKey = "meter_number:" + apply.getContractId() + ":" + apply.getContractPeriodId();
|
|
|
+ String lockKey = "meter_number_lock:" + apply.getContractId() + ":" + apply.getContractPeriodId();
|
|
|
String requestId = UUID.randomUUID().toString();
|
|
|
|
|
|
try {
|
|
|
- // 尝试获取分布式锁
|
|
|
- boolean locked = false;
|
|
|
- int lockRetry = 0;
|
|
|
- int maxLockRetry = 3;
|
|
|
-
|
|
|
- while (!locked && lockRetry < maxLockRetry) {
|
|
|
- // 先检查是否已存在锁
|
|
|
- if (!bladeRedis.exists(lockKey)) {
|
|
|
- // 不存在时尝试设置锁
|
|
|
- bladeRedis.setEx(lockKey, requestId,10L);
|
|
|
- // 再次检查确认自己是锁的持有者
|
|
|
- String currentOwner = bladeRedis.get(lockKey);
|
|
|
- if (requestId.equals(currentOwner)) {
|
|
|
- locked = true;
|
|
|
- }
|
|
|
- }
|
|
|
- if (!locked) {
|
|
|
- lockRetry++;
|
|
|
- try {
|
|
|
- Thread.sleep(100 * lockRetry);
|
|
|
- } catch (InterruptedException e) {
|
|
|
- Thread.currentThread().interrupt();
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (!locked) {
|
|
|
+ // 获取分布式锁
|
|
|
+ if (!tryLock(lockKey, requestId, 10L)) {
|
|
|
return "";
|
|
|
}
|
|
|
- // 获取锁成功后的业务逻辑
|
|
|
+
|
|
|
+ // 生成并检查编号
|
|
|
int maxRetries = 5;
|
|
|
- int retryCount = 0;
|
|
|
- while (retryCount < maxRetries) {
|
|
|
+ for (int retryCount = 0; retryCount < maxRetries; retryCount++) {
|
|
|
+ String number = generateMeterNumber(apply);
|
|
|
+ if (StringUtils.isBlank(number)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在Redis中预占编号
|
|
|
+ if (reserveNumber(number)) {
|
|
|
+ return number;
|
|
|
+ }
|
|
|
+
|
|
|
try {
|
|
|
- String number = generateMeterNumber(apply);
|
|
|
- if (!isNumberExists(number)) {
|
|
|
- return number;
|
|
|
- }
|
|
|
- retryCount++;
|
|
|
- try {
|
|
|
- Thread.sleep(100 * retryCount);
|
|
|
- } catch (InterruptedException e) {
|
|
|
- Thread.currentThread().interrupt();
|
|
|
- break;
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- retryCount++;
|
|
|
+ Thread.sleep(100 * (retryCount + 1));
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
return "";
|
|
|
|
|
|
} finally {
|
|
|
- // 安全释放锁
|
|
|
- try {
|
|
|
- String currentValue = bladeRedis.get(lockKey);
|
|
|
- if (requestId.equals(currentValue)) {
|
|
|
- bladeRedis.del(lockKey);
|
|
|
+ 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;
|
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
}
|
|
|
}
|
|
|
+ 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();
|
|
|
|
|
@@ -793,7 +820,7 @@ public class MiddleMeterApplyServiceImpl extends BaseServiceImpl<MiddleMeterAppl
|
|
|
}
|
|
|
str.append(contractMeterPeriod.getPeriodNumber()).append("-");
|
|
|
|
|
|
- // 获取流水号
|
|
|
+ // 从Redis获取流水号
|
|
|
Integer nextSerial = getNextSerialNumber(apply);
|
|
|
if (nextSerial == null) {
|
|
|
log.error("获取流水号失败");
|
|
@@ -804,43 +831,119 @@ public class MiddleMeterApplyServiceImpl extends BaseServiceImpl<MiddleMeterAppl
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取下一个流水号
|
|
|
+ * 使用Redis管理可用流水号
|
|
|
*/
|
|
|
private Integer getNextSerialNumber(MiddleMeterApply apply) {
|
|
|
- List<MiddleMeterApply> allApply = baseMapper.getAllAPPly(apply.getContractId(), apply.getContractPeriodId());
|
|
|
- if (allApply.isEmpty()) {
|
|
|
- return 1;
|
|
|
+ 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);
|
|
|
}
|
|
|
- // 提取已使用的流水号
|
|
|
- List<Integer> usedSerials = allApply.stream()
|
|
|
- .filter(l -> l.getApproveStatus() != null)
|
|
|
- .map(MiddleMeterApply::getApproveStatus)
|
|
|
- .sorted()
|
|
|
- .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化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 boolean isNumberExists(String number) {
|
|
|
- if (StringUtils.isBlank(number)) {
|
|
|
- return true; // 空编号视为已存在,需要重新生成
|
|
|
+ private Integer getNextSerialNumberFromDB(MiddleMeterApply apply) {
|
|
|
+ try {
|
|
|
+ List<Integer> usedSerials = getUsedSerialsFromDB(apply);
|
|
|
+ return findNextAvailableSerial(usedSerials);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("从数据库获取流水号失败", e);
|
|
|
+ return null;
|
|
|
}
|
|
|
- LambdaQueryWrapper<MiddleMeterApply> query = new LambdaQueryWrapper<>();
|
|
|
- query.eq(MiddleMeterApply::getMeterNumber, number);
|
|
|
- return count(query) > 0;
|
|
|
}
|
|
|
|
|
|
|