📖 鸿蒙NEXT开发实战系列 | 第22篇 | 网络篇 🎯 适合人群:有鸿蒙基础的开发者 ⏰ 阅读时间:约15分钟 | 💻 开发环境:DevEco Studio 5.0+

导航上一篇:数据存储篇 | 系列目录 | 下一篇:WebSocket篇


网络请求是现代App的必备能力!无论是获取用户信息、提交表单数据,还是上传文件图片,都离不开网络请求。本文从鸿蒙原生 HttpURLConnection 讲起,再到 axios 等第三方库的使用,覆盖 GET 请求、POST 提交、文件上传、请求拦截、错误处理等全场景,附完整封装代码,助你一文掌握鸿蒙网络编程。


目录


一、权限配置:网络请求的第一步

在鸿蒙系统中,进行网络请求必须先声明网络权限。否则请求会直接失败。

1.1 声明网络权限

module.json5 文件中添加 INTERNET 权限:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "用于访问网络获取数据",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

注意:鸿蒙 NEXT 版本中,INTERNET 权限属于 system_grant 类型,无需用户手动授权,声明即可使用。

1.2 网络状态检测

建议在请求前检测网络状态:

import { connection } from '@kit.NetworkKit';

// 检测网络是否可用
async function checkNetwork(): Promise<boolean> {
  try {
    const netHandle = await connection.getDefaultNet();
    const netCapabilities = await connection.getNetCapabilities(netHandle);
    return netCapabilities?.bearerTypes?.length > 0;
  } catch (err) {
    console.error('网络检测失败', err);
    return false;
  }
}

二、HttpURLConnection 基础用法

http 模块是鸿蒙提供的原生网络请求 API,无需引入第三方库即可使用。

2.1 基础引入

import { http } from '@kit.NetworkKit';

2.2 创建请求对象

// 创建 httpRequest 实例
const httpRequest = http.createHttp();

// 请求配置
const requestOptions: http.HttpRequestOptions = {
  method: http.RequestMethod.GET,
  header: {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  },
  extraData: '',  // POST 请求体
  connectTimeout: 10000,  // 连接超时 10 秒
  readTimeout: 10000      // 读取超时 10 秒
};

三、GET 请求实战

GET 请求是最常见的请求方式,用于获取服务器数据。

3.1 基础 GET 请求

import { http } from '@kit.NetworkKit';

function doGetRequest() {
  const httpRequest = http.createHttp();

  httpRequest.request(
    'https://api.example.com/users',
    {
      method: http.RequestMethod.GET,
      header: {
        'Content-Type': 'application/json'
      },
      connectTimeout: 10000,
      readTimeout: 10000
    },
    (err, data) => {
      if (!err) {
        // 请求成功
        console.info('状态码:', data.responseCode);
        const result = JSON.parse(data.result as string);
        console.info('返回数据:', JSON.stringify(result));
      } else {
        // 请求失败
        console.error('请求失败:', err.message);
      }
      // 销毁请求对象,释放资源
      httpRequest.destroy();
    }
  );
}

3.2 Promise 方式的 GET 请求

async function doGetWithPromise(): Promise<any> {
  const httpRequest = http.createHttp();

  try {
    const response = await httpRequest.request(
      'https://api.example.com/users',
      {
        method: http.RequestMethod.GET,
        header: { 'Content-Type': 'application/json' }
      }
    );

    if (response.responseCode === 200) {
      const result = JSON.parse(response.result as string);
      console.info('请求成功:', result);
      return result;
    } else {
      throw new Error(`请求失败,状态码: ${response.responseCode}`);
    }
  } catch (err) {
    console.error('请求异常:', err);
    throw err;
  } finally {
    httpRequest.destroy();
  }
}

3.3 GET 请求带查询参数

function buildQueryString(params: Record<string, string>): string {
  return Object.entries(params)
    .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
    .join('&');
}

async function doGetWithParams(params: Record<string, string>) {
  const httpRequest = http.createHttp();
  const queryString = buildQueryString(params);
  const url = `https://api.example.com/users?${queryString}`;

  try {
    const response = await httpRequest.request(url, {
      method: http.RequestMethod.GET
    });
    return JSON.parse(response.result as string);
  } finally {
    httpRequest.destroy();
  }
}

