网络请求与数据交互:http 模块、拦截器与状态封装

网络请求与数据交互:http 模块、拦截器与状态封装

几乎所有应用都要联网。鸿蒙通过 @kit.NetworkKithttp 模块发起请求。本文讲清 GET/POST、请求头、文件上传、错误处理、JSON 封装,以及如何用拦截器与状态机把网络层做得可维护、可测试。


一、权限与基础请求

网络请求需在 module.json5 声明权限:

json5 复制代码
"requestPermissions": [
  { "name": "ohos.permission.INTERNET" }
]

最简 GET 请求:

typescript 复制代码
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

async function getProducts(): Promise<Product[]> {
  const req = http.createHttp();
  try {
    const resp = await req.request(
      'https://api.example.com/products',
      { method: http.RequestMethod.GET, connectTimeout: 10000 }
    );
    if (resp.responseCode === 200) {
      return JSON.parse(resp.result as string) as Product[];
    }
    throw new Error('HTTP ' + resp.responseCode);
  } finally {
    req.destroy(); // 务必释放,避免资源泄漏
  }
}

http.createHttp() 返回的对象用完要 destroy(),否则连接泄漏。


二、POST 与请求体

typescript 复制代码
async function login(username: string, pwd: string): Promise<void> {
  const req = http.createHttp();
  const resp = await req.request('https://api.example.com/login', {
    method: http.RequestMethod.POST,
    header: { 'Content-Type': 'application/json' },
    extraData: JSON.stringify({ username, password: pwd }),
    expectDataType: http.HttpDataType.OBJECT // 直接解析为对象
  });
  req.destroy();
  const body = resp.result as Product; // 已是对象
  console.info('登录结果:' + JSON.stringify(body));
}

expectDataType: OBJECT 让框架自动 JSON 解析,省去手写 JSON.parse


三、统一封装:ApiClient

把重复逻辑(baseURL、token 注入、错误统一处理)抽成单例客户端:

typescript 复制代码
class ApiClient {
  private baseUrl = 'https://api.example.com';
  private token: string = '';

  setToken(t: string): void { this.token = t; }

  async request<T>(path: string, options: http.HttpOptions): Promise<T> {
    const req = http.createHttp();
    try {
      const resp = await req.request(this.baseUrl + path, {
        ...options,
        header: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + this.token,
          ...(options.header ?? {})
        }
      });
      if (resp.responseCode >= 200 && resp.responseCode < 300) {
        return resp.result as T;
      }
      // 统一错误
      if (resp.responseCode === 401) {
        throw new ApiError('未授权', 401);
      }
      throw new ApiError('请求失败', resp.responseCode);
    } finally {
      req.destroy();
    }
  }

  get<T>(path: string): Promise<T> {
    return this.request<T>(path, { method: http.RequestMethod.GET });
  }

  post<T>(path: string, data: Object): Promise<T> {
    return this.request<T>(path, {
      method: http.RequestMethod.POST,
      extraData: JSON.stringify(data)
    });
  }
}

export const api = new ApiClient();

class ApiError extends Error {
  code: number;
  constructor(msg: string, code: number) { super(msg); this.code = code; }
}

这样业务层只写 api.get<Product[]>('/products'),token 与错误自动处理。


四、拦截器思路

ArkTS 没有内置拦截器,但可在 ApiClient 里用中间件函数模拟:

typescript 复制代码
type Interceptor = (req: http.HttpOptions) => http.HttpOptions;

private interceptors: Interceptor[] = [
  (opt) => ({ ...opt, header: { ...opt.header, 'X-Client': 'HarmonyOS' } })
];

async request<T>(path: string, options: http.HttpOptions): Promise<T> {
  let opt = options;
  this.interceptors.forEach(i => { opt = i(opt); });
  // ... 发请求
}

需要统一加签名、埋点、日志时,往 interceptors 数组加函数即可。


五、文件上传与下载

上传(multipart 形式可用 requestmultipart 字段,或第三方库)。下载用 request.downloadFile

typescript 复制代码
import { request } from '@kit.ArkTS';

async function download(url: string): Promise<string> {
  const task = await request.downloadFile(getContext(this), {
    url,
    filePath: getContext(this).filesDir + '/file.apk'
  });
  return new Promise((resolve, reject) => {
    task.on('complete', () => resolve('下载完成'));
    task.on('fail', (err: BusinessError) => reject(err));
  });
}

六、网络状态监听

@kit.NetworkKitconnection 监听网络变化,断网时提示:

typescript 复制代码
import { connection } from '@kit.NetworkKit';

