HarmonyOS 5开发从入门到精通(七):网络请求与数据获取

HarmonyOS 5开发从入门到精通(七):网络请求与数据获取

网络请求是现代移动应用与外界交互的核心能力,无论是获取数据、提交表单还是实时通信,都离不开网络请求的支持。HarmonyOS 5提供了强大的网络请求能力,支持HTTP/HTTPS、WebSocket等多种协议。本篇将深入讲解网络请求的实现、优化和实战应用。

一、网络请求基础配置

1.1 权限配置

在发起网络请求前,需要在配置文件中声明网络权限:

// entry/src/main/module.json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:internet_permission_reason"
      },
      {
        "name": "ohos.permission.GET_NETWORK_INFO",
        "reason": "$string:network_info_permission_reason"
      }
    ]
  }
}

1.2 网络状态检测

在发起请求前,建议先检测网络状态:

import netConnection from '@ohos.net.connection';
import { BusinessError } from '@ohos.base';

class NetworkUtils {
  // 检测网络是否可用
  static async isNetworkAvailable(): Promise<boolean> {
    try {
      const netCap = await netConnection.getDefaultNet();
      if (!netCap) {
        return false;
      }
      const netInfo = await netConnection.getConnectionProperties(netCap);
      return netInfo.isAvailable;
    } catch (error) {
      console.error(`检测网络失败: ${(error as BusinessError).message}`);
      return false;
    }
  }

  // 获取网络类型
  static getNetworkType(): string {
    const netCap = netConnection.getDefaultNetSync();
    return netCap ? netCap.type : 'unknown';
  }
}

二、HTTP请求基础

2.1 导入HTTP模块

import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';

2.2 GET请求

// 创建HTTP请求实例
let httpRequest = http.createHttp();

// 发起GET请求
httpRequest.request(
  'https://jsonplaceholder.typicode.com/posts',
  {
    method: http.RequestMethod.GET,
    connectTimeout: 10000,  // 连接超时10秒
    readTimeout: 15000,     // 读取超时15秒
    header: {
      'Content-Type': 'application/json'
    }
  },
  (err: BusinessError, data: http.HttpResponse) => {
    if (err) {
      console.error(`请求失败: ${err.message}`);
      return;
    }
    
    if (data.responseCode === 200) {
      const result = JSON.parse(data.result as string);
      console.info('请求成功:', result);
    } else {
      console.error(`请求失败,状态码: ${data.responseCode}`);
    }
  }
);

2.3 POST请求

// 发起POST请求
httpRequest.request(
  'https://jsonplaceholder.typicode.com/posts',
  {
    method: http.RequestMethod.POST,
    connectTimeout: 10000,
    readTimeout: 15000,
    header: {
      'Content-Type': 'application/json'
    },
    extraData: JSON.stringify({
      title: '测试标题',
      body: '测试内容',
      userId: 1
    })
  },
  (err: BusinessError, data: http.HttpResponse) => {
    if (err) {
      console.error(`请求失败: ${err.message}`);
      return;
    }
    
    if (data.responseCode === 201) {
      const result = JSON.parse(data.result as string);
      console.info('创建成功:', result);
    } else {
      console.error(`创建失败,状态码: ${data.responseCode}`);
    }
  }
);

2.4 响应头事件监听

// 订阅响应头事件
httpRequest.on('headersReceive', (header: Object) => {
  console.info('响应头:', header);
});

// 取消订阅
httpRequest.off('headersReceive');

2.5 请求销毁

// 销毁请求实例
httpRequest.destroy();

三、网络请求封装优化

3.1 统一请求封装

// utils/request.ts
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';

interface RequestOptions {
  url: string;
  method: http.RequestMethod;
  data?: any;
  headers?: Record<string, string>;
  connectTimeout?: number;
  readTimeout?: number;
}

interface Response<T = any> {
  code: number;
  data: T;
  message: string;
}

class Request {
  private static instance: Request;
  private httpRequest: http.HttpRequest;

  private constructor() {
    this.httpRequest = http.createHttp();
  }

  static getInstance(): Request {
    if (!Request.instance) {
      Request.instance = new Request();
    }
    return Request.instance;
  }

