news 2026/10/12 20:39:26

使用Node.js构建Fish-Speech 1.5的REST API服务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用Node.js构建Fish-Speech 1.5的REST API服务

使用Node.js构建Fish-Speech 1.5的REST API服务

1. 引言

Fish-Speech 1.5作为当前最先进的开源文本转语音模型,凭借其出色的多语言支持和高质量的语音合成能力,在开发者社区中广受好评。虽然官方提供了WebUI界面,但在实际生产环境中,我们往往需要通过API方式集成TTS功能到自己的应用中。

本文将带你一步步使用Node.js为Fish-Speech 1.5构建一个高性能的REST API服务。无论你是想要为你的聊天机器人添加语音功能,还是需要为内容创作平台集成文本转语音服务,这个方案都能满足你的需求。我们将重点介绍异步处理、负载均衡等关键技术,确保你的API服务既稳定又高效。

2. 环境准备与项目搭建

2.1 Node.js环境配置

首先确保你的系统已经安装了Node.js 18.0或更高版本。建议使用nvm(Node Version Manager)来管理多个Node.js版本:

# 安装nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # 安装Node.js 18 nvm install 18 nvm use 18

2.2 创建项目并安装依赖

创建一个新的项目目录并初始化:

mkdir fish-speech-api cd fish-speech-api npm init -y

安装必要的依赖包:

# 核心框架依赖 npm install express cors helmet morgan npm install axios form-data multer # 开发依赖 npm install -D nodemon typescript @types/node @types/express npm install -D @types/cors @types/multer # 可选:进程管理和负载均衡 npm install pm2

2.3 项目结构设计

建议采用以下目录结构来组织你的代码:

fish-speech-api/ ├── src/ │ ├── controllers/ # 路由控制器 │ ├── services/ # 业务逻辑层 │ ├── utils/ # 工具函数 │ ├── middleware/ # 自定义中间件 │ ├── types/ # TypeScript类型定义 │ └── app.ts # 应用入口 ├── config/ # 配置文件 ├── logs/ # 日志文件 └── package.json

3. 核心API服务实现

3.1 基础Express服务器搭建

创建一个基础的Express服务器,包含必要的中间件和安全配置:

// src/app.ts import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import morgan from 'morgan'; import { config } from './config'; import { ttsRouter } from './controllers/ttsController'; const app = express(); // 安全中间件 app.use(helmet()); app.use(cors()); app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); // 日志中间件 app.use(morgan('combined')); // 路由注册 app.use('/api/tts', ttsRouter); // 健康检查端点 app.get('/health', (req, res) => { res.status(200).json({ status: 'OK', timestamp: new Date().toISOString() }); }); // 启动服务器 const PORT = config.port || 3000; app.listen(PORT, () => { console.log(`Fish-Speech API服务运行在端口 ${PORT}`); });

3.2 Fish-Speech服务集成

创建与Fish-Speech WebUI服务交互的核心服务类:

// src/services/fishSpeechService.ts import axios from 'axios'; import FormData from 'form-data'; import { config } from '../config'; export class FishSpeechService { private baseURL: string; constructor() { this.baseURL = `http://${config.fishSpeechHost}:${config.fishSpeechPort}`; } async generateSpeech(text: string, referenceAudio?: Buffer): Promise<Buffer> { try { const formData = new FormData(); formData.append('text', text); if (referenceAudio) { formData.append('reference_audio', referenceAudio, { filename: 'reference.wav', contentType: 'audio/wav' }); } const response = await axios.post( `${this.baseURL}/generate`, formData, { headers: formData.getHeaders(), responseType: 'arraybuffer', timeout: 120000 // 2分钟超时 } ); return Buffer.from(response.data); } catch (error) { console.error('语音生成失败:', error); throw new Error(`语音生成服务错误: ${error.message}`); } } async getAvailableVoices(): Promise<any[]> { try { const response = await axios.get(`${this.baseURL}/voices`); return response.data; } catch (error) { console.error('获取可用音色失败:', error); return []; } } }

3.3 异步任务处理机制

对于长时间的语音生成任务,实现异步处理机制:

