package com.ruoyi.cc.service.impl; import java.util.*; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.ruoyi.cc.service.ICcParamsService; import com.ruoyi.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.cc.mapper.CcExtNumMapper; import com.ruoyi.cc.domain.CcExtNum; import com.ruoyi.cc.service.ICcExtNumService; import com.ruoyi.common.core.text.Convert; /** * 【请填写功能名称】Service业务层处理 * * @author ruoyi * @date 2024-12-22 */ @Service @Slf4j public class CcExtNumServiceImpl implements ICcExtNumService { @Autowired private CcExtNumMapper ccExtNumMapper; @Autowired private ICcParamsService ccParamsService; private String authTokenSecret; // 缓存最大分机号,初始值为0表示未初始化,需要从数据库查询 // 使用volatile保证多线程可见性 private volatile long cachedMaxExtNum = 0; // 用于同步获取和更新缓存的锁对象 private final Object cacheLock = new Object(); /** * 查询【请填写功能名称】 * * @param extId 【请填写功能名称】主键 * @return 【请填写功能名称】 */ @Override public CcExtNum selectCcExtNumByExtId(Long extId) { return ccExtNumMapper.selectCcExtNumByExtId(extId); } /** * 查询【请填写功能名称】列表 * * @param ccExtNum 【请填写功能名称】 * @return 【请填写功能名称】 */ @Override public List selectCcExtNumList(CcExtNum ccExtNum) { return ccExtNumMapper.selectCcExtNumList(ccExtNum); } /** * 新增【请填写功能名称】 * * @param ccExtNum 【请填写功能名称】 * @return 结果 */ @Override public int insertCcExtNum(CcExtNum ccExtNum) { return ccExtNumMapper.insertCcExtNum(ccExtNum); } /** * 修改【请填写功能名称】 * * @param ccExtNum 【请填写功能名称】 * @return 结果 */ @Override public int updateCcExtNum(CcExtNum ccExtNum) { return ccExtNumMapper.updateCcExtNum(ccExtNum); } /** * 批量删除【请填写功能名称】 * * @param extIds 需要删除的【请填写功能名称】主键 * @return 结果 */ @Override public int deleteCcExtNumByExtIds(String extIds) { return ccExtNumMapper.deleteCcExtNumByExtIds(Convert.toStrArray(extIds)); } /** * 删除【请填写功能名称】信息 * * @param extId 【请填写功能名称】主键 * @return 结果 */ @Override public int deleteCcExtNumByExtId(Long extId) { return ccExtNumMapper.deleteCcExtNumByExtId(extId); } @Override public CcExtNum selectCcExtNumByExtNum(Long extNum) { List list = selectCcExtNumList(new CcExtNum().setExtNum(extNum)); if (!list.isEmpty()) { return list.get(0); } return null; } @Override public CcExtNum selectCcExtNumByUserCode(String userCode) { List list = selectCcExtNumList(new CcExtNum().setUserCode(userCode)); if (!list.isEmpty()) { return list.get(0); } return null; } @Override public String createToken(String extnum, String opnum, String groupId, String skillLevel, String projectId) { try { log.info("extnum:{}, opnum:{}, groupId:{}", extnum, opnum, groupId); authTokenSecret = ccParamsService.getParamValueByCode( "ws-server-auth-token-secret", "123456"); //登录成功后生成JWT //JWT的header部分,该map可以是空的,因为有默认值{"alg":HS256,"typ":"JWT"} Map map = new HashMap<>(); Calendar instance = Calendar.getInstance(); instance.add(Calendar.HOUR, 24); return JWT.create() //添加头部 .withHeader(map) //添加payload .withClaim("extnum", extnum) .withClaim("opnum", opnum) .withClaim("groupId", groupId) .withClaim("skillLevel", skillLevel) .withClaim("projectId", projectId) //设置过期时间 .withExpiresAt(instance.getTime()) //设置签名 密钥 .sign(Algorithm.HMAC256(authTokenSecret)); } catch (Exception err) { log.error("error:" + err.getMessage()); } return null; } @Override public List selectUnBindCcExtNumList() { return ccExtNumMapper.selectUnBindCcExtNumList(); } @Override public int cleanCcExtNumByUserCode(String loginName) { return ccExtNumMapper.cleanCcExtNumByUserCode(loginName); } @Override public int updateCcExtNumByUserCode(CcExtNum extNum) { return ccExtNumMapper.updateCcExtNumByUserCode(extNum); } @Override public List selectUnBindCcExtNumListPage(CcExtNum ccExtNum) { return ccExtNumMapper.selectUnBindCcExtNumListPage(ccExtNum); } @Override public int updateCompanyBindExtNum(CcExtNum extNum) { return ccExtNumMapper.updateCompanyBindExtNum(extNum); } @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 existingExtNums = ccExtNumMapper.selectExtNumsGreaterThan(actualStartExtNum - 1); Set 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 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 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); } } @Override public int companyBatchUnbindSipExt(Set extNums) { return ccExtNumMapper.companyBatchUnbindSipExt(extNums); } }