  async request<T>(options: RequestOptions): Promise<Response<T>> {
    return new Promise((resolve, reject) => {
      this.httpRequest.request(
        options.url,
        {
          method: options.method,
          connectTimeout: options.connectTimeout || 10000,
          readTimeout: options.readTimeout || 15000,
          header: {
            'Content-Type': 'application/json',
            ...options.headers
          },
          extraData: options.data ? JSON.stringify(options.data) : undefined
        },
        (err: BusinessError, data: http.HttpResponse) => {
          if (err) {
            reject(new Error(`网络请求失败: ${err.message}`));
            return;
          }

          if (data.responseCode >= 200 && data.responseCode < 300) {
            try {
              const result = data.result ? JSON.parse(data.result as string) : {};
              resolve({
                code: data.responseCode,
                data: result,
                message: '请求成功'
              });
            } catch (parseError) {
              reject(new Error('数据解析失败'));
            }
          } else {
            reject(new Error(`请求失败,状态码: ${data.responseCode}`));
          }
        }
      );
    });
  }

  // GET请求
  async get<T>(url: string, params?: Record<string, any>, headers?: Record<string, string>): Promise<Response<T>> {
    let requestUrl = url;
    if (params) {
      const queryParams = new URLSearchParams(params).toString();
      requestUrl = `${url}?${queryParams}`;
    }
    return this.request<T>({
      url: requestUrl,
      method: http.RequestMethod.GET,
      headers
    });
  }

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

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

  // DELETE请求
  async delete<T>(url: string, headers?: Record<string, string>): Promise<Response<T>> {
    return this.request<T>({
      url,
      method: http.RequestMethod.DELETE,
      headers
    });
  }

  destroy() {
    this.httpRequest.destroy();
  }
}

export default Request.getInstance();

3.2 拦截器实现

// utils/interceptor.ts
import { BusinessError } from '@ohos.base';

interface Interceptor {
  onRequest?: (config: any) => Promise<any> | any;
  onResponse?: (response: any) => Promise<any> | any;
  onError?: (error: BusinessError) => Promise<any> | any;
}

class InterceptorManager {
  private interceptors: Interceptor[] = [];

  use(interceptor: Interceptor): number {
    this.interceptors.push(interceptor);
    return this.interceptors.length - 1;
  }

  eject(id: number): void {
    this.interceptors.splice(id, 1);
  }

  async runRequestInterceptors(config: any): Promise<any> {
    let currentConfig = config;
    for (const interceptor of this.interceptors) {
      if (interceptor.onRequest) {
        currentConfig = await interceptor.onRequest(currentConfig);
      }
    }
    return currentConfig;
  }

  async runResponseInterceptors(response: any): Promise<any> {
    let currentResponse = response;
    for (const interceptor of this.interceptors) {
      if (interceptor.onResponse) {
        currentResponse = await interceptor.onResponse(currentResponse);
      }
    }
    return currentResponse;
  }

  async runErrorInterceptors(error: BusinessError): Promise<any> {
    let currentError = error;
    for (const interceptor of this.interceptors) {
      if (interceptor.onError) {
        currentError = await interceptor.onError(currentError);
      }
    }
    return currentError;
  }
}

export default new InterceptorManager();

3.3 带拦截器的请求封装

// utils/requestWithInterceptor.ts
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';
import interceptorManager from './interceptor';

class RequestWithInterceptor {
  private httpRequest: http.HttpRequest;

  constructor() {
    this.httpRequest = http.createHttp();
  }

  async request<T>(options: any): Promise<T> {
    try {
      // 执行请求拦截器
      const config = await interceptorManager.runRequestInterceptors(options);

      return new Promise((resolve, reject) => {
        this.httpRequest.request(
          config.url,
          {
            method: config.method,
            connectTimeout: config.connectTimeout || 10000,
            readTimeout: config.readTimeout || 15000,
            header: config.headers,
            extraData: config.data ? JSON.stringify(config.data) : undefined
          },
          async (err: BusinessError, data: http.HttpResponse) => {
            if (err) {
              const error = await interceptorManager.runErrorInterceptors(err);
              reject(error);
              return;
            }

            try {
              let responseData = data.result ? JSON.parse(data.result as string) : {};
              if (data.responseCode >= 200 && data.responseCode < 300) {
                // 执行响应拦截器
                responseData = await interceptorManager.runResponseInterceptors({
                  code: data.responseCode,
                  data: responseData,
                  message: '请求成功'
                });
                resolve(responseData);
              } else {
                const error = new Error(`请求失败,状态码: ${data.responseCode}`);
                const processedError = await interceptorManager.runErrorInterceptors(error as BusinessError);
                reject(processedError);
              }
            } catch (parseError) {
              const error = new Error('数据解析失败');
              const processedError = await interceptorManager.runErrorInterceptors(error as BusinessError);
              reject(processedError);
            }
          }
        );
      });
    } catch (error) {
      const processedError = await interceptorManager.runErrorInterceptors(error as BusinessError);
      throw processedError;
    }
  }

