# 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
```

## 💰 成本与定价

> 基于阿里云百炼官方计费（2026-05），单位已对齐官方文档。

### API 成本

| 服务 | 模型 | 单价 | 单位 |
|------|------|------|------|
| AI 文本生成 | 通义千问 Qwen-Plus | 输入 ¥0.8 / 输出 ¥2.0 | /百万 token |
| TTS 语音合成 | CosyVoice v3-flash | ¥1.0 | /万字符 |

### 综合成本（1中文 ≈ 1.5 token，150字/分钟）

| 字数 | 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，可调整）
```

### Token 包（按量购买）

| 项目 | 值 |
|------|-----|
| 每包 | 100 分钟 |
| 单价 | ¥0.048/分钟（月价的 1.5 倍） |
| 包价 | ¥4.80 |
| 获得额度 | ¥3.20（按月价 ¥0.032/分钟计） |

## 🚀 快速开始

### 环境要求

- 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` 参数修复。

## 📦 部署方式

### 推荐：SSH 一行部署

零中间环节，最可靠。代码推送到 Git 后执行：

```bash
# 仅后端
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`

### 部署前端

```bash
# 本地构建
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"
```

### 部署后验证

```bash
# 检查服务状态
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/
```

### 手动步骤（无 SSH 别名时）

```bash
# 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](./TESTING.md)

### 测试等级

| 级别 | 命令 | 耗时 | 覆盖范围 | 适用场景 |
|------|------|------|----------|----------|
| 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