// src/services/queueService.ts import { Worker, Queue, Job } from 'bullmq'; import IORedis from 'ioredis'; import { FishSpeechService } from './fishSpeechService'; const connection = new IORedis({ host: config.redisHost, port: config.redisPort }); export const ttsQueue = new Queue('tts-generation', { connection }); export class QueueService { private fishSpeechService: FishSpeechService; constructor() { this.fishSpeechService = new FishSpeechService(); this.setupWorker(); } private setupWorker(): void { new Worker('tts-generation', async (job: Job) => { const { text, referenceAudio } = job.data; return await this.fishSpeechService.generateSpeech(text, referenceAudio); }, { connection }); } async addTTSTask(text: string, referenceAudio?: Buffer): Promise<string> { const job = await ttsQueue.add('generate-speech', { text, referenceAudio }); return job.id; } async getJobStatus(jobId: string): Promise<any> { const job = await ttsQueue.getJob(jobId); if (!job) { return { status: 'not_found' }; } const state = await job.getState(); return { status: state, result: job.returnvalue, progress: job.progress }; } }

4. REST API控制器实现

4.1 文本转语音端点

创建主要的TTS生成端点:

// src/controllers/ttsController.ts import express from 'express'; import multer from 'multer'; import { QueueService } from '../services/queueService'; import { FishSpeechService } from '../services/fishSpeechService'; const router = express.Router(); const upload = multer({ storage: multer.memoryStorage() }); const queueService = new QueueService(); const fishSpeechService = new FishSpeechService(); // 同步生成语音 router.post('/generate', upload.single('reference_audio'), async (req, res) => { try { const { text } = req.body; const referenceAudio = req.file?.buffer; if (!text) { return res.status(400).json({ error: '缺少文本参数' }); } const audioBuffer = await fishSpeechService.generateSpeech(text, referenceAudio); res.set({ 'Content-Type': 'audio/wav', 'Content-Length': audioBuffer.length }); res.send(audioBuffer); } catch (error) { console.error('语音生成错误:', error); res.status(500).json({ error: '语音生成失败' }); } }); // 异步生成语音 router.post('/generate-async', upload.single('reference_audio'), async (req, res) => { try { const { text } = req.body; const referenceAudio = req.file?.buffer; if (!text) { return res.status(400).json({ error: '缺少文本参数' }); } const jobId = await queueService.addTTSTask(text, referenceAudio); res.json({ jobId, statusUrl: `/api/tts/job/${jobId}/status` }); } catch (error) { console.error('创建异步任务错误:', error); res.status(500).json({ error: '创建任务失败' }); } }); // 获取任务状态 router.get('/job/:jobId/status', async (req, res) => { try { const { jobId } = req.params; const status = await queueService.getJobStatus(jobId); res.json(status); } catch (error) { console.error('获取任务状态错误:', error); res.status(500).json({ error: '获取状态失败' }); } }); // 获取可用音色 router.get('/voices', async (req, res) => { try { const voices = await fishSpeechService.getAvailableVoices(); res.json(voices); } catch (error) { console.error('获取音色列表错误:', error); res.status(500).json({ error: '获取音色失败' }); } }); export { router as ttsRouter };

4.2 请求验证中间件

添加输入验证中间件确保数据安全:

// src/middleware/validation.ts import { Request, Response, NextFunction } from 'express'; export const validateTTSParams = (req: Request, res: Response, next: NextFunction): void => { const { text } = req.body; if (!text || typeof text !== 'string') { res.status(400).json({ error: '文本参数必须为非空字符串' }); return; } if (text.length > 1000) { res.status(400).json({ error: '文本长度不能超过1000字符' }); return; } next(); }; export const validateAudioFile = (req: Request, res: Response, next: NextFunction): void => { if (req.file) { const allowedTypes = ['audio/wav', 'audio/mpeg', 'audio/ogg']; if (!allowedTypes.includes(req.file.mimetype)) { res.status(400).json({ error: '不支持音频格式,请使用WAV、MP3或OGG格式' }); return; } if (req.file.size > 10 * 1024 * 1024) { // 10MB限制 res.status(400).json({ error: '音频文件大小不能超过10MB' }); return; } } next(); };

5. 性能优化与部署

5.1 负载均衡配置

使用PM2实现多进程负载均衡:

// ecosystem.config.js module.exports = { apps: [{ name: 'fish-speech-api', script: './dist/app.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000 }, env_production: { NODE_ENV: 'production', PORT: 3000 }, max_memory_restart: '1G', watch: false, merge_logs: true, error_file: './logs/err.log', out_file: './logs/out.log', log_file: './logs/combined.log' }] };

启动服务:

npm run build pm2 start ecosystem.config.js --env production

5.2 Redis缓存优化

添加结果缓存减少重复生成:

