鸿蒙 HarmonyOS NEXT 网络请求封装实践:打造通用 Networking 工具类(附上传支持与示例)
在使用 HarmonyOS NEXT(ArkTS)开发应用的过程中,网络请求是每个项目中不可或缺的部分。鸿蒙官方提供了 @ohos.net.http 模块来实现网络通信,但原始写法通常较为冗长,特别是在需要频繁调用接口、处理 token 和上传文件时,代码极易重复、出错。
为了提高开发效率和项目的可维护性,我封装了一个通用的 Networking 类,支持:
-
GET、POST 请求封装
-
自动注入 token
-
统一错误处理
-
支持文件上传(带沙箱处理)
-
泛型返回值
这篇文章将手把手教你如何实现,并分享使用方法与注意事项。
📦 为什么要封装?
鸿蒙原生的 http 请求示例如下:
let httpRequest = http.createHttp();
let result = await httpRequest.request("https://api.example.com/data", {
method: http.RequestMethod.GET,
header: {
"Content-Type": "application/json"
},
expectDataType: http.HttpDataType.OBJECT
});
这种方式虽然灵活,但在实际开发中,我们通常还需要:
-
带上登录 token
-
统一处理错误码(如 403 重定向登录)
-
上传文件时构造 multipart body
-
重复导入配置、接口路径等
因此,封装一个 Networking 工具类能极大提升开发体验。
🔧 Networking 工具类核心代码
以下是已经优化过的完整 Networking 类代码,适配鸿蒙 HarmonyOS NEXT,兼容 ArkTS。
注意:文中示例 IP 和路径均为示意,请替换为你自己的服务器地址。
✅ 主要特性
-
get<T>()和post<T>()支持泛型参数 -
自动注入 token
-
支持 multipart 文件上传
-
沙箱文件复制与二进制读取
-
支持响应体结构统一解析(code/message/data)
📄 完整代码
💡 建议将以下代码保存为:
/common/network/Networking.ts
import http from '@ohos.net.http';
import { promptAction, router } from '@kit.ArkUI';
import loginManager from '../../viewModel/Login/LoginManager';
import { JSON } from '@kit.ArkTS';
import util from '@ohos.util';
import fs from '@ohos.file.fs';
import systemDateTime from '@ohos.systemDateTime';
import buffer from '@ohos.buffer';
export interface IResponseModel<T> {
message: string;
data?: T;
code: number;
}
type AnyObject = Record<string, any>;
class Networking {
private baseURL: string;
constructor() {
this.baseURL = 'https://api.example.com/api'; // ← 替换成你自己的 API 地址
}
private async createRequestOptions(method: http.RequestMethod, extraData?: AnyObject): Promise<http.HttpRequestOptions> {
const token = await loginManager.getToken();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return {
method,
header: headers,
expectDataType: http.HttpDataType.OBJECT,
extraData,
};
}
private handleResponse<T>(resData: IResponseModel<T>): IResponseModel<T> {
if (resData.code !== 200) {
if (resData.code === 404 || resData.code === 403) {
promptAction.showToast({ message: 'Token失效,请重新登录' });
router.replaceUrl({ url: 'pages/login/Login' });
} else {
promptAction.showToast({ message: resData.message });
}
}
return resData;
}
async post<T>(url: string, extraData?: AnyObject): Promise<IResponseModel<T>> {
try {
const req = http.createHttp();
const options = await this.createRequestOptions(http.RequestMethod.POST, extraData);
const res = await req.request(this.baseURL + url, options);
const resData = res.result as IResponseModel<T>;
return this.handleResponse(resData);
} catch (err) {
promptAction.showToast({ message: '请求网络错误' });
console.error('Networking POST Error:', JSON.stringify(err));
return Promise.reject(err);
}
}
async get<T>(url: string, extraData?: AnyObject): Promise<IResponseModel<T>> {
try {
const req = http.createHttp();
const options = await this.createRequestOptions(http.RequestMethod.GET, extraData);
const res = await req.request(this.baseURL + url, options);
const resData = res.result as IResponseModel<T>;
return this.handleResponse(resData);
} catch (err) {
promptAction.showToast({ message: '请求网络错误' });
console.error('Networking GET Error:', JSON.stringify(err));
return Promise.reject(err);
}
}
async upload<T>(url: string, uploadFilePath: string, uploadFileName: string, fieldName: string = 'images'): Promise<IResponseModel<T>> {
try {
const boundary = '----Boundary' + (await systemDateTime.getCurrentTime(true)).toString();
const sandFile = await this.copyToSandbox(uploadFilePath, uploadFileName);
const fileContent = new Uint8Array(this.readContentFromFile(sandFile));
const bodyContent = this.buildBodyContent(boundary, fieldName, uploadFileName, fileContent);
const req = http.createHttp();
const token = await loginManager.getToken();
const headers: Record<string, string> = {
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'Content-Length': bodyContent.byteLength.toString(),
'Accept': 'application/json',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const options: http.HttpRequestOptions = {
method: http.RequestMethod.POST,
header: headers,
extraData: bodyContent,
};
const res = await req.request(this.baseURL + url, options);
const resData = JSON.parse(res.result as string) as IResponseModel<T>;
return this.handleResponse(resData);
} catch (err) {
promptAction.showToast({ message: '上传失败,请检查网络' });
console.error('Networking Upload Error:', JSON.stringify(err));
return Promise.reject(err);
}
}
private buildBodyContent(boundary: string, fieldName: string, fileName: string, content: Uint8Array, contentType: string = 'image/jpeg'): ArrayBuffer {
const textEncoder = new util.TextEncoder();
const preFileContent = `--${boundary}\r\nContent-Disposition: form-data; name="${fieldName}"; filename="${fileName}"\r\nContent-Type: ${contentType}\r\n\r\n`;
const preArray = textEncoder.encodeInto(preFileContent);
const aftArray = textEncoder.encodeInto(`\r\n--${boundary}--\r\n`);
const bodyBuf = buffer.concat([preArray, content, aftArray]);
return bodyBuf.buffer;
}
private async copyToSandbox(srcUri: string, fileName: string): Promise<string> {
const context = getContext(this);
const realUri = `${context.cacheDir}/${fileName}`;
try {
const file = await fs.open(srcUri);
fs.copyFileSync(file.fd, realUri);
fs.close(file);
} catch (err) {
console.error('Copy to Sandbox Error:', JSON.stringify(err));
}
return realUri;
}
private readContentFromFile(fileUri: string): ArrayBuffer {
const file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);
const fsStat = fs.lstatSync(fileUri);
const buf = new ArrayBuffer(fsStat.size);
fs.readSync(file.fd, buf);
fs.fsyncSync(file.fd);
fs.closeSync(file);
return buf;
}
}
const networking = new Networking();
export default networking;
✅ 使用示例
1. 普通 GET 请求
interface UserProfile {
username: string;
avatar: string;
}
const res = await networking.get<UserProfile>('/users/profile');
console.log('用户名:', res.data?.username);
2. POST 请求(登录)
interface LoginResult {
token: string;
userId: string;
}
const loginData = { username: 'test', password: '123456' };
const res = await networking.post<LoginResult>('/users/login', loginData);
console.log('登录成功,Token:', res.data?.token);
3. 文件上传(头像或图片)
interface UploadResponse {
url: string;
}
const res = await networking.upload<UploadResponse>('/upload/avatar', '/storage/media/file.jpg', 'file.jpg', 'avatar');
console.log('文件已上传:', res.data?.url);
🛡 安全建议
-
❗ 请勿将服务器 IP 写死为
localhost,使用实际 IP 或域名 -
⚠️ token 失效应统一跳转登录(已封装)
-
✅ 后端建议统一使用
code/message/data格式返回 -
🚫 避免重复文件上传,建议使用 MD5 去重策略
🔚 总结
这个 Networking 封装类几乎涵盖了中小项目中所有的网络请求场景,它不仅简化了调用逻辑,还提高了代码的健壮性和复用性。如果你正在写 ArkTS 项目,完全可以拷贝并集成这套封装逻辑,让你专注业务开发。
希望这篇博客对你有帮助!
如果你有其他模块封装需求,欢迎留言,我会继续更新组件化开发经验
更多推荐


所有评论(0)