refactor(service): 用 RestClient 重写 ElevenLabs 调用并替换 UUID 工具类

- 将手写 HttpURLConnection 改为 Spring RestClient,精简 64 行冗余代码
- 引入 RestClientConfig 统一配置
- 统一使用 Hutool 的 IdUtil 生成文件名称
This commit is contained in:
2026-01-26 13:49:50 +08:00
parent 6a1bb50318
commit 8783a4c2af
3 changed files with 54 additions and 93 deletions

View File

@@ -0,0 +1,25 @@
package com.yolo.keyborad.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
/**
* RestClient 配置类
* 提供连接池复用,优化 HTTP 请求性能
*/
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(30000);
factory.setReadTimeout(60000);
return RestClient.builder()
.requestFactory(factory)
.build();
}
}

View File

@@ -481,7 +481,7 @@ public class ChatServiceImpl implements ChatService {
} }
byte[] audioBytes = Base64.getDecoder().decode(audioBase64); byte[] audioBytes = Base64.getDecoder().decode(audioBase64);
String fileName = UUID.randomUUID() + ".mp3"; String fileName = IdUtil.fastSimpleUUID() + ".mp3";
FileInfo fileInfo = fileStorageService.of(new ByteArrayInputStream(audioBytes)) FileInfo fileInfo = fileStorageService.of(new ByteArrayInputStream(audioBytes))
.setPath(userId + "/") .setPath(userId + "/")

View File

