HarmonyOS《柚兔学伴》项目实战15-网络请求封装
2026/7/22 1:34:07 网站建设 项目流程

第15篇:网络请求封装

引言

柚兔学伴需要与多个后端服务交互——Coze 智能体 API、火山引擎 TTS 语音合成、百度翻译、汉字字典等。每个服务的域名、请求格式、响应结构各不相同。项目通过HttpManagerHttpRequest两层架构,实现了统一的网络请求封装,让上层 Model 层只需关注业务参数,无需关心底层 HTTP 细节。

HttpManager 单例

HttpManager作为网络请求的统一入口,采用单例模式确保全局唯一:

// network/src/main/ets/HttpManager.etsexportclassHttpManager{privatestaticmInstance:HttpManager;privateBASE_URL:string=UrlConstants.SERVERprivateconstructor(){}staticgetInstance():HttpManager{if(!HttpManager.mInstance){HttpManager.mInstance=newHttpManager();}returnHttpManager.mInstance;}setBaseUrl(url:string){this.BASE_URL=url}}
  • 私有构造函数:防止外部new实例化
  • 懒加载单例:首次调用getInstance()时创建
  • 可配置 BASE_URL:通过setBaseUrl支持动态切换服务器地址
  • 默认域名UrlConstants.SERVERhttps://api.coze.cn/

RequestOptions 接口

所有请求方法共享统一的参数接口:

