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();
    }
}