@@ -1,7 +1,6 @@
package com.yolo.keyborad.service.impl; package com.yolo.keyborad.service.impl;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.yolo.keyborad.common.ErrorCode; import com.yolo.keyborad.common.ErrorCode;
import com.yolo.keyborad.config.ElevenLabsProperties; import com.yolo.keyborad.config.ElevenLabsProperties;
@@ -10,14 +9,10 @@ import com.yolo.keyborad.model.vo.TextToSpeechVO;
import com.yolo.keyborad.service.ElevenLabsService; import com.yolo.keyborad.service.ElevenLabsService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -34,6 +29,9 @@ public class ElevenLabsServiceImpl implements ElevenLabsService {
@Resource @Resource
private ElevenLabsProperties elevenLabsProperties; private ElevenLabsProperties elevenLabsProperties;
@Resource
private RestClient restClient;
private static final int MAX_TEXT_LENGTH = 5000; private static final int MAX_TEXT_LENGTH = 5000;
@Override @Override
@@ -43,7 +41,6 @@ public class ElevenLabsServiceImpl implements ElevenLabsService {
@Override @Override
public TextToSpeechVO textToSpeechWithTimestamps(String text, String voiceId) { public TextToSpeechVO textToSpeechWithTimestamps(String text, String voiceId) {
// 1. 参数验证
if (StrUtil.isBlank(text)) { if (StrUtil.isBlank(text)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "文本内容不能为空"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "文本内容不能为空");
} }
@@ -57,111 +54,50 @@ public class ElevenLabsServiceImpl implements ElevenLabsService {
voiceId = elevenLabsProperties.getVoiceId(); voiceId = elevenLabsProperties.getVoiceId();
} }
HttpURLConnection connection = null; String requestUrl = buildRequestUrl(voiceId);
Map<String, Object> requestBody = buildRequestBody(text);
log.info("调用 ElevenLabs TTS API, voiceId: {}, 文本长度: {}", voiceId, text.length());
long startTime = System.currentTimeMillis();
try { try {
// 2. 构建请求 URL String responseJson = restClient.post()
String requestUrl = buildRequestUrl(voiceId); .uri(requestUrl)
URL url = new URL(requestUrl); .contentType(MediaType.APPLICATION_JSON)
.header("xi-api-key", elevenLabsProperties.getApiKey())
.body(requestBody)
.retrieve()
.body(String.class);
// 3. 创建连接
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setConnectTimeout(30000);
connection.setReadTimeout(60000);
// 4. 设置请求头
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("xi-api-key", elevenLabsProperties.getApiKey());
// 5. 构建请求体
Map<String, Object> requestBody = buildRequestBody(text);
String jsonBody = JSON.toJSONString(requestBody);
log.info("调用 ElevenLabs TTS API, voiceId: {}, 文本长度: {}", voiceId, text.length());
long startTime = System.currentTimeMillis();
// 6. 发送请求
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 7. 获取响应
int responseCode = connection.getResponseCode();
long duration = System.currentTimeMillis() - startTime; long duration = System.currentTimeMillis() - startTime;
log.info("ElevenLabs TTS API 响应码: {}, 耗时: {}ms", responseCode, duration); log.info("ElevenLabs TTS API 响应成功, 耗时: {}ms", duration);
if (responseCode == HttpURLConnection.HTTP_OK) { JSONObject jsonResponse = JSONObject.parseObject(responseJson);
// 读取响应 JSON String audioBase64 = jsonResponse.getString("audio_base64");
try (InputStream is = connection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
String responseJson = baos.toString(StandardCharsets.UTF_8);
JSONObject jsonResponse = JSON.parseObject(responseJson);
String audioBase64 = jsonResponse.getString("audio_base64"); log.info("语音合成成功Base64长度: {}", audioBase64.length());
log.info("语音合成成功Base64长度: {}", audioBase64.length()); return TextToSpeechVO.builder()
.audioBase64(audioBase64)
.build();
return TextToSpeechVO.builder()
.audioBase64(audioBase64)
.build();
}
} else {
// 读取错误信息
String errorMsg = "";
try (InputStream es = connection.getErrorStream()) {
if (es != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = es.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
errorMsg = baos.toString(StandardCharsets.UTF_8);
}
}
log.error("ElevenLabs TTS API 调用失败, 状态码: {}, 错误信息: {}", responseCode, errorMsg);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "语音合成服务异常: " + responseCode);
}
} catch (BusinessException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
log.error("调用 ElevenLabs TTS API 发生异常", e); log.error("调用 ElevenLabs TTS API 发生异常", e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "语音合成服务异常: " + e.getMessage()); throw new BusinessException(ErrorCode.SYSTEM_ERROR, "语音合成服务异常: " + e.getMessage());
} finally {
if (connection != null) {
connection.disconnect();
}
} }
} }
/**
* 构建 ElevenLabs TTS API 请求 URL带时间戳
*/
private String buildRequestUrl(String voiceId) { private String buildRequestUrl(String voiceId) {
StringBuilder url = new StringBuilder(elevenLabsProperties.getBaseUrl()); return elevenLabsProperties.getBaseUrl() +
url.append("/text-to-speech/").append(voiceId).append("/with-timestamps"); "/text-to-speech/" + voiceId + "/with-timestamps" +
url.append("?output_format=").append(elevenLabsProperties.getOutputFormat()); "?output_format=" + elevenLabsProperties.getOutputFormat();
return url.toString();
} }
/**
* 构建请求体
*/
private Map<String, Object> buildRequestBody(String text) { private Map<String, Object> buildRequestBody(String text) {
Map<String, Object> requestBody = new HashMap<>(); Map<String, Object> requestBody = new HashMap<>();
requestBody.put("text", text); requestBody.put("text", text);
requestBody.put("model_id", elevenLabsProperties.getModelId()); requestBody.put("model_id", elevenLabsProperties.getModelId());
// 设置语音参数
Map<String, Object> voiceSettings = new HashMap<>(); Map<String, Object> voiceSettings = new HashMap<>();
voiceSettings.put("stability", elevenLabsProperties.getStability()); voiceSettings.put("stability", elevenLabsProperties.getStability());
voiceSettings.put("similarity_boost", elevenLabsProperties.getSimilarityBoost()); voiceSettings.put("similarity_boost", elevenLabsProperties.getSimilarityBoost());