connection.getDefaultNet().then((netHandle) => {
  connection.getNetCapabilities(netHandle).then((cap) => {
    const hasNet = cap.networkCap !== undefined;
    console.info('有网络:' + hasNet);
  });
});

// 订阅变化
connection.on('netAvailable', () => { console.info('网络恢复'); });
connection.on('netUnavailable', () => { console.info('网络断开'); });

七、在组件中优雅使用

结合状态管理,把"加载中/数据/错误"三态写出来:

typescript 复制代码
@Entry
@Component
struct ProductList {
  @State loading: boolean = false;
  @State list: Product[] = [];
  @State error: string = '';

  aboutToAppear(): void { this.load(); }

  async load(): Promise<void> {
    this.loading = true;
    this.error = '';
    try {
      this.list = await api.get<Product[]>('/products');
    } catch (e) {
      this.error = (e as Error).message;
    } finally {
      this.loading = false;
    }
  }

  build() {
    Column() {
      if (this.loading) {
        LoadingProgress()
      } else if (this.error !== '') {
        Text('加载失败:' + this.error)
        Button('重试').onClick(() => { this.load(); })
      } else {
        List() {
          ForEach(this.list, (p: Product) => {
            ListItem() { Text(p.name) }
          }, (p: Product) => p.id.toString())
        }
      }
    }
  }
}

这种"三态"模式是网络数据展示的标准写法。


八、防抖与竞态

快速输入搜索时,要防抖 + 取消旧请求,避免旧结果覆盖新结果:

typescript 复制代码
private timer: number = -1;

onSearchChange(v: string): void {
  clearTimeout(this.timer);
  this.timer = setTimeout(() => { this.doSearch(v); }, 300);
}

http 请求对象可调用 req.destroy() 中断进行中的请求,处理竞态更彻底。


九、Mock 与测试

开发期可用本地 Mock 数据,避免依赖后端:

typescript 复制代码
async function getProducts(): Promise<Product[]> {
  if (BuildProfile.DEBUG) {
    return [{ id: 1, name: 'Mock商品', price: 99 }]; // 本地假数据
  }
  return api.get<Product[]>('/products');
}

配合接口契约,前后端可并行开发。


十、安全注意事项

  • 不要在客户端硬编码密钥 ,敏感信息放服务端或用 Asset 存储。
  • HTTPS 必须开启证书校验(默认开启,勿随意关闭)。
  • 用户输入防注入:参数走 body,不拼 URL。
  • token 用 @StorageLink + 安全存储,别明文写文件。

十一、常见坑

现象 原因 解决
请求无响应 未申 INTERNET 权限 module.json5 加权限
连接泄漏 忘记 destroy finally 中 destroy
中文乱码 未设 UTF-8 header 加 charset
解析报错 result 是 string 不是对象 用 expectDataType: OBJECT
真机失败模拟器成功 模拟器无网络限制 检查网络/代理

十二、总结

网络层的质量直接决定应用稳定性。本文从基础 http 请求,到统一 ApiClient 封装、拦截器、文件下载、网络监听、三态展示、防抖竞态与安全防护,给出了一套可落地的方案。核心原则:统一出口(一个 ApiClient)、统一错误、统一 token、请求必 destroy。把网络细节收进封装层,业务页面只关心"拿数据",代码既干净又稳健。

相关推荐
●VON19 小时前
鸿蒙 PC Markdown 编辑器工程基线:用 PRD、ADR 与阶段闸门控制技术风险
华为·性能优化·编辑器·harmonyos·鸿蒙
解局易否结局19 小时前
鸿蒙端侧 NLP 实战:分词器 + TF-IDF + 朴素贝叶斯分类 + 文本相似度
华为·自然语言处理·分类·harmonyos·tf-idf
世人万千丶20 小时前
鸿蒙Flutter Image图片组件
flutter·华为·harmonyos·鸿蒙·鸿蒙系统
●VON20 小时前
鸿蒙 PC Markdown 编辑器桌面效率:查找面板焦点与快捷键路由
华为·编辑器·harmonyos·鸿蒙
解局易否结局20 小时前
鸿蒙跨平台开发实战:应用权限管理体系——从声明到运行时的全链路实践
华为·harmonyos
YM52e20 小时前
鸿蒙Flutter Positioned定位组件:精确定位子组件
学习·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
kiros_wang20 小时前
鸿蒙面试高频考点
华为·面试·harmonyos
listening77721 小时前
HarmonyOS 6.1 空间计算初探:把电商展厅搬进“虚实融合”新世界
华为·harmonyos·空间计算
不羁的木木1 天前
HarmonyOS技术精讲-Connectivity Kit:实战——多屏协同与文件快传应用
华为·wpf·harmonyos