前言
语音合成技术(Text-To-Speech,TTS)在现代应用中越来越重要,从智能客服到有声阅读都有广泛应用。本文将介绍如何使用SpringBoot后端和Vue前端整合百度语音合成API,实现一个完整的文本转语音解决方案。
一、技术架构
后端:SpringBoot 2.x + Apache HttpClient
前端:Vue 2/3 + Element UI
API:百度语音合成开放平台
二、后端实现
1. SpringBoot控制器实现
我们创建了一个BaiduTtsController
来处理语音合成请求:
@RestController
@RequestMapping("/api/tts")
public class BaiduTtsController {
// 配置百度API参数
private static final String API_KEY = "your_api_key";
private static final String SECRET_KEY = "your_secret_key";
private static final String APP_ID = "your_app_id";
// Token缓存机制
private static String cachedToken = null;
private static long tokenExpireTime = 0;
// HTTP客户端(带连接池)
private static final CloseableHttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(20)
.setMaxConnPerRoute(10)
.build();
@PostMapping("/synthesize")
public ResponseEntity<?> synthesize(@RequestBody Map<String, Object> requestBody) {
// 参数校验和业务逻辑
}
}
2. 核心功能实现
2.1 获取AccessToken
private String getBaiduAccessToken() {
// 检查缓存有效性(提前5分钟刷新)
if (cachedToken != null && System.currentTimeMillis() < tokenExpireTime - 300000) {
return cachedToken;
}
try {
HttpPost httpPost = new HttpPost(TOKEN_URL);
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("grant_type", "client_credentials"));
params.add(new BasicNameValuePair("client_id", API_KEY));
params.add(new BasicNameValuePair("client_secret", SECRET_KEY));
httpPost.setEntity(new UrlEncodedFormEntity(params));
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
String json = EntityUtils.toString(response.getEntity());
JSONObject result = JSON.parseObject(json);
if (result.containsKey("error")) {
log.error("获取Token失败: {}", json);
return null;
}
cachedToken = result.getString("access_token");
tokenExpireTime = System.currentTimeMillis() +
result.getIntValue("expires_in") * 1000L;
return cachedToken;
}
} catch (Exception e) {
log.error("获取Token异常", e);
return null;
}
}
2.2 带重试机制的语音合成请求
private ResponseEntity<byte[]> sendTtsRequestWithRetry(String token,
Map<String, Object> params, int maxRetry) throws Exception {
Exception lastException = null;
for (int i = 0; i <= maxRetry; i++) {
try {
return sendTtsRequest(token, params);
} catch (Exception e) {
lastException = e;
if (e.getMessage().contains("invalid token") && i == 0) {
// 第一次遇到token失效时刷新token
cachedToken = null;
token = getBaiduAccessToken();
}
log.warn("TTS请求失败(尝试 {}/{}): {}", i+1, maxRetry+1, e.getMessage());
Thread.sleep(500 * (i + 1)); // 指数退避
}
}
throw lastException;
}
3. 参数验证与错误处理
// 验证发音人参数有效性
private int validatePer(Object per) {
Set<Integer> validPers = new HashSet<>(Arrays.asList(0,1,3,4,5003,5118,106,110,111,103,5));
int perValue = (per instanceof Number) ? ((Number)per).intValue() : 0;
return validPers.contains(perValue) ? perValue : 0; // 默认度小美
}
// 构建标准错误响应
private ResponseEntity<String> buildErrorResponse(String message, HttpStatus status) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", message);
errorResponse.put("status", status.value());
errorResponse.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(status)
.contentType(MediaType.APPLICATION_JSON)
.body(JSON.toJSONString(errorResponse));
}
三、Vue前端实现
1. 语音合成组件
<template>
<div class="tts-container">
<el-form :model="form" label-width="80px">
<el-form-item label="文本内容">
<el-input
type="textarea"
:rows="5"
v-model="form.text"
placeholder="请输入要转换的文本(不超过1024字节)"
></el-input>
</el-form-item>
<el-form-item label="发音人">
<el-select v-model="form.per" placeholder="请选择发音人">
<el-option label="度小美" :value="0"></el-option>
<el-option label="度小宇" :value="1"></el-option>
<el-option label="度逍遥" :value="3"></el-option>
<el-option label="度丫丫" :value="4"></el-option>
</el-select>
</el-form-item>
<el-form-item label="语速">
<el-slider v-model="form.spd" :min="0" :max="15" :step="1"></el-slider>
</el-form-item>
<el-form-item label="音调">
<el-slider v-model="form.pit" :min="0" :max="15" :step="1"></el-slider>
</el-form-item>
<el-form-item label="音量">
<el-slider v-model="form.vol" :min="0" :max="15" :step="1"></el-slider>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="synthesize" :loading="loading">
合成语音
</el-button>
<el-button @click="play" :disabled="!audioUrl">播放</el-button>
<el-button @click="stop" :disabled="!audioUrl">停止</el-button>
</el-form-item>
</el-form>
<audio ref="audioPlayer" :src="audioUrl" hidden></audio>
</div>
</template>
<script>
export default {
data() {
return {
form: {
text: '',
per: 0,
spd: 5,
pit: 5,
vol: 5
},
loading: false,
audioUrl: null,
audioObject: null
}
},
methods: {
async synthesize() {
if (!this.form.text) {
this.$message.error('请输入要转换的文本');
return;
}
this.loading = true;
try {
const response = await this.$axios.post('/api/tts/synthesize', this.form, {
responseType: 'blob'
});
const blob = new Blob([response.data], { type: 'audio/mp3' });
this.audioUrl = URL.createObjectURL(blob);
this.$message.success('语音合成成功');
} catch (error) {
console.error('语音合成失败:', error);
this.$message.error('语音合成失败: ' + (error.response?.data?.error || error.message));
} finally {
this.loading = false;
}
},
play() {
if (this.audioUrl) {
const playPromise = this.$refs.audioPlayer.play();
// 处理自动播放策略限制
if (playPromise !== undefined) {
playPromise.catch(error => {
console.error('播放失败:', error);
this.$message.error('播放失败: 请点击页面后重试');
});
}
}
},
stop() {
this.$refs.audioPlayer.pause();
this.$refs.audioPlayer.currentTime = 0;
}
}
}
</script>
2. 解决浏览器自动播放限制
现代浏览器对自动播放有限制,需要用户交互后才能播放音频。我们通过以下方式处理:
play() {
if (this.audioUrl) {
const playPromise = this.$refs.audioPlayer.play();
// 处理自动播放策略限制
if (playPromise !== undefined) {
playPromise.catch(error => {
console.error('播放失败:', error);
this.$message.error('播放失败: 请点击页面后重试');
});
}
}
}
四、部署与优化
1. 后端优化
连接池管理:使用Apache HttpClient连接池提高性能
Token缓存:减少Token获取频率
重试机制:提高接口稳定性
参数校验:确保API调用安全
2. 前端优化
Blob URL管理:及时释放内存
加载状态:提升用户体验
错误处理:友好的错误提示
五、常见问题解决
Token失效问题:实现自动刷新机制
文本长度限制:前端后端双重校验
浏览器兼容性:处理不同浏览器的音频播放差异
跨域问题:确保前后端配置正确
结语
通过本文的介绍,我们实现了一个完整的基于SpringBoot和Vue的百度语音合成应用。这个方案不仅可以直接用于生产环境,还可以根据需要进行扩展,比如:
增加语音合成队列
实现语音合成结果缓存
添加更多发音人选项
实现批量文本转换功能
完整代码已提供,开发者可以根据实际需求进行调整和优化。希望本文能帮助您快速集成语音合成功能到您的应用中。