import Router from '@koa/router'; import { Context } from 'koa'; import * as TtsService from './tts.service'; import { BadRequestError, NotFoundError } from '../../middleware/errorHandler'; import { optionalAuth } from '../../middleware/auth'; import { usageLimitMiddleware, checkWordLimit } from '../../middleware/usageLimit'; import { prisma } from '../../models'; const router = new Router(); // 获取可用音色列表 router.get('/voices', async (ctx: Context) => { const voices = TtsService.getVoices(); ctx.body = { code: 0, message: 'success', data: { voices }, }; }); // 测试 MySQL 连接 router.get('/test-db', async (ctx: Context) => { try { const count = await prisma.book.count(); ctx.body = { code: 0, message: 'success', data: { count, connectionState: 'connected' }, }; } catch (error: any) { ctx.body = { code: 500, message: 'error', data: { error: error.message }, }; } }); // 生成音频(异步模式) router.post( '/generate', optionalAuth, async (ctx: Context) => { const userId = ctx.state.user?.userId; const { text, voiceId, voiceParams, bookId, chapterTitle } = ctx.request.body as { text: string; voiceId: string; voiceParams?: { speed?: number; pitch?: number; volume?: number }; bookId?: string; chapterTitle?: string; }; // 调试日志 console.log('📥 收到 TTS 请求:', { textLength: text?.length, voiceId, voiceParams, bookId, chapterTitle }); // 参数验证 if (!text || text.trim().length === 0) { throw new BadRequestError('请输入要转换的文本'); } if (!voiceId) { throw new BadRequestError('请选择音色'); } // 如果指定了 bookId,验证书籍是否存在 if (bookId) { const book = await prisma.book.findUnique({ where: { id: parseInt(bookId) } }); if (!book) { throw new NotFoundError('书籍不存在'); } } // 检查字数限制(测试模式:无限制) const wordCount = text.length; const quota = ctx.state.userQuota || { dailyLimit: -1, wordLimit: -1 }; if (quota.wordLimit !== -1 && wordCount > quota.wordLimit) { throw new BadRequestError(`文本字数超出限制(${quota.wordLimit}字)`); } // 默认参数 const params = { speed: voiceParams?.speed || 1.0, pitch: voiceParams?.pitch || 0, volume: voiceParams?.volume || 50, }; // 异步生成音频(立即返回) const result = await TtsService.generateAudio(userId, text, voiceId, params, undefined, { bookId, chapterTitle, }); // 如果用户已登录,更新使用次数 if (userId) { const user = await prisma.user.findUnique({ where: { id: parseInt(userId) } }); if (user) { await prisma.user.update({ where: { id: parseInt(userId) }, data: { dailyUsage: user.dailyUsage + 1 }, }); } } ctx.body = { code: 0, message: '音频生成任务已创建', data: result, // { audioId, audioUrl: '' } }; } ); // 获取音频生成状态 router.get('/status/:audioId', async (ctx: Context) => { const { audioId } = ctx.params; const result = await TtsService.getAudioStatus(audioId); if (result.status === 'not_found') { throw new NotFoundError('音频不存在'); } ctx.body = { code: 0, message: 'success', data: result, }; }); // 预览音色 - 生成短音频 router.post('/preview', async (ctx: Context) => { const { voiceId, voiceParams } = ctx.request.body as { voiceId: string; voiceParams?: { speed?: number; pitch?: number; volume?: number }; }; if (!voiceId) { throw new BadRequestError('请选择音色'); } // 预览文本(固定短文本) const previewText = '你好,欢迎使用AI有声书'; try { // 生成预览音频 const result = await TtsService.generatePreview(voiceId, voiceParams); ctx.body = { code: 0, message: 'success', data: { previewText, voiceId, audioUrl: result.audioUrl, }, }; } catch (error: any) { ctx.body = { code: 500, message: '预览生成失败:' + error.message, data: null, }; } }); export default router;