一、背景
1.1 Android端网络层
Android应用使用OkHttp + Retrofit,服务端API需要MD5签名认证。
1.2 迁移挑战
- HarmonyOS没有直接的OkHttp对应物
- 需要使用@kit.NetworkKit的http模块
- MD5签名需要使用@kit.CryptoArchitectureKit
- Cookie管理机制不同
二、API签名机制
2.1 签名规则
签名字符串构成:
signSource = APP_KEY + APP_SECRET + queryString + request_time sign = MD5(signSource)最终URL:
finalUrl = baseUrl + "?" + queryString + "&sign=" + sign2.2 Android端实现
// Android端MD5实现publicstaticStringmd5(Stringtext){try{MessageDigestmd=MessageDigest.getInstance("MD5");byte[]bytes=md.digest(text.getBytes());StringBuildersb=newStringBuilder();for(byteb:bytes){sb.append(String.format("%02x",b));}returnsb.toString();}catch(Exceptione){return"";}}// 构建签名URLStringqueryString="request_time="+requestTime+"&app_key="+APP_KEY+"&client=android";StringsignSource=APP_KEY+APP_SECRET+queryString+requestTime;Stringsign=md5(signSource);StringfinalUrl=baseUrl+"?"+queryString+"&sign="+sign;三、HarmonyOS网络层实现
3.1 MD5签名实现
import{cryptoFramework}from'@kit.CryptoArchitectureKit'import{util}from'@kit.ArkTS'exportclassUrlDef{privatestaticAPP_KEY:string='1c218829f927e2cae8ef6bdc0ca3a090'privatestaticAPP_SECRET:string='70b37149b3276009a0b0eb643a316e00'// MD5哈希privatestaticasyncmd5(text:string):Promise<string>{// 创建MD5实例constmd=cryptoFramework.createMd('MD5')// 文本转字节constdata:Uint8Array=newutil.TextEncoder().encodeInto(text)// 更新数据awaitmd.update({data:data})// 计算哈希constresult:cryptoFramework.DataBlob=awaitmd.digest()// 转换为十六进制字符串consthexString=Array.from(result.data).map((byte:number)=>{returnbyte.toString(16).padStart(2,'0')}).join('')returnhexString}}关键点:
- 使用
cryptoFramework.createMd('MD5')创建MD5实例 TextEncoder().encodeInto()将字符串转为Uint8Arraymd.update()更新数据md.digest()计算哈希- 转换为小写十六进制字符串(
padStart(2, '0'))
3.2 URL构建
privatestaticasyncbuildAuthUrl(baseUrl:string,extraParams?:Record<string,string>):Promise<string>{constrequestTime=Math.floor(Date.now()/1000).toString()// 构建查询字符串(按照Android端的顺序)constqueryParts:string[]=[]queryParts.push(`request_time=${requestTime}`)queryParts.push(`app_key=${UrlDef.APP_KEY}`)queryParts.push(`client=android`)// 添加额外参数if(extraParams){constkeys=Object.keys(extraParams)for(leti=0;i<keys.length;i++){constkey=keys[i]queryParts.push(`${key}=${encodeURIComponent(extraParams[key])}`)}}constqueryString=queryParts.join('&')// 构建签名源字符串constsignSource=`${UrlDef.APP_KEY}${UrlDef.APP_SECRET}${queryString}${requestTime}`// 计算签名constsign=awaitUrlDef.md5(signSource)// 最终URLconstfinalUrl=`${baseUrl}?${queryString}&sign=${sign}`returnfinalUrl}注意事项:
- 参数顺序必须与Android端一致
encodeURIComponent()编码特殊字符- 签名源字符串格式严格遵守:
APP_KEY + APP_SECRET + queryString + request_time
3.3 API定义
// 登录接口staticasynclogin():Promise<string>{returnawaitUrlDef.buildAuthUrl(UrlDef.getFinalHost()+'login_do')}// 获取最佳服务器staticasyncopenHomeBest(appCateId:string):Promise<string>{constparams:Record<string,string>={}asRecord<string,string>params['app_cate_id']=appCateId params['request_time']=Math.floor(Date.now()/1000).toString()params['session_id']='harmonyos_session'returnawaitUrlDef.buildAuthUrl(UrlDef.getFinalHost()+'home/best',params)}// 启动应用staticasynchomeStart(streamId:string):Promise<string>{constparams:Record<string,string>={}asRecord<string,string>params['streamer_id']=streamIdreturnawaitUrlDef.buildAuthUrl(UrlDef.getFinalHost()+'home/start',params)}四、HTTP客户端实现
4.1 HttpClient封装
import{http}from'@kit.NetworkKit'exportclassHttpClient{privatestaticinstance:HttpClientprivatecookie:string=''staticgetInstance():HttpClient{if(!HttpClient.instance){HttpClient.instance=newHttpClient()}returnHttpClient.instance}setCookie(cookie:string):void{this.cookie=cookieconsole.log('[HttpClient] Cookie set:',cookie)}getCookie():string{returnthis.cookie}asyncget(url:string):Promise<string>{returnnewPromise((resolve,reject)=>{constrequest=http.createHttp()constoptions:http.HttpRequestOptions={method:http.RequestMethod.GET,header:{'Content-Type':'application/json','Cookie':this.cookie},connectTimeout:60000,readTimeout:60000}console.log('[HttpClient] GET:',url)console.log('[HttpClient] Cookie:',this.cookie)request.request(url,options,(err,data)=>{if(!err){// 保存Cookieif(data.header&&data.header['set-cookie']){constsetCookie=data.header['set-cookie']asstringthis.cookie=setCookieconsole.log('[HttpClient] Received Cookie:',setCookie)}constresult=data.resultasstringresolve(result)}else{console.error('[HttpClient] Error:',err.message)reject(newError(err.message))}request.destroy()})})}asyncpost(url:string,body:object):Promise<string>{returnnewPromise((resolve,reject)=>{constrequest=http.createHttp()constoptions:http.HttpRequestOptions={method:http.RequestMethod.POST,header:{'Content-Type':'application/json','Cookie':this.cookie},extraData:JSON.stringify(body),connectTimeout:60000,readTimeout:60000}console.log('[HttpClient] POST:',url)console.log('[HttpClient] Body:',JSON.stringify(body))request.request(url,options,(err,data)=>{if(!err){// 保存Cookieif(data.header&&data.header['set-cookie']){constsetCookie=data.header['set-cookie']asstringthis.cookie=setCookie}constresult=data.resultasstringresolve(result)}else{reject(newError(err.message))}request.destroy()})})}}关键点:
- 单例模式管理HttpClient
- 自动保存和发送Cookie
- 统一的错误处理
- 超时配置
4.2 使用示例
import{HttpClient}from'../network/HttpClient'import{UrlDef}from'../network/UrlDef'// 获取HttpClient实例consthttpClient=HttpClient.getInstance()// 登录constloginUrl=awaitUrlDef.login()constloginBody={username:'user',password:'pass',verify_code:'code'}constresponse=awaithttpClient.post(loginUrl,loginBody)// 获取应用列表(自动带Cookie)constappListUrl=awaitUrlDef.homeAppList()constappList=awaithttpClient.get(appListUrl)五、常见问题
5.1 签名验证失败
错误:服务端返回"签名错误"
原因:
- 参数顺序不一致
- MD5计算错误
- URL编码问题
- 时间戳格式错误
调试方法:
console.log('[UrlDef] baseUrl:',baseUrl)console.log('[UrlDef] queryString:',queryString)console.log('[UrlDef] signSource:',signSource)console.log('[UrlDef] sign:',sign)console.log('[UrlDef] finalUrl:',finalUrl)对比Android端日志,确保:
queryString完全一致signSource完全一致sign完全一致
5.2 API路径错误
错误:“资源不存在”(code 50016)
原因:HarmonyOS使用了错误的API路径
示例:
// ❌ 错误 - 多了'open/'前缀awaitUrlDef.buildAuthUrl(UrlDef.getFinalHost()+'open/home/best',params)// ✅ 正确awaitUrlDef.buildAuthUrl(UrlDef.getFinalHost()+'home/best',params)解决方案:
参考Android端的API路径定义,确保完全一致。
5.3 Cookie丢失
现象:登录后的请求返回"未登录"
原因:Cookie未正确保存或发送
解决:
// 检查Cookie是否保存console.log('[HttpClient] Current cookie:',this.cookie)// 确保每次请求都带Cookieheader:{'Cookie':this.cookie}// 检查服务端返回的set-cookieif(data.header&&data.header['set-cookie']){constsetCookie=data.header['set-cookie']asstringthis.cookie=setCookie}六、与Android对比
| 特性 | Android | HarmonyOS |
|---|---|---|
| HTTP库 | OkHttp | @kit.NetworkKit |
| MD5库 | MessageDigest | CryptoArchitectureKit |
| 文本编码 | getBytes() | TextEncoder |
| 字节转十六进制 | String.format(“%02x”) | toString(16).padStart(2, ‘0’) |
| Cookie管理 | 自动 | 手动保存和发送 |
| 异步模式 | 回调/RxJava | Promise/async-await |
七、最佳实践
7.1 统一错误处理
interfaceApiResponse<T>{code:numbermessage?:stringresult?:T}asyncfunctioncallApi<T>(url:string):Promise<T>{constresponse=awaithttpClient.get(url)constresult=JSON.parse(response)asApiResponse<T>if(result.code!==0){thrownewError(result.message||'请求失败')}if(!result.result){thrownewError('返回数据为空')}returnresult.result}7.2 请求超时重试
asyncfunctionretryRequest(url:string,maxRetries:number=3):Promise<string>{for(leti=0;i<maxRetries;i++){try{returnawaithttpClient.get(url)}catch(e){if(i===maxRetries-1){throwe}awaitnewPromise(resolve=>setTimeout(resolve,1000*(i+1)))}}thrownewError('Max retries exceeded')}7.3 日志脱敏
functionlogUrl(url:string){// 隐藏敏感参数constsanitized=url.replace(/sign=[^&]+/,'sign=***').replace(/token=[^&]+/,'token=***')console.log('[API]',sanitized)}八、总结
HarmonyOS网络层迁移的关键点:
- MD5签名:使用CryptoArchitectureKit,确保与Android端结果一致
- 参数顺序:严格遵守Android端的参数顺序
- Cookie管理:手动保存和发送,不像OkHttp自动处理
- 异步编程:使用Promise和async/await替代回调
- API路径:确保与Android端完全一致
完成网络层迁移后,应用可以正常与服务端通信,进行登录、获取应用列表、启动云桌面等操作。