import { InternalAxiosRequestConfig, AxiosResponse, FormData } from '@ohos/axios'; import { Signature } from '../constants/AppConstants'; import { Logger } from '../utils/Logger'; import { RandomUtil, MD5, JSONUtil, StrUtil } from '@pura/harmony-utils'; import systemDateTime from '@ohos.systemDateTime'; export interface EncryptConfig extends InternalAxiosRequestConfig { startTime?: number; params?: Record; } export class RequestInterceptor { static readonly TAG: string = "RabbitLog_Request"; static async onPrepare(config: EncryptConfig): Promise { config.startTime = Date.now(); // 1. 获取请求方法 const method = (config.method ?? "get").toLowerCase(); // 2. 准备基础参数 (Query Params) config.params = config.params || {}; config.params.nonce = RandomUtil.generateUUID36() config.params.timestamp = Math.trunc(systemDateTime.getTime() / 1000) // 3. 对 Query 参数进行字典排序并拼接 let paramsMap = JSONUtil.jsonToMap(JSON.stringify(config.params)); let arrayMap = Array.from(paramsMap); arrayMap.sort((a, b) => { return a[0].localeCompare(b[0]) }) paramsMap = new Map(arrayMap) let sortQueryString = ""; paramsMap.forEach((value, key) => { sortQueryString += key + "=" + value + "&" }) sortQueryString = encodeURI(sortQueryString.substring(0, sortQueryString.length - 1)) // 4. 准备签名盐值 let signature = MD5.digestSync(sortQueryString + '&' + MD5.digestSync(Signature)); // 5. 【核心判断】识别是否为文件上传 const contentType = String(config.headers?.['Content-Type'] || ""); const isFormData = config.data instanceof FormData || contentType.includes('multipart/form-data'); // 6. 根据请求类型和内容计算签名 if ((method === 'post' || method === 'put') && config.data) { if(!isFormData){ let dataStr = JSON.stringify(config.data); if (StrUtil.isNotEmpty(dataStr)) { signature = MD5.digestSync(sortQueryString + '&' + dataStr + "&" + MD5.digestSync(Signature)); } } } // 7. 回填带签名的参数 config.params.signature = signature; // 8. 打印请求日志 RequestInterceptor.logRequest(config, isFormData); return config; } static onResponse(response: AxiosResponse): AxiosResponse { const config = response.config as EncryptConfig; const startTime: number = config.startTime ?? Date.now(); RequestInterceptor.logResponse(response, startTime); return response; } private static logRequest(config: EncryptConfig, isFormData: boolean): void { let logString = `\n┌─── 📤 Request [ ${config.method?.toUpperCase()} ] ───\n`; logString += `│ Url: ${config.baseURL}${config.url}\n`; logString += `│ Final Params: ${JSON.stringify(config.params)}\n`; if (isFormData) { logString += `│ Body: [ Multipart/FormData - File Upload ]\n`; } else { logString += `│ Body: ${JSON.stringify(config.data)}\n`; } logString += `└─────────────────────────────────────\n`; Logger.info(RequestInterceptor.TAG, logString); } private static logResponse(response: AxiosResponse, startTime: number): void { const duration: number = Date.now() - startTime; const config = response.config as EncryptConfig; // 0. 完整的请求地址(包含参数拼接) let fullUrl = `${config.baseURL ?? ""}${config.url ?? ""}`; const params = config.params; let queryStr = ""; if (params !== undefined && params !== null) { const keys = Object.keys(params); if (keys.length > 0) { const queryParts: string[] = []; for (const key of keys) { const value = params[key]; if (value !== undefined && value !== null) { queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); } } queryStr = queryParts.join('&'); const separator = fullUrl.includes('?') ? '&' : '?'; fullUrl += `${separator}${queryStr}`; } } // 1. 获取完整的 Data 字符串 const dataStr: string = JSON.stringify(response.data); // 2. 构造基础信息 let header =''; header =`\n┌─── 📥 响应开始🔜Response [ ${response.status} ] [ ${duration}ms ] ───\n`; header += `│ Full URL: ${fullUrl}\n`; header += `│ Data Content: `; // 3. 拼接完整报文 const fullLog = header + dataStr + `\n└────────────────────响应结束🔚─────────────────`; // 4. 执行分段连贯打印 RequestInterceptor.printInChunks(fullLog); } /** * 分段打印并确保数据完全打印 */ private static printInChunks(message: string): void { const CHUNK_SIZE = 3000; let start = 0; while (start < message.length) { let end = Math.min(start + CHUNK_SIZE, message.length); let chunk = message.substring(start, end); Logger.info(RequestInterceptor.TAG, chunk); start = end; } } /* private static logResponse(response: AxiosResponse, startTime: number): void { const duration: number = Date.now() - startTime; const config = response.config as EncryptConfig; // 显式类型转换 // 1. 安全地获取 BaseURL 和 URL const baseUrl: string = config.baseURL ?? ""; let urlPath: string = config.url ?? ""; let fullUrl: string = `${baseUrl}${urlPath}`; // 2. 强类型处理 Query Params (对应 Android 的 request.url().toString()) const params = config.params; if (params !== undefined && params !== null) { const keys: string[] = Object.keys(params); if (keys.length > 0) { const queryParts: string[] = []; for (const key of keys) { const value = params[key]; if (value !== undefined && value !== null) { queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); } } const queryString: string = queryParts.join('&'); const separator: string = fullUrl.includes('?') ? '&' : '?'; fullUrl += `${separator}${queryString}`; } } // 3. 构造日志字符串 let logString ='' logString =`\n┌─── 📥 Response [ ${String(response.status)} ] ───\n`; logString += `│ Time: ${String(duration)}ms\n`; logString += `│ Full Request URL: ${fullUrl}\n`; // 4. 处理 Response Data (禁止使用 any,需转换为 string) const dataObj = response.data as object; // 假设返回的是对象 const dataStr: string = JSON.stringify(dataObj); const displayData: string = dataStr.length > 1000 ? `${dataStr.substring(0, 1000)}... [Truncated]` : dataStr; logString += `│ Data: ${displayData}\n`; logString += `└─────────────────────────────────────\n`; Logger.info(RequestInterceptor.TAG, logString); } */ } /** * 时间同步工具 */ export class TimeSync { static timeOffset: number = 0; // 服务器时间 - 本地时间 static async sync(serverTimeStr: string) { const serverTime = parseInt(serverTimeStr, 10); const localTime = Math.trunc(Date.now() / 1000); TimeSync.timeOffset = serverTime - localTime; console.info(`[TimeSync] Offset calculated: ${TimeSync.timeOffset}s`); } static getCorrectedTime(): number { // return Math.trunc(Date.now() / 1000) + TimeSync.timeOffset; return Math.trunc(systemDateTime.getTime() / 1000); } }