三七 1 день назад
Родитель
Сommit
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')")
     @PreAuthorize("@ss.hasPermi('qw:sopUserLogsInfo:msgSop')")
     @Log(title = "sendUserLogsInfoMsgSop", businessType = BusinessType.INSERT,isSaveRequestData=false)
     @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.QwCreateLinkByAppVO;
 import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysConfigService;
+import com.fs.utils.ShortCodeGeneratorUtils;
 import com.fs.voice.utils.StringUtil;
 import com.fs.voice.utils.StringUtil;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
@@ -1104,6 +1105,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         setting.setMiniprogramAppid("未找到匹配的公司的自定义小程序:"+companyId);
                         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;
                     break;
                 default:
                 default:
                     break;
                     break;
@@ -1280,6 +1300,107 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         return sortLink.replaceAll("^[\\s\\u2005]+", "");
         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,
     private QwCreateLinkByAppVO createLinkByApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                                 Long courseId, Long videoId, String qwUserId,
                                                 Long courseId, Long videoId, String qwUserId,
                                                 String companyUserId, String companyId, String externalId,String corpId,String qwUserName){
                                                 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 Integer defaultLine;//默认看课线路
     private String realLinkDomainName;//真链域名
     private String realLinkDomainName;//真链域名
     private String authDomainName;//网页授权域名
     private String authDomainName;//网页授权域名
+    private String smsDomainName;//短信推送域名
+    private String smsDomain;//短信推送域名
     private String mpAppId;//看课公众号APPID
     private String mpAppId;//看课公众号APPID
     private String registerDomainName;//注册域名
     private String registerDomainName;//注册域名
     private String courseDomainName;//链接域名
     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;
         private String sendRemarks;
 
 
+        // 短信CODE
+        private String smsTemplateCode;
+
+        /**
+         * 短信模板id
+         */
+        private Integer smsTemplateId;
+        /**
+         * 短信模板标题
+         */
+        private String smsTemplateTitle;
+        /**
+         * 短信模板内容
+         */
+        private String smsTemplateContent;
+
         @Override
         @Override
         public Setting clone() {
         public Setting clone() {
             try {
             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用的参数
             //app显示标题 app用的参数
             private String title;
             private String title;
 
 
+            // 短信CODE
+            private String smsTemplateCode;
+
+            /**
+             * 短信模板id
+             */
+            private Integer smsTemplateId;
+            /**
+             * 短信模板标题
+             */
+            private String smsTemplateTitle;
+            /**
+             * 短信模板内容
+             */
+            private String smsTemplateContent;
+
+
             @Override
             @Override
             public Setting clone() {
             public Setting clone() {
                 try {
                 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 lombok.Data;
 
 
+import java.util.List;
+
 /**
 /**
 * 一键群发
 * 一键群发
 */
 */
@@ -30,4 +32,13 @@ public class SendUserLogsInfoMsgParam {
 
 
     private String sendTime;
     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.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 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.SopUserLogsInfoVOE;
 import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysConfigService;
+import com.fs.utils.ShortCodeGeneratorUtils;
 import com.fs.voice.utils.StringUtil;
 import com.fs.voice.utils.StringUtil;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 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 SHORT_LINK_PREFIX = "/courseH5/pages/course/learning?s=";
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String appRealLink = "/pages/courseAnswer/index?link=";
     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 appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
 //    private static final String miniappRealLink = "/pages/index/index?course=";
 //    private static final String miniappRealLink = "/pages/index/index?course=";
 
 
@@ -935,6 +938,26 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             }
                             }
 
 
                             break;
                             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:
                         default:
                             break;
                             break;
 
 
@@ -1180,7 +1203,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             switch (finalSendType){
             switch (finalSendType){
                 case 5:
                 case 5:
                     List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,
                     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);
                     setting.setSetting(list);
                     break;
                     break;
                 case 9:
                 case 9:
@@ -1244,7 +1267,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                                                       CourseConfig config,QwCompany qwCompany,String companyUserId, String companyId,
                                                                       CourseConfig config,QwCompany qwCompany,String companyUserId, String companyId,
                                                                       QwExternalContact contact,Date dataTime,String domainName,
                                                                       QwExternalContact contact,Date dataTime,String domainName,
                                                                       Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
                                                                       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);
         List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(param.getSetting(),QwSopCourseFinishTempSetting.Setting.class);
 
 
         for (QwSopCourseFinishTempSetting.Setting st : list) {
         for (QwSopCourseFinishTempSetting.Setting st : list) {
@@ -1377,6 +1400,26 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     }
                     }
 
 
                     break;
                     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:
                 default:
                     break;
                     break;
 
 
@@ -1386,6 +1429,46 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return  list;
         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) {
     private List<QwSopCourseFinishTempSetting.Setting> parseSettings(String jsonData) {
         try {
         try {
             if (jsonData.startsWith("[") && jsonData.endsWith("]")) {
             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;
+            }
+        }
+    }
+}