| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- 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<CcExtNum> 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<CcExtNum> list = selectCcExtNumList(new CcExtNum().setExtNum(extNum));
- if (!list.isEmpty()) {
- return list.get(0);
- }
- return null;
- }
- @Override
- public CcExtNum selectCcExtNumByUserCode(String userCode) {
- List<CcExtNum> 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<String, Object> 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<CcExtNum> 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<CcExtNum> 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<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);
- }
- }
- @Override
- public int companyBatchUnbindSipExt(Set<Long> extNums) {
- return ccExtNumMapper.companyBatchUnbindSipExt(extNums);
- }
- }
|