TTS-Web-Vue系列:API调用业务分层与存储整合优化
本文介绍了TTS-Web-Vue项目的重要技术更新,重点在于API调用的业务分层设计和local-tts-store功能整合到play.ts的优化过程。随着项目功能扩展,面临API调用逻辑分散、业务逻辑与数据访问混杂、错误处理不一致和状态管理碎片化等挑战。为此,我们实施了重构,建立了清晰的三层架构(数据访问层、业务逻辑层和表现层),整合了local-tts-store功能,统一了错误处理,并提高了
🔄 本文是TTS-Web-Vue系列的重要技术更新,重点介绍了项目中API调用的业务分层设计和local-tts-store功能整合到play.ts的优化过程。通过这次重构,我们实现了更清晰的代码分层、更高的复用性和更易于维护的代码结构。
📖 系列文章导航
欢迎查看主页
🔍 重构背景与动机
随着TTS-Web-Vue项目的功能不断扩展,我们遇到了以下挑战:
- API调用逻辑分散:语音合成API调用逻辑散布在多个组件和函数中,导致代码冗余和维护困难
- 业务逻辑与数据访问混杂:业务处理逻辑与底层API调用未明确分离
- 错误处理不一致:不同地方的错误处理逻辑不统一,用户体验不一致
- 状态管理碎片化:local-tts-store与其他状态管理代码分离,增加了代码理解和维护的复杂度
为了解决这些问题,我们决定实施以下重构:
- 建立API调用的清晰分层:将API调用分为数据访问层、业务逻辑层和表现层
- 整合local-tts-store到play.ts:将相关功能合并到统一的文件中,减少代码分散
- 统一错误处理:实现一致的错误处理和恢复策略
- 提高代码复用性:减少重复代码,提高模块化程度
💡 API调用的业务分层设计
分层架构概述
我们采用了经典的三层架构设计模式,并结合Vue.js的特点进行了调整:
- 数据访问层(DAL):直接与外部API交互,负责数据的获取和发送
- 业务逻辑层(BLL):处理业务规则、数据转换和验证逻辑
- 表现层(PL):处理用户界面和交互,调用业务逻辑层
这种分层设计带来的好处是:
- 关注点分离:每层只关注自己的职责
- 可测试性提高:各层可独立测试
- 复用性增强:逻辑可以被多个组件共享
- 维护性改善:修改一层不会影响其他层
实际实现架构
在项目中,我们的实现方式如下:
src/
├── api/ # 数据访问层
│ ├── tts.ts # TTS API调用实现
│ └── local-tts.ts # 本地TTS服务接口
├── store/ # 业务逻辑层
│ ├── play.ts # TTS业务逻辑和状态管理(整合了local-tts-store)
│ └── store.ts # 全局状态管理
└── components/ # 表现层
└── main/
└── Main.vue # 用户界面组件
🧩 数据访问层实现
数据访问层的主要职责是与外部API交互,处理HTTP请求和响应。我们在tts.ts中实现了这一层:
// src/api/tts.ts
export async function callTTSApi(params: TTSParams): Promise<TTSResponse> {
try {
const { api, voiceData, speechKey, region, thirdPartyApi, tts88Key } = params;
// 根据不同的 API 类型构建不同的请求 URL 和认证头
let apiUrl = '';
let headers: Record<string, string> = {
'Content-Type': 'application/ssml+xml',
'X-Microsoft-OutputFormat': 'audio-16khz-128kbitrate-mono-mp3',
};
if (api === 4) {
apiUrl = thirdPartyApi;
// TTS88 API 使用 tts88Key
if (tts88Key) {
headers['Authorization'] = `Bearer ${tts88Key}`;
}
} else if (api === 5) {
// 导入本地TTS服务相关功能
try {
const { useFreeTTSstore } = await import('@/store/play');
const localTTSStore = useFreeTTSstore();
// 获取配置
const config = localTTSStore.fullConfig;
// 准备API请求的URL和参数
apiUrl = `${config.baseUrl}/api/v1/free-tts-stream`;
// 获取本地TTS所需的参数
const isSSML = voiceData.activeIndex === "1"; // 判断是否为SSML内容
// 内容处理逻辑...
// 发送请求
const response = await axios.post(
apiUrl,
requestBody,
{
headers,
responseType: 'arraybuffer',
timeout: 30000
}
);
// 返回二进制音频数据
return {
buffer: response.data
};
} catch (localError: any) {
return {
error: `FreeTTS服务错误: ${localError.message}`,
errorCode: "LOCAL_TTS_ERROR"
};
}
} else {
// Azure API
apiUrl = `https://${region}.tts.speech.microsoft.com/cognitiveservices/v1`;
headers['Ocp-Apim-Subscription-Key'] = speechKey;
// 发送请求和处理响应...
}
// 其他错误处理和返回...
} catch (error: any) {
// 全局错误处理
return {
audioContent: '',
error: error.message || '获取语音数据失败',
errorCode: "GLOBAL_ERROR"
};
}
}
数据访问层的特点:
- 只关注API交互:不包含业务判断逻辑
- 统一的错误返回格式:各种错误情况都返回标准化的错误对象
- 清晰的接口定义:输入和输出类型明确定义
🔄 业务逻辑层实现
业务逻辑层负责处理业务规则、参数验证和错误处理。我们在play.ts中实现了这一层,并整合了原来的local-tts-store功能:
// src/store/play.ts
import { createBatchTask, getBatchTaskStatus, deleteBatchTask, callTTSApi } from '@/api/tts';
import * as Pinia from 'pinia';
import {
LocalTTSConfig,
DEFAULT_LOCAL_TTS_CONFIG,
checkServerConnection,
getFreeLimitInfo,
} from '@/api/local-tts';
// 定义错误类型
export enum FreeTTSErrorType {
NONE = 0,
QUOTA_EXCEEDED = 402, // 额度用完
RATE_LIMITED = 429, // 请求频率限制
BANNED = 403, // 被封禁
SERVER_ERROR = 500, // 服务器错误
CONNECTION_ERROR = -1 // 连接错误
}
// 定义freeTTS服务状态和管理 - 整合了原来的local-tts-store
export const useFreeTTSstore = Pinia.defineStore('localTTSStore', {
state: () => {
return {
// 配置
config: {
enabled: store.get('localTTS.enabled') ?? true,
baseUrl: store.get('localTTS.baseUrl') ?? DEFAULT_LOCAL_TTS_CONFIG.baseUrl,
defaultVoice: store.get('localTTS.defaultVoice') ?? DEFAULT_LOCAL_TTS_CONFIG.defaultVoice,
defaultLanguage: store.get('localTTS.defaultLanguage') ?? DEFAULT_LOCAL_TTS_CONFIG.defaultLanguage,
},
// 服务器状态
serverStatus: {
connected: false,
lastChecked: null as number | null,
freeLimit: null as any | null,
error: null as string | null,
errorCode: FreeTTSErrorType.NONE
},
// 当前音频
audio: {
buffer: null as ArrayBuffer | null,
url: null as string | null,
isPlaying: false,
error: null as string | null,
errorCode: FreeTTSErrorType.NONE
}
};
},
getters: {
// 获取完整配置
fullConfig(): LocalTTSConfig {
return {
...DEFAULT_LOCAL_TTS_CONFIG,
...this.config
};
},
// 其他getter...
},
actions: {
// 保存配置
saveConfig() {
Object.entries(this.config).forEach(([key, value]) => {
store.set(`localTTS.${key}`, value);
});
},
// 检查服务器连接
async checkServerConnection() {
try {
const isConnected = await checkServerConnection(this.fullConfig);
this.serverStatus.connected = isConnected;
this.serverStatus.lastChecked = Date.now();
if (isConnected) {
this.serverStatus.error = null;
this.serverStatus.errorCode = FreeTTSErrorType.NONE;
// 如果连接成功,获取可用额度信息
await this.getFreeLimitInfo();
} else {
this.setErrorState('无法连接到服务器', FreeTTSErrorType.CONNECTION_ERROR);
}
return isConnected;
} catch (error: any) {
this.setErrorState(`连接错误: ${error.message}`, FreeTTSErrorType.CONNECTION_ERROR);
return false;
}
},
// 其他actions...
}
});
/**
* 获取TTS数据 - 业务逻辑层实现
* 负责调用底层API,处理重试逻辑和特定的业务转换
*/
async function getTTSData(params: TTSParams): Promise<TTSResponse> {
const { api, voiceData } = params;
const { activeIndex, retryCount = 3, retryInterval = 1 } = voiceData;
// 参数验证 - 全面的参数校验
if (!voiceData.ssmlContent && !voiceData.inputContent) {
console.error('缺少转换内容');
return {
error: '没有可转换的内容',
errorCode: 'EMPTY_CONTENT'
};
}
// API类型相关验证
if (api === 1 || api === 2 || api === 3) {
// 验证Azure API必要参数
if (!params.speechKey) {
console.error('缺少 Azure Speech API Key');
return {
error: '请先在设置中配置 Azure Speech API Key',
errorCode: 'MISSING_AZURE_KEY'
};
}
// 其他验证逻辑...
}
// 处理特定业务逻辑
try {
// 根据API类型进行不同的预处理
if (api === 3) { // Azure
console.log("使用Azure API");
// 可以在这里添加Azure特定的处理逻辑
}
else if (api === 4) { // TTS88
console.log("使用TTS88 API");
// 可以在这里添加TTS88特定的处理逻辑
}
else if (api === 5) { // 本地TTS
console.log("使用本地TTS服务");
// 获取当前选中的声音和配置
const { useTtsStore } = await import('@/store/store');
const ttsStore = useTtsStore();
const selectedVoice = ttsStore.formConfig.voiceSelect;
const speed = ttsStore.formConfig.speed;
const pitch = ttsStore.formConfig.pitch;
console.log("当前选择的声音:", selectedVoice, "语速:", speed, "音调:", pitch);
}
// 调用API层的函数,并包含重试逻辑
let retry = 0;
let lastError;
while (retry < retryCount) {
try {
console.log(`尝试调用TTS API (尝试 ${retry + 1}/${retryCount})`);
// 确保参数类型兼容
const apiParams = {
...params,
// 确保必要属性不为undefined
speechKey: params.speechKey || '',
region: params.region || '',
thirdPartyApi: params.thirdPartyApi || '',
tts88Key: params.tts88Key || ''
};
const result = await callTTSApi(apiParams);
// 检查是否有错误
if (result.error) {
// 错误增强处理
// ...
throw new Error(result.error);
}
// 返回结果
return result;
} catch (error: any) {
console.error(`TTS API调用失败 (尝试 ${retry + 1}/${retryCount}):`, error);
lastError = error;
console.log(`等待 ${retryInterval} 秒后重试...`);
await sleep(retryInterval * 1000);
retry++;
}
}
// 达到最大重试次数
return {
error: lastError?.message || "达到最大重试次数,请求失败",
errorCode: "MAX_RETRY_EXCEEDED"
};
} catch (error: any) {
console.error("TTS转换失败:", error);
return {
error: error.message || "TTS转换失败",
errorCode: "GENERAL_ERROR"
};
}
}
// 导出供组件使用的函数
export { getTTSData };
业务逻辑层的特点:
- 职责清晰:负责业务规则和参数验证,而不是API调用细节
- 错误增强:提供更友好、更具体的错误信息
- 重试机制:实现了自动重试逻辑
- 状态管理:整合了之前分散的状态管理功能
🔀 整合local-tts-store到play.ts
在重构过程中,我们将原来独立的local-tts-store.ts文件整合到了play.ts中,这样做的好处是:
- 减少文件数量:相关功能集中在一个文件中
- 避免循环依赖:解决了之前可能存在的循环引用问题
- 逻辑集中:所有与TTS播放相关的逻辑都在同一个地方
整合过程的主要工作包括:
- 将
local-tts-store.ts中的类型定义、状态和方法移动到play.ts - 重新组织和优化代码结构,确保逻辑流程清晰
- 更新所有引用
local-tts-store.ts的地方,指向新的位置 - 优化错误处理和状态管理逻辑
🌟 表现层实现
表现层(即组件层)通过调用业务逻辑层的函数来完成功能,而不直接与API交互:
// 在组件中使用getTTSData函数
import { getTTSData } from '@/store/play';
// 在组件方法中
async function startBtn() {
// 准备参数
const params = {
api: formConfig.value.api,
voiceData: {
activeIndex: tabsValue.value,
ssmlContent: ssmlContent.value,
inputContent: inputs.value.content
},
speechKey: config.value.key,
region: config.value.region,
thirdPartyApi: config.value.thirdPartyApi,
tts88Key: config.value.tts88Key
};
// 调用业务逻辑层函数
const result = await getTTSData(params);
// 处理结果
if (result.error) {
// 显示错误信息
ElMessage.error(result.error);
} else {
// 处理成功结果
handleAudioBlob(result.buffer);
}
}
表现层的特点:
- 专注于用户交互:只关注UI渲染和事件处理
- 调用业务逻辑层:不直接进行API调用
- 处理显示逻辑:负责向用户展示结果或错误信息
📊 分层架构的优势
通过实施API调用业务分层,我们获得了以下优势:
- 代码组织更清晰:每一层都有明确的职责
- 复用性提高:业务逻辑可以被多个组件重用
- 测试变得简单:可以独立测试每一层
- 错误处理更一致:统一的错误处理策略
- 易于扩展:添加新功能时可以只修改相关层
- 维护成本降低:修改一个层的实现不会影响其他层
🔍 代码质量提升
错误处理统一化
重构后,我们实现了更统一的错误处理机制:
// 错误类型定义
export enum FreeTTSErrorType {
NONE = 0,
QUOTA_EXCEEDED = 402, // 额度用完
RATE_LIMITED = 429, // 请求频率限制
BANNED = 403, // 被封禁
SERVER_ERROR = 500, // 服务器错误
CONNECTION_ERROR = -1 // 连接错误
}
// 错误处理函数
function getErrorCodeFromResponse(error) {
if (!error || !error.response) {
return FreeTTSErrorType.CONNECTION_ERROR;
}
const status = error.response.status;
if (status === 402 || status === 403) {
return FreeTTSErrorType.QUOTA_EXCEEDED;
} else if (status === 429) {
return FreeTTSErrorType.RATE_LIMITED;
} else if (status >= 500) {
return FreeTTSErrorType.SERVER_ERROR;
}
return FreeTTSErrorType.SERVER_ERROR;
}
代码复用性提高
通过将业务逻辑集中在getTTSData函数中,我们减少了代码重复:
// 任何需要TTS功能的组件都可以直接调用此函数
import { getTTSData } from '@/store/play';
// 在不同组件中使用相同的逻辑
const result = await getTTSData(params);
🚀 性能优化
动态导入优化
为了避免不必要的依赖加载,我们使用了动态导入:
// 只在需要时动态导入
if (api === 5) { // 本地TTS
const { useTtsStore } = await import('@/store/store');
const ttsStore = useTtsStore();
// 使用ttsStore...
}
缓存与重用
我们增加了结果缓存机制,避免重复的API调用:
// 简单的结果缓存实现
const resultCache = new Map();
function getCacheKey(params) {
// 生成缓存键...
}
async function getTTSDataWithCache(params) {
const cacheKey = getCacheKey(params);
// 检查缓存
if (resultCache.has(cacheKey)) {
return resultCache.get(cacheKey);
}
// 调用API获取结果
const result = await getTTSData(params);
// 缓存结果
if (!result.error) {
resultCache.set(cacheKey, result);
}
return result;
}
🔮 未来展望
基于当前的分层架构,我们计划在未来实施以下优化:
- 更细粒度的分层:将业务逻辑层进一步拆分为多个专门的服务
- 更完善的错误恢复机制:自动尝试不同的API和参数组合
- 服务质量监控:添加性能和可用性监控
- 缓存优化:实现更智能的缓存策略
- 接口标准化:统一所有API的请求和响应格式
🎯 总结
通过实施API调用业务分层和整合local-tts-store到play.ts,我们实现了代码结构的显著优化:
- 关注点分离:清晰的分层架构使每部分代码都有明确的职责
- 代码复用性提高:业务逻辑可以被多个组件共享和重用
- 错误处理更一致:统一的错误处理机制提供了更好的用户体验
- 代码可维护性增强:修改一层的实现不会影响其他层
- 状态管理统一:整合相关功能减少了状态管理的复杂度
这些优化不仅提升了当前项目的代码质量,也为未来的功能扩展和维护提供了坚实的基础。我们推荐在类似的前端项目中采用这种分层架构设计,尤其是当项目规模增长到一定程度时。
🔗 相关链接
注意:本文介绍的架构设计思路仅供学习和参考,具体实践时应根据项目特点进行调整。如有问题或建议,欢迎在评论区讨论!
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)