React+Spring Boot全栈实战:TTS语音合成宠物匹配项目
简介PetMatch 是一份面向全栈开发学习者的 React Spring Boot 宠物配对应用实战项目适合希望打通前端交互与后端服务、积累完整 Web 开发经验的中级开发者。项目以 JavaScriptReact构建组件化界面结合 Spring Java 后端提供 RESTful 接口覆盖 JSX 语法、组件状态与属性、生命周期方法、React Router 路由以及 Spring Boot 自动配置、JPA 与 Hibernate 数据持久化、Spring Security 安全控制等核心知识点帮助读者理解前后端分离架构下的协作方式。资源包共 69 个文件约 2.72MB包含 13 个 js、11 个 java、11 个 css 源码文件以及 json 配置、html 页面、properties 配置、图片素材与 Maven 构建脚本等目录结构清晰便于按模块查阅。目前已有 111 人学习可作为课程设计或毕业项目的参考方案帮助读者掌握从界面组件到数据库操作的完整开发链路。1. 从 petmatch 说起一个 TTS Capstone 到底在做什么第一次看到「petmatch:使用React和SpringJava的TTS Capstone」这个标题很多人会愣一下petmatch 听起来像个宠物社交或领养匹配的应用TTS 又是语音合成SpringJava 这个写法本身就不太标准通常写作 Spring Boot / Java三者凑在一起到底想干嘛我先把结论摆出来这是一个典型的全栈毕业设计型项目前端用 React 做交互界面后端用 Java 生态大概率是 Spring Boot提供接口核心功能是把文本转成语音Text-to-Speech播放出来业务外壳套在「宠物匹配」这个场景上。它解决的不是算法难题而是「一个能跑通、能演示、能讲清楚数据怎么从输入流到扬声器」的完整链路问题。适合谁适合正在做课程设计、Capstone 或想补齐全栈 第三方 AI 能力接入经验的同学。下面我按真实落地顺序拆开讲不玩虚的。2. 技术选型为什么是 React Spring Boot TTS而不是别的组合2.1 前端为什么锁定 React 而不是模板引擎宠物匹配这类界面天然是「列表 卡片 实时状态」的形态用户点一只宠物、看详情、触发语音介绍状态变化频繁。用 Thymeleaf 或 JSP 这类服务端渲染每次交互都要整页刷新体验割裂而且语音播放是浏览器端行为服务端模板很难优雅控制音频生命周期。React 的组件状态和useEffect能很自然地把「请求文本 → 拿到音频 → 播放 → 清理」这条链管起来。另一个现实原因是生态音频播放、加载态、错误提示都有现成模式Capstone 周期短不该把时间耗在造轮子上。2.2 后端为什么用 Spring Boot 承载 TTS 调用TTS 的密钥绝不能放前端这是硬性安全底线。Spring Boot 在这里的角色是「代理 编排」接收前端文本调用 TTS 服务把音频流或音频地址返回。选 Java 生态的另一个理由是类型稳定Capstone 答辩时接口契约清晰RestController配合 DTO 能让评审一眼看懂数据流。如果换成 Node其实也能做但标题既然写了 SpringJava就按 Java 路线走别中途换栈给自己找麻烦。2.3 TTS 能力从哪来三条常见路线对比路线接入方式优点坑点云厂商 TTS APIHTTP 调用返回音频二进制或 URL音质好、多音色有额度限制、需处理鉴权本地开源引擎部署服务内网调用无外部依赖资源占用高、部署重浏览器 Web Speech API前端直接调用零后端成本音色受浏览器限制、不可控Capstone 场景我一般推荐云 API 路线因为演示效果稳定且能体现「后端代理」这一层设计。下面所有代码都按这条路线写但把调用层抽象出来换引擎只改一个类。2.4 最小可运行骨架的搭建步骤先建后端。用 Spring Initializr 生成项目依赖勾选 Spring Web 和 Lombok 即可TTS 的 HTTP 调用用 Java 自带的HttpClient避免额外引入重型客户端。# 生成后端骨架groupId 和 artifactId 按自己项目改 curl https://start.spring.io/starter.zip \ -d dependenciesweb,lombok \ -d typemaven-project \ -d languagejava \ -d bootVersion3.2.0 \ -d groupIdcom.example \ -d artifactIdpetmatch \ -o petmatch.zip unzip petmatch.zip -d petmatch-backend这段命令的作用是拉取一个标准 Spring Boot 工程省去手写pom.xml的功夫。bootVersion按你本地 JDK 版本选JDK 17 配 3.x 系列最稳。生成后先跑一次mvn spring-boot:run确认能起来再动业务代码这是血泪经验——很多人一上来就写业务结果启动失败分不清是环境还是代码问题。前端用 Vite 起 React 工程比 CRA 快得多npm create vitelatest petmatch-frontend -- --template react cd petmatch-frontend npm install npm install axiosaxios用来调后端接口。到这里前后端骨架都有了下一章开始接 TTS。3. 把 TTS 接进 Spring Boot接口设计与音频返回方式3.1 接口该返回音频流还是音频 URL这是第一个必须想清楚的设计点。返回二进制流byte[]或StreamingResponseBody的好处是前端拿到就能播不依赖外部存储坏处是每次请求都重新合成重复文本浪费额度。返回 URL 则需要先把音频存到对象存储或本地磁盘多一步落盘但可缓存。Capstone 演示场景我建议先用二进制流简单直接等要优化再加缓存层。下面按二进制流实现。3.2 后端 TTS 服务的核心代码Service public class TtsService { // 从环境变量读取绝不硬编码在代码里 private final String apiKey System.getenv(TTS_API_KEY); private final String endpoint System.getenv(TTS_ENDPOINT); private final HttpClient httpClient HttpClient.newHttpClient(); /** * 把文本合成音频返回字节数组 * param text 待合成文本长度建议不超过 200 字 * param voice 音色标识不同厂商取值不同 */ public byte[] synthesize(String text, String voice) throws Exception { if (text null || text.isBlank()) { throw new IllegalArgumentException(文本不能为空); } // 构造请求体字段名按你选用的 TTS 厂商文档调整 String body { text: %s, voice: %s, format: mp3 } .formatted(text.replace(\, \\\), voice); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(endpoint)) .header(Content-Type, application/json) .header(Authorization, Bearer apiKey) .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponsebyte[] response httpClient.send( request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() ! 200) { // 把状态码带出来方便排查是鉴权还是额度问题 throw new RuntimeException(TTS 调用失败状态码 response.statusCode()); } return response.body(); } }逻辑说明synthesize做了三件事——校验输入、拼装请求、检查响应。参数上text做了引号转义防止 JSON 拼接被特殊字符破坏这是手写 JSON 的常见翻车点voice交给调用方传方便前端做音色切换。apiKey和endpoint走环境变量答辩演示时也不会把密钥暴露在代码里。3.3 暴露给前端的 ControllerRestController RequestMapping(/api/tts) public class TtsController { private final TtsService ttsService; public TtsController(TtsService ttsService) { this.ttsService ttsService; } PostMapping(value /speak, produces audio/mpeg) public ResponseEntitybyte[] speak(RequestBody SpeakRequest req) { try { byte[] audio ttsService.synthesize(req.getText(), req.getVoice()); return ResponseEntity.ok() .contentType(MediaType.parseMediaType(audio/mpeg)) .body(audio); } catch (Exception e) { // 出错时返回 500前端据此提示 return ResponseEntity.status(500).build(); } } }produces audio/mpeg是关键告诉浏览器这是音频而不是 JSON否则前端blob处理会出玄学问题。SpeakRequest是个简单 DTO含text和voice两个字段用 Lombok 的Data即可。3.4 前端如何播放返回的音频import axios from axios; // 触发语音播放text 来自宠物详情 async function playPetVoice(text, voice default) { try { const res await axios.post( /api/tts/speak, { text, voice }, { responseType: blob } // 必须声明否则音频被当文本解析 ); const url URL.createObjectURL(res.data); const audio new Audio(url); audio.play(); // 播放结束后释放内存避免多次播放累积 audio.onended () URL.revokeObjectURL(url); } catch (e) { console.error(语音播放失败, e); } }responseType: blob是必须的漏了它 axios 会按 JSON 解析二进制直接报错。URL.createObjectURL生成临时地址onended里释放这是防止内存泄漏的标准做法。到这一步一条「点击宠物 → 后端合成 → 前端播放」的链路就通了。4. 避坑与排查TTS 全栈项目最容易翻车的五个点4.1 音频能拿到但播放没声音现象接口返回 200前端也拿到 blob但扬声器没动静。原因通常是Content-Type不对浏览器把音频当未知类型拒绝播放。解决后端produces和ResponseEntity的contentType都要设成audio/mpeg两处一致。排查时打开浏览器开发者工具的 Network 面板看响应头里的Content-Type到底是什么。4.2 中文文本合成出来是乱码或报错现象英文正常中文一调就 400 或返回空。原因多半是请求体编码问题手写 JSON 拼接时没指定 UTF-8。解决HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)显式指定编码别依赖默认值。另外确认 TTS 厂商是否支持中文音色有些默认音色只支持英文。4.3 密钥泄露到前端或 Git 仓库现象代码提交后密钥出现在仓库历史里。原因图省事把 key 写进application.yml或前端.env。解决后端一律走环境变量.gitignore里加上本地配置文件。已经提交的立刻去厂商后台轮换密钥别想着删 commit 就完事历史里还在。4.4 长文本合成超时现象短句正常超过几百字就超时或返回 500。原因TTS 接口对单次文本长度有限制且合成耗时随长度线性增长。解决后端做分句按标点切成不超过 200 字的片段逐段合成后拼接音频或者前端分次请求。分句逻辑放在TtsService里别让前端操心。4.5 跨域请求被浏览器拦截现象前端本地 5173 端口调后端 8080控制台报 CORS 错误。原因前后端不同源。解决后端加全局 CORS 配置开发阶段允许本地来源上线时收紧到具体域名。别用*一把梭带鉴权的接口配*会出问题。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:5173) // 按实际前端地址改 .allowedMethods(GET, POST); } }5. 让 petmatch 更像一个完整作品缓存、音色切换与演示技巧5.1 加一层文本哈希缓存省额度也提速演示时同一只宠物的介绍会被反复播放每次都调 TTS 既慢又费额度。做法是在TtsService里加一个ConcurrentHashMapkey 用「文本 音色」的哈希value 存音频字节。命中直接返回未命中再调接口。Capstone 阶段不用上 Redis内存缓存足够但要在答辩时说明「生产环境应换成带过期策略的分布式缓存」这句话能体现你的工程判断。private final MapString, byte[] cache new ConcurrentHashMap(); public byte[] synthesizeWithCache(String text, String voice) throws Exception { String key Integer.toHexString((text | voice).hashCode()); byte[] cached cache.get(key); if (cached ! null) { return cached; // 命中缓存直接返回 } byte[] audio synthesize(text, voice); cache.put(key, audio); return audio; }参数说明用hashCode做 key 简单但有碰撞概率Capstone 够用要严谨就换 SHA-256。缓存没有过期策略长时间运行会吃内存演示场景可接受但心里要有数。5.2 音色切换的前后端配合前端加一个下拉框选项对应后端支持的音色标识选中后作为voice参数传给/api/tts/speak。后端不用改因为voice本来就是透传的。这里有个细节不同音色对同一段文本的合成时长不同前端播放时要禁用重复点击否则会叠音。用一个isPlaying状态控制按钮的disabled属性即可。5.3 答辩演示的三个实用技巧第一提前把网络请求的响应时间测出来演示时留足等待别在台上干等尴尬。第二准备一段离线兜底音频万一现场网络抖动直接播本地文件保证演示不中断。第三把「文本 → 后端 → TTS → 音频 → 播放」这条链路画在一页 PPT 上评审最想看的就是数据流而不是你用了多少花哨库。5.4 验证链路是否真的通了别只看页面能响要做分层验证。后端层用curl直接打接口确认返回的是二进制且状态码 200。curl -X POST http://localhost:8080/api/tts/speak \ -H Content-Type: application/json \ -d {text:这是一只温顺的猫,voice:default} \ --output test.mp3 -w 状态码:%{http_code} 大小:%{size_download}\n如果test.mp3能正常播放说明后端链路没问题剩下的问题一定在前端。这种「先隔离后端再查前端」的习惯能帮你省下大量瞎猜的时间。我自己做这类项目时习惯先把后端用 curl 跑通再写前端翻车次数少了一大半。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