exportinterfaceRequestOptions{domain?:string;// 自定义域名(可选,默认 BASE_URL)url:string;// 请求路径queryParams?:Record<string,string>;// URL 查询参数postBody?:object;// POST 请求体header?:Record<string,string>;// 自定义请求头multiFormDataList?:Array<http.MultiFormData>;// 文件上传的表单数据}

这个接口的设计哲学是"一个接口覆盖所有场景"——通过可选字段适配不同类型的请求,而非为每种请求定义独立参数类型。

通用响应模型

所有 Coze API 的响应都遵循统一结构:

// network/src/main/ets/common/CommonResponseModel.etsexportinterfaceCommonResponseModel<T>{code:numberdesc:stringmsg:stringdata:Tresult:Ttransparent:number;success:boolean;}

泛型T代表具体的业务数据类型,code字段用于判断请求是否成功。Coze API 的成功码为0

staticreadonlyCODE_SUCCESS:number=0

requestPost:键值对参数 POST

asyncrequestPost<T>(option:RequestOptions):Promise<T>{if(option.queryParams==null){option.queryParams={}}letrequest=newPostRequest<T>(option.queryParams!!,option.url);request.domain=option.domain?option.domain:this.BASE_URLreturnnewPromise<T>((resolve,reject)=>{request.execute().then((data:CommonResponseModel<T>)=>{if(data.code===UrlConstants.CODE_SUCCESS){resolve(data.data);}else{reject(data.desc);}}).catch((err:Error)=>{reject('请求失败');ToastUtil.showToast('请求失败')});})}

使用场景:当请求参数为简单的键值对时(如queryParams),使用PostRequest将参数作为postBody发送。成功时resolve(data.data),只返回业务数据;失败时reject(data.desc)返回错误描述。

requestPostBody:对象参数 POST

requestPostBody<T>(option:RequestOptions):Promise<T>{letrequest=newPostBodyRequest<T>(option.postBody!!,option.url);request.domain=this.BASE_URLreturnnewPromise<T>((resolve,reject)=>{request.execute().then((data:CommonResponseModel<T>)=>{if(data.code===UrlConstants.CODE_SUCCESS){resolve(data.data);}else{reject(data.desc);}}).catch((err:Error)=>{reject('请求失败');ToastUtil.showToast(err.message)});})}

requestPost的区别:requestPostBody传递的是完整的object对象作为请求体,适用于结构化参数(如创建会话的CreatParam)。这是项目中最常用的方法,ChatModel 中的conversationCreatechat均使用此方法。

requestTtsPostBody:TTS 专用请求

requestTtsPostBody<T>(option:RequestOptions):Promise<T>{letrequest=newPostBodyRequest<T>(option.postBody!!,option.url,option.header);request.domain=UrlConstants.TTS_DOMAIN_URLreturnnewPromise<T>((resolve,reject)=>{request.execute().then((data:CommonResponseModel<T>)=>{if(data.code===UrlConstants.CODE_TTS_SUCCESS||data.code===200){resolve(data.data);}else{reject(data.desc);}}).catch((err:Error)=>{reject('请求失败');});})}

TTS 请求的特殊之处:

  • 独立域名UrlConstants.TTS_DOMAIN_URLhttps://openspeech.bytedance.com/api/),不走 Coze 服务器
  • 自定义请求头:TTS API 要求特定的Authorization格式
  • 不同的成功码CODE_TTS_SUCCESS = 3000,同时兼容200

requestSpeechPostBody:语音识别请求

requestSpeechPostBody<T>(option:RequestOptions):Promise<T>{letrequest=newPostTRequest<T>(option.postBody!!,option.url,option.header);request.domain=option.domain?option.domain:UrlConstants.TTS_DOMAIN_URLreturnnewPromise<T>((resolve,reject)=>{request.execute().then((data:T)=>{resolve(data);}).catch((err:Error)=>{reject('请求失败');});})}

语音识别请求的独特之处在于直接返回原始数据T,而非包裹在CommonResponseModel中。这是因为语音识别 API 的响应结构不同于 Coze,不走统一的code/data包装。它使用PostTRequest而非PostBodyRequest,泛型绑定类型不同。

requestGet:GET 请求

requestGet<T>(option:RequestOptions):Promise<T>{letrequest=newGetRequest<T>(option.url,option.queryParams!!);request.domain=this.BASE_URLreturnnewPromise<T>((resolve,reject)=>{request.execute().then((data:CommonResponseModel<T>)=>{if(data.code===UrlConstants.CODE_SUCCESS){resolve(data.data);}else{reject(data.msg);}}).catch((err:Error)=>{reject('请求失败');ToastUtil.showToast(err.message)});})}

GET 请求将参数放入queryParams,在 URL 中拼接。用于retrieve(查看对话详情)和messageList(查看消息列表)等查询接口。注意失败时使用data.msg而非data.desc,因为 GET 接口的错误信息字段名不同。

uploadFiles:文件上传

uploadFiles<T>(option:RequestOptions):Promise<T>{letrequest=newPostMultipartRequest<T>(option.url,option.multiFormDataList!!);request.domain=this.BASE_URLreturnnewPromise<T>((resolve,reject)=>{request.execute().then((data:CommonResponseModel<T>)=>{if(data.code===UrlConstants.CODE_SUCCESS){resolve(data.data);}else{reject(data.desc);}}).catch((err:Error)=>{reject('请求失败');ToastUtil.showToast(err.message)});})}

文件上传使用PostMultipartRequest,发送multipart/form-data格式请求。在 ChatModel 中用于上传录音文件:

uploadFile(cacheFilePath:string,completeCallback:CompleteCallback){letcloudPath='voice/'+cacheFilePath.split('/').pop()asstring;bucket.uploadFile(getContext(this),{localPath:cacheFilePath,cloudPath:cloudPath,}).then(task=>{this.addEventListener(task,this.onUploadCompleted(cloudPath,cacheFilePath,completeCallback));task.start();})}

注意:文件上传实际使用的是 AGC 云存储 SDK(cloudStorage),而非HttpManager.uploadFilesuploadFiles方法为其他文件上传场景预留。

HttpRequest 请求类体系

HttpRequest.ets定义了五种请求类,均继承自HttpRequest基类:

请求类方法参数类型Content-Type
GetRequestGETqueryParamsapplication/json
PostRequestPOSTqueryParams(作为 body)application/json
PostBodyRequestPOSTobjectapplication/json
PostTRequestPOSTobjectapplication/json
PostMultipartRequestPOSTMultiFormData[]multipart/form-data

所有 Coze 相关请求类自动注入认证头:

publicheader:Record<string,string>={'Content-Type':'application/json','Authorization':`Bearer${UrlConstants.COZE_SECRET_TOKEN}`,}

COZE_SECRET_TOKEN是 Coze API 的 Service Account Token,以sat_开头,用于服务端对服务端的认证。

UrlConstants 常量管理

// network/src/main/ets/common/UrlConstants.etsexportclassUrlConstants{staticreadonlySERVER:string='https://api.coze.cn/'staticreadonlyCONVERSATION_CREATE_URL='v1/conversation/create'staticreadonlyGET_ONLINE_INFO_URL='v1/bot/get_online_info'staticreadonlyCHAT_URL='v3/chat'staticreadonlyRETRIEVE_URL='v3/chat/retrieve'staticreadonlyMSG_LIST_URL='v3/chat/message/list'staticreadonlyTTS_DOMAIN_URL='https://openspeech.bytedance.com/api/'staticreadonlyTTS_URL='v1/tts'staticreadonlyVOICE_RECOGNIZE_URL='v3/auc/bigmodel/submit'staticreadonlyVOICE_QUERY_URL='v3/auc/bigmodel/query'staticreadonlyCODE_SUCCESS:number=0staticreadonlyCODE_TTS_SUCCESS:number=3000}

所有 API 路径和状态码集中管理,避免硬编码散落在各处。URL 只存储相对路径,域名通过domain字段在运行时拼接。

错误处理策略

HttpManager采用了分层的错误处理:

  1. 业务错误code !== CODE_SUCCESS):reject(data.desc)reject(data.msg),将服务端错误描述传给调用方
  2. 网络异常(catch 分支):reject('请求失败'),并调用ToastUtil.showToast直接提示用户
  3. 调用方处理:Model 层通过.catch()捕获 reject 值,设置LoadingStatus.FAILED
.catch((err:BusinessError)=>{this.loadingStatus=LoadingStatus.FAILEDreturnthis.loadingStatus});

这种三层设计确保了:网络层统一 Toast 提示、业务层获取具体错误信息、Model 层更新状态供 UI 响应。

方法选择指南

场景推荐方法示例
简单键值对 POSTrequestPost
对象参数 POSTrequestPostBodyconversationCreate、chat
TTS 语音合成requestTtsPostBodyttsMaker
语音识别requestSpeechPostBodyvoiceRecognition
查询接口requestGetretrieve、messageList
文件上传uploadFiles录音文件上传

小结

本篇详细介绍了柚兔学伴的网络请求封装架构:

  • HttpManager 单例:统一入口,私有构造,懒加载,可配置域名
  • RequestOptions 统一参数:一个接口覆盖所有请求类型,通过可选字段适配
  • 六种请求方法:针对不同场景(键值对、对象体、TTS、语音、GET、上传)提供专门方法
  • CommonResponseModel 泛型:统一响应解析,code判断成功,data返回业务数据
  • 自动认证注入:所有 Coze 请求自动携带 Service Account Token
  • 三层错误处理:Toast 提示 + reject 传递 + Model 状态更新

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询