三七 1 napja
szülő
commit
9b23b07e5d

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsInfoController.java

@@ -520,7 +520,7 @@ public class SopUserLogsInfoController extends BaseController
     }
 
     /**
-     * 一键群发sopUserLogsInfo
+     * 营期一键群发sopUserLogsInfo
      */
     @PreAuthorize("@ss.hasPermi('qw:sopUserLogsInfo:msgSop')")
     @Log(title = "sendUserLogsInfoMsgSop", businessType = BusinessType.INSERT,isSaveRequestData=false)

+ 121 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -44,6 +44,7 @@ import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.sop.vo.QwCreateLinkByAppVO;
 import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.system.service.ISysConfigService;
+import com.fs.utils.ShortCodeGeneratorUtils;
 import com.fs.voice.utils.StringUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
@@ -1104,6 +1105,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         setting.setMiniprogramAppid("未找到匹配的公司的自定义小程序:"+companyId);
                     }
 
+                    break;
+                case "21"://短信看课
+                    if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {
+                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
+                        sortLink = generateSmsShortLink(setting, logVo, sendTime, courseId, videoId,
+                                qwUserId, companyUserId, companyId, externalId, isOfficial, sopLogs.getFsUserId());
+
+                        if (StringUtils.isNotBlank(sortLink)) {
+                            if(StringUtils.isNotBlank(setting.getSmsTemplateContent()) && setting.getSmsTemplateContent().contains("${sms.courseUrl}")){
+                                setting.setValue(setting.getSmsTemplateContent()
+                                        .replaceAll("【(.*?)】", "【" + cachedCourseConfig.getSmsDomain() + "】")
+                                        .replace("${sms.courseUrl}", sortLink));
+                            }else{
+                                log.error("生成看课短链时检测到短信模板选择错误,跳过设置 URL。");
+                            }
+                        } else {
+                            log.error("生成看课短链失败,跳过设置 URL。");
+                        }
+                    }
                     break;
                 default:
                     break;
@@ -1280,6 +1300,107 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         return sortLink.replaceAll("^[\\s\\u2005]+", "");
     }
 
