package com.agent.management.service.impl; import com.agent.management.model.vo.TranslationVO; import com.agent.management.service.AiModelService; import com.agent.management.service.SseService; import com.agent.management.service.TranslationService; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * 基于 Spring AI 的翻译服务实现 * 支持同步/异步翻译去重:同一文本只会发起一次 AI 请求,后续调用复用结果 */ @Slf4j @Service public class TranslationServiceImpl implements TranslationService { private static final long TRANSLATE_TIMEOUT_SECONDS = 120; private static final int MAX_CHUNK_LENGTH = 4000; private final ChatClient chatClient; private final SseService sseService; /** 已完成的翻译缓存:原文 -> 译文 */ private final Map translationCache = new ConcurrentHashMap<>(); /** 正在进行中的翻译:原文 -> CompletableFuture<译文> */ private final Map> pendingTranslations = new ConcurrentHashMap<>(); public TranslationServiceImpl(ChatClient.Builder chatClientBuilder, AiModelService aiModelService, SseService sseService) { this.chatClient = aiModelService.getChatClientOrFallback(chatClientBuilder); this.sseService = sseService; } @Override public String translateToChinese(String text) { if (text == null || text.trim().isEmpty()) return text; if (containsChinese(text)) return text; String key = text.trim(); // 1. 检查已完成缓存 String cached = translationCache.get(key); if (cached != null) return cached; // 2. 获取或创建 Future(去重核心) CompletableFuture future = getOrCreateFuture(key, text); // 3. 阻塞等待结果 try { return future.get(TRANSLATE_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (TimeoutException e) { log.warn("[翻译] 等待翻译超时({}s), 返回原文: {}", TRANSLATE_TIMEOUT_SECONDS, truncate(text, 80)); return text; } catch (Exception e) { log.error("[翻译] 等待翻译异常: {}", e.getMessage()); return text; } } @Override public String getCachedTranslation(String text) { if (text == null || text.trim().isEmpty()) return text; if (containsChinese(text)) return text; return translationCache.get(text.trim()); } @Async @Override public void asyncTranslateAndPush(String folderName, String name, String description) { log.info("[翻译-SSE] 开始异步翻译, folderName={}", folderName); String translatedName = translateToChinese(name); String translatedDescription = translateToChinese(description); TranslationVO vo = new TranslationVO(folderName, translatedName, translatedDescription); sseService.pushTranslation(vo); log.info("[翻译-SSE] 推送翻译结果完成, folderName={}", folderName); } /** * 获取或创建翻译 Future。使用 putIfAbsent 保证同一文本只创建一个 Future。 * 如果已有 pending Future,直接复用(阻塞等待其完成)。 * 如果缓存已存在,返回已完成的 Future。 */ private CompletableFuture getOrCreateFuture(String key, String text) { // 快速路径:已完成缓存 String cached = translationCache.get(key); if (cached != null) { return CompletableFuture.completedFuture(cached); } // 创建新 Future CompletableFuture newFuture = new CompletableFuture<>(); CompletableFuture existing = pendingTranslations.putIfAbsent(key, newFuture); if (existing != null) { // 另一个线程已经在翻译该文本,复用其 Future log.info("[翻译] 复用已有的翻译任务: {}", truncate(text, 80)); return existing; } // 当前线程负责执行翻译 try { String result = doTranslate(text); newFuture.complete(result); } catch (Exception e) { newFuture.complete(text); // 降级返回原文 } finally { pendingTranslations.remove(key); } return newFuture; } private String doTranslate(String text) { String key = text.trim(); String cached = translationCache.get(key); if (cached != null) return cached; // 短文本直接翻译 if (text.length() <= MAX_CHUNK_LENGTH) { return doTranslateSingle(text); } // 长文本:按段落分段翻译 return doTranslateChunked(text); } private String doTranslateSingle(String text) { String key = text.trim(); String cached = translationCache.get(key); if (cached != null) return cached; log.info("[翻译] 开始调用大模型翻译, 原文长度: {} 字符", text.length()); long startTime = System.currentTimeMillis(); try { String translated = chatClient.prompt() .user(u -> u.text("将以下英文翻译为简体中文,专有英文缩写、代码等不用翻译,只返回翻译结果,不要添加任何解释或额外文本:\n\n{text}") .param("text", text)) .call() .content(); long elapsed = System.currentTimeMillis() - startTime; log.info("[翻译] 大模型响应耗时: {}ms", elapsed); if (translated != null && !translated.trim().isEmpty()) { String result = translated.trim(); translationCache.put(key, result); log.info("[翻译] 翻译成功: {} -> {}", truncate(text, 80), truncate(result, 80)); return result; } log.warn("[翻译] 大模型返回空结果, 耗时: {}ms", elapsed); return text; } catch (Exception e) { log.error("[翻译] 大模型调用异常: {}", e.getMessage()); return text; } } /** * 长文本分段翻译:按空行切分段落,每段不超过 MAX_CHUNK_LENGTH,逐段翻译后拼接 */ private String doTranslateChunked(String text) { log.info("[翻译] 长文本分段翻译开始, 总长度: {} 字符", text.length()); long startTime = System.currentTimeMillis(); String[] paragraphs = text.split("\n\\s*\n"); List chunks = new ArrayList<>(); StringBuilder currentChunk = new StringBuilder(); for (String para : paragraphs) { if (currentChunk.length() + para.length() + 2 > MAX_CHUNK_LENGTH && currentChunk.length() > 0) { chunks.add(currentChunk.toString()); currentChunk = new StringBuilder(); } if (currentChunk.length() > 0) currentChunk.append("\n\n"); currentChunk.append(para); } if (currentChunk.length() > 0) { chunks.add(currentChunk.toString()); } log.info("[翻译] 分为 {} 段", chunks.size()); StringBuilder result = new StringBuilder(); for (int i = 0; i < chunks.size(); i++) { String chunk = chunks.get(i); log.info("[翻译] 翻译第 {}/{} 段, 长度: {}", i + 1, chunks.size(), chunk.length()); String translated = doTranslateSingle(chunk); if (i > 0) result.append("\n\n"); result.append(translated); } long elapsed = System.currentTimeMillis() - startTime; log.info("[翻译] 长文本分段翻译完成, 总耗时: {}ms", elapsed); String finalResult = result.toString(); translationCache.put(text.trim(), finalResult); return finalResult; } private String truncate(String text, int maxLen) { if (text == null) return "null"; if (text.length() <= maxLen) return text; return text.substring(0, maxLen) + "..."; } private boolean containsChinese(String text) { if (text == null) return false; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (c >= '\u4e00' && c <= '\u9fff') return true; } return false; } }