diff --git a/UniApp微信小程序TTS接口调用说明.md b/UniApp微信小程序TTS接口调用说明.md new file mode 100644 index 0000000..5237333 --- /dev/null +++ b/UniApp微信小程序TTS接口调用说明.md @@ -0,0 +1,364 @@ +# UniApp微信小程序TTS接口调用说明 + +本文档详细介绍如何在UniApp微信小程序中调用后端的文字转语音(TTS)接口。 + +## 📁 文件说明 + +### 1. uniapp-tts-example.vue +完整的TTS功能页面组件,包含: +- 文本输入界面 +- 音色选择功能 +- 语速和音量调节 +- 音频格式选择 +- 音频播放功能 +- 完整的错误处理 + +### 2. uniapp-tts-config.js +TTS功能的配置文件,包含: +- API接口配置 +- 参数验证工具 +- 错误处理配置 +- 通用工具函数 + +## 🚀 快速开始 + +### 1. 文件集成 + +将以下文件复制到你的UniApp项目中: + +``` +your-project/ +├── pages/ +│ └── tts/ +│ └── index.vue # 复制 uniapp-tts-example.vue 内容 +├── utils/ +│ └── tts-config.js # 复制 uniapp-tts-config.js 内容 +└── pages.json # 添加页面路由配置 +``` + +### 2. 页面路由配置 + +在 `pages.json` 中添加TTS页面路由: + +```json +{ + "pages": [ + { + "path": "pages/tts/index", + "style": { + "navigationBarTitleText": "文字转语音", + "navigationBarBackgroundColor": "#007aff", + "navigationBarTextStyle": "white" + } + } + ] +} +``` + +### 3. 配置API地址 + +修改 `uniapp-tts-config.js` 中的API地址: + +```javascript +export const API_CONFIG = { + // 开发环境API地址 + DEV_BASE_URL: 'http://localhost:8080', // 修改为你的开发环境地址 + + // 生产环境API地址 + PROD_BASE_URL: 'https://your-domain.com', // 修改为你的生产环境地址 +}; +``` + +## 🔧 功能特性 + +### 1. 音色管理 +- 自动加载后端音色列表 +- 支持音色选择和切换 +- 音色信息缓存 + +### 2. 参数控制 +- **语速调节**:支持-2到6的语速范围 +- **音量控制**:支持-10到10的音量范围 +- **格式选择**:支持WAV、MP3、PCM格式 + +### 3. 音频处理 +- 二进制音频数据处理 +- 本地音频文件创建 +- 音频播放控制 +- 文件大小显示 + +### 4. 用户体验 +- 实时状态反馈 +- 转换进度提示 +- 错误信息展示 +- 响应式界面设计 + +## 📋 接口说明 + +### 1. 获取音色列表 + +**接口地址:** `GET /appletApi/tts/list` + +**响应格式:** +```json +{ + "success": true, + "result": [ + { + "id": 0, + "name": "云小宁", + "description": "甜美女声" + } + ] +} +``` + +### 2. 文字转语音 + +**接口地址:** `GET /appletApi/tts/textToVoice` + +**请求参数:** +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| text | String | 是 | 要转换的文本内容 | +| speed | Float | 否 | 语速,范围[-2,6],默认0 | +| voiceType | Integer | 否 | 音色ID,默认0 | +| volume | Float | 否 | 音量,范围[-10,10],默认0 | +| codec | String | 否 | 音频格式,默认wav | +| userId | String | 否 | 用户ID,用于日志记录 | + +**响应格式:** 二进制音频数据 + +## 💡 使用示例 + +### 1. 基础调用 + +```javascript +import { API_CONFIG, UTILS } from '@/utils/tts-config.js'; + +// 调用TTS接口 +async function callTTS() { + try { + const response = await uni.request({ + url: UTILS.buildApiUrl(API_CONFIG.ENDPOINTS.TEXT_TO_VOICE), + method: 'GET', + data: { + text: '你好,世界!', + speed: 0, + voiceType: 0, + volume: 0, + codec: 'wav', + userId: 'user123' + }, + responseType: 'arraybuffer' + }); + + if (response.statusCode === 200) { + // 处理音频数据 + console.log('TTS调用成功'); + } + } catch (error) { + console.error('TTS调用失败:', error); + } +} +``` + +### 2. 参数验证 + +```javascript +import { UTILS } from '@/utils/tts-config.js'; + +// 验证文本 +const textValidation = UTILS.validateText('要转换的文本'); +if (!textValidation.valid) { + console.error(textValidation.message); + return; +} + +// 验证语速 +const speedValidation = UTILS.validateSpeed(1); +if (!speedValidation.valid) { + console.error(speedValidation.message); + return; +} +``` + +### 3. 音频播放 + +```javascript +// 创建音频上下文 +const audioContext = wx.createInnerAudioContext(); +audioContext.src = audioFilePath; + +// 监听播放事件 +audioContext.onPlay(() => { + console.log('开始播放'); +}); + +audioContext.onEnded(() => { + console.log('播放结束'); +}); + +audioContext.onError((error) => { + console.error('播放失败:', error); +}); + +// 开始播放 +audioContext.play(); +``` + +## ⚠️ 注意事项 + +### 1. 权限配置 + +在 `manifest.json` 中配置必要的权限: + +```json +{ + "mp-weixin": { + "permission": { + "scope.writePhotosAlbum": { + "desc": "保存音频文件到相册" + } + } + } +} +``` + +### 2. 网络配置 + +在微信小程序后台配置服务器域名,将你的API域名添加到request合法域名中。 + +### 3. 文件大小限制 + +- 微信小程序本地文件存储有限制 +- 建议音频文件不超过10MB +- 长文本建议分段处理 + +### 4. 错误处理 + +```javascript +// 统一错误处理 +function handleError(error, context = '') { + console.error(`${context}错误:`, error); + + let message = '操作失败,请重试'; + + if (error.errMsg) { + if (error.errMsg.includes('network')) { + message = '网络连接失败,请检查网络'; + } else if (error.errMsg.includes('timeout')) { + message = '请求超时,请重试'; + } + } + + uni.showToast({ + title: message, + icon: 'error' + }); +} +``` + +## 🔍 调试技巧 + +### 1. 开启调试日志 + +```javascript +// 在开发环境开启详细日志 +if (process.env.NODE_ENV === 'development') { + console.log('TTS请求参数:', params); + console.log('TTS响应数据:', response); +} +``` + +### 2. 网络请求监控 + +```javascript +// 监控请求状态 +uni.onNetworkStatusChange((res) => { + console.log('网络状态:', res.isConnected ? '已连接' : '已断开'); + console.log('网络类型:', res.networkType); +}); +``` + +### 3. 性能监控 + +```javascript +// 监控转换耗时 +const startTime = Date.now(); +// ... TTS调用 ... +const endTime = Date.now(); +console.log(`TTS转换耗时: ${(endTime - startTime) / 1000}秒`); +``` + +## 📈 性能优化 + +### 1. 音色列表缓存 + +```javascript +// 缓存音色列表,避免重复请求 +const VOICE_CACHE_KEY = 'tts_voice_list'; +const CACHE_EXPIRE_TIME = 24 * 60 * 60 * 1000; // 24小时 + +function getCachedVoiceList() { + const cached = uni.getStorageSync(VOICE_CACHE_KEY); + if (cached && (Date.now() - cached.timestamp) < CACHE_EXPIRE_TIME) { + return cached.data; + } + return null; +} +``` + +### 2. 音频文件管理 + +```javascript +// 清理过期的音频文件 +function cleanupAudioFiles() { + const fileManager = wx.getFileSystemManager(); + // 实现文件清理逻辑 +} +``` + +### 3. 请求防抖 + +```javascript +// 防止重复请求 +let isConverting = false; + +async function convertToVoice() { + if (isConverting) { + console.log('正在转换中,请稍候...'); + return; + } + + isConverting = true; + try { + // TTS转换逻辑 + } finally { + isConverting = false; + } +} +``` + +## 🤝 技术支持 + +如果在使用过程中遇到问题,请检查: + +1. **网络连接**:确保设备网络正常 +2. **API地址**:确认API地址配置正确 +3. **参数格式**:检查请求参数是否符合要求 +4. **权限设置**:确认小程序权限配置正确 +5. **后端服务**:确认后端TTS服务正常运行 + +## 📝 更新日志 + +### v1.0.0 (2025-01-XX) +- 初始版本发布 +- 支持基础TTS功能 +- 完整的参数控制 +- 音频播放功能 +- 错误处理机制 + +--- + +**注意:** 本示例基于JeecgBoot 3.8.1框架开发,使用腾讯云TTS服务。在实际使用时,请根据你的具体环境进行相应调整。 \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java index e3afa3c..53a77f6 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java @@ -33,7 +33,7 @@ public class AppletApiTTSController { } @Operation(summary = "文字转语音", description = "文字转语音") - @GetMapping(value = "/textToVoice") + @PostMapping(value = "/textToVoice") @IgnoreAuth public ResponseEntity textToVoice( @Parameter(description = "要转换的文本内容", required = true) String text, @@ -45,8 +45,7 @@ public class AppletApiTTSController { "6代表2.5倍") Float speed, @Parameter(description = "音色ID,默认为0") Integer voiceType, @Parameter(description = "音量大小,范围[-10,10],默认为0") Float volume, - @Parameter(description = "返回音频格式,可取值:wav(默认),mp3,pcm") String codec, - @Parameter(description = "用户ID,用于记录日志") String userId) { + @Parameter(description = "返回音频格式,可取值:wav(默认),mp3,pcm") String codec) { try { byte[] audioData = appletApiTTService.textToVoice(text, speed, voiceType, volume, codec, userId); diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/resources/application.yml b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/resources/application.yml deleted file mode 100644 index 7640f66..0000000 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -# 腾讯云配置 -tencent: - # 腾讯云API密钥ID - secretId: your_secret_id_here - # 腾讯云API密钥Key - secretKey: your_secret_key_here \ No newline at end of file diff --git a/uniapp-tts-config.js b/uniapp-tts-config.js new file mode 100644 index 0000000..bfab470 --- /dev/null +++ b/uniapp-tts-config.js @@ -0,0 +1,243 @@ +/** + * UniApp TTS接口配置文件 + * 用于配置文字转语音相关的API接口和参数 + */ + +// API配置 +export const API_CONFIG = { + // 开发环境API地址 + DEV_BASE_URL: 'http://localhost:8080', + + // 生产环境API地址(请根据实际情况修改) + PROD_BASE_URL: 'https://your-domain.com', + + // 接口路径 + ENDPOINTS: { + // 获取音色列表 + VOICE_LIST: '/appletApi/tts/list', + // 文字转语音 + TEXT_TO_VOICE: '/appletApi/tts/textToVoice' + }, + + // 请求超时时间(毫秒) + TIMEOUT: 30000 +}; + +// TTS参数配置 +export const TTS_CONFIG = { + // 语速配置 + SPEED: { + MIN: -2, + MAX: 6, + DEFAULT: 0, + OPTIONS: [ + { value: -2, label: '0.6倍速', description: '很慢' }, + { value: -1, label: '0.8倍速', description: '慢' }, + { value: 0, label: '1.0倍速', description: '正常' }, + { value: 1, label: '1.2倍速', description: '快' }, + { value: 2, label: '1.5倍速', description: '很快' }, + { value: 6, label: '2.5倍速', description: '极快' } + ] + }, + + // 音量配置 + VOLUME: { + MIN: -10, + MAX: 10, + DEFAULT: 0 + }, + + // 音频格式配置 + CODEC: { + OPTIONS: [ + { value: 'wav', label: 'WAV', description: '无损音质,文件较大' }, + { value: 'mp3', label: 'MP3', description: '压缩音质,文件适中' }, + { value: 'pcm', label: 'PCM', description: '原始音频,文件最大' } + ], + DEFAULT: 'wav' + }, + + // 文本限制 + TEXT: { + MAX_LENGTH: 500, + MIN_LENGTH: 1 + } +}; + +// 错误码配置 +export const ERROR_CODES = { + // 网络错误 + NETWORK_ERROR: 'NETWORK_ERROR', + // 参数错误 + PARAM_ERROR: 'PARAM_ERROR', + // 服务器错误 + SERVER_ERROR: 'SERVER_ERROR', + // 音频播放错误 + AUDIO_ERROR: 'AUDIO_ERROR', + // 文件操作错误 + FILE_ERROR: 'FILE_ERROR' +}; + +// 错误消息配置 +export const ERROR_MESSAGES = { + [ERROR_CODES.NETWORK_ERROR]: '网络连接失败,请检查网络设置', + [ERROR_CODES.PARAM_ERROR]: '参数错误,请检查输入内容', + [ERROR_CODES.SERVER_ERROR]: '服务器错误,请稍后重试', + [ERROR_CODES.AUDIO_ERROR]: '音频播放失败,请重试', + [ERROR_CODES.FILE_ERROR]: '文件操作失败,请重试' +}; + +// 工具函数 +export const UTILS = { + /** + * 获取当前环境的API基础地址 + */ + getBaseUrl() { + // #ifdef MP-WEIXIN + // 微信小程序环境 + return process.env.NODE_ENV === 'production' ? API_CONFIG.PROD_BASE_URL : API_CONFIG.DEV_BASE_URL; + // #endif + + // #ifdef H5 + // H5环境 + return process.env.NODE_ENV === 'production' ? API_CONFIG.PROD_BASE_URL : API_CONFIG.DEV_BASE_URL; + // #endif + + // #ifdef APP-PLUS + // App环境 + return API_CONFIG.PROD_BASE_URL; + // #endif + + return API_CONFIG.DEV_BASE_URL; + }, + + /** + * 构建完整的API地址 + * @param {string} endpoint 接口路径 + */ + buildApiUrl(endpoint) { + return this.getBaseUrl() + endpoint; + }, + + /** + * 格式化文件大小 + * @param {number} bytes 字节数 + */ + formatFileSize(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }, + + /** + * 验证文本长度 + * @param {string} text 文本内容 + */ + validateText(text) { + if (!text || typeof text !== 'string') { + return { valid: false, message: '请输入文本内容' }; + } + + const trimmedText = text.trim(); + if (trimmedText.length < TTS_CONFIG.TEXT.MIN_LENGTH) { + return { valid: false, message: '文本内容不能为空' }; + } + + if (trimmedText.length > TTS_CONFIG.TEXT.MAX_LENGTH) { + return { valid: false, message: `文本长度不能超过${TTS_CONFIG.TEXT.MAX_LENGTH}个字符` }; + } + + return { valid: true, text: trimmedText }; + }, + + /** + * 验证语速参数 + * @param {number} speed 语速值 + */ + validateSpeed(speed) { + if (typeof speed !== 'number') { + return { valid: false, message: '语速参数必须为数字' }; + } + + if (speed < TTS_CONFIG.SPEED.MIN || speed > TTS_CONFIG.SPEED.MAX) { + return { valid: false, message: `语速范围为${TTS_CONFIG.SPEED.MIN}到${TTS_CONFIG.SPEED.MAX}` }; + } + + return { valid: true, speed }; + }, + + /** + * 验证音量参数 + * @param {number} volume 音量值 + */ + validateVolume(volume) { + if (typeof volume !== 'number') { + return { valid: false, message: '音量参数必须为数字' }; + } + + if (volume < TTS_CONFIG.VOLUME.MIN || volume > TTS_CONFIG.VOLUME.MAX) { + return { valid: false, message: `音量范围为${TTS_CONFIG.VOLUME.MIN}到${TTS_CONFIG.VOLUME.MAX}` }; + } + + return { valid: true, volume }; + }, + + /** + * 获取语速描述 + * @param {number} speed 语速值 + */ + getSpeedDescription(speed) { + const option = TTS_CONFIG.SPEED.OPTIONS.find(item => item.value === speed); + return option ? option.description : '未知'; + }, + + /** + * 获取音频格式描述 + * @param {string} codec 音频格式 + */ + getCodecDescription(codec) { + const option = TTS_CONFIG.CODEC.OPTIONS.find(item => item.value === codec); + return option ? option.description : '未知格式'; + }, + + /** + * 生成唯一ID + */ + generateId() { + return 'id_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); + }, + + /** + * 获取当前时间戳 + */ + getCurrentTimestamp() { + return Date.now(); + }, + + /** + * 格式化时间 + * @param {number} timestamp 时间戳 + */ + formatTime(timestamp) { + const date = new Date(timestamp); + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + } +}; + +// 默认导出配置对象 +export default { + API_CONFIG, + TTS_CONFIG, + ERROR_CODES, + ERROR_MESSAGES, + UTILS +}; \ No newline at end of file diff --git a/uniapp-tts-example.vue b/uniapp-tts-example.vue new file mode 100644 index 0000000..e2f5a99 --- /dev/null +++ b/uniapp-tts-example.vue @@ -0,0 +1,591 @@ +