From 7d5a604665f89626b962628c0afa076db0cb1fdf Mon Sep 17 00:00:00 2001 From: xiaoxiao Date: Thu, 31 Jul 2025 11:44:46 +0800 Subject: [PATCH] request --- commons/basic/src/main/ets/utils/request.ets | 216 ++++++++++++++----- 1 file changed, 161 insertions(+), 55 deletions(-) diff --git a/commons/basic/src/main/ets/utils/request.ets b/commons/basic/src/main/ets/utils/request.ets index 946248a..aa5ade2 100644 --- a/commons/basic/src/main/ets/utils/request.ets +++ b/commons/basic/src/main/ets/utils/request.ets @@ -3,12 +3,15 @@ import { authStore } from './auth'; import { promptAction, router } from '@kit.ArkUI'; import { BusinessError } from '@ohos.base'; import { logger } from './logger'; -import { HashMap } from '@kit.ArkTS'; +import { buffer, HashMap, util } from '@kit.ArkTS'; import { CryptoJS } from '@ohos/crypto-js' import { Base64Util } from './Base64Util'; import { ChangeUtil } from './ChangeUtil' import { bundleManager} from '@kit.AbilityKit'; import { deviceInfo } from '@kit.BasicServicesKit'; +import { cryptoFramework } from '@kit.CryptoArchitectureKit'; +import { rcp } from '@kit.RemoteCommunicationKit'; + interface HdRequestOptions { baseURL?: string } @@ -28,12 +31,14 @@ export interface TimestampBean { class HdHttp { baseURL: string - osFullName: string = deviceInfo.marketName+'/'+ deviceInfo.osFullName; + osFullName: string = deviceInfo.marketName + '/' + deviceInfo.osFullName; + constructor(options: HdRequestOptions) { this.baseURL = options.baseURL || '' } - private request(path: string, method: http.RequestMethod = http.RequestMethod.POST, extraDatas :HashMap) { + private request(path: string, method: http.RequestMethod = http.RequestMethod.POST, + extraDatas: HashMap) { const httpInstance = http.createHttp() const options: http.HttpRequestOptions = { @@ -45,56 +50,71 @@ class HdHttp { // 开发者根据自身业务需要添加header字段 header: { 'Content-Type': 'application/json', - 'sign':this.getSign(extraDatas), - 'User-Agent':this.osFullName + 'sign': this.getSign(extraDatas), + 'User-Agent': this.osFullName }, - extraData:ChangeUtil.map2Json(extraDatas) + extraData: ChangeUtil.map2Json(extraDatas) } let fullUrl = this.baseURL + path return httpInstance.request(fullUrl, options).then((res) => { - logger.info('Response param'+JSON.stringify(extraDatas)) - logger.info('Response fullUrl:' +fullUrl+ res.result); + logger.info('Response param' + JSON.stringify(extraDatas)) + logger.info('Response fullUrl:' + fullUrl + res.result); const result = res.result as HdResponse return result }).catch((err: BusinessError) => { - logger.info(fullUrl+`Response succeeded: ${err}`); + logger.info(fullUrl + `Response succeeded: ${err}`); promptAction.showToast({ message: err.message || '网络错误' }) return Promise.reject(err) }).finally(() => { httpInstance.destroy() }) } - private requestObject(path: string, method: http.RequestMethod = http.RequestMethod.POST, extraDatas :HashMap) { + + private requestObject(path: string, method: http.RequestMethod = http.RequestMethod.POST, + extraDatas: HashMap) { const httpInstance = http.createHttp() + // 1. 拼接参数 + let signStr = this.concatParams(extraDatas); + console.info('拼接signStr=',signStr) + // 2. 字符清理 + signStr = cleanString(signStr) + console.info('异常字符转化后,signStr',signStr) + const timestamp = extraDatas.get('timestamp') + const timeStampMd5 = CryptoJS.MD5(timestamp.toString()).toString() + console.info('时间戳:timeStampStr=',timeStampMd5) + signStr += timeStampMd5 + console.info('时间戳+signStr=',signStr) + signStr = String(CryptoJS.MD5(signStr).toString()) + console.info('时间戳+signStr的MD5后=',signStr) + signStr = this.safeUrlBase64Encode(signStr) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, '') + console.info('时间戳+signStr的MD5后的base64=',signStr) const options: http.HttpRequestOptions = { method: http.RequestMethod.POST, - // 可选,默认为60s connectTimeout: 60000, - // 可选,默认为60s readTimeout: 60000, - // 开发者根据自身业务需要添加header字段 header: { 'Content-Type': 'application/json', - 'sign':this.getSignO(extraDatas), - 'User-Agent':this.osFullName + 'sign': signStr, + 'User-Agent': this.osFullName }, - extraData:ChangeUtil.map2JsonO(extraDatas) + extraData: ChangeUtil.map2JsonO(extraDatas) } - let fullUrl = this.baseURL + path - return httpInstance.request(fullUrl, options).then((res) => { - logger.info('Response param'+JSON.stringify(extraDatas)) - logger.info('Response fullUrl:' +fullUrl+ res.result); + logger.info('Response param' + JSON.stringify(extraDatas)) + logger.info('Response fullUrl:' + fullUrl + res.result); const result = res.result as HdResponse return result }).catch((err: BusinessError) => { - logger.info(fullUrl+`Response succeeded: ${err}`); + logger.info(fullUrl + `Response succeeded: ${err}`); promptAction.showToast({ message: err.message || '网络错误' }) return Promise.reject(err) }).finally(() => { @@ -129,11 +149,11 @@ class HdHttp { } return httpInstance.request(fullUrl, options).then((res) => { - logger.info('Response fullUrl:' +fullUrl+ res.result); + logger.info('Response fullUrl:' + fullUrl + res.result); const result = res.result as HdResponse return result }).catch((err: BusinessError) => { - logger.info(fullUrl+`Response succeeded: ${err}`); + logger.info(fullUrl + `Response succeeded: ${err}`); promptAction.showToast({ message: err.message || '网络错误' }) return Promise.reject(err) }).finally(() => { @@ -141,9 +161,6 @@ class HdHttp { }) } - - - get(url: string, data?: Object): Promise> { return this.requestafter(url, http.RequestMethod.GET, data) } @@ -159,12 +176,15 @@ class HdHttp { delete(url: string, data?: Object): Promise> { return this.requestafter(url, http.RequestMethod.DELETE, data) } + posts(url: string, data: HashMap): Promise> { return this.request(url, http.RequestMethod.POST, data) } + postO(url: string, data: HashMap): Promise> { return this.requestObject(url, http.RequestMethod.POST, data) } + httpReq(url: string, datas: HashMap): Promise> { // 创建httpRequest对象。 let httpRequest = http.createHttp(); @@ -188,18 +208,18 @@ class HdHttp { return promise.then(async (data) => { if (data.responseCode === http.ResponseCode.OK) { logger.info('Response httpReq:' + data.result); - let json:TimestampBean = JSON.parse(data.result.toString()) as TimestampBean; + let json: TimestampBean = JSON.parse(data.result.toString()) as TimestampBean; let tp = json.timestamp; - datas.set("user_uuid", authStore.getUser().uuid?authStore.getUser().uuid:''); + datas.set("user_uuid", authStore.getUser().uuid ? authStore.getUser().uuid : ''); datas.set("client_type", 'H'); - datas.set("version", await this.getVersion() ); - datas.set('timestamp',tp+''); + datas.set("version", await this.getVersion()); + datas.set('timestamp', tp + ''); return this.posts(url, datas); } else { return this.posts(url, datas); } } - ).catch((err:BusinessError) => { + ).catch((err: BusinessError) => { logger.info('Response httpReq error:' + JSON.stringify(err)); return Promise.reject(err); }).finally(() => { @@ -230,24 +250,25 @@ class HdHttp { return promise.then(async (data) => { if (data.responseCode === http.ResponseCode.OK) { logger.info('Response httpReq:' + data.result); - let json:TimestampBean = JSON.parse(data.result.toString()) as TimestampBean; + let json: TimestampBean = JSON.parse(data.result.toString()) as TimestampBean; let tp = json.timestamp; - datas.set("user_uuid", authStore.getUser().uuid?authStore.getUser().uuid:''); + datas.set("user_uuid", authStore.getUser().uuid ? authStore.getUser().uuid : ''); datas.set("client_type", 'H'); - datas.set("version", await this.getVersion() ); - datas.set('timestamp',tp+''); - return this.postO(url, datas); + datas.set("version", await this.getVersion()); + datas.set('timestamp', tp + ''); + return this.postO(url, datas ); } else { return this.postO(url, datas); } } - ).catch((err:BusinessError) => { + ).catch((err: BusinessError) => { logger.info('Response httpReq error:' + JSON.stringify(err)); return Promise.reject(err); }).finally(() => { httpRequest.destroy() }) } + httpReqSimply(url: string) { // 创建httpRequest对象。 let httpRequest = http.createHttp(); @@ -272,7 +293,7 @@ class HdHttp { const result = data.result as HdResponse return result } - ).catch((err:BusinessError) => { + ).catch((err: BusinessError) => { logger.info('Response httpReq error:' + JSON.stringify(err)); return Promise.reject(err); }).finally(() => { @@ -280,45 +301,45 @@ class HdHttp { }) } - getSignO(extraDatas1:HashMap): string { - let secret= extraDatas1.get("timestamp")+'' - if(secret!=null) { + getSignO(extraDatas1: HashMap): string { + let secret = extraDatas1.get("timestamp") + '' + if (secret != null) { let keyValueStr: string = ""; let entriesArray: Array = Array.from(extraDatas1.keys()); entriesArray.sort(); - let sortedMap:HashMap = new HashMap(); + let sortedMap: HashMap = new HashMap(); entriesArray.forEach((value: string, index: number) => { - sortedMap.set(value,extraDatas1.get(value)); + sortedMap.set(value, extraDatas1.get(value)); // keyValueStr +=value+extraDatas1.get(value) - keyValueStr +=value+JSON.stringify(extraDatas1.get(value)) + keyValueStr += value + JSON.stringify(extraDatas1.get(value)) }); keyValueStr = keyValueStr + CryptoJS.MD5(secret).toString(); - keyValueStr = keyValueStr.replaceAll(" ", "").replaceAll("\"", "").replaceAll(":","="); + keyValueStr = keyValueStr.replaceAll(" ", "").replaceAll("\"", "").replaceAll(":", "="); let Md5keyValueStr: string = CryptoJS.MD5(keyValueStr).toString(); - let base64Str:string=Base64Util.encodeToStrSync(Md5keyValueStr); + let base64Str: string = Base64Util.encodeToStrSync(Md5keyValueStr); return base64Str; } else { return ''; } } - getSign(extraDatas1:HashMap): string { - let secret= extraDatas1.get("timestamp") - if(secret!=null) { + getSign(extraDatas1: HashMap): string { + let secret = extraDatas1.get("timestamp") + if (secret != null) { let keyValueStr: string = ""; let entriesArray: Array = Array.from(extraDatas1.keys()); entriesArray.sort(); - let sortedMap:HashMap = new HashMap(); + let sortedMap: HashMap = new HashMap(); entriesArray.forEach((value: string, index: number) => { - sortedMap.set(value,extraDatas1.get(value)); - keyValueStr +=value+extraDatas1.get(value) + sortedMap.set(value, extraDatas1.get(value)); + keyValueStr += value + extraDatas1.get(value) }); keyValueStr = keyValueStr.replace(" ", ""); keyValueStr = keyValueStr + CryptoJS.MD5(secret).toString(); let Md5keyValueStr: string = CryptoJS.MD5(keyValueStr).toString(); - let base64Str:string=Base64Util.encodeToStrSync(Md5keyValueStr); + let base64Str: string = Base64Util.encodeToStrSync(Md5keyValueStr); return base64Str; } else { return ''; @@ -326,13 +347,98 @@ class HdHttp { } async getVersion() { - let res=await bundleManager.getBundleInfoForSelf( + let res = await bundleManager.getBundleInfoForSelf( bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA ) - return res.versionName + return res.versionName } + + // 安全Base64编码(URL安全) + safeUrlBase64Encode(input: string): string { + const base64 = new util.Base64Helper(); + const encoded = base64.encodeToStringSync(new util.TextEncoder().encode(input)); + return encoded.replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); + } + + async md5To32bit(input: string): Promise { + // 空值安全处理 + if (!input || input.length === 0) { + return ''; + } + try { + // 1. 创建MD5实例 + const md = cryptoFramework.createMd('MD5'); + // 2. 数据转换(严格使用UTF8编码) + const dataBlob:DataBlob = { + data: new Uint8Array(buffer.from(input, 'utf-8').buffer) + } + // 3. 更新数据 + await md.update(dataBlob); + // 4. 获取摘要 + const mdResult = await md.digest(); + // 5. 转换为16进制小写字符串 + return Array.from(mdResult.data) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(''); + } catch (err) { + console.error('MD5加密失败:', err.message); + return ''; + } + } + + concatParams(params: HashMap): string { + const keys = this.iosStyleSort(Array.from(params.keys())); // 使用新排序 + let result = ''; + for (const key of keys) { + const value = params.get(key); + if (value === null || value === undefined) { + result += key; + continue; + } + // 严格匹配iOS的JSON处理(保留空格仅删除换行) + if (typeof value === 'object') { + const jsonStr = JSON.stringify(value) + .replace(/\n/g, '') // 仅删除换行符 + .replace(/\\"/g, '"'); // 处理转义引号 + result += key + jsonStr; + } else { + result += key + String(value); + } + } + return result; + } + + iosStyleSort(keys: string[]): string[] { + return keys.sort((a, b) => { + // 1. 主要排序:忽略大小写和数字值排序 + let result = a.localeCompare(b, undefined, { + sensitivity: 'base', + numeric: true + }); + + // 2. 次要排序:宽度不敏感(全角/半角等效) + if (result === 0) { + const normalize = (str: string) => + str.normalize('NFKC').replace(/[\uFF00-\uFFEF]/g, ''); + result = normalize(a).localeCompare(normalize(b)); + } + // 3. 强制确定顺序 + return result === 0 ? (a < b ? -1 : 1) : result; + }); + } +} + +interface DataBlob { + data: Uint8Array; +} + +function cleanString(str: string) { + const pattern: RegExp = /[\s\\;":]/g + return str.replace(pattern, (match) => match === ':' ? '=' : '') } export const hdHttp = new HdHttp({ baseURL: '' })