// 使用示例
doGetWithParams({ page: '1', pageSize: '10', keyword: '鸿蒙' });

四、POST 请求实战

POST 请求用于向服务器提交数据,常见于登录、注册、表单提交等场景。

4.1 JSON 格式 POST 请求

async function doPostJson(url: string, data: object): Promise<any> {
  const httpRequest = http.createHttp();

  try {
    const response = await httpRequest.request(url, {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': 'application/json'
      },
      extraData: JSON.stringify(data),
      connectTimeout: 10000,
      readTimeout: 10000
    });

    if (response.responseCode === 200 || response.responseCode === 201) {
      return JSON.parse(response.result as string);
    } else {
      throw new Error(`请求失败,状态码: ${response.responseCode}`);
    }
  } finally {
    httpRequest.destroy();
  }
}

// 使用示例:用户登录
async function login(username: string, password: string) {
  const result = await doPostJson('https://api.example.com/login', {
    username,
    password
  });
  console.info('登录结果:', result);
}

4.2 表单格式 POST 请求

async function doFormPost(url: string, formData: Record<string, string>) {
  const httpRequest = http.createHttp();
  const formBody = buildQueryString(formData);

  try {
    const response = await httpRequest.request(url, {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      extraData: formBody
    });
    return JSON.parse(response.result as string);
  } finally {
    httpRequest.destroy();
  }
}

五、PUT 与 DELETE 请求

RESTful API 中常见的 PUT(更新)和 DELETE(删除)请求。

5.1 PUT 请求

async function doPut(url: string, data: object): Promise<any> {
  const httpRequest = http.createHttp();

  try {
    const response = await httpRequest.request(url, {
      method: http.RequestMethod.PUT,
      header: { 'Content-Type': 'application/json' },
      extraData: JSON.stringify(data)
    });
    return JSON.parse(response.result as string);
  } finally {
    httpRequest.destroy();
  }
}

// 使用示例:更新用户信息
doPut('https://api.example.com/users/1001', {
  name: '张三',
  age: 25
});

5.2 DELETE 请求

async function doDelete(url: string): Promise<any> {
  const httpRequest = http.createHttp();

  try {
    const response = await httpRequest.request(url, {
      method: http.RequestMethod.DELETE,
      header: { 'Content-Type': 'application/json' }
    });
    return response.responseCode === 200 ? JSON.parse(response.result as string) : null;
  } finally {
    httpRequest.destroy();
  }
}

// 使用示例:删除用户
doDelete('https://api.example.com/users/1001');

六、文件上传实现

文件上传是实际开发中的高频需求,使用 multipart/form-data 格式实现。

6.1 基础文件上传

import { http } from '@kit.NetworkKit';
import { fileIo } from '@kit.CoreFileKit';

