// ==================== backend-proxy.js ====================
// 安装依赖:npm install express cors node-fetch
// 运行:node backend-proxy.js
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch');
const app = express();
const PORT = 3001;
// 百度 API 配置
const BAIDU_CONFIG = {
API_KEY: "YOUR_API_KEY", // 替换为你的 API Key
SECRET_KEY: "YOUR_SECRET_KEY", // 替换为你的 Secret Key
};
// 允许跨域
app.use(cors());
app.use(express.json());
// Token 缓存
let cachedToken = null;
let tokenExpireTime = 0;
// 获取百度 Access Token
async function getBaiduToken() {
if (cachedToken && Date.now() < tokenExpireTime) {
return cachedToken;
}
try {
const response = await fetch(
`https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${BAIDU_CONFIG.API_KEY}&client_secret=${BAIDU_CONFIG.SECRET_KEY}`,
{ method: 'POST' }
);
const data = await response.json();
if (data.access_token) {
cachedToken = data.access_token;
tokenExpireTime = Date.now() + (data.expires_in - 60) * 1000;
return cachedToken;
} else {
throw new Error('Failed to get token: ' + JSON.stringify(data));
}
} catch (error) {
console.error('Token request error:', error);
throw error;
}
}
// 代理接口:获取语音
app.post('/api/tts', async (req, res) => {
try {
const { text, lang = 'zh-CN' } = req.body;
if (!text) {
return res.status(400).json({ error: 'Text is required' });
}
// 获取 token
const token = await getBaiduToken();
// 语音参数
const voiceParams = {
per: lang === 'zh-CN' ? 4 : 1, // 发音人
spd: 5, // 语速
pit: 5, // 音调
vol: 5, // 音量
};
// 调用百度 TTS API
const response = await fetch('https://tsn.baidu.com/text2audio', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
tex: text,
tok: token,
cuid: 'web_client',
ctp: 1,
lan: lang === 'zh-CN' ? 'zh' : 'en',
...voiceParams,
}),
});
if (response.ok) {
// 返回音频流
const audioBuffer = await response.buffer();
res.set('Content-Type', 'audio/mp3');
res.send(audioBuffer);
} else {
const errorText = await response.text();
res.status(500).json({ error: 'Baidu TTS error', details: errorText });
}
} catch (error) {
console.error('TTS request error:', error);
res.status(500).json({ error: error.message });
}
});
// 健康检查
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.listen(PORT, () => {
console.log(`✅ Baidu TTS Proxy running at http://localhost:${PORT}`);
console.log(` API endpoint: http://localhost:${PORT}/api/tts`);
});
完成!现在给你完整的部署步骤:
📦 完整部署方案
1️⃣ 创建后端代理服务
创建文件 backend-proxy.js(代码已在上面的 artifact 中)
bash
# 初始化项目
npm init -y
# 安装依赖
npm install express cors node-fetch
# 修改 backend-proxy.js 中的 API_KEY 和 SECRET_KEY
# 启动服务
node backend-proxy.js
2️⃣ 前端配置
在前端代码中,确保 PROXY_URL 指向你的后端:
javascript
const BAIDU_TTS_CONFIG = {
PROXY_URL: "http://localhost:3001/api/tts", // 开发环境
// 生产环境改为: "https://your-domain.com/api/tts"
};
```
### 3️⃣ 工作流程
```
前端调用 speak()
↓
尝试浏览器在线语音 (Google/Microsoft)
↓
网络正常?
├─ 是 → ✅ 使用在线语音(不访问百度)
└─ 否 → 检测到失败(3秒超时)
↓
尝试本地语音
↓
有本地语音?
├─ 是 → ✅ 使用本地语音(不访问百度)
└─ 否 → 🎤 调用后端代理
↓
后端代理 → 百度 API → 返回音频
↓
前端播放音频 ✅
🚀 生产环境部署
选项 A:部署到 Vercel/Railway(推荐)
javascript
// vercel.json 或 railway.json
{
"version": 2,
"builds": [
{
"src": "backend-proxy.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "backend-proxy.js"
}
]
}
选项 B:使用 Serverless 函数
如果你使用的是 Next.js,可以创建 API 路由:
javascript
// pages/api/tts.js
export default async function handler(req, res) {
// 后端代理逻辑
}
✅ 优势
- 无 CORS 问题 - 通过后端代理解决
- API 密钥安全 - 密钥保存在后端
- 完整降级机制 - 在线语音 → 本地语音 → 百度 TTS
- 网络正常时不影响 - 只在必要时才调用百度
🧪 测试
bash
# 测试后端代理
curl -X POST http://localhost:3001/api/tts \
-H "Content-Type: application/json" \
-d '{"text":"你好世界","lang":"zh-CN"}' \
--output test.mp3
# 播放测试
# macOS: open test.mp3
# Linux: xdg-open test.mp3
# Windows: start test.mp3
这样就完美解决了 CORS 问题,并且只在真正需要时才使用百度 TTS!