212 lines
7.6 KiB
Plaintext
212 lines
7.6 KiB
Plaintext
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<string, string | number | boolean | null | undefined>;
|
||
}
|
||
|
||
export class RequestInterceptor {
|
||
static readonly TAG: string = "RabbitLog_Request";
|
||
|
||
static async onPrepare(config: EncryptConfig): Promise<EncryptConfig> {
|
||
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);
|
||
}
|
||
}
|