// src/services/cacheService.ts import IORedis from 'ioredis'; export class CacheService { private redis: IORedis; constructor() { this.redis = new IORedis({ host: config.redisHost, port: config.redisPort }); } async getCachedAudio(text: string, voiceId?: string): Promise<Buffer | null> { const key = this.generateCacheKey(text, voiceId); const cached = await this.redis.getBuffer(key); return cached; } async cacheAudio(text: string, audio: Buffer, voiceId?: string, ttl: number = 3600): Promise<void> { const key = this.generateCacheKey(text, voiceId); await this.redis.setex(key, ttl, audio); } private generateCacheKey(text: string, voiceId?: string): string { const normalizedText = text.trim().toLowerCase(); return voiceId ? `tts:${voiceId}:${normalizedText}` : `tts:${normalizedText}`; } }

5.3 配置文件管理

创建统一的配置文件:

// src/config/index.ts export const config = { port: process.env.PORT || 3000, fishSpeechHost: process.env.FISH_SPEECH_HOST || 'localhost', fishSpeechPort: process.env.FISH_SPEECH_PORT || 6006, redisHost: process.env.REDIS_HOST || 'localhost', redisPort: parseInt(process.env.REDIS_PORT || '6379'), maxTextLength: parseInt(process.env.MAX_TEXT_LENGTH || '1000'), rateLimit: { windowMs: 15 * 60 * 1000, // 15分钟 max: 100 // 每窗口最大请求数 } };

6. 总结

通过本文的指导,你应该已经成功构建了一个基于Node.js的Fish-Speech 1.5 REST API服务。这个服务不仅提供了同步和异步的语音生成接口,还包含了负载均衡、缓存优化等生产环境需要的功能。

实际使用中,这个API服务可以轻松集成到各种应用中,比如为内容管理系统添加语音朗读功能,或者为聊天机器人提供语音交互能力。异步处理机制确保了即使生成长文本时也不会阻塞请求,而Redis缓存则显著提升了重复请求的响应速度。

部署到生产环境时,记得根据实际流量调整PM2的实例数量,并配置合适的监控和告警机制。对于更高可用性的需求,可以考虑使用Docker容器化部署,或者结合Kubernetes进行集群管理。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 0:39:20

单片机入门指南(一)—— C51编程基础与实践

1. 从零开始&#xff1a;为什么选择C51单片机&#xff1f; 如果你对电子制作、智能硬件或者嵌入式开发感兴趣&#xff0c;那么“单片机”这个词你一定不陌生。它就像是一个微型的、功能齐全的电脑&#xff0c;被塞进了一个指甲盖大小的芯片里。而在这片广阔的天地中&#xff0c…

作者头像 李华
网站建设 2026/10/5 7:50:51

GLM-4-9B-Chat-1M部署避坑指南:日志排查、加载等待与Function Call调试

GLM-4-9B-Chat-1M部署避坑指南&#xff1a;日志排查、加载等待与Function Call调试 1. 模型介绍与环境准备 GLM-4-9B-Chat-1M是智谱AI推出的新一代大语言模型&#xff0c;支持惊人的1M上下文长度&#xff08;约200万中文字符&#xff09;&#xff0c;在多语言支持、长文本推理…

作者头像 李华
网站建设 2026/10/5 7:55:29

5个开源TTS模型部署推荐:IndexTTS-2-LLM免配置镜像实测

5个开源TTS模型部署推荐&#xff1a;IndexTTS-2-LLM免配置镜像实测 1. 开篇引言&#xff1a;为什么需要免配置TTS方案 语音合成技术正在改变我们与数字内容的交互方式&#xff0c;但传统TTS部署往往面临复杂的依赖安装、环境配置和硬件要求问题。对于开发者和内容创作者来说&…

作者头像 李华
网站建设 2026/10/5 7:55:29

基于UtoVR Player的前端全景视频开发实战:从零构建360°VR播放体验

1. 从零开始&#xff1a;为什么选择UtoVR Player&#xff1f; 如果你是一个前端开发者&#xff0c;最近接到了要做一个全景视频展示页面的需求&#xff0c;比如公司要做一个虚拟看房、线上旅游或者产品360度展示的功能&#xff0c;那你可能正在纠结技术选型。自己从头写一个Web…

作者头像 李华
网站建设 2026/10/5 7:55:29

Qwen2.5-VL跨平台开发:嵌入式系统集成方案

Qwen2.5-VL跨平台开发&#xff1a;嵌入式系统集成方案 1. 引言 想象一下&#xff0c;你的嵌入式设备突然拥有了"眼睛"和"大脑"——能够看懂周围环境&#xff0c;理解图像内容&#xff0c;甚至与你进行智能对话。这不是科幻电影&#xff0c;而是Qwen2.5-V…

作者头像 李华