async function uploadFile(url: string, filePath: string, fileName: string): Promise<any> {
  const httpRequest = http.createHttp();

  try {
    // 读取文件为 ArrayBuffer
    const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
    const stat = fileIo.statSync(filePath);
    const arrayBuffer = new ArrayBuffer(stat.size);
    fileIo.readSync(file.fd, arrayBuffer);
    fileIo.closeSync(file);

    // 构建 multipart 请求
    const boundary = '----HarmonyOSBoundary' + Date.now();

    // 构建请求体
    const headerPart =
      `--${boundary}\r\n` +
      `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
      `Content-Type: application/octet-stream\r\n\r\n`;

    const footerPart = `\r\n--${boundary}--\r\n`;

    // 拼接完整请求体
    const encoder = new util.TextEncoder();
    const headerBuffer = encoder.encodeInto(headerPart);
    const footerBuffer = encoder.encodeInto(footerPart);

    const totalLength = headerBuffer.byteLength + arrayBuffer.byteLength + footerBuffer.byteLength;
    const bodyBuffer = new Uint8Array(totalLength);
    bodyBuffer.set(new Uint8Array(headerBuffer), 0);
    bodyBuffer.set(new Uint8Array(arrayBuffer), headerBuffer.byteLength);
    bodyBuffer.set(new Uint8Array(footerBuffer), headerBuffer.byteLength + arrayBuffer.byteLength);

    const response = await httpRequest.request(url, {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': `multipart/form-data; boundary=${boundary}`
      },
      extraData: bodyBuffer.buffer as ArrayBuffer
    });

    return JSON.parse(response.result as string);
  } finally {
    httpRequest.destroy();
  }
}

// 使用示例
uploadFile(
  'https://api.example.com/upload',
  '/data/storage/el2/base/haps/entry/files/photo.jpg',
  'photo.jpg'
);

提示:鸿蒙 NEXT 中更推荐使用 request 模块的上传能力,系统会自动处理 multipart 格式。

6.2 使用系统 upload API

import { request } from '@kit.NetworkKit';

async function uploadWithSystemAPI(url: string, filePath: string) {
  const uploadTask = await request.uploadFile(getContext(), {
    url: url,
    method: 'POST',
    files: [{ filename: 'file', name: 'file', uri: filePath }],
    data: [{ name: 'description', value: '用户上传图片' }]
  });

  uploadTask.on('complete', (taskState) => {
    console.info('上传完成:', taskState);
  });

  uploadTask.on('fail', (taskState) => {
    console.error('上传失败:', taskState);
  });
}

七、使用 axios 第三方库

axios 是目前最流行的 HTTP 客户端库之一,鸿蒙社区也提供了适配版本。

7.1 安装 axios

ohpm install @ohos/axios

7.2 基础用法

import axios from '@ohos/axios';

// GET 请求
async function getUsers() {
  const response = await axios.get('https://api.example.com/users');
  console.info('用户列表:', response.data);
  return response.data;
}

// POST 请求
async function createUser(userData: object) {
  const response = await axios.post('https://api.example.com/users', userData);
  console.info('创建成功:', response.data);
  return response.data;
}

7.3 axios 配置项

import axios, { AxiosRequestConfig } from '@ohos/axios';

// 创建 axios 实例
const service = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 15000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// 请求配置
const config: AxiosRequestConfig = {
  method: 'post',
  url: '/users',
  data: { name: '张三', age: 25 }
};

service(config).then(response => {
  console.info(response.data);
});

八、网络请求工具类封装

实际开发中,我们需要封装一个通用的网络请求工具类,统一管理请求配置和错误处理。

8.1 完整封装代码

// utils/HttpRequest.ets
import { http } from '@kit.NetworkKit';

// 响应数据结构
interface ApiResponse<T = any> {
  code: number;
  message: string;
  data: T;
}

// 请求配置
interface RequestConfig {
  url: string;
  method?: http.RequestMethod;
  data?: object | string;
  header?: Record<string, string>;
  timeout?: number;
}

class HttpRequest {
  private baseUrl: string = '';
  private defaultHeader: Record<string, string> = {
    'Content-Type': 'application/json'
  };
  private timeout: number = 15000;

  // 设置基础配置
  setBaseUrl(url: string): void {
    this.baseUrl = url;
  }

  setToken(token: string): void {
    this.defaultHeader['Authorization'] = `Bearer ${token}`;
  }

  // 核心请求方法
  async request<T = any>(config: RequestConfig): Promise<ApiResponse<T>> {
    const httpRequest = http.createHttp();
    const fullUrl = this.baseUrl + config.url;

    try {
      const response = await httpRequest.request(fullUrl, {
        method: config.method || http.RequestMethod.GET,
        header: { ...this.defaultHeader, ...config.header },
        extraData: typeof config.data === 'object' ?
          JSON.stringify(config.data) : config.data,
        connectTimeout: config.timeout || this.timeout,
        readTimeout: config.timeout || this.timeout
      });

      const result: ApiResponse<T> = JSON.parse(response.result as string);

      if (response.responseCode === 200) {
        return result;
      } else {
        throw new Error(result.message || `请求失败: ${response.responseCode}`);
      }
    } catch (err) {
      console.error('请求异常:', err);
      throw err;
    } finally {
      httpRequest.destroy();
    }
  }

  // GET 请求
  async get<T = any>(url: string, params?: Record<string, string>): Promise<ApiResponse<T>> {
    let fullUrl = url;
    if (params) {
      const queryString = Object.entries(params)
        .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
        .join('&');
      fullUrl += `?${queryString}`;
    }
    return this.request<T>({ url: fullUrl, method: http.RequestMethod.GET });
  }

  // POST 请求
  async post<T = any>(url: string, data?: object): Promise<ApiResponse<T>> {
    return this.request<T>({
      url,
      method: http.RequestMethod.POST,
      data
    });
  }

  // PUT 请求
  async put<T = any>(url: string, data?: object): Promise<ApiResponse<T>> {
    return this.request<T>({
      url,
      method: http.RequestMethod.PUT,
      data
    });
  }

  // DELETE 请求
  async delete<T = any>(url: string): Promise<ApiResponse<T>> {
    return this.request<T>({
      url,
      method: http.RequestMethod.DELETE
    });
  }
}

// 导出单例
export const httpRequest = new HttpRequest();

8.2 使用示例

import { httpRequest } from '../utils/HttpRequest';

// 初始化配置
httpRequest.setBaseUrl('https://api.example.com');
httpRequest.setToken('your-token-here');

// 页面中使用
@Entry
@Component
struct UserListPage {
  @State users: User[] = [];

  async aboutToAppear() {
    try {
      // GET 请求获取用户列表
      const result = await httpRequest.get<User[]>('/users', {
        page: '1',
        pageSize: '10'
      });
      this.users = result.data;

      // POST 请求创建用户
      await httpRequest.post('/users', {
        name: '新用户',
        age: 20
      });

      // PUT 请求更新用户
      await httpRequest.put('/users/1001', {
        name: '更新后的名字'
      });

      // DELETE 请求删除用户
      await httpRequest.delete('/users/1001');
    } catch (err) {
      console.error('操作失败:', err);
    }
  }

  build() {
    List({ space: 10 }) {
      ForEach(this.users, (user: User) => {
        ListItem() {
          Text(user.name)
            .fontSize(16)
        }
      })
    }
  }
}

九、请求拦截器与错误处理

9.1 封装拦截器

// utils/HttpInterceptor.ets
import { http } from '@kit.NetworkKit';

type RequestInterceptor = (config: RequestConfig) => RequestConfig;
type ResponseInterceptor = (response: any) => any;
type ErrorInterceptor = (error: Error) => void;

class HttpInterceptor {
  private requestInterceptors: RequestInterceptor[] = [];
  private responseInterceptors: ResponseInterceptor[] = [];
  private errorInterceptors: ErrorInterceptor[] = [];

  // 添加请求拦截器
  addRequestInterceptor(interceptor: RequestInterceptor): void {
    this.requestInterceptors.push(interceptor);
  }

  // 添加响应拦截器
  addResponseInterceptor(interceptor: ResponseInterceptor): void {
    this.responseInterceptors.push(interceptor);
  }

  // 添加错误拦截器
  addErrorInterceptor(interceptor: ErrorInterceptor): void {
    this.errorInterceptors.push(interceptor);
  }

  // 执行请求拦截
  applyRequestInterceptors(config: RequestConfig): RequestConfig {
    let processedConfig = { ...config };
    for (const interceptor of this.requestInterceptors) {
      processedConfig = interceptor(processedConfig);
    }
    return processedConfig;
  }

  // 执行响应拦截
  applyResponseInterceptors(response: any): any {
    let processedResponse = response;
    for (const interceptor of this.responseInterceptors) {
      processedResponse = interceptor(processedResponse);
    }
    return processedResponse;
  }

  // 执行错误拦截
  applyErrorInterceptors(error: Error): void {
    for (const interceptor of this.errorInterceptors) {
      interceptor(error);
    }
  }
}

export const httpInterceptor = new HttpInterceptor();

9.2 配置拦截器

import { httpInterceptor } from '../utils/HttpInterceptor';
import { router } from '@kit.ArkUI';

// 请求拦截器:自动添加 token
httpInterceptor.addRequestInterceptor((config) => {
  const token = AppStorage.get<string>('userToken');
  if (token) {
    config.header = {
      ...config.header,
      'Authorization': `Bearer ${token}`
    };
  }
  console.info(`[请求] ${config.method} ${config.url}`);
  return config;
});

// 响应拦截器:统一处理响应
httpInterceptor.addResponseInterceptor((response) => {
  console.info(`[响应] 状态码: ${response.responseCode}`);

  // 业务状态码处理
  if (response.data?.code === 401) {
    // Token 过期,跳转登录页
    router.pushUrl({ url: 'pages/LoginPage' });
    throw new Error('登录已过期,请重新登录');
  }

  return response;
});

// 错误拦截器:统一错误处理
httpInterceptor.addErrorInterceptor((error) => {
  console.error('[错误]', error.message);

  // 网络错误提示
  if (error.message.includes('timeout')) {
    promptAction.showToast({ message: '网络超时,请稍后重试' });
  } else if (error.message.includes('Network')) {
    promptAction.showToast({ message: '网络连接失败,请检查网络' });
  }
});

9.3 错误码处理

function handleHttpError(statusCode: number): string {
  const errorMap: Record<number, string> = {
    400: '请求参数错误',
    401: '未授权,请登录',
    403: '拒绝访问',
    404: '请求资源不存在',
    408: '请求超时',
    500: '服务器内部错误',
    502: '网关错误',
    503: '服务不可用',
    504: '网关超时'
  };
  return errorMap[statusCode] || `未知错误: ${statusCode}`;
}

十、最佳实践与性能优化

10.1 请求并发控制

// 限制并发请求数量
async function batchRequest(urls: string[], maxConcurrent: number = 5) {
  const results = [];
  for (let i = 0; i < urls.length; i += maxConcurrent) {
    const batch = urls.slice(i, i + maxConcurrent);
    const batchResults = await Promise.all(
      batch.map(url => httpRequest.get(url))
    );
    results.push(...batchResults);
  }
  return results;
}

10.2 请求缓存策略

const cacheMap = new Map<string, { data: any; timestamp: number }>();
const CACHE_DURATION = 5 * 60 * 1000; // 5 分钟缓存

async function getWithCache<T>(url: string): Promise<T> {
  const cached = cacheMap.get(url);
  if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
    return cached.data;
  }

  const result = await httpRequest.get<T>(url);
  cacheMap.set(url, { data: result.data, timestamp: Date.now() });
  return result.data;
}

10.3 请求重试机制

async function requestWithRetry(
  requestFn: () => Promise<any>,
  maxRetries: number = 3,
  delay: number = 1000
): Promise<any> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await requestFn();
    } catch (err) {
      if (i === maxRetries - 1) throw err;
      console.warn(`请求失败,${delay}ms 后重试 (${i + 1}/${maxRetries})`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

总结

本文从基础到进阶,完整介绍了鸿蒙网络请求的各个方面:

内容

说明

权限配置

ohos.permission.INTERNET 必须声明

HttpURLConnection

原生 API,无需额外依赖

GET/POST/PUT/DELETE

覆盖 RESTful 全部请求方式

文件上传

支持 multipart 和系统 upload API

axios 第三方库

更简洁的 API,支持拦截器

工具类封装

统一管理请求配置和错误处理

拦截器

请求/响应拦截,统一添加 token

性能优化

并发控制、缓存策略、重试机制

选择建议

  • 简单项目:直接使用 http 模块

  • 复杂项目:封装工具类 + 拦截器

  • 团队协作:使用 axios 等成熟库

掌握这些知识,你就能应对鸿蒙开发中 99% 的网络请求场景了!


📚 系列文章推荐


标签鸿蒙网络请求 HTTP axios 网络编程 HarmonyOS HttpURLConnection 文件上传 请求封装


💡 下期预告:WebSocket 实时通信实战,实现聊天室功能!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