Explorar el Código

销售端sop任务增加看课短链功能

peicj hace 1 semana
padre
commit
cefbbcdbea

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

@@ -52,6 +52,7 @@ import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 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;
@@ -1883,6 +1884,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         log.error("浏览器看课模板解析失败:" + e);
                     }
 
+                    break;
+                case "21"://短信看课
+                    if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {
+                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo,2);
+                        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;
@@ -2300,6 +2320,108 @@ 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());
+        courseMap.setUNo(link.getUNo());
+
+        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

@@ -22,6 +22,8 @@ public class CourseConfig implements Serializable {
     private String realLinkH5DomainName;//H5通用看课域名
     private String realLinkH5LiveName;//H5通用直播域名
     private String authDomainName;//网页授权域名
+    private String smsDomainName;//短信推送域名
+    private String smsDomain;//短信推送域名
     private String mpAppId;//看课公众号APPID
     private String registerDomainName;//注册域名
     private String courseDomainName;//链接域名

+ 1 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseRealLink.java

@@ -43,6 +43,7 @@ public class FsCourseRealLink implements Serializable
     private Long id;
 
     private String chatId;
+    private String uNo;
 
     private Long projectId;//项目ID
 

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

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

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

@@ -161,7 +161,21 @@ public class QwSopTempSetting implements Serializable{
              * 业务id
              */
             private String businessId;
+            // 短信CODE
+            private String smsTemplateCode;
 
+            /**
+             * 短信模板id
+             */
+            private Integer smsTemplateId;
+            /**
+             * 短信模板标题
+             */
+            private String smsTemplateTitle;
+            /**
+             * 短信模板内容
+             */
+            private String smsTemplateContent;
 
             @Override
             public Setting clone() {

+ 78 - 1
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -65,6 +65,7 @@ import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 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;
@@ -1545,6 +1546,25 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 log.error("浏览器看课模板解析失败:" + e);
                             }
 
+                            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,2);
+                                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;
                         //群公告(仅用于一键群发,个人不应该有群公告)
                         case "11":
@@ -2240,7 +2260,26 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                         log.error("赋值-小程序封面地址失败-" + e);
                     }
                     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,2);
+                        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().replace("${sms.courseUrl}", link));
+                                st.setValue(st.getSmsTemplateContent()
+                                        .replaceAll("【(.*?)】", "【" + config.getSmsDomain() + "】")
+                                        .replace("${sms.courseUrl}", link));
+                            }else{
+                                log.error("生成看课短链时检测到短信模板选择错误,跳过设置 URL。");
+                            }
+                        } else {
+                            log.error("生成看课短链失败,跳过设置 URL。");
+                        }
+                    }
+                    break;
                 default:
                     break;
 
@@ -2584,6 +2623,44 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return link.getRealLink().replaceAll("^[\\s\\u2005]+", "");
     }
 
+    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 QwCreateLinkByAppVO createLinkByApp(QwSopCourseFinishTempSetting.Setting setting, String corpId,
                                                 Date sendTime, Integer courseId, Integer videoId, Long qwUserId,
                                                 String companyUserId, String companyId, Long externalId,

+ 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;
+            }
+        }
+    }
+}