From 2d76ed507e9d76f963ed8519f79493828153084d Mon Sep 17 00:00:00 2001 From: lbw <1192299468@qq.com> Date: Mon, 29 Dec 2025 12:44:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(tts):=20=E9=9B=86=E6=88=90OpenAI=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E5=90=88=E6=88=90=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=AE=A1=E5=88=92=E5=8D=95=E8=AF=8D=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增TTS工具类TTSUtil,实现文本到语音的转换并通过HTTP响应返回音频流 - 在LessonPlanController添加获取计划单词列表及单词语音生成接口 - 前端新增PlanTTS页面,实现计划单词TTS的加载、生成、播放及下载功能 - 路由新增PlanTTS路由,支持访问TTS生成功能页面 - 配置文件application-dev.yml新增OpenAI TTS相关配置 - WordExportUtil生成计划文档时嵌入对应页面二维码图片 - 引入spring-ai-openai相关依赖支持OpenAI模型调用 - 新增单词语音相关请求与响应VO类,方便接口数据传输 - 新增计划单词获取接口plan/word/voice对应前端api - 新增计划单词语音合成接口plan/word/voice/tts对应前端api - 添加二维码生成逻辑,用于生成计划文档中的二维码图片链接 - 添加单元测试模版VoiceTest,预留TTS工具类测试接口 --- debug_roi.jpg | Bin 23016 -> 0 bytes enlish-service/pom.xml | 19 ++ .../controller/LessonPlanController.java | 35 +++- .../model/vo/plan/FindWordTTSVoiceReqVO.java | 17 ++ .../model/vo/plan/FindWordVoiceReqVO.java | 15 ++ .../model/vo/plan/FindWordVoiceRspVO.java | 17 ++ .../enlish/service/utils/TTSUtil.java | 66 +++++++ .../enlish/service/utils/WordExportUtil.java | 52 +++++- .../main/resources/config/application-dev.yml | 13 +- .../templates/tem_study_plan_v3.docx | Bin 0 -> 44766 bytes .../enlish/service/voice/VoiceTest.java | 18 ++ enlish-vue/src/api/plan.js | 6 + enlish-vue/src/api/tts.js | 12 ++ enlish-vue/src/pages/PlanTTS.vue | 167 ++++++++++++++++++ enlish-vue/src/router/index.js | 8 + pom.xml | 21 +++ 16 files changed, 458 insertions(+), 8 deletions(-) delete mode 100644 debug_roi.jpg create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordTTSVoiceReqVO.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceReqVO.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceRspVO.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/TTSUtil.java create mode 100644 enlish-service/src/main/resources/templates/tem_study_plan_v3.docx create mode 100644 enlish-service/src/test/java/com/yinlihupo/enlish/service/voice/VoiceTest.java create mode 100644 enlish-vue/src/api/tts.js create mode 100644 enlish-vue/src/pages/PlanTTS.vue diff --git a/debug_roi.jpg b/debug_roi.jpg deleted file mode 100644 index a4cd0253cb75ae98a4763337754a6f33ca60a466..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23016 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 diff --git a/enlish-service/pom.xml b/enlish-service/pom.xml index de04116..56c5dd0 100644 --- a/enlish-service/pom.xml +++ b/enlish-service/pom.xml @@ -153,6 +153,25 @@ java-jwt + + org.springframework.ai + spring-ai-openai + + + + org.springframework.ai + spring-ai-starter-model-openai + + + + com.google.zxing + core + + + + com.google.zxing + javase + diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/LessonPlanController.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/LessonPlanController.java index 600ed99..c2249c3 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/LessonPlanController.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/LessonPlanController.java @@ -1,9 +1,10 @@ package com.yinlihupo.enlish.service.controller; import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO; -import com.yinlihupo.enlish.service.model.vo.plan.AddLessonPlanReqVO; -import com.yinlihupo.enlish.service.model.vo.plan.DownLoadLessonPlanReqVO; +import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO; +import com.yinlihupo.enlish.service.model.vo.plan.*; import com.yinlihupo.enlish.service.service.LessonPlansService; +import com.yinlihupo.enlish.service.utils.TTSUtil; import com.yinlihupo.enlish.service.utils.WordExportUtil; import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog; import com.yinlihupo.framework.common.response.Response; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; import java.util.Map; import java.util.concurrent.Executor; @@ -30,6 +32,8 @@ public class LessonPlanController { @Resource(name = "taskExecutor") private Executor taskExecutor; + @Resource + private TTSUtil ttsUtil; @Value("${templates.plan.weekday}") private String planWeekday; @@ -60,13 +64,36 @@ public class LessonPlanController { try { Map map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class); if (!lessonPlanById.getTitle().contains("复习")) { - WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekday, true); + WordExportUtil.generateLessonPlanDocx(map, lessonPlanById, response, planWeekday, true); } else { - WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekend, false); + WordExportUtil.generateLessonPlanDocx(map, lessonPlanById, response, planWeekend, false); } } catch (Exception e) { throw new RuntimeException(e); } } + + @PostMapping("word/voice") + @ApiOperationLog(description = "获取单词") + public Response findPlanWordVoice(@RequestBody FindWordVoiceReqVO findWordVoiceReqVO) { + Integer id = findWordVoiceReqVO.getPlanId(); + LessonPlansDO lessonPlanById = lessonPlanService.findLessonPlanById(id); + try { + Map map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class); + Object syncVocabList = map.get("syncVocabList"); + List list = JsonUtils.parseList(JsonUtils.toJsonString(syncVocabList), VocabularyBankDO.class); + List words = list.stream().map(VocabularyBankDO::getWord).toList(); + + return Response.success(FindWordVoiceRspVO.builder().words(words).build()); + } catch (Exception e) { + log.error(e.getMessage()); + return Response.fail("获取单词失败"); + } + } + + @PostMapping("word/voice/tts") + public void findPlanWordVoiceTTS(@RequestBody FindWordTTSVoiceReqVO findWordVoiceReqVO, HttpServletResponse response) { + ttsUtil.generateWordVoice(findWordVoiceReqVO.getText(), response); + } } diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordTTSVoiceReqVO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordTTSVoiceReqVO.java new file mode 100644 index 0000000..7773785 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordTTSVoiceReqVO.java @@ -0,0 +1,17 @@ +package com.yinlihupo.enlish.service.model.vo.plan; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class FindWordTTSVoiceReqVO { + + String text; + String voice; + String format; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceReqVO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceReqVO.java new file mode 100644 index 0000000..b390b64 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceReqVO.java @@ -0,0 +1,15 @@ +package com.yinlihupo.enlish.service.model.vo.plan; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class FindWordVoiceReqVO { + + private Integer planId; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceRspVO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceRspVO.java new file mode 100644 index 0000000..fd1aa8a --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/plan/FindWordVoiceRspVO.java @@ -0,0 +1,17 @@ +package com.yinlihupo.enlish.service.model.vo.plan; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class FindWordVoiceRspVO { + + private List words; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/TTSUtil.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/TTSUtil.java new file mode 100644 index 0000000..400b020 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/TTSUtil.java @@ -0,0 +1,66 @@ +package com.yinlihupo.enlish.service.utils; + +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.openai.OpenAiAudioSpeechModel; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.IOException; + +@Component +@Slf4j +public class TTSUtil { + + private final OpenAiAudioSpeechModel speechModel; + + public TTSUtil(OpenAiAudioSpeechModel speechModel) { + this.speechModel = speechModel; + } + + /** + * 生成语音并将二进制流写入 HTTP 响应 + * + * @param text 需要转换的文本 + * @param response HTTP 响应对象 + */ + public void generateWordVoice(String text, HttpServletResponse response) { + // 1. 参数校验 + if (!StringUtils.hasText(text)) { + sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Input text cannot be empty"); + return; + } + + try { + // 3. 调用 OpenAI 接口获取音频数据 + byte[] audioBytes = speechModel.call(text); + + // 4. 设置 HTTP 响应头 + response.setContentType("audio/mpeg"); + response.setContentLength(audioBytes.length); + + // 可选:如果不希望浏览器自动播放,而是强制下载,请取消下面这行的注释 + // response.setHeader("Content-Disposition", "attachment; filename=\"speech.mp3\""); + + // 5. 将音频数据写入响应流 + try (ServletOutputStream outputStream = response.getOutputStream()) { + outputStream.write(audioBytes); + outputStream.flush(); + } + + } catch (Exception e) { + log.error("TTS Generation failed: {}", e.getMessage()); + sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "TTS Generation failed: " + e.getMessage()); + } + } + + // 辅助方法:发送错误响应 + private void sendError(HttpServletResponse response, int status, String message) { + try { + response.sendError(status, message); + } catch (IOException e) { + // 忽略这里的错误,因为响应可能已经提交 + } + } +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java index e6e7c9b..1621afe 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java @@ -2,13 +2,25 @@ package com.yinlihupo.enlish.service.utils; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; +import com.deepoove.poi.data.PictureType; +import com.deepoove.poi.data.Pictures; import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsDO; +import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO; import jakarta.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; import java.io.*; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; @@ -68,8 +80,8 @@ public class WordExportUtil { } } - public static void generateLessonPlanDocx(Map map, String fileName, HttpServletResponse response, String templateWordPath, boolean isWeekday) throws IOException { - fileName = URLEncoder.encode(fileName + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + public static void generateLessonPlanDocx(Map map, LessonPlansDO lessonPlan, HttpServletResponse response, String templateWordPath, boolean isWeekday) throws IOException { + String fileName = URLEncoder.encode(lessonPlan.getTitle() + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20"); // 3. 设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); @@ -82,6 +94,8 @@ public class WordExportUtil { } else { template = XWPFTemplate.compile(inputStream, configLessonPlanWeekend); } + String url = "http://localhost:5173/#/plan/tts?planId=" + lessonPlan.getId(); + map.put("img", Pictures.ofBytes(generateQR(url), PictureType.PNG).create()); OutputStream out = response.getOutputStream(); template.render(map); template.write(out); @@ -161,4 +175,38 @@ public class WordExportUtil { out.flush(); } } + + private static byte[] generateQR(String content) { + int width = 300; + int height = 300; + String format = "png"; + + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + hints.put(EncodeHintType.MARGIN, 1); + + try { + // 1. 生成比特矩阵 + BitMatrix bitMatrix = new MultiFormatWriter().encode( + content, + BarcodeFormat.QR_CODE, + width, + height, + hints + ); + + // 2. 将 BitMatrix 转为字节数组 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + // MatrixToImageWriter 是 Zxing 提供的工具类 + MatrixToImageWriter.writeToStream(bitMatrix, format, outputStream); + + return outputStream.toByteArray(); + + } catch (WriterException | IOException e) { + // 建议增加对 IOException 的捕获,因为涉及流操作 + throw new RuntimeException("生成二维码失败", e); + } + } } diff --git a/enlish-service/src/main/resources/config/application-dev.yml b/enlish-service/src/main/resources/config/application-dev.yml index 86fc7de..17ed3d5 100644 --- a/enlish-service/src/main/resources/config/application-dev.yml +++ b/enlish-service/src/main/resources/config/application-dev.yml @@ -19,6 +19,15 @@ spring: max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) min-idle: 0 # 连接池中的最小空闲连接 max-idle: 10 # 连接池中的最大空闲连接 + ai: + openai: + api-key: your_api_key_here + base-url: http://124.220.58.5:2233 + audio: + speech: + options: + model: tts-1 + voice: alloy templates: @@ -26,7 +35,7 @@ templates: count: 100 data: C:\project\tess plan: - weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v2.docx + weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v3.docx weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v1.docx plan_day: 7 tmp: @@ -38,4 +47,4 @@ ai: aliyun: accessKeyId: - accessKeySecret: \ No newline at end of file + accessKeySecret: diff --git a/enlish-service/src/main/resources/templates/tem_study_plan_v3.docx b/enlish-service/src/main/resources/templates/tem_study_plan_v3.docx new file mode 100644 index 0000000000000000000000000000000000000000..3d4fb8deac6ee59de94341a3576d4c7ec1418af5 GIT binary patch literal 44766 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 literal 0 HcmV?d00001 diff --git a/enlish-service/src/test/java/com/yinlihupo/enlish/service/voice/VoiceTest.java b/enlish-service/src/test/java/com/yinlihupo/enlish/service/voice/VoiceTest.java new file mode 100644 index 0000000..dfa5e73 --- /dev/null +++ b/enlish-service/src/test/java/com/yinlihupo/enlish/service/voice/VoiceTest.java @@ -0,0 +1,18 @@ +package com.yinlihupo.enlish.service.voice; + +import com.yinlihupo.enlish.service.utils.TTSUtil; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class VoiceTest { + + @Resource + private TTSUtil ttsUtil; + + @Test + public void test() { + + } +} diff --git a/enlish-vue/src/api/plan.js b/enlish-vue/src/api/plan.js index 44aa105..c0f6e65 100644 --- a/enlish-vue/src/api/plan.js +++ b/enlish-vue/src/api/plan.js @@ -45,6 +45,12 @@ export function downloadLessonPlan(data) { }); } +export function getLessonPlanWords(planId) { + return axios.post('plan/word/voice', { + planId: planId + }) +} + const resolveBlob = (res, fileName) => { // 创建 Blob 对象,可以指定 type,也可以让浏览器自动推断 const blob = new Blob([res], { type: 'application/octet-stream' }); diff --git a/enlish-vue/src/api/tts.js b/enlish-vue/src/api/tts.js new file mode 100644 index 0000000..2b9cc68 --- /dev/null +++ b/enlish-vue/src/api/tts.js @@ -0,0 +1,12 @@ +import axios from "@/axios"; + +export function synthesizeOpenAITTS(text, voice = 'alloy', format = 'mp3') { + return axios.post('/plan/word/voice/tts', { + text, + voice, + format + }, { + responseType: 'blob' + }) +} + diff --git a/enlish-vue/src/pages/PlanTTS.vue b/enlish-vue/src/pages/PlanTTS.vue new file mode 100644 index 0000000..bb983cb --- /dev/null +++ b/enlish-vue/src/pages/PlanTTS.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/enlish-vue/src/router/index.js b/enlish-vue/src/router/index.js index 902246c..37ba295 100644 --- a/enlish-vue/src/router/index.js +++ b/enlish-vue/src/router/index.js @@ -4,6 +4,7 @@ import Class from '@/pages/class.vue' import { createRouter, createWebHashHistory } from 'vue-router' import Admid from '@/pages/admid/admid.vue' import Student from '@/pages/student.vue' +import PlanTTS from '@/pages/PlanTTS.vue' // 统一在这里声明所有路由 const routes = [ @@ -41,6 +42,13 @@ const routes = [ meta: { // meta 信息 title: '管理员页面' // 页面标题 } + }, + { + path: '/plan/tts', + component: PlanTTS, + meta: { + title: 'TTS生成' + } } ] diff --git a/pom.xml b/pom.xml index e73f516..b2decd8 100644 --- a/pom.xml +++ b/pom.xml @@ -281,6 +281,27 @@ ${jaxb-runtime.version} + + org.springframework.ai + spring-ai-bom + 1.1.0 + pom + import + + + + + com.google.zxing + core + 3.5.2 + + + + com.google.zxing + javase + 3.5.2 + +