将文字转换为高质量音频,让阅读变成聆听
📖 项目 Wiki:.qoder/repowiki
修改 / 提交 / 发布代码前必须先跑核心业务测试。 完整规范见 CONTRIBUTING.md,不读不算入职完成。
修改 → 跑核心业务测试 → 通过 → 提交 → 推送/发布
| 场景 | 命令 |
|---|---|
改了 server/src/modules/book-generator/** |
./scripts/run-core-business-test.sh |
改了 my-uniapp-vue3/src/pages/book-generator/** |
./scripts/run-core-business-test.sh |
改了 my-uniapp-vue3/src/utils/book-generator-api.ts |
./scripts/run-core-business-test.sh |
改了 server/prisma/schema.prisma(Book / BookChapter) |
./scripts/run-core-business-test.sh |
每次 git commit / git push |
git hooks 自动(需先 ./scripts/install-hooks.sh) |
| 每次部署 / 发布 | ./scripts/run-core-business-test.sh 或 cd server && npm run test:core |
核心业务 = 书籍 大纲 → 内容 → 音频 全链路。 测试用例:tests/e2e/core-business/,覆盖 API + UI 全流程,3 ~ 5 分钟跑完。
👉 详细规则、失败处理、新人入职清单见 CONTRIBUTING.md
AI 有声书生成工具是一款面向内容创作者的音频制作工具,帮助自媒体作者、小说爱好者、学习者低成本制作音频内容。
audio/
├── client/ # uniapp 前端项目
│ ├── src/
│ │ ├── pages/ # 页面
│ │ ├── store/ # 状态管理
│ │ ├── utils/ # 工具函数
│ │ └── types/ # 类型定义
│ └── package.json
│
├── server/ # Node.js 后端项目
│ ├── src/
│ │ ├── modules/ # 业务模块
│ │ ├── models/ # 数据模型
│ │ ├── middleware/ # 中间件
│ │ └── config/ # 配置
│ └── package.json
│
└── README.md
基于阿里云百炼官方计费(2026-05),单位已对齐官方文档。
| 服务 | 模型 | 单价 | 单位 |
|---|---|---|---|
| AI 文本生成 | 通义千问 Qwen-Plus | 输入 ¥0.8 / 输出 ¥2.0 | /百万 token |
| TTS 语音合成 | CosyVoice v3-flash | ¥1.0 | /万字符 |
| 字数 | AI文本 | TTS合成 | API成本 | 售价(×2) | 音频时长 |
|---|---|---|---|---|---|
| 1千字 | ¥0.003 | ¥0.10 | ¥0.10 | ¥0.21 | ~7分钟 |
| 1万字 | ¥0.03 | ¥1.00 | ¥1.03 | ¥2.06 | ~67分钟 |
| 10万字 | ¥0.30 | ¥10.00 | ¥10.30 | ¥20.61 | ~667分钟 |
| 100万字 | ¥3.00 | ¥100.00 | ¥103.00 | ¥206.00 | ~6,667分钟 |
TTS 占比 ~96%,文本生成几乎可忽略。
售价 = API成本 × PRICING_COEFFICIENT(默认 2.0,可调整)
| 项目 | 值 |
|---|---|
| 每包 | 100 分钟 |
| 单价 | ¥0.048/分钟(月价的 1.5 倍) |
| 包价 | ¥4.80 |
| 获得额度 | ¥3.20(按月价 ¥0.032/分钟计) |
cd server
# 安装依赖
npm install
# 复制环境配置
cp .env.example .env
# 启动开发服务
npm run dev
cd client
# 安装依赖
npm install
# 启动 H5 开发服务
npm run dev:h5
# 编译微信小程序
npm run build:mp-weixin
# 服务配置
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
POST /api/auth/send-code - 发送验证码POST /api/auth/login - 手机号登录GET /api/auth/user-info - 获取用户信息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字 | 无限 |
| 音色选择 | 基础 | 全部 | 全部 |
| 服务商 | 接口类型 | 单次最大长度 | 约等于音频 | 备注 |
|---|---|---|---|---|
| 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 脚本,用于检测和修复音频合并异常(如段落丢失、格式不匹配、时长不正确等问题)。
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% | 章级合并丢失子节 |
mergeChapterAudios 重新下载子节音频并合并⚠️ 脚本扫描时如果发现"疑似分段目录"(远程 URL 无法直接关联本地文件),仅作提示,不会自动修复,需人工确认后手动指定
--chapter参数修复。
零中间环节,最可靠。代码推送到 Git 后执行:
# 仅后端
ssh aliyunos4 "cd /data/ai/audio_codebuddy && git fetch origin master && git reset --hard origin/master && cd server && npx prisma generate && rm -rf dist && npm run build && pm2 reload server"
流程:git push → SSH → git fetch + reset --hard → npm build → pm2 reload
# 本地构建
cd my-uniapp-vue3 && npm run build:h5
# 上传到服务器
scp -r dist/build/h5/* aliyunos4:/data/ai/audio/frontend/build/h5/
# 重启前端
ssh aliyunos4 "pm2 reload book-frontend"
# 检查服务状态
ssh aliyunos4 "pm2 status"
# 检查后端健康
ssh aliyunos4 "curl -s http://localhost:3100/health"
# 检查域名可访问
curl -sk -o /dev/null -w "HTTP %{http_code}" https://book.rrbrr.com/
# 1. SSH 登录
ssh -p 22622 root@8.159.134.106
# 2. 服务器上执行
cd /data/ai/audio_codebuddy
git fetch origin master
git reset --hard origin/master
cd server
npx prisma generate
rm -rf dist
npm run build
pm2 reload server
项目内置两级自动化测试体系,确保每次上线前所有业务功能可用。
完整测试文档: TESTING.md
| 级别 | 命令 | 耗时 | 覆盖范围 | 适用场景 |
|---|---|---|---|---|
| L1 冒烟 | npm run test:smoke |
~2 min | 健康检查 + 27个 GET 端点 + 8个核心页面加载 | 每次部署后 |
| L2 回归 | npm run test:regression |
~15 min | 23个业务模块完整 CRUD + 前端 E2E | 正式发版前 |
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):
test smoke # 冒烟测试
test regression # 完整回归
test all # 全部测试
# 启动服务并自动运行冒烟测试
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/ # 截图
测试运行后自动生成 tests/test-results/ai-summary.md,包含:
AI 编辑器可直接读取该文件判断测试结果。
每个用例遵循 Navigate → Locate → Assert → Screenshot 流程,验证具体 UI 元素和视觉效果。
用例文件:tests/regression/23-frontend-e2e.spec.ts
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/index/index |
页面加载完成(networkidle) |
| 2 | 检查根元素 | #app 可见 |
| 3 | 定位搜索栏 input | 存在 placeholder="搜索..." 的输入框 |
| 4 | 定位通知按钮 | 页面包含 🔔 元素 |
| 5 | 检查内容区域 | 存在"最近收听"或"欢迎"引导文字 |
| 6 | 截图保存 | → test-results/screenshots/e2e-E01-index.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/create/index |
页面加载完成 |
| 2 | 验证页面标题 | "生成音频" 文字可见 |
| 3 | 验证文本输入区 | textarea[placeholder*="请输入"] 可见 |
| 4 | 验证音色卡片 | "选择音色" 文字可见 |
| 5 | 验证参数调节卡片 | "参数调节" 文字可见 |
| 6 | 验证 AI 生成入口 | "AI 生成内容" 按钮可见(如存在) |
| 7 | 验证主操作按钮 | "生成音频" 按钮可见 |
| 8 | 截图保存 | → test-results/screenshots/e2e-E02-create.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/book-generator/index |
页面加载完成 |
| 2 | 验证页面标题 | "书籍生成" 可见 |
| 3 | 验证创建入口 | "创建新书籍" 按钮可见 |
| 4 | 验证列表或空状态 | 存在"暂无书籍"空状态 或 书籍列表卡片 |
| 5 | 截图保存 | → test-results/screenshots/e2e-E03-book-list.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/mine/index |
页面加载完成 |
| 2 | 验证根元素 | #app 可见 |
| 3 | 验证菜单项 | 至少一项存在:"会员中心"、"设置"、"历史记录"、"书籍生成" |
| 4 | 验证退出登录 | "退出登录" 按钮可见(如已登录) |
| 5 | 截图保存 | → test-results/screenshots/e2e-E04-mine.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/player/index |
页面加载完成 |
| 2 | 验证播放器 UI | 存在"正在播放"标题、或"播放列表"区域、或"暂无音频"空状态 |
| 3 | 截图保存 | → test-results/screenshots/e2e-E05-player.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 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 |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 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 |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/book-generator/chapter-detail |
页面加载完成 |
| 2 | 验证页面框架 | #app 可见,页面至少渲染基础框架 |
| 3 | 验证章节信息 | 存在"章节"或"第N章"编号标签 |
| 4 | 验证内容/操作区 | 页面包含内容渲染区或音频/视频操作按钮 |
| 5 | 截图保存 | → test-results/screenshots/e2e-E08-chapter-detail.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/member/index |
页面加载完成 |
| 2 | 验证页面标题 | "订阅" 文字可见 |
| 3 | 验证 Token 余额 | "Token" 余额数字可见 |
| 4 | 验证套餐卡片 | 至少一个 "选择此套餐" 按钮可见 |
| 5 | 验证支付方式 | "支付宝" 或 "微信支付" 标签可见 |
| 6 | 验证订阅按钮 | "立即订阅" 或 "免费套餐" 按钮可见 |
| 7 | 截图保存 | → test-results/screenshots/e2e-E09-member.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/albums/index |
页面加载完成 |
| 2 | 验证页面标题 | "专辑" 文字可见 |
| 3 | 验证内容 | 存在专辑网格列表 或 "暂无专辑" + "创建你的第一个专辑" 空状态 |
| 4 | 截图保存 | → test-results/screenshots/e2e-E10-albums.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/search/index |
页面加载完成 |
| 2 | 验证搜索框 | input[placeholder*="搜索"] 可见 |
| 3 | 验证搜索按钮 | "搜索" 按钮可见 |
| 4 | 验证推荐区域 | 存在"搜索历史"标签列表、或"热门推荐"区域、或空提示文字 |
| 5 | 截图保存 | → test-results/screenshots/e2e-E11-search.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/history/index |
页面加载完成 |
| 2 | 验证时间筛选 | "全部" 标签可见 |
| 3 | 验证时间标签 | "今天" 或 "本周" 标签可见 |
| 4 | 验证视图切换 | "聚合" 或 "列表" 视图切换按钮可见 |
| 5 | 截图保存 | → test-results/screenshots/e2e-E12-history.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | Navigate to /#/pages/video-generator/index |
页面加载完成 |
| 2 | 验证页面标题 | "视频生成" 可见 |
| 3 | 验证创建入口 | "创建新视频" 按钮可见 |
| 4 | 验证引导文案 | "图片 + 音频 = 精美视频" 可见 |
| 5 | 截图保存 | → test-results/screenshots/e2e-E13-video-gen.png |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | 注册 pageerror 监听器 | 收集所有 JS 运行时错误 |
| 2 | Navigate to /#/pages/index/index |
页面加载完成,等待 5 秒 |
| 3 | 过滤非关键错误 | 忽略 ResizeObserver、Script error、favicon 等 |
| 4 | 断言 | 无 is not a function / Cannot read properties 类严重错误 |
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 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 |
MIT License