+    private String generateSmsShortLink(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
+                                        Long courseId, Long videoId, String qwUserId,
+                                        String companyUserId, String companyId, String externalId, String isOfficial, Long fsUserId) {
+        // 获取缓存的配置
+        CourseConfig config;
+        synchronized (configLock) {
+            config = cachedCourseConfig;
+        }
+
+        if (config == null) {
+            log.error("CourseConfig is not loaded.");
+            return "";
+        }
+
+        // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(Long.parseLong(companyId));
+        link.setUNo(UUID.randomUUID().toString());
+        link.setQwUserId(Long.valueOf(qwUserId));
+        link.setCompanyUserId(Long.parseLong(companyUserId));
+        link.setVideoId(videoId.longValue());
+        link.setCorpId(logVo.getCorpId());
+        link.setCourseId(courseId.longValue());
+        link.setQwExternalId(Long.parseLong(externalId));
+
+        if (StringUtil.strIsNullOrEmpty(isOfficial)) {
+            link.setLinkType(0);
+        } else {
+//            link.setLinkType(isOfficial.equals("1") ? 5 : 0);
+            if (isOfficial.equals("1")) {
+                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
+                    link.setLinkType(0);
+                } else {
+                    link.setLinkType(5);
+                }
+            } else if (isOfficial.equals("0")) {
+                link.setLinkType(0);
+            } else {
+                link.setLinkType(0);
+            }
+        }
+
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        courseMap.setCompanyId(link.getCompanyId());
+        courseMap.setQwUserId(link.getQwUserId());
+        courseMap.setCompanyUserId(link.getCompanyUserId());
+        courseMap.setVideoId(link.getVideoId());
+        courseMap.setCorpId(link.getCorpId());
+        courseMap.setCourseId(link.getCourseId());
+        courseMap.setQwExternalId(link.getQwExternalId());
+
+
+        if (StringUtil.strIsNullOrEmpty(isOfficial)) {
+            courseMap.setLinkType(0);
+        } else {
+            if (isOfficial.equals("1")) {
+                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
+                    courseMap.setLinkType(0);
+                } else {
+                    courseMap.setLinkType(5);
+                }
+            } else if (isOfficial.equals("0")) {
+                courseMap.setLinkType(0);
+            } else {
+                courseMap.setLinkType(0);
+            }
+        }
+
+        String courseJson = JSON.toJSONString(courseMap);
+        String realLinkFull = REAL_LINK_PREFIX + courseJson;
+        link.setRealLink(realLinkFull);
+
+        String randomString = ShortCodeGeneratorUtils.generate8();
+        if (StringUtil.strIsNullOrEmpty(randomString)) {
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        } else {
+            link.setLink(randomString);
+        }
+
+        link.setCreateTime(sendTime);
+
+        Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
+                ? config.getVideoLinkExpireDate()
+                : setting.getExpiresDays();
+
+        // 使用 Java 8 时间 API 计算过期时间
+        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
+        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+        link.setUpdateTime(updateTime);
+        if(StringUtils.isEmpty(config.getSmsDomainName())){
+            log.error("检测到未配置看课短信链接域名");
+            return null;
+        }
+        String sortLink = config.getSmsDomainName()+ "/" + link.getLink();
+        enqueueCourseLink(link);
+        return sortLink;
+    }
+
     private QwCreateLinkByAppVO createLinkByApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                                 Long courseId, Long videoId, String qwUserId,
                                                 String companyUserId, String companyId, String externalId,String corpId,String qwUserName){

+ 2 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -19,6 +19,8 @@ public class CourseConfig implements Serializable {
     private Integer defaultLine;//默认看课线路
     private String realLinkDomainName;//真链域名
     private String authDomainName;//网页授权域名
+    private String smsDomainName;//短信推送域名
+    private String smsDomain;//短信推送域名
     private String mpAppId;//看课公众号APPID
     private String registerDomainName;//注册域名
     private String courseDomainName;//链接域名

+ 16 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopCourseFinishTempSetting.java

@@ -112,6 +112,22 @@ public class QwSopCourseFinishTempSetting implements Serializable,Cloneable{
         // 原因
         private String sendRemarks;
 
+        // 短信CODE
+        private String smsTemplateCode;
+
+        /**
+         * 短信模板id
+         */
+        private Integer smsTemplateId;
+        /**
+         * 短信模板标题
+         */
+        private String smsTemplateTitle;
+        /**
+         * 短信模板内容
+         */
+        private String smsTemplateContent;
+
         @Override
         public Setting clone() {
             try {

+ 17 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java

@@ -150,6 +150,23 @@ public class QwSopTempSetting implements Serializable{
             //app显示标题 app用的参数
             private String title;
 
+            // 短信CODE
+            private String smsTemplateCode;
+
+            /**
+             * 短信模板id
+             */
+            private Integer smsTemplateId;
+            /**
+             * 短信模板标题
+             */
+            private String smsTemplateTitle;
+            /**
+             * 短信模板内容
+             */
+            private String smsTemplateContent;
+
+
             @Override
             public Setting clone() {
                 try {

+ 11 - 0
fs-service/src/main/java/com/fs/sop/params/SendUserLogsInfoMsgParam.java

@@ -2,6 +2,8 @@ package com.fs.sop.params;
 
 import lombok.Data;
 
+import java.util.List;
+
 /**
 * 一键群发
 */
@@ -30,4 +32,13 @@ public class SendUserLogsInfoMsgParam {
 
     private String sendTime;
 
+    private Integer isMine; //1:我的营期
+
+    private List<String> qwUserIds; //当isMine为1 需要查询该主体下该员工的营期
+
+    /**
+     * 直播间id
+     */
+    private Long liveId;
+
 }

+ 85 - 2
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
@@ -58,6 +59,7 @@ import com.fs.sop.vo.QwCreateLinkByAppVO;
 import com.fs.sop.vo.SopUserLogsInfoVOE;
 import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.system.service.ISysConfigService;
+import com.fs.utils.ShortCodeGeneratorUtils;
 import com.fs.voice.utils.StringUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -88,6 +90,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     private static final String SHORT_LINK_PREFIX = "/courseH5/pages/course/learning?s=";
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String appRealLink = "/pages/courseAnswer/index?link=";
+    private static final String registeredRealLink = "/pages_course/register.html?link=";
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
 //    private static final String miniappRealLink = "/pages/index/index?course=";
 
@@ -935,6 +938,26 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             }
 
                             break;
+                        case "21":
+                            if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {
+                                addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(), item.getFsUserId(), qwUserId, companyUserId, companyId,
+                                        item.getExternalId(), item.getStartTime(), createTime);
+                                String link = createSmsShortLink(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
+                                        String.valueOf(qwUser.getId()), companyUserId, companyId, item.getExternalId(), config);
+                                if (StringUtils.isNotBlank(link)) {
+                                    if(StringUtils.isNotBlank(st.getSmsTemplateContent()) && st.getSmsTemplateContent().contains("${sms.courseUrl}")){
+                                        st.setValue(st.getSmsTemplateContent()
+                                                .replaceAll("【(.*?)】", "【" + config.getSmsDomain() + "】")
+                                                .replace("${sms.courseUrl}", link));
+                                    }else{
+                                        log.error("生成看课短链时检测到短信模板选择错误,跳过设置 URL。");
+                                    }
+                                } else {
+                                    log.error("生成看课短链失败,跳过设置 URL。");
+                                }
+                            }
+                            break;
+
                         default:
                             break;
 
@@ -1180,7 +1203,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             switch (finalSendType){
                 case 5:
                     List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,
-                            contact,dataTime, finalDomainName,miniMap,companies);
+                            contact,dataTime, finalDomainName,miniMap,companies,sopLogs);
                     setting.setSetting(list);
                     break;
                 case 9:
@@ -1244,7 +1267,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                                                       CourseConfig config,QwCompany qwCompany,String companyUserId, String companyId,
                                                                       QwExternalContact contact,Date dataTime,String domainName,
                                                                       Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
-                                                                      List<Company> companies ){
+                                                                      List<Company> companies,QwSopLogs sopLogs ){
         List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(param.getSetting(),QwSopCourseFinishTempSetting.Setting.class);
 
         for (QwSopCourseFinishTempSetting.Setting st : list) {
@@ -1377,6 +1400,26 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     }
 
                     break;
+
+                case "21":
+                    if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {
+                        addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(), item.getFsUserId(), String.valueOf(qwUser.getId()), companyUserId, companyId,
+                                item.getExternalId(), item.getStartTime(), dataTime);
+                        String link = createSmsShortLink(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
+                                String.valueOf(qwUser.getId()), companyUserId, companyId, item.getExternalId(), config);
+                        if (StringUtils.isNotBlank(link)) {
+                            if(StringUtils.isNotBlank(st.getSmsTemplateContent()) && st.getSmsTemplateContent().contains("${sms.courseUrl}")){
+                                st.setValue(st.getSmsTemplateContent()
+                                        .replaceAll("【(.*?)】", "【" + config.getSmsDomain() + "】")
+                                        .replace("${sms.courseUrl}", link));
+                            }else{
+                                log.error("生成看课短链时检测到短信模板选择错误,跳过设置 URL。");
+                            }
+                        } else {
+                            log.error("生成看课短链失败,跳过设置 URL。");
+                        }
+                    }
+                    break;
                 default:
                     break;
 
@@ -1386,6 +1429,46 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return  list;
     }
 
+
+    private String createSmsShortLink(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
+                                      Integer courseId, Integer videoId, String qwUserId,
+                                      String companyUserId, String companyId, Long externalId, CourseConfig config) {
+
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(Long.parseLong(companyId));
+        link.setQwUserId(Long.valueOf(qwUserId));
+        link.setCompanyUserId(Long.parseLong(companyUserId));
+        link.setVideoId(videoId.longValue());
+        link.setCorpId(corpId);
+        link.setCourseId(courseId.longValue());
+        link.setQwExternalId(externalId);
+        link.setLinkType(0); //正常链接
+        link.setUNo(UUID.randomUUID().toString());
+        String randomString = ShortCodeGeneratorUtils.generate8();
+        if (StringUtil.strIsNullOrEmpty(randomString)) {
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        } else {
+            link.setLink(randomString);
+        }
+        link.setCreateTime(sendTime);;
+        Date updateTime = createUpdateTime(setting, sendTime, config);
+        link.setUpdateTime(updateTime);
+
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+
+        String realLinkFull = registeredRealLink + JSON.toJSONString(courseMap);
+        link.setRealLink(realLinkFull);
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        if(StringUtils.isEmpty(config.getSmsDomainName())){
+            log.error("检测到未配置看课短信链接域名");
+            return null;
+        }
+        return config.getSmsDomainName() + "/" + link.getLink();
+    }
+
+
     private List<QwSopCourseFinishTempSetting.Setting> parseSettings(String jsonData) {
         try {
             if (jsonData.startsWith("[") && jsonData.endsWith("]")) {

+ 424 - 0
fs-service/src/main/java/com/fs/utils/ShortCodeGeneratorUtils.java

@@ -0,0 +1,424 @@
+package com.fs.utils;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * 短码生成工具类 - 高并发、高唯一性短码生成器
+ * 修复版本:解决数组越界和线程获取问题
+ *
+ * @author ToolKit
+ * @version 1.0.2
+ */
+public final class ShortCodeGeneratorUtils {
+
+    // 私有构造器,防止实例化
+    private ShortCodeGeneratorUtils() {
+        throw new AssertionError("Cannot instantiate utility class");
+    }
+
+    /**
+     * 字符集定义枚举
+     */
+    public enum CharsetType {
+
+        /** URL安全字符集(推荐) */
+        URL_SAFE("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+
+        private final String chars;
+
+        CharsetType(String chars) {
+            this.chars = chars;
+        }
+
+        public String getChars() {
+            return chars;
+        }
+
+        public char[] getCharArray() {
+            return chars.toCharArray();
+        }
+
+        public int size() {
+            return chars.length();
+        }
+    }
+
+    // 默认配置
+    private static final CharsetType DEFAULT_CHARSET = CharsetType.URL_SAFE;
+
+    // 单例实例(延迟加载)
+    private static class InstanceHolder {
+        static final ShortCodeGenerator INSTANCE = new ShortCodeGenerator(DEFAULT_CHARSET);
+    }
+
+    /**
+     * 获取默认生成器实例
+     */
+    public static ShortCodeGenerator getInstance() {
+        return InstanceHolder.INSTANCE;
+    }
+
+    /**
+     * 获取指定字符集的生成器实例
+     */
+    public static ShortCodeGenerator getInstance(CharsetType charsetType) {
+        return new ShortCodeGenerator(charsetType);
+    }
+
+    /**
+     * 快速生成8位短码(使用默认配置)
+     */
+    public static String generate8() {
+        return getInstance().generate8CharCode();
+    }
+
+    /**
+     * 快速生成6位短码(使用默认配置)
+     */
+    public static String generate6() {
+        return getInstance().generate6CharCode();
+    }
+
+    /**
+     * 快速生成指定长度短码(使用默认配置)
+     */
+    public static String generate(int length) {
+        return getInstance().generateCode(length);
+    }
+
+    /**
+     * 短码生成器核心实现 - 完全修复版本
+     */
+    public static class ShortCodeGenerator {
+
+        private final char[] charset;
+        private final int charsetSize;
+        private final AtomicLong counter = new AtomicLong(0);
+        private volatile long lastTimestamp = 0;
+        private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+        private final SecureRandom secureRandom;
+        private static final long PROCESS_ID = getProcessId();
+
+        /**
+         * 使用默认字符集创建生成器
+         */
+        public ShortCodeGenerator() {
+            this(DEFAULT_CHARSET);
+        }
+
+        /**
+         * 使用指定字符集创建生成器
+         */
+        public ShortCodeGenerator(CharsetType charsetType) {
+            this.charset = charsetType.getCharArray();
+            this.charsetSize = charsetType.size();
+            this.secureRandom = new SecureRandom();
+            // 初始化安全随机数生成器
+            secureRandom.nextBytes(new byte[16]);
+        }
+
+        /**
+         * 生成8位短码
+         */
+        public String generate8CharCode() {
+            return generateCode(8);
+        }
+
+        /**
+         * 生成6位短码
+         */
+        public String generate6CharCode() {
+            return generateCode(6);
+        }
+
+        /**
+         * 生成指定长度短码
+         */
+        public String generateCode(int length) {
+            validateLength(length);
+
+            try {
+                // 组合多种生成策略降低碰撞概率
+                String codeByTime = generateByTimestamp(length);
+                String codeByHash = generateByHash(length);
+
+                return mixAndFinalize(codeByTime, codeByHash, length);
+            } catch (Exception e) {
+                // 如果任何方法失败,使用安全的降级方案
+                return generateSafeFallbackCode(length);
+            }
+        }
+
+        /**
+         * 批量生成短码
+         */
+        public String[] generateBatch(int count, int length) {
+            validateLength(length);
+
+            String[] codes = new String[count];
+
+            for (int i = 0; i < count; i++) {
+                codes[i] = generateCode(length);
+            }
+
+            return codes;
+        }
+
+        /**
+         * 获取字符集大小
+         */
+        public int getCharsetSize() {
+            return charsetSize;
+        }
+
+        /**
+         * 获取字符集
+         */
+        public String getCharsetString() {
+            return new String(charset);
+        }
+
+        /**
+         * 验证长度参数
+         */
+        private void validateLength(int length) {
+            if (length < 4 || length > 16) {
+                throw new IllegalArgumentException("Length must be between 4 and 16");
+            }
+        }
+
+        /**
+         * 安全的降级生成方案
+         */
+        private String generateSafeFallbackCode(int length) {
+            char[] result = new char[length];
+            ThreadLocalRandom random = ThreadLocalRandom.current();
+
+            for (int i = 0; i < length; i++) {
+                // 使用安全的随机数生成,确保索引为正数
+                int index = random.nextInt(charsetSize);
+                result[i] = charset[index];
+            }
+
+            return new String(result);
+        }
+
+        /**
+         * 基于时间戳生成 - 修复线程ID获取问题
+         */
+        private String generateByTimestamp(int length) {
+            long currentTime = System.currentTimeMillis();
+            long sequence;
+
+            lock.writeLock().lock();
+            try {
+                if (currentTime == lastTimestamp) {
+                    sequence = counter.incrementAndGet();
+                } else {
+                    lastTimestamp = currentTime;
+                    counter.set(0);
+                    sequence = 0;
+                }
+            } finally {
+                lock.writeLock().unlock();
+            }
+
+            ThreadLocalRandom random = ThreadLocalRandom.current();
+            long randomPart = random.nextLong();
+
+            // 获取当前线程ID
+            long threadId = Thread.currentThread().getId();
+
+            // 组合各种熵源
+            long combined = currentTime ^ (sequence << 16) ^ randomPart;
+            combined ^= System.nanoTime();
+            combined ^= (PROCESS_ID << 48);
+            combined ^= (threadId << 32);
+
+            // 确保为正数
+            combined = Math.abs(combined);
+            if (combined == Long.MIN_VALUE) {
+                combined = Long.MAX_VALUE; // 处理边界情况
+            }
+
+            return convertToBaseSafe(combined, length);
+        }
+
+        /**
+         * 基于哈希算法生成 - 修复线程ID获取问题
+         */
+        private String generateByHash(int length) {
+            try {
+                MessageDigest md = MessageDigest.getInstance("SHA-256");
+
+                // 使用多个熵源确保唯一性
+                StringBuilder entropy = new StringBuilder();
+                entropy.append(System.nanoTime())
+                        .append(Thread.currentThread().getId())  // 修复这里
+                        .append(System.identityHashCode(this))
+                        .append(secureRandom.nextLong())
+                        .append(counter.get())
+                        .append(Runtime.getRuntime().freeMemory());
+
+                byte[] hash = md.digest(entropy.toString().getBytes());
+
+                // 使用BigInteger避免负数和溢出问题
+                BigInteger bigInt = new BigInteger(1, hash); // 正数模式
+                BigInteger modValue = BigInteger.valueOf(charsetSize).pow(length);
+
+                // 取模确保值在范围内
+                BigInteger result = bigInt.mod(modValue);
+
+                return convertBigIntegerToBase(result, length);
+
+            } catch (NoSuchAlgorithmException e) {
+                // 降级方案:使用增强随机数
+                return generateEnhancedRandomCode(length);
+            }
+        }
+
+        /**
+         * 增强随机数生成
+         */
+        private String generateEnhancedRandomCode(int length) {
+            char[] result = new char[length];
+
+            for (int i = 0; i < length; i++) {
+                // 使用安全的随机索引生成
+                int index = secureRandom.nextInt(charsetSize);
+                result[i] = charset[index];
+            }
+
+            return new String(result);
+        }
+
+        /**
+         * 安全的进制转换 - 完全修复版本
+         */
+        private String convertToBaseSafe(long number, int length) {
+            char[] result = new char[length];
+
+            // 确保number为正数
+            long temp = Math.abs(number);
+            if (temp == 0) {
+                temp = System.nanoTime() & 0x7FFFFFFF; // 使用纳秒时间作为备用值
+            }
+
+            // 基本转换
+            for (int i = length - 1; i >= 0; i--) {
+                long remainder = temp % charsetSize;
+                int index = (int) remainder;
+
+                // 确保索引在有效范围内
+                if (index < 0) {
+                    index = (int) Math.abs(remainder);
+                }
+                index = index % charsetSize;
+
+                result[i] = charset[index];
+                temp = temp / charsetSize;
+            }
+
+            return new String(result);
+        }
+
+        /**
+         * BigInteger版本的安全进制转换
+         */
+        private String convertBigIntegerToBase(BigInteger number, int length) {
+            char[] result = new char[length];
+            BigInteger base = BigInteger.valueOf(charsetSize);
+            BigInteger temp = number;
+
+            // 确保temp为正数
+            if (temp.signum() < 0) {
+                temp = temp.abs();
+            }
+
+            for (int i = length - 1; i >= 0; i--) {
+                BigInteger[] divResult = temp.divideAndRemainder(base);
+                int index = divResult[1].intValue();
+
+                // 确保索引为正数且在范围内
+                if (index < 0) {
+                    index = -index;
+                }
+                index = index % charsetSize;
+
+                result[i] = charset[index];
+                temp = divResult[0];
+            }
+
+            return new String(result);
+        }
+
+        /**
+         * 混合并最终化代码
+         */
+        private String mixAndFinalize(String code1, String code2, int length) {
+            char[] result = new char[length];
+
+            for (int i = 0; i < length; i++) {
+                char c1 = code1.charAt(i % code1.length());
+                char c2 = code2.charAt((i + 1) % code2.length());
+
+                // 使用安全的混合方法
+                int mixed = safeMixChars(c1, c2, i);
+                result[i] = charset[mixed];
+            }
+
+            return new String(result);
+        }
+
+        /**
+         * 安全的字符混合
+         */
+        private int safeMixChars(char c1, char c2, int position) {
+            long l1 = (long) c1;
+            long l2 = (long) c2;
+
+            // 使用不同的混合策略,但都确保结果为非负数
+            long mixedLong;
+            switch (position % 4) {
+                case 0:
+                    mixedLong = (l1 + l2 * 31L) % charsetSize;
+                    break;
+                case 1:
+                    mixedLong = (l1 ^ l2) % charsetSize;
+                    break;
+                case 2:
+                    mixedLong = (l1 * 17L + l2) % charsetSize;
+                    break;
+                default:
+                    mixedLong = (l1 * 3L + l2 * 7L) % charsetSize;
+                    break;
+            }
+
+            // 确保结果为正数
+            int mixed = (int) Math.abs(mixedLong) % charsetSize;
+            return mixed;
+        }
+
+        /**
+         * 获取进程ID
+         */
+        private static long getProcessId() {
+            try {
+                String processName = java.lang.management.ManagementFactory
+                        .getRuntimeMXBean()
+                        .getName();
+                String pidStr = processName.split("@")[0];
+                return Long.parseLong(pidStr) & 0xFFFF; // 限制范围
+            } catch (Exception e) {
+                // 返回备用值
+                return Thread.currentThread().getId() & 0xFFFF;
+            }
+        }
+    }
+}