# AI 有声书生成工具 > 将文字转换为高质量音频,让阅读变成聆听 📖 **项目 Wiki**:[.qoder/repowiki](.qoder/repowiki) ## 🎯 项目简介 AI 有声书生成工具是一款面向内容创作者的音频制作工具,帮助自媒体作者、小说爱好者、学习者低成本制作音频内容。 ### 核心功能 - 📝 **文本转音频**:支持长文本输入,智能分段处理 - 🎵 **多种音色**:10+ 种优质音色可选 - 🎛️ **参数调节**:语速、音调、音量自由控制 - 🎧 **在线播放**:支持倍速播放、进度控制 - 📚 **历史管理**:音频记录、收藏功能 - 👑 **会员体系**:分级权益,灵活选择 ## 🛠️ 技术栈 ### 前端 - **uniapp** + **Vue 3** + **TypeScript** - **Pinia** 状态管理 - 支持 H5 和微信小程序 ### 后端 - **Node.js 18** + **Koa 2.x** - **MongoDB** + **Mongoose** - **FFmpeg** 音频处理 - 阿里云 TTS 语音合成 ## 📁 项目结构 ``` audio/ ├── client/ # uniapp 前端项目 │ ├── src/ │ │ ├── pages/ # 页面 │ │ ├── store/ # 状态管理 │ │ ├── utils/ # 工具函数 │ │ └── types/ # 类型定义 │ └── package.json │ ├── server/ # Node.js 后端项目 │ ├── src/ │ │ ├── modules/ # 业务模块 │ │ ├── models/ # 数据模型 │ │ ├── middleware/ # 中间件 │ │ └── config/ # 配置 │ └── package.json │ └── README.md ``` ## 🚀 快速开始 ### 环境要求 - Node.js >= 18 - MongoDB >= 6.0 - FFmpeg(用于音频处理) ### 后端启动 ```bash cd server # 安装依赖 npm install # 复制环境配置 cp .env.example .env # 启动开发服务 npm run dev ``` ### 前端启动 ```bash cd client # 安装依赖 npm install # 启动 H5 开发服务 npm run dev:h5 # 编译微信小程序 npm run build:mp-weixin ``` ## ⚙️ 配置说明 ### 后端环境变量 ```env # 服务配置 PORT=3000 NODE_ENV=development # MySQL 数据库 DATABASE_URL="mysql://root:password@localhost:3306/audio-book" # JWT JWT_SECRET=your-jwt-secret JWT_EXPIRES_IN=7d # 阿里云 TTS ALIYUN_ACCESS_KEY=your-access-key ALIYUN_ACCESS_SECRET=your-access-secret ALIYUN_APP_KEY=your-app-key ``` ## 📡 API 接口 ### 认证模块 - `POST /api/auth/send-code` - 发送验证码 - `POST /api/auth/login` - 手机号登录 - `GET /api/auth/user-info` - 获取用户信息 ### TTS 模块 - `GET /api/tts/voices` - 获取音色列表 - `POST /api/tts/generate` - 生成音频 ### 音频模块 - `GET /api/audio/list` - 获取音频列表 - `GET /api/audio/:id` - 获取音频详情 - `DELETE /api/audio/:id` - 删除音频 - `PUT /api/audio/:id/favorite` - 切换收藏 ### 会员模块 - `GET /api/member/benefits` - 获取会员权益 - `GET /api/member/status` - 获取会员状态 - `POST /api/member/order` - 创建订单 ## 👑 会员权益 | 权益 | 免费版 | 月度会员 | 年度会员 | |------|--------|----------|----------| | 价格 | ¥0 | ¥19.9/月 | ¥199/年 | | 每日次数 | 3次 | 20次 | 无限 | | 字数限制 | 5000字 | 50000字 | 无限 | | 音色选择 | 基础 | 全部 | 全部 | ## 🔊 TTS 语音合成 ### 设计原则 - **统一分段**:所有 TTS Provider 使用 1000 字符/段,优先在标点处断开,确保韵律自然 - **统一异步**:所有 Provider 使用异步模式(提交任务 → 轮询 → 下载),避免超时丢失音频 - **降级策略**:MiniMax(默认)→ 阿里云 → Mock 模拟 ### 各家 TTS API 限制参考 | 服务商 | 接口类型 | 单次最大长度 | 约等于音频 | 备注 | |--------|---------|-------------|-----------|------| | MiniMax | 异步长文本 | 1,000,000 字符 | ~33 小时 | 业界最长 | | MiniMax | 同步 | 10,000 字符 | ~20 分钟 | >3,000 推荐异步 | | 阿里云 CosyVoice | 非流式/单向流式 | 20,000 字符 | ~33 分钟 | 当前使用异步模式 | | 阿里云 CosyVoice | WebSocket 流式 | 20,000/累计 200,000 | ~66 分钟 | 流式最佳 | | 阿里云 | 传统长文本合成 | 80,000 字符 | ~4.4 小时 | 建议 40,000 以内 | | 火山引擎/豆包 | 异步长文本 | 100,000 字符 | ~3.3 小时 | 音频保存 7 天 | | 火山引擎/豆包 | 非流式 | 1,024 字节 | ~20 秒 | 建议 <300 字符 | | 百度 | 长文本异步 | 100,000 字符 | ~3.3 小时 | 一次性合成 | | 百度 | 短文本 | 5,120 汉字 | ~1.7 分钟 | ~10,240 字节 | | 讯飞 | 长文本 TTS | 100,000 字符 | ~3.3 小时 | 万字级别快速合成 | | 讯飞 | 流式在线 | 8,000 字节 | ~1.3 分钟 | ~4,000 汉字 | | 腾讯云 | 长文本语音合成 | 10,000+ 字符 | ~20 分钟 | 每个 speak 标签 ≤150 字 | | 腾讯云 | 基础语音合成 | 150 汉字 | ~15 秒 | 中文限制严格 | | OpenAI | TTS-1 / TTS-1-HD | 4,096 字符 | ~7 分钟 | 隐藏限制 | | Google Cloud | Text-to-Speech | 5,000 字节 | ~3 分钟 | SSML 也计入 | | Azure | 实时合成 | ~10 分钟音频 | ~10 分钟 | 按音频时长限制 | | Azure | Batch 批量合成 | 无硬性限制 | >10 分钟 | 异步批量 | | ElevenLabs | Flash/Turbo | 5,000 字符 | ~8 分钟 | 付费计划 | | ElevenLabs | eleven_v3 | 3,000 字符 | ~5 分钟 | 表现力最强但限制低 | | Fish Audio | Fish Speech | ~8,192 tokens | ~5 分钟 | 上下文窗口限制 | > **当前分段策略**:1000 字符/段,覆盖所有主流接口。不满足 1000 字的接口(火山 300/腾讯 150/百度 60)应走各自异步长文本 API,而非短文本接口。 ## 🔧 运维工具 ### 音频问题扫描与修复 项目提供 `server/scripts/fix-audio-issues.ts` 脚本,用于检测和修复音频合并异常(如段落丢失、格式不匹配、时长不正确等问题)。 #### 用法 ```bash cd server # 扫描所有音频问题(仅检查,不修复) npx tsx scripts/fix-audio-issues.ts # 扫描并自动修复所有问题 npx tsx scripts/fix-audio-issues.ts --fix # 仅修复指定章节 npx tsx scripts/fix-audio-issues.ts --fix --chapter 11 # 仅修复指定书籍的所有章节 npx tsx scripts/fix-audio-issues.ts --fix --book 5 # 重新合并所有 level=1 章节的音频(下载子节远程音频后合并) npx tsx scripts/fix-audio-issues.ts --merge-all # 重新合并指定书籍的 level=1 章节音频 npx tsx scripts/fix-audio-issues.ts --merge-all --book 5 ``` #### 检测能力 | 标记 | 问题类型 | 判断标准 | 说明 | |------|---------|---------|------| | 🔴 | 严重异常 | 语速 > 10 字/秒 | 合并丢失大部分段落,音频严重不完整 | | 🟡 | 轻微异常 | 语速 6~10 字/秒 | 可能丢失部分段落,音频略短 | | 🟠 | 格式不匹配 | WAV 内容存为 .mp3 | 浏览器无法正常播放 | | 🔵 | 时长缺失 | audioDuration=0 但有 audioUrl | 数据库记录不完整 | | 🟣 | 合并不完整 | level=1 章音频 < 子节时长总和 80% | 章级合并丢失子节 | #### 修复策略 1. **有本地分段文件** → 用 FFmpeg 重新合并所有分段,上传新音频 2. **WAV 存为 .mp3** → 转码为真正 MP3 格式,上传替换 3. **合并不完整** → 调用 `mergeChapterAudios` 重新下载子节音频并合并 4. **时长缺失** → 检测实际音频时长,更新数据库 5. **远程 URL 且无本地分段** → 提示需重新生成音频(需人工处理) > ⚠️ 脚本扫描时如果发现"疑似分段目录"(远程 URL 无法直接关联本地文件),仅作提示,不会自动修复,需人工确认后手动指定 `--chapter` 参数修复。 ## 📦 部署方式 > ⚠️ 旧方案(Gogs Webhook)链路过长(push → Gogs → webhook → 脚本 → build → restart),常有部署失败。 ### 推荐:deploy-simple.sh(SSH 直推) 零中间环节,最可靠。前提:代码已 git push。 ```bash # 一行部署 bash deploy-simple.sh ``` 流程:`SSH → git pull → npm build → pm2 reload` ### 备选:deploy-fast.sh(本地构建 + 增量同步) 不依赖服务器拉取 git,适合大文件或网络不稳定时。 ```bash # 部署后端 bash deploy-fast.sh # 部署后端+前端 bash deploy-fast.sh --all # 预览变更(不真部署) bash deploy-fast.sh --dry ``` 流程:`本地 npm build → rsync dist/ → pm2 reload` ### 对比 | | Webhook(旧) | deploy-simple | deploy-fast | |---|:--:|:--:|:--:| | 环节数 | 5 | 1 | 1 | | 依赖 Git 服务器 | ✅ | 服务器需拉取 | ❌ | | 部署验证 | ❌ 无 | ✅ git hash 对比 | ✅ API 健康检查 | | 适用场景 | — | 常规迭代 | 紧急修复 | ### 部署后验证 ```bash # 检查 API 健康 curl https://book.rrbrr.com/api/book-generator/langgraph/book-types # SSH 检查服务状态 ssh -p 22622 root@8.159.134.106 "pm2 status" ``` ### 手动部署(无脚本可用时) ```bash # SSH 登录 ssh -p 22622 root@8.159.134.106 # 服务器上执行 cd /data/ai/audio_codebuddy git pull origin master cd server npx prisma generate rm -rf dist npm run build pm2 reload server ``` 或一行命令(Windows CMD / Git Bash 均可): ```bash ssh -p 22622 root@8.159.134.106 "cd /data/ai/audio_codebuddy && git pull origin master && cd server && npx prisma generate && rm -rf dist && npm run build && pm2 reload server" ``` ## 🧪 自动化回归测试 项目内置两级自动化测试体系,确保每次上线前所有业务功能可用。 ### 测试等级 | 级别 | 命令 | 耗时 | 覆盖范围 | 适用场景 | |------|------|------|----------|----------| | L1 冒烟 | `npm run test:smoke` | ~2 min | 健康检查 + 27个 GET 端点 + 8个核心页面加载 | 每次部署后 | | L2 回归 | `npm run test:regression` | ~15 min | 23个业务模块完整 CRUD + 前端 E2E | 正式发版前 | ### 快速使用 ```bash cd server # 冒烟测试(部署后快速验证) npm run test:smoke # 完整回归测试(发版前全面检查) npm run test:regression # 运行全部测试 npm run test:all # 查看 HTML 报告 npm run test:report # 部署前快速检查 npm run deploy:check ``` 也可以通过项目根目录的 `test.bat`(Windows): ```batch test smoke # 冒烟测试 test regression # 完整回归 test all # 全部测试 ``` ### 部署集成 ```bash # 启动服务并自动运行冒烟测试 bash init.sh --test ``` ### 覆盖的业务模块 认证、TTS语音合成、音频播放器、收藏、播放历史、搜索、评论、通知、背景音乐、音频编辑、AI书籍生成、专辑管理、播放列表、草稿箱、视频生成、订阅支付、跨平台发布、会员中心、用户反馈、分享、用户偏好、分类浏览、签到 — 共 23 个模块。 ### 测试结构 > 测试目录位于项目根 `tests/`,覆盖前后端全项目。 ``` tests/ ├── playwright.config.ts # Playwright 两级项目配置 ├── global-setup.ts # 全局初始化(健康检查 + 测试用户) ├── global-teardown.ts # 全局清理(自动清理测试数据) ├── helpers/ │ ├── api-client.ts # 统一 API 请求封装 │ ├── test-data.ts # 测试数据工厂(创建/追踪/清理) │ └── reporter.ts # 自定义报告(AI 可读 Markdown + JSON) ├── smoke/ # L1 冒烟测试 │ ├── health.spec.ts │ ├── api-readonly.spec.ts │ └── pages-load.spec.ts ├── regression/ # L2 回归测试(按模块拆分) │ ├── 01-auth.spec.ts │ ├── 02-tts.spec.ts │ ├── ... │ └── 23-frontend-e2e.spec.ts └── test-results/ # 测试输出(自动生成) ├── ai-summary.md # 🤖 AI 可读摘要 ├── ai-summary.json # 结构化 JSON 结果 └── screenshots/ # 截图 ``` ### AI 可读输出 测试运行后自动生成 `tests/test-results/ai-summary.md`,包含: - **汇总表格**:总数 / 通过 / 失败 / 跳过 / 通过率 - **按模块详情**:每个用例的结果 + 耗时 + 截图路径 - **失败详情**:失败用例的错误信息 AI 编辑器可直接读取该文件判断测试结果。 ### 前端 E2E 验收用例详情 每个用例遵循 **Navigate → Locate → Assert → Screenshot** 流程,验证具体 UI 元素和视觉效果。 用例文件:`tests/regression/23-frontend-e2e.spec.ts` --- #### E01 — 首页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/index/index` | 页面加载完成(networkidle) | | 2 | 检查根元素 | `#app` 可见 | | 3 | 定位搜索栏 input | 存在 `placeholder="搜索..."` 的输入框 | | 4 | 定位通知按钮 | 页面包含 `🔔` 元素 | | 5 | 检查内容区域 | 存在"最近收听"或"欢迎"引导文字 | | 6 | 截图保存 | → `test-results/screenshots/e2e-E01-index.png` | --- #### E02 — 生成音频页(Tab 页) | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/create/index` | 页面加载完成 | | 2 | 验证页面标题 | `"生成音频"` 文字可见 | | 3 | 验证文本输入区 | `textarea[placeholder*="请输入"]` 可见 | | 4 | 验证音色卡片 | `"选择音色"` 文字可见 | | 5 | 验证参数调节卡片 | `"参数调节"` 文字可见 | | 6 | 验证 AI 生成入口 | `"AI 生成内容"` 按钮可见(如存在) | | 7 | 验证主操作按钮 | `"生成音频"` 按钮可见 | | 8 | 截图保存 | → `test-results/screenshots/e2e-E02-create.png` | --- #### E03 — 书籍生成列表页(Tab 页) | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/book-generator/index` | 页面加载完成 | | 2 | 验证页面标题 | `"书籍生成"` 可见 | | 3 | 验证创建入口 | `"创建新书籍"` 按钮可见 | | 4 | 验证列表或空状态 | 存在"暂无书籍"空状态 或 书籍列表卡片 | | 5 | 截图保存 | → `test-results/screenshots/e2e-E03-book-list.png` | --- #### E04 — 我的(Tab 页) | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/mine/index` | 页面加载完成 | | 2 | 验证根元素 | `#app` 可见 | | 3 | 验证菜单项 | 至少一项存在:`"会员中心"`、`"设置"`、`"历史记录"`、`"书籍生成"` | | 4 | 验证退出登录 | `"退出登录"` 按钮可见(如已登录) | | 5 | 截图保存 | → `test-results/screenshots/e2e-E04-mine.png` | --- #### E05 — 音频播放器页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/player/index` | 页面加载完成 | | 2 | 验证播放器 UI | 存在"正在播放"标题、或"播放列表"区域、或"暂无音频"空状态 | | 3 | 截图保存 | → `test-results/screenshots/e2e-E05-player.png` | --- #### E06 — 创建书籍页(一键/简洁模式) | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/book-generator/create` | 页面加载完成 | | 2 | 验证页面标题 | `"创建书籍"` 可见 | | 3 | 验证书名输入框 | `input[placeholder*="书名"]` 可见(可选字段) | | 4 | 验证描述输入框 | `textarea[placeholder*="描述"]` 可见(必填字段) | | 5 | 验证智能推荐 | `"智能推荐"` 按钮可见 | | 6 | 验证高级选项 | `"高级选项"` 折叠面板可见 | | 7 | 验证创建按钮 | `"创建书籍"` 主按钮可见 | | 8 | 验证模式切换 | `"切换到交互模式"` 按钮可见 | | 9 | 截图保存 | → `test-results/screenshots/e2e-E06-book-create.png` | --- #### E07 — 交互式创建页(4步向导) | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/book-generator/interactive` | 页面加载完成 | | 2 | 验证页面标题 | `"交互式创建"` 可见 | | 3 | 验证步骤 Step 1 | `"信息输入"` 标签可见 | | 4 | 验证步骤 Step 2 | `"AI规划"` 标签可见 | | 5 | 验证 Step 1 标题 | `"第一步"` 文字可见 | | 6 | 验证书名输入 | `input[placeholder*="《"]` 可见(必填) | | 7 | 验证 AI 推荐 | `"AI智能推荐"` 按钮可见 | | 8 | 验证下一步按钮 | `"AI分析"` 按钮可见 | | 9 | 验证模式切换 | `"切换到一键模式"` 按钮可见 | | 10 | 截图保存 | → `test-results/screenshots/e2e-E07-interactive.png` | --- #### E08 — 章节详情页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/book-generator/chapter-detail` | 页面加载完成 | | 2 | 验证页面框架 | `#app` 可见,页面至少渲染基础框架 | | 3 | 验证章节信息 | 存在"章节"或"第N章"编号标签 | | 4 | 验证内容/操作区 | 页面包含内容渲染区或音频/视频操作按钮 | | 5 | 截图保存 | → `test-results/screenshots/e2e-E08-chapter-detail.png` | --- #### E09 — 会员订阅页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/member/index` | 页面加载完成 | | 2 | 验证页面标题 | `"订阅"` 文字可见 | | 3 | 验证 Token 余额 | `"Token"` 余额数字可见 | | 4 | 验证套餐卡片 | 至少一个 `"选择此套餐"` 按钮可见 | | 5 | 验证支付方式 | `"支付宝"` 或 `"微信支付"` 标签可见 | | 6 | 验证订阅按钮 | `"立即订阅"` 或 `"免费套餐"` 按钮可见 | | 7 | 截图保存 | → `test-results/screenshots/e2e-E09-member.png` | --- #### E10 — 专辑列表页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/albums/index` | 页面加载完成 | | 2 | 验证页面标题 | `"专辑"` 文字可见 | | 3 | 验证内容 | 存在专辑网格列表 或 `"暂无专辑"` + `"创建你的第一个专辑"` 空状态 | | 4 | 截图保存 | → `test-results/screenshots/e2e-E10-albums.png` | --- #### E11 — 搜索页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/search/index` | 页面加载完成 | | 2 | 验证搜索框 | `input[placeholder*="搜索"]` 可见 | | 3 | 验证搜索按钮 | `"搜索"` 按钮可见 | | 4 | 验证推荐区域 | 存在"搜索历史"标签列表、或"热门推荐"区域、或空提示文字 | | 5 | 截图保存 | → `test-results/screenshots/e2e-E11-search.png` | --- #### E12 — 历史记录页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/history/index` | 页面加载完成 | | 2 | 验证时间筛选 | `"全部"` 标签可见 | | 3 | 验证时间标签 | `"今天"` 或 `"本周"` 标签可见 | | 4 | 验证视图切换 | `"聚合"` 或 `"列表"` 视图切换按钮可见 | | 5 | 截图保存 | → `test-results/screenshots/e2e-E12-history.png` | --- #### E13 — 视频生成页 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/video-generator/index` | 页面加载完成 | | 2 | 验证页面标题 | `"视频生成"` 可见 | | 3 | 验证创建入口 | `"创建新视频"` 按钮可见 | | 4 | 验证引导文案 | `"图片 + 音频 = 精美视频"` 可见 | | 5 | 截图保存 | → `test-results/screenshots/e2e-E13-video-gen.png` | --- #### E14 — 首页 JS 运行时错误检查 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | 注册 pageerror 监听器 | 收集所有 JS 运行时错误 | | 2 | Navigate to `/#/pages/index/index` | 页面加载完成,等待 5 秒 | | 3 | 过滤非关键错误 | 忽略 ResizeObserver、Script error、favicon 等 | | 4 | 断言 | 无 `is not a function` / `Cannot read properties` 类严重错误 | --- #### E15 — TabBar 导航一致性 | 步骤 | 操作 | 预期结果 | |------|------|----------| | 1 | Navigate to `/#/pages/index/index` | `#app` 可见 | | 2 | Navigate to `/#/pages/create/index` | `#app` 可见 | | 3 | Navigate to `/#/pages/book-generator/index` | `#app` 可见 | | 4 | Navigate to `/#/pages/mine/index` | `#app` 可见 | | 5 | 截图保存 | → `test-results/screenshots/e2e-E15-tabs.png` | ## 📝 开发计划 ### MVP 阶段(2周) - [x] 项目初始化 - [x] 用户登录(手机号) - [x] TTS 核心功能 - [x] 音频播放器 - [x] 历史记录 - [x] 会员系统基础版 ### V1.0 阶段(1个月) - [ ] 智能简介生成 - [ ] 更多音色选择 - [ ] 分享功能 - [ ] 管理后台 ### V1.5 阶段(2-3个月) - [ ] 批量生成 - [ ] 音频编辑功能 - [ ] 导出多种格式 ## 📄 License MIT License