  destroy() {
    this.httpRequest.destroy();
  }
}

export default RequestWithInterceptor;

四、网络请求优化策略

4.1 多级缓存架构

// utils/cache.ts
interface CacheItem {
  data: any;
  timestamp: number;
  ttl: number; // 缓存有效时间(毫秒)
}

class CacheManager {
  private memoryCache: Map<string, CacheItem> = new Map();
  private static instance: CacheManager;

  static getInstance(): CacheManager {
    if (!CacheManager.instance) {
      CacheManager.instance = new CacheManager();
    }
    return CacheManager.instance;
  }

  set(key: string, data: any, ttl: number = 5 * 60 * 1000): void {
    this.memoryCache.set(key, {
      data,
      timestamp: Date.now(),
      ttl
    });
  }

  get(key: string): any | null {
    const item = this.memoryCache.get(key);
    if (!item) {
      return null;
    }

    // 检查缓存是否过期
    if (Date.now() - item.timestamp > item.ttl) {
      this.memoryCache.delete(key);
      return null;
    }

    return item.data;
  }

  delete(key: string): void {
    this.memoryCache.delete(key);
  }

  clear(): void {
    this.memoryCache.clear();
  }
}

export default CacheManager.getInstance();

4.2 请求合并与去重

// utils/requestManager.ts
import { BusinessError } from '@ohos.base';

class RequestManager {
  private pendingRequests: Map<string, Promise<any>> = new Map();
  private static instance: RequestManager;

  static getInstance(): RequestManager {
    if (!RequestManager.instance) {
      RequestManager.instance = new RequestManager();
    }
    return RequestManager.instance;
  }

  async execute<T>(key: string, request: () => Promise<T>): Promise<T> {
    // 如果已有相同请求正在进行,直接返回该请求的Promise
    if (this.pendingRequests.has(key)) {
      return this.pendingRequests.get(key) as Promise<T>;
    }

    const promise = request()
      .then(response => {
        this.pendingRequests.delete(key);
        return response;
      })
      .catch(error => {
        this.pendingRequests.delete(key);
        throw error;
      });

    this.pendingRequests.set(key, promise);
    return promise;
  }

  cancel(key: string): void {
    this.pendingRequests.delete(key);
  }

  clear(): void {
    this.pendingRequests.clear();
  }
}

export default RequestManager.getInstance();

4.3 智能重试机制

// utils/retry.ts
import { BusinessError } from '@ohos.base';

interface RetryOptions {
  maxRetries?: number;
  retryDelay?: number;
  shouldRetry?: (error: BusinessError) => boolean;
}

class RetryManager {
  static async withRetry<T>(
    operation: () => Promise<T>,
    options: RetryOptions = {}
  ): Promise<T> {
    const {
      maxRetries = 3,
      retryDelay = 1000,
      shouldRetry = (error: BusinessError) => true
    } = options;

    let lastError: BusinessError;
    let attempt = 0;

    while (attempt <= maxRetries) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as BusinessError;
        attempt++;

        if (attempt > maxRetries || !shouldRetry(lastError)) {
          break;
        }

        // 指数退避策略
        const delay = retryDelay * Math.pow(2, attempt - 1);
        console.info(`请求失败,第${attempt}次重试,等待${delay}ms`);
        await this.delay(delay);
      }
    }

    throw lastError;
  }

  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

export default RetryManager;

五、网络请求实战案例

5.1 用户登录案例

// services/authService.ts
import request from '../utils/request';

interface LoginParams {
  username: string;
  password: string;
}

interface LoginResponse {
  token: string;
  userInfo: {
    id: number;
    username: string;
    avatar: string;
  };
}

class AuthService {
  async login(params: LoginParams): Promise<LoginResponse> {
    const response = await request.post<LoginResponse>('/api/auth/login', params);
    return response.data;
  }

  async logout(): Promise<void> {
    await request.post('/api/auth/logout');
  }

  async getProfile(): Promise<any> {
    const response = await request.get('/api/auth/profile');
    return response.data;
  }
}

export default new AuthService();

5.2 商品列表案例

// services/productService.ts
import request from '../utils/request';
import CacheManager from '../utils/cache';

interface Product {
  id: number;
  name: string;
  price: number;
  image: string;
  description: string;
}

