# CacheKeyUtil.java

    package com.yumu.noveltranslator.util;

    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;

    /**
     * 缓存 Key 生成工具类
     * 使用 MD5 算法生成唯一缓存键，避免简单 hashCode() 的碰撞问题
     * 支持版本号前缀，用于缓存一致性管理
     */
    public class CacheKeyUtil {

        private static final String DEFAULT_SOURCE_LANG = "auto";

        /**
         * 构建基础缓存 Key（不含引擎名，使用默认版本 "auto"）
         * 格式：v{version}:MD5(sourceText + "|" + sourceLang + "|" + targetLang)
         *
         * @param sourceText 原文文本
         * @param targetLang 目标语言
         * @return MD5 缓存键（带版本号前缀）
         */
        public static String buildCacheKey(String sourceText, String targetLang) {
            return buildCacheKey(sourceText, DEFAULT_SOURCE_LANG, targetLang);
        }

        /**
         * 构建缓存 Key（含源语言，不含引擎名，使用默认版本 "auto"）
         * 格式：v{version}:MD5(sourceText + "|" + sourceLang + "|" + targetLang)
         *
         * @param sourceText 原文文本
         * @param sourceLang 源语言
         * @param targetLang 目标语言
         * @return MD5 缓存键（带版本号前缀）
         */
        public static String buildCacheKey(String sourceText, String sourceLang, String targetLang) {
            String normalizedText = normalizeText(sourceText);
            String normalizedSourceLang = normalizeLang(sourceLang);
            String normalizedTargetLang = normalizeLang(targetLang);

            String rawKey = normalizedText + "|" + normalizedSourceLang + "|" + normalizedTargetLang;
            String md5Hash = md5(rawKey);

            // 版本号前缀，默认值为 1（实际版本由 TranslationCacheService 在写入时指定）
            return "v1:" + md5Hash;
        }

        /**
         * 构建带指定版本号的缓存 Key
         * 格式：v{version}:MD5(sourceText + "|" + sourceLang + "|" + targetLang)
         *
         * @param sourceText 原文文本
         * @param sourceLang 源语言
         * @param targetLang 目标语言
         * @param version    缓存版本号
         * @return MD5 缓存键（带版本号前缀）
         */
        public static String buildCacheKey(String sourceText, String sourceLang, String targetLang, String version) {
            String normalizedText = normalizeText(sourceText);
            String normalizedSourceLang = normalizeLang(sourceLang);
            String normalizedTargetLang = normalizeLang(targetLang);

            String rawKey = normalizedText + "|" + normalizedSourceLang + "|" + normalizedTargetLang;
            String md5Hash = md5(rawKey);

            return "v" + version + ":" + md5Hash;
        }

        /**
         * 标准化文本：去除首尾空白，压缩内部空白
         */
        private static String normalizeText(String text) {
            if (text == null) {
                return "";
            }
            // 去除首尾空白并将连续空白替换为单个空格
            return text.trim().replaceAll("\\s+", " ");
        }

        /**
         * 标准化语言代码：转为小写
         */
        private static String normalizeLang(String lang) {
            if (lang == null || lang.trim().isEmpty()) {
                return DEFAULT_SOURCE_LANG;
            }
            return lang.trim().toLowerCase();
        }

        /**
         * 计算 MD5 哈希值
         */
        private static String md5(String input) {
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                byte[] messageDigest = md.digest(input.getBytes());
                return bytesToHex(messageDigest);
            } catch (NoSuchAlgorithmException e) {
                // MD5 算法不存在时，回退到简单哈希（理论上不会发生）
                return Integer.toHexString(input.hashCode());
            }
        }

        /**
         * 字节数组转十六进制字符串
         */
        private static String bytesToHex(byte[] bytes) {
            StringBuilder hexString = new StringBuilder(2 * bytes.length);
            for (byte b : bytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        }
    }