FreeType字体渲染实战:从灰度位图到彩色像素的完整指南

FreeType字体渲染实战:从灰度位图到彩色像素的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:12:06 阅读更多 →
鸿蒙Unity崩溃排查:用Sentry将堆栈还原到C#行号

鸿蒙Unity崩溃排查:用Sentry将堆栈还原到C#行号

接手一个要往鸿蒙设备上发的 Unity 项目时,最头疼的往往不是打包失败,而是打出来的包一崩,日志却完全看不懂。当时我用的是团结引擎,导出鸿蒙工程后真机测试,崩溃时系统日志里只有libil2cpp.so 0x123ab4这种地址偏移&…

2026/10/13 14:12:06 阅读更多 →
汽车电子培训怎么选?从课程大纲到硬件设备的避坑指南

汽车电子培训怎么选?从课程大纲到硬件设备的避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:12:06 阅读更多 →

最新新闻

基于模型的测试(MBT)实战:状态机建模与用例自动生成

基于模型的测试(MBT)实战:状态机建模与用例自动生成

1. 为什么我会推荐把MBT放进测试技术栈1.1 传统用例设计的三道坎做测试这行时间长了,你会慢慢发现一个有意思的现象:真正让人头疼的不是测试执行,而是测试设计。需求评审完了,开发方案定了,接下来就是用例设计。但用例…