interface ProductListParams {
  page: number;
  pageSize: number;
  category?: string;
  keyword?: string;
}

class ProductService {
  private cacheKey = 'product_list';

  async getProducts(params: ProductListParams): Promise<Product[]> {
    const cacheKey = `${this.cacheKey}_${JSON.stringify(params)}`;
    
    // 检查缓存
    const cachedData = CacheManager.get(cacheKey);
    if (cachedData) {
      return cachedData;
    }

    const response = await request.get<Product[]>('/api/products', params);
    
    // 缓存数据,有效期5分钟
    CacheManager.set(cacheKey, response.data, 5 * 60 * 1000);
    
    return response.data;
  }

  async getProductDetail(id: number): Promise<Product> {
    const cacheKey = `product_detail_${id}`;
    
    const cachedData = CacheManager.get(cacheKey);
    if (cachedData) {
      return cachedData;
    }

    const response = await request.get<Product>(`/api/products/${id}`);
    
    // 缓存详情,有效期10分钟
    CacheManager.set(cacheKey, response.data, 10 * 60 * 1000);
    
    return response.data;
  }

  async searchProducts(keyword: string): Promise<Product[]> {
    const response = await request.get<Product[]>('/api/products/search', { keyword });
    return response.data;
  }
}

export default new ProductService();

5.3 文件上传案例

// services/uploadService.ts
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';

class UploadService {
  async uploadFile(fileUri: string, onProgress?: (progress: number) => void): Promise<string> {
    return new Promise((resolve, reject) => {
      const httpRequest = http.createHttp();
      
      httpRequest.request(
        '/api/upload',
        {
          method: http.RequestMethod.POST,
          header: {
            'Content-Type': 'multipart/form-data'
          },
          extraData: {
            file: {
              filename: 'image.jpg',
              uri: fileUri,
              type: 'image/jpeg'
            }
          }
        },
        (err: BusinessError, data: http.HttpResponse) => {
          if (err) {
            reject(new Error(`上传失败: ${err.message}`));
            return;
          }
          
          if (data.responseCode === 200) {
            const result = JSON.parse(data.result as string);
            resolve(result.url);
          } else {
            reject(new Error(`上传失败,状态码: ${data.responseCode}`));
          }
        }
      );
      
      // 监听上传进度
      httpRequest.on('dataProgress', (progress: number) => {
        if (onProgress) {
          onProgress(progress);
        }
      });
    });
  }
}

export default new UploadService();

六、网络请求状态管理

6.1 加载状态管理

// types/loadState.ts
export class LoadState<T> {
  static readonly INITIAL = new LoadState<any>('initial', undefined, undefined);
  static readonly LOADING = new LoadState<any>('loading', undefined, undefined);
  static readonly SUCCESS = <T>(data: T) => new LoadState<T>('success', data, undefined);
  static readonly ERROR = (error: Error) => new LoadState<any>('error', undefined, error);

  private constructor(
    private readonly status: 'initial' | 'loading' | 'success' | 'error',
    private readonly data?: T,
    private readonly error?: Error
  ) {}

  isInitial(): boolean {
    return this.status === 'initial';
  }

  isLoading(): boolean {
    return this.status === 'loading';
  }

  isSuccess(): boolean {
    return this.status === 'success';
  }

  isError(): boolean {
    return this.status === 'error';
  }

  getData(): T | undefined {
    return this.data;
  }

  getError(): Error | undefined {
    return this.error;
  }

  map<U>(mapper: (data: T) => U): LoadState<U> {
    if (this.isSuccess()) {
      return LoadState.SUCCESS(mapper(this.data!));
    }
    return new LoadState<U>(this.status, undefined, this.error);
  }
}

6.2 状态管理ViewModel

// viewmodels/productViewModel.ts
import { ViewModel } from '@ohos.arkui';
import productService from '../services/productService';
import { LoadState } from '../types/loadState';

interface Product {
  id: number;
  name: string;
  price: number;
  image: string;
  description: string;
}

export class ProductViewModel extends ViewModel {
  @State products: LoadState<Product[]> = LoadState.INITIAL;
  @State currentPage: number = 1;
  @State hasMore: boolean = true;
  @State isLoadingMore: boolean = false;

  async loadProducts(page: number = 1) {
    try {
      this.products = LoadState.LOADING;
      const data = await productService.getProducts({
        page,
        pageSize: 20
      });
      this.products = LoadState.SUCCESS(data);
      this.currentPage = page;
      this.hasMore = data.length === 20;
    } catch (error) {
      this.products = LoadState.ERROR(error as Error);
    }
  }

