|
@@ -5,15 +5,14 @@ import java.util.*;
|
|
|
import com.auth0.jwt.JWT;
|
|
import com.auth0.jwt.JWT;
|
|
|
import com.auth0.jwt.algorithms.Algorithm;
|
|
import com.auth0.jwt.algorithms.Algorithm;
|
|
|
import com.ruoyi.cc.service.ICcParamsService;
|
|
import com.ruoyi.cc.service.ICcParamsService;
|
|
|
|
|
+import com.ruoyi.common.utils.StringUtils;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import com.ruoyi.cc.mapper.CcExtNumMapper;
|
|
import com.ruoyi.cc.mapper.CcExtNumMapper;
|
|
|
import com.ruoyi.cc.domain.CcExtNum;
|
|
import com.ruoyi.cc.domain.CcExtNum;
|
|
|
import com.ruoyi.cc.service.ICcExtNumService;
|
|
import com.ruoyi.cc.service.ICcExtNumService;
|
|
|
import com.ruoyi.common.core.text.Convert;
|
|
import com.ruoyi.common.core.text.Convert;
|
|
|
-import org.springframework.util.CollectionUtils;
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 【请填写功能名称】Service业务层处理
|
|
* 【请填写功能名称】Service业务层处理
|
|
@@ -30,6 +29,13 @@ public class CcExtNumServiceImpl implements ICcExtNumService
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private ICcParamsService ccParamsService;
|
|
private ICcParamsService ccParamsService;
|
|
|
private String authTokenSecret;
|
|
private String authTokenSecret;
|
|
|
|
|
+
|
|
|
|
|
+ // 缓存最大分机号,初始值为0表示未初始化,需要从数据库查询
|
|
|
|
|
+ // 使用volatile保证多线程可见性
|
|
|
|
|
+ private volatile long cachedMaxExtNum = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 用于同步获取和更新缓存的锁对象
|
|
|
|
|
+ private final Object cacheLock = new Object();
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 查询【请填写功能名称】
|
|
* 查询【请填写功能名称】
|
|
@@ -106,7 +112,7 @@ public class CcExtNumServiceImpl implements ICcExtNumService
|
|
|
@Override
|
|
@Override
|
|
|
public CcExtNum selectCcExtNumByExtNum(Long extNum) {
|
|
public CcExtNum selectCcExtNumByExtNum(Long extNum) {
|
|
|
List<CcExtNum> list = selectCcExtNumList(new CcExtNum().setExtNum(extNum));
|
|
List<CcExtNum> list = selectCcExtNumList(new CcExtNum().setExtNum(extNum));
|
|
|
- if (list.size() > 0) {
|
|
|
|
|
|
|
+ if (!list.isEmpty()) {
|
|
|
return list.get(0);
|
|
return list.get(0);
|
|
|
}
|
|
}
|
|
|
return null;
|
|
return null;
|
|
@@ -115,7 +121,7 @@ public class CcExtNumServiceImpl implements ICcExtNumService
|
|
|
@Override
|
|
@Override
|
|
|
public CcExtNum selectCcExtNumByUserCode(String userCode) {
|
|
public CcExtNum selectCcExtNumByUserCode(String userCode) {
|
|
|
List<CcExtNum> list = selectCcExtNumList(new CcExtNum().setUserCode(userCode));
|
|
List<CcExtNum> list = selectCcExtNumList(new CcExtNum().setUserCode(userCode));
|
|
|
- if (list.size() > 0) {
|
|
|
|
|
|
|
+ if (!list.isEmpty()) {
|
|
|
return list.get(0);
|
|
return list.get(0);
|
|
|
}
|
|
}
|
|
|
return null;
|
|
return null;
|
|
@@ -134,7 +140,7 @@ public class CcExtNumServiceImpl implements ICcExtNumService
|
|
|
Map<String, Object> map = new HashMap<>();
|
|
Map<String, Object> map = new HashMap<>();
|
|
|
Calendar instance = Calendar.getInstance();
|
|
Calendar instance = Calendar.getInstance();
|
|
|
instance.add(Calendar.HOUR, 24);
|
|
instance.add(Calendar.HOUR, 24);
|
|
|
- String token = JWT.create()
|
|
|
|
|
|
|
+ return JWT.create()
|
|
|
//添加头部
|
|
//添加头部
|
|
|
.withHeader(map)
|
|
.withHeader(map)
|
|
|
//添加payload
|
|
//添加payload
|
|
@@ -147,7 +153,6 @@ public class CcExtNumServiceImpl implements ICcExtNumService
|
|
|
.withExpiresAt(instance.getTime())
|
|
.withExpiresAt(instance.getTime())
|
|
|
//设置签名 密钥
|
|
//设置签名 密钥
|
|
|
.sign(Algorithm.HMAC256(authTokenSecret));
|
|
.sign(Algorithm.HMAC256(authTokenSecret));
|
|
|
- return token;
|
|
|
|
|
|
|
|
|
|
} catch (Exception err) {
|
|
} catch (Exception err) {
|
|
|
log.error("error:" + err.getMessage());
|
|
log.error("error:" + err.getMessage());
|
|
@@ -182,4 +187,169 @@ public class CcExtNumServiceImpl implements ICcExtNumService
|
|
|
public int companyUnbindExtNum(List<String> userCodeList) {
|
|
public int companyUnbindExtNum(List<String> userCodeList) {
|
|
|
return ccExtNumMapper.companyUnbindExtNum(userCodeList);
|
|
return ccExtNumMapper.companyUnbindExtNum(userCodeList);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public int batchInsertCcExtNum(int count, String extPass, String userCode, Long startExtNum) {
|
|
|
|
|
+ // ==================== 1. 参数校验(安全检查)====================
|
|
|
|
|
+ if (count <= 0 || count > 10000) {
|
|
|
|
|
+ throw new RuntimeException("生成数量必须在1-10000之间");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 密码可以为空字符串,但不能为null
|
|
|
|
|
+ if (extPass == null) {
|
|
|
|
|
+ extPass = "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 密码长度校验(最多50个字符)
|
|
|
|
|
+ if (extPass.length() > 50) {
|
|
|
|
|
+ throw new RuntimeException("分机密码长度不能超过50个字符");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 工号长度校验(最多32个字符)
|
|
|
|
|
+ if (StringUtils.isNotEmpty(userCode) && userCode.length() > 32) {
|
|
|
|
|
+ throw new RuntimeException("工号长度不能超过32个字符");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 2. 工号唯一性校验 ====================
|
|
|
|
|
+ if (StringUtils.isNotEmpty(userCode)) {
|
|
|
|
|
+ CcExtNum checkUserCode = selectCcExtNumByUserCode(userCode);
|
|
|
|
|
+ if (checkUserCode != null) {
|
|
|
|
|
+ throw new RuntimeException("该工号已经绑定分机" + checkUserCode.getExtNum() + ",不允许重复绑定!");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 3. 确定起始分机号(使用缓存+数据库双重保障)====================
|
|
|
|
|
+ long actualStartExtNum;
|
|
|
|
|
+ long currentMaxExtNum;
|
|
|
|
|
+
|
|
|
|
|
+ synchronized (cacheLock) {
|
|
|
|
|
+ // 如果缓存为0,说明是第一次使用或系统刚重启,需要从数据库查询
|
|
|
|
|
+ if (cachedMaxExtNum == 0) {
|
|
|
|
|
+ Long dbMaxExtNum = ccExtNumMapper.selectMaxExtNum();
|
|
|
|
|
+ currentMaxExtNum = (dbMaxExtNum == null) ? 0L : dbMaxExtNum;
|
|
|
|
|
+ cachedMaxExtNum = currentMaxExtNum; // 更新缓存
|
|
|
|
|
+ log.info("首次初始化分机号缓存,数据库最大分机号: {}", currentMaxExtNum);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ currentMaxExtNum = cachedMaxExtNum;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确定实际起始分机号
|
|
|
|
|
+ if (startExtNum == null || startExtNum <= 0) {
|
|
|
|
|
+ actualStartExtNum = currentMaxExtNum + 1;
|
|
|
|
|
+ if (actualStartExtNum <= 0) {
|
|
|
|
|
+ actualStartExtNum = 1L; // 确保起始值至少为1
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 校验用户指定的起始分机号不能小于当前最大值
|
|
|
|
|
+ if (startExtNum <= currentMaxExtNum) {
|
|
|
|
|
+ throw new RuntimeException("起始分机号(" + startExtNum + ")不能小于等于当前最大分机号(" + currentMaxExtNum + "),可选择不填起始分机号");
|
|
|
|
|
+ }
|
|
|
|
|
+ actualStartExtNum = startExtNum;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 4. 查询已存在的分机号(用于去重)====================
|
|
|
|
|
+ List<Long> existingExtNums = ccExtNumMapper.selectExtNumsGreaterThan(actualStartExtNum - 1);
|
|
|
|
|
+ Set<Long> existingExtNumSet = new HashSet<>(existingExtNums);
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 5. 分批生成分机号并插入(防止内存溢出)====================
|
|
|
|
|
+ int totalInserted = 0;
|
|
|
|
|
+ long currentExtNum = actualStartExtNum;
|
|
|
|
|
+ int remainingCount = count;
|
|
|
|
|
+
|
|
|
|
|
+ // 分机号最大值限制(防止Long溢出)
|
|
|
|
|
+ final long MAX_EXT_NUM = Long.MAX_VALUE - 10000; // 预留安全空间
|
|
|
|
|
+
|
|
|
|
|
+ // 外层批次大小:每次生成1000个分机号
|
|
|
|
|
+ final int GENERATE_BATCH_SIZE = 1000;
|
|
|
|
|
+ // 内层插入批次大小:每次插入500条到数据库
|
|
|
|
|
+ final int INSERT_BATCH_SIZE = 500;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ while (remainingCount > 0) {
|
|
|
|
|
+ // 计算当前批次要生成的数量
|
|
|
|
|
+ int currentBatchSize = Math.min(GENERATE_BATCH_SIZE, remainingCount);
|
|
|
|
|
+ List<CcExtNum> currentBatchList = new ArrayList<>(currentBatchSize);
|
|
|
|
|
+
|
|
|
|
|
+ // 生成当前批次的分机号
|
|
|
|
|
+ int generatedInBatch = 0;
|
|
|
|
|
+ long attempts = 0;
|
|
|
|
|
+ // 防死循环:每个批次最多尝试 currentBatchSize * 10 次
|
|
|
|
|
+ long maxAttemptsPerBatch = (long) currentBatchSize * 10L;
|
|
|
|
|
+
|
|
|
|
|
+ while (generatedInBatch < currentBatchSize) {
|
|
|
|
|
+ attempts++;
|
|
|
|
|
+
|
|
|
|
|
+ // 【关键】安全检查1:防止死循环
|
|
|
|
|
+ if (attempts > maxAttemptsPerBatch) {
|
|
|
|
|
+ String errorMsg = String.format(
|
|
|
|
|
+ "生成分机号失败:在区间[%d, %d]内可用分机号不足(已尝试%d次)。建议:1)删除部分已有分机号 2)指定更大的起始位置",
|
|
|
|
|
+ actualStartExtNum, currentExtNum, attempts
|
|
|
|
|
+ );
|
|
|
|
|
+ log.error(errorMsg);
|
|
|
|
|
+ throw new RuntimeException(errorMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 【关键】安全检查2:防止Long溢出
|
|
|
|
|
+ if (currentExtNum <= 0 || currentExtNum >= MAX_EXT_NUM) {
|
|
|
|
|
+ String errorMsg = String.format(
|
|
|
|
|
+ "分机号已达到系统上限(currentExtNum=%d, MAX_EXT_NUM=%d),无法继续生成",
|
|
|
|
|
+ currentExtNum, MAX_EXT_NUM
|
|
|
|
|
+ );
|
|
|
|
|
+ log.error(errorMsg);
|
|
|
|
|
+ throw new RuntimeException(errorMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果当前分机号不存在,则添加到当前批次列表
|
|
|
|
|
+ if (!existingExtNumSet.contains(currentExtNum)) {
|
|
|
|
|
+ CcExtNum newExt = new CcExtNum();
|
|
|
|
|
+ newExt.setExtNum(currentExtNum);
|
|
|
|
|
+ newExt.setExtPass(extPass);
|
|
|
|
|
+ newExt.setUserCode(userCode != null ? userCode : "");
|
|
|
|
|
+ currentBatchList.add(newExt);
|
|
|
|
|
+ generatedInBatch++;
|
|
|
|
|
+
|
|
|
|
|
+ // 将新生成的分机号加入已存在集合,避免同一批次内重复
|
|
|
|
|
+ existingExtNumSet.add(currentExtNum);
|
|
|
|
|
+ }
|
|
|
|
|
+ currentExtNum++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 6. 分批插入数据库 ====================
|
|
|
|
|
+ if (!currentBatchList.isEmpty()) {
|
|
|
|
|
+ for (int i = 0; i < currentBatchList.size(); i += INSERT_BATCH_SIZE) {
|
|
|
|
|
+ int endIndex = Math.min(i + INSERT_BATCH_SIZE, currentBatchList.size());
|
|
|
|
|
+ List<CcExtNum> insertBatchList = currentBatchList.subList(i, endIndex);
|
|
|
|
|
+ int inserted = ccExtNumMapper.batchInsertCcExtNum(insertBatchList);
|
|
|
|
|
+ totalInserted += inserted;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新剩余计数
|
|
|
|
|
+ remainingCount -= generatedInBatch;
|
|
|
|
|
+
|
|
|
|
|
+ // 【关键】更新缓存的最大分机号(在同步块中保证线程安全)
|
|
|
|
|
+ synchronized (cacheLock) {
|
|
|
|
|
+ long newMaxExtNum = currentBatchList.get(currentBatchList.size() - 1).getExtNum();
|
|
|
|
|
+ if (newMaxExtNum > cachedMaxExtNum) {
|
|
|
|
|
+ cachedMaxExtNum = newMaxExtNum;
|
|
|
|
|
+ log.debug("更新分机号缓存为: {}", cachedMaxExtNum);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 记录进度日志
|
|
|
|
|
+ log.info("批量生成分机号进度: {}/{}", totalInserted, count);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("批量生成分机号完成,共生成 {} 条记录", totalInserted);
|
|
|
|
|
+ return totalInserted;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (RuntimeException e) {
|
|
|
|
|
+ log.error("批量生成分机号失败: {}", e.getMessage(), e);
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("批量生成分机号发生未知错误", e);
|
|
|
|
|
+ throw new RuntimeException("批量生成分机号失败: " + e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|