| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- 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<String, String> translationCache = new ConcurrentHashMap<>();
- /** 正在进行中的翻译:原文 -> CompletableFuture<译文> */
- private final Map<String, CompletableFuture<String>> 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<String> 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<String> getOrCreateFuture(String key, String text) {
- // 快速路径:已完成缓存
- String cached = translationCache.get(key);
- if (cached != null) {
- return CompletableFuture.completedFuture(cached);
- }
- // 创建新 Future
- CompletableFuture<String> newFuture = new CompletableFuture<>();
- CompletableFuture<String> 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<String> 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;
- }
- }
|