  async loadMore() {
    if (this.isLoadingMore || !this.hasMore) {
      return;
    }

    try {
      this.isLoadingMore = true;
      const nextPage = this.currentPage + 1;
      const data = await productService.getProducts({
        page: nextPage,
        pageSize: 20
      });

      if (this.products.isSuccess()) {
        const currentData = this.products.getData() || [];
        this.products = LoadState.SUCCESS([...currentData, ...data]);
      } else {
        this.products = LoadState.SUCCESS(data);
      }

      this.currentPage = nextPage;
      this.hasMore = data.length === 20;
    } catch (error) {
      console.error('加载更多失败:', error);
    } finally {
      this.isLoadingMore = false;
    }
  }

  async refresh() {
    await this.loadProducts(1);
  }
}

七、网络请求最佳实践

7.1 错误处理策略

// utils/errorHandler.ts
import { BusinessError } from '@ohos.base';
import prompt from '@ohos.prompt';

class ErrorHandler {
  static handleNetworkError(error: BusinessError): void {
    const errorCode = error.code;
    
    switch (errorCode) {
      case 401:
        // 未授权,跳转到登录页
        this.showToast('登录已过期,请重新登录');
        break;
      case 403:
        this.showToast('权限不足');
        break;
      case 404:
        this.showToast('请求的资源不存在');
        break;
      case 500:
        this.showToast('服务器内部错误');
        break;
      case 503:
        this.showToast('服务不可用');
        break;
      default:
        this.showToast('网络请求失败,请稍后重试');
    }
  }

  static handleBusinessError(error: any): void {
    if (error.message) {
      this.showToast(error.message);
    } else {
      this.showToast('操作失败,请稍后重试');
    }
  }

  private static showToast(message: string): void {
    prompt.showToast({
      message,
      duration: 2000
    });
  }
}

export default ErrorHandler;

7.2 弱网环境适配

// utils/networkAdapter.ts
import netConnection from '@ohos.net.connection';
import { BusinessError } from '@ohos.base';

class NetworkAdapter {
  private static instance: NetworkAdapter;
  private currentNetworkType: string = 'unknown';

  static getInstance(): NetworkAdapter {
    if (!NetworkAdapter.instance) {
      NetworkAdapter.instance = new NetworkAdapter();
    }
    return NetworkAdapter.instance;
  }

  constructor() {
    this.initNetworkListener();
  }

  private initNetworkListener(): void {
    netConnection.on('netAvailable', (netHandle: netConnection.NetHandle) => {
      this.updateNetworkType();
    });

    netConnection.on('netCapabilitiesChange', () => {
      this.updateNetworkType();
    });
  }

  private async updateNetworkType(): Promise<void> {
    try {
      const netCap = await netConnection.getDefaultNet();
      if (netCap) {
        this.currentNetworkType = netCap.type;
      }
    } catch (error) {
      console.error('获取网络类型失败:', error);
    }
  }

  isWeakNetwork(): boolean {
    return this.currentNetworkType === 'cellular' || this.currentNetworkType === 'unknown';
  }

  getNetworkType(): string {
    return this.currentNetworkType;
  }

  shouldUseLowQualityImage(): boolean {
    return this.isWeakNetwork();
  }

  shouldReduceRequest(): boolean {
    return this.isWeakNetwork();
  }
}

export default NetworkAdapter.getInstance();

八、总结

通过本篇学习,您已经掌握了:

网络请求基础:GET、POST请求的实现,权限配置和网络状态检测

请求封装优化:统一请求封装、拦截器机制、多级缓存架构

性能优化策略:请求合并与去重、智能重试机制、弱网环境适配

状态管理:加载状态管理、ViewModel模式的应用

实战案例:用户登录、商品列表、文件上传等完整案例

最佳实践:错误处理策略、网络适配、性能监控

关键知识点回顾

  • 使用@ohos.net.http模块发起HTTP请求
  • 合理设置超时时间和请求头
  • 通过拦截器统一处理Token、错误等逻辑
  • 使用多级缓存减少网络请求次数
  • 通过状态管理实现优雅的加载和错误处理
  • 弱网环境下自动降级策略

下一篇我们将学习本地数据存储,掌握如何在设备本地持久化存储应用数据,包括Preferences、关系型数据库等存储方案。

posted @ 2025-12-23 20:00  奇崽  阅读(0)  评论(0)    收藏  举报