在開發鴻蒙HarmonyOS應用時,網絡請求功能是必不可少的。
axios
是一個非常流行的基于Promise的HTTP客戶端,適用于瀏覽器和Node.js環境。本文將介紹如何在鴻蒙HarmonyOS中封裝axios
庫,使其能夠支持文件上傳,并提供額外的配置選項以滿足不同的業務需求。
封裝目的
- 簡化網絡請求:通過封裝,我們可以將常用的HTTP請求操作(如GET、POST等)封裝成簡潔易用的方法。
- 統一錯誤處理:封裝后的庫可以統一處理HTTP請求中的錯誤,提供更友好的錯誤提示和處理邏輯。
- 支持文件上傳:提供對文件上傳的支持,包括單個文件和多個文件的上傳。
使用的庫
axios
:一個流行的JavaScript庫,用于處理HTTP請求。
封裝實現
以下是封裝后的AxiosHttpRequest
類的實現,支持文件上傳:
/*** author:csdn貓哥* qq:534117529* blog:https://blog.csdn.net/yyz_1987*/
//axiosHttp.etsimport axios, {AxiosError,AxiosInstance,AxiosHeaders,AxiosRequestHeaders,AxiosResponse,FormData,AxiosProgressEvent,InternalAxiosRequestConfig
} from "@ohos/axios";interface HttpResponse<T>{data: T;status: number;statusText: string;config: HttpRequestConfig;
}
export type HttpPromise<T> = Promise<HttpResponse<T>>;// 鴻蒙ArkTS文件上傳相關接口定義
/**
上傳類型支持uri和ArrayBuffer,
uri支持“internal”協議類型和沙箱路徑。
"internal://cache/"為必填字段,示例: internal://cache/path/to/file.txt;
沙箱路徑示例:cacheDir + '/hello.txt'*/
export interface UploadFile {buffer?: ArrayBuffer;fileName?: string;mimeType?: string;uri?:string;
}export interface FileUploadConfig extends HttpRequestConfig {file?: UploadFile | UploadFile[];fileFieldName?: string; // 文件字段名,默認為 'file'additionalData?: Record<string, any>; // 額外的表單數據onUploadProgress?: (progressEvent: any) => void; // 上傳進度回調
}export interface FileInfo {name: string;size: number;type: string;
}
/*** 封裝后,不支持傳入攔截器* 需要自己定義接口繼承 AxiosRequestConfig類型* 從而支持傳入攔截器,但攔截器選項應為可選屬性* 之后請求實例傳入的options為繼承了AxiosRequestConfig的自定義類型*/
interface InterceptorHooks {requestInterceptor?: (config: HttpRequestConfig) => Promise<HttpRequestConfig>;requestInterceptorCatch?: (error: any) => any;responseInterceptor?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;responseInterceptorCatch?: (error: any) => any;
}// @ts-ignore
interface HttpRequestConfig extends InternalAxiosRequestConfig {showLoading?: boolean; //是否展示請求loadingcheckResultCode?: boolean; //是否檢驗響應結果碼checkLoginState?: boolean; //校驗用戶登陸狀態needJumpToLogin?: boolean; //是否需要跳轉到登陸頁面interceptorHooks?: InterceptorHooks;//攔截器headers?: AxiosRequestHeaders;errorHandler?: (error: any) => void; //錯誤處理
}/*** 網絡請求構造* 基于axios框架實現*/
export class AxiosHttpRequest {config: HttpRequestConfig;interceptorHooks?: InterceptorHooks;instance: AxiosInstance;constructor(options: HttpRequestConfig) {this.config = options;this.interceptorHooks = options.interceptorHooks;this.instance = axios.create(options);this.setupInterceptor()}setupInterceptor(): void {this.instance.interceptors.request.use(// 這里主要是高版本的axios中設置攔截器的時候里面的Config屬性必須是InternalAxiosRequestConfig,// 但是InternalAxiosRequestConfig里面的headers是必傳,所以在實現的子類我設置成非必傳會報錯,加了個忽略注解// @ts-ignorethis.interceptorHooks?.requestInterceptor,this.interceptorHooks?.requestInterceptorCatch,);this.instance.interceptors.response.use(this.interceptorHooks?.responseInterceptor,this.interceptorHooks?.responseInterceptorCatch,);}// 類型參數的作用,T決定AxiosResponse實例中data的類型request<T = any>(config: HttpRequestConfig): HttpPromise<T> {return new Promise<HttpResponse<T>>((resolve, reject) => {this.instance.request<any, HttpResponse<T>>(config).then(res => {resolve(res);}).catch((err) => {// 使用傳入的 errorHandler 處理錯誤const errorHandler = config.errorHandler || errorHandlerDefault;errorHandler(err); if (err) {reject(err);}});});}get<T = any>(config: HttpRequestConfig): HttpPromise<T> {return this.request({ ...config, method: 'GET' });}post<T = any>(config: HttpRequestConfig): HttpPromise<T> {return this.request({ ...config, method: 'POST' });}delete<T = any>(config: HttpRequestConfig): HttpPromise<T> {return this.request({ ...config, method: 'DELETE' });}patch<T = any>(config: HttpRequestConfig): HttpPromise<T> {return this.request({ ...config, method: 'PATCH' });}/*** 上傳單個文件或多個文件* @param config 文件上傳配置* @returns Promise<HttpResponse<T>>*/uploadFile<T = any>(config: FileUploadConfig): HttpPromise<T> {return new Promise<HttpResponse<T>>((resolve, reject) => {if (!config.file) {reject(new Error('文件不能為空'));return;}const formData = new FormData();const fileFieldName = config.fileFieldName || 'file';// 處理單個或多個文件const files = Array.isArray(config.file) ? config.file : [config.file];files.forEach((file, index) => {const fieldName = Array.isArray(config.file) ? `${fileFieldName}[${index}]` : fileFieldName;// 鴻蒙ArkTS FormData.append 支持第三個參數設置文件名和類型if (file.mimeType) {formData.append(fieldName, file.buffer, {filename: file.fileName,type: file.mimeType});} else if (file.buffer){formData.append(fieldName, file.buffer, {filename: file.fileName});}else if (file.uri){formData.append(fieldName, file.uri);}});// 添加額外的表單數據if (config.additionalData) {Object.keys(config.additionalData).forEach(key => {formData.append(key, config.additionalData![key]);});}const uploadConfig: HttpRequestConfig = {...config,method: 'POST',data: formData,headers: new AxiosHeaders({...config.headers,'Content-Type': 'multipart/form-data'})};// 添加上傳進度監聽if (config.onUploadProgress) {uploadConfig.onUploadProgress = config.onUploadProgress;}this.request<T>(uploadConfig).then(resolve).catch(reject);});}/*** 上傳多個文件* @param config 文件上傳配置* @returns Promise<HttpResponse<T>>*/uploadFiles<T = any>(config: FileUploadConfig): HttpPromise<T> {if (!Array.isArray(config.file)) {return Promise.reject(new Error('uploadFiles方法需要傳入文件數組'));}return this.uploadFile<T>(config);}/*** 獲取文件信息* @param file 文件對象* @returns FileInfo*/getFileInfo(file: UploadFile): FileInfo {return {name: file.fileName,size: file.buffer.byteLength,type: file.mimeType || 'application/octet-stream'};}/*** 驗證文件類型* @param file 文件對象* @param allowedTypes 允許的文件類型數組* @returns boolean*/validateFileType(file: UploadFile, allowedTypes: string[]): boolean {const fileType = file.mimeType || 'application/octet-stream';return allowedTypes.includes(fileType);}/*** 驗證文件大小* @param file 文件對象* @param maxSize 最大文件大小(字節)* @returns boolean*/validateFileSize(file: UploadFile, maxSize: number): boolean {return file.buffer.byteLength <= maxSize;}/*** 創建文件上傳配置* @param url 上傳地址* @param file 文件對象* @param options 其他配置選項* @returns FileUploadConfig*/createUploadConfig(url: string,file: UploadFile | UploadFile[],options: Partial<FileUploadConfig> = {}): FileUploadConfig {return {url,file,fileFieldName: 'file',...options};}
}function errorHandlerDefault(error: any) {if (error instanceof AxiosError) {//showToast(error.message)} else if (error != undefined && error.response != undefined && error.response.status) {switch (error.response.status) {// 401: 未登錄// 未登錄則跳轉登錄頁面,并攜帶當前頁面的路徑// 在登錄成功后返回當前頁面,這一步需要在登錄頁操作。case 401:break;// 403 token過期// 登錄過期對用戶進行提示// 清除本地token和清空vuex中token對象// 跳轉登錄頁面case 403://showToast("登錄過期,請重新登錄")// 清除token// localStorage.removeItem('token');break;// 404請求不存在case 404://showToast("網絡請求不存在")break;// 其他錯誤,直接拋出錯誤提示default://showToast(error.response.data.message)}}
}
export{AxiosRequestHeaders,AxiosError,AxiosHeaders,AxiosProgressEvent,FormData};
export default AxiosHttpRequest;
使用舉例
import fs from '@ohos.file.fs';
import { axiosClient, FileUploadConfig, HttpPromise, UploadFile } from '../../utils/axiosClient';
import { AxiosProgressEvent } from '@nutpi/axios';async function uploadSingleFile() {try {// 創建測試文件并讀取為ArrayBufferlet context = getContext() as common.UIAbilityContext;const cacheDir = context.cacheDir;const path = cacheDir + '/test.jpg';// 寫入測試文件const file = fs.openSync(path, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);fs.writeSync(file.fd, "這是一個測試文件內容");fs.fsyncSync(file.fd);fs.closeSync(file.fd);// 讀取文件為ArrayBufferconst file2 = fs.openSync(path, 0o2);const stat = fs.lstatSync(path);const buffer = new ArrayBuffer(stat.size);fs.readSync(file2.fd, buffer);fs.fsyncSync(file2.fd);fs.closeSync(file2.fd);const uploadFile:UploadFile = {fileName: 'test.jpg',mimeType: 'text/plain',uri:path};const config:FileUploadConfig = axiosClient.createUploadConfig('upload/image',uploadFile,{context:getContext(),onUploadProgress: (progressEvent:AxiosProgressEvent) => {const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent?.total ?? 1));console.log(`上傳進度: ${percentCompleted}%`);}});axiosClient.uploadFile<string>(config).then((res) => {//Log.debug(res.data.code)console.log('文件上傳成功:');}).catch((err:BusinessError) => {Log.debug("request","err.code:%d",err.code)Log.debug("request",err.message)console.error('文件上傳失敗:', err);});} catch (error) {}
}
核心功能解析
1. 文件上傳接口
- UploadFile接口:定義了文件上傳的數據結構,支持
uri
和ArrayBuffer
兩種方式。 - FileUploadConfig接口:繼承自
HttpRequestConfig
,增加了對文件和額外表單數據的支持。 - uploadFile方法:實現文件上傳邏輯,支持單個文件和多個文件的上傳。
- uploadFiles方法:專門用于上傳文件數組。
2. 文件信息處理
- getFileInfo方法:獲取文件的名稱、大小和類型。
- validateFileType方法:驗證文件類型是否在允許的類型列表中。
- validateFileSize方法:驗證文件大小是否超過指定的最大值。
3. 請求攔截器與響應攔截器
- setupInterceptor方法:設置請求和響應攔截器,以便在請求發送前和響應返回后執行自定義邏輯。
4. 統一錯誤處理
- errorHandlerDefault函數:定義了默認的錯誤處理邏輯。根據響應的狀態碼提供不同的錯誤提示。
使用示例
const httpRequest = new AxiosHttpRequest({baseURL: 'https://example.com/api',interceptorHooks: {requestInterceptor: (config) => {console.log('Request Interceptor:', config);return config;},responseInterceptor: (response) => {console.log('Response Interceptor:', response);return response;},},errorHandler: (error) => {console.error('Custom Error Handler:', error);}
});const file: UploadFile = {buffer: new ArrayBuffer(1024),fileName: 'example.txt',mimeType: 'text/plain'
};const config = httpRequest.createUploadConfig('https://example.com/api/upload', file, {additionalData: { description: '這是一個示例文件' },onUploadProgress: (progress) => {console.log('上傳進度:', progress);}
});httpRequest.uploadFile(config).then((response) => {console.log('上傳成功:', response);
}).catch((error) => {console.error('上傳失敗:', error);
});
總結
通過封裝axios
庫,我們可以在鴻蒙HarmonyOS中更方便地實現網絡請求和文件上傳功能。封裝后的庫提供了統一的錯誤處理機制和豐富的配置選項,使得我們的網絡請求更加高效和靈活。希望本文能幫助大家更好地理解和使用封裝后的axios
庫。