2026/10/13 15:09:41 阅读更多 →
从数据抓取到统计回测:足球数据下载搜索统计系统设计

从数据抓取到统计回测:足球数据下载搜索统计系统设计

简介:这套面向足球爱好者和彩票玩家的数据处理套件,围绕赔率、盘口、竞彩数据,提供下载、检索与统计能力,适合跟踪赛事动态、辅助投注决策的用户。压缩包共18个文件、约1.53MB,exe主程序承担下载与查询核心工作&#x…

2026/10/13 15:09:41 阅读更多 →
PaperSpine 本地参考文献摄入(Local Reference Ingestion)实战指南:local-first 文献索引机制与源码级解析

PaperSpine 本地参考文献摄入(Local Reference Ingestion)实战指南:local-first 文献索引机制与源码级解析

AI 技能AI 写作人工智能深度研究AI 应用 【免费下载链接】PaperSpine PaperSpine5 — local-first, evidence-bound paper research, writing, figures, review and delivery. Download: https://wubing2023.github.io/PaperSpine/v5/ 项目地址: https://gitcode.co…

2026/10/13 15:09:41 阅读更多 →
AI如何“看懂“屏幕?typesafe-computer-use五步决策循环完全解析

AI如何“看懂“屏幕?typesafe-computer-use五步决策循环完全解析

【免费下载链接】typesafe-computer-use Computer use for about $0.0002 a step: OCR the screen, classify the next action with TypeSafe, click. macOS. 项目地址: https://gitcode.com/gh_mirrors/ty/typesafe-computer-use 点击查看 免费下载 typesafe-comp…

2026/10/13 15:09:41 阅读更多 →
Rune主题与外观定制:从主题包到壁纸的5个快速美化技巧

Rune主题与外观定制:从主题包到壁纸的5个快速美化技巧

【免费下载链接】rune the development environment for pros 项目地址: https://gitcode.com/gh_mirrors/rune13/rune 点击查看 免费下载 Rune 是一款面向专业开发者的键盘驱动 IDE,它的主题与外观定制能力远超想象:内置 16 个高质量主题、…

2026/10/13 15:09:41 阅读更多 →
掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

1. 为什么“会用AI工具”正在变成一项硬技能 这两年我身边做开发的朋友、做设计的朋友、甚至做运营和行政的朋友,都在聊同一件事:AI工具怎么用才能真正落到工作里。不是那种“打开网页问一句、复制答案”的浅层玩法,而是把它当成一个能持续产…

2026/10/13 15:08:40 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →