HarmonyOS APP开发---"菜谱汇"美食菜谱App,需要用到这个库

7 阅读2分钟

HarmonyOS APP开发---"菜谱汇"美食菜谱App,需要用到这个库

菜谱数据分散在三个平台,要同时拉取、合并、按评分排序。麻烦的是三个源的鉴权方式还不一样:一个用 Header Token,一个用 Query 参数,一个要签名。@ohos/axios 的多实例 + 拦截器,让每个源的鉴权各管各的,业务代码一行都不用重复。

📦 仓库地址 | ohpm install @ohos/axios


场景

"菜谱汇"把下厨房、美食杰、豆果的菜谱聚到一处,用户搜"川菜"能一次看到全网结果。

三件事要做:

  1. 三个源并发请求,合并去重后按评分排序
  2. 每个源的鉴权方式不同,得隔离管理
  3. 菜谱封面图下载到本地缓存,要显示进度条

用原生 @ohos.net.http 的话,三套鉴权逻辑、三次回调嵌套、三次文件流处理——全是重复代码。


为什么是 axios

需求原生 http@ohos/axios
三源并发手写 Promise.all + 回调axios.all 一行
各源独立鉴权每次手动加 header多实例拦截器
错误兜底每个请求各写 catch响应拦截器统一
下载进度手写流式处理onDownloadProgress
某源挂了要容错手写Promise.allSettled

关键实现

每个源一个实例,鉴权互不干扰

import axios, { AxiosResponse } from '@ohos/axios'

interface Recipe { id: string; title: string; cover: string; rating: number }

// 源 A:Token 放 Header
const srcA = axios.create({ baseURL: 'https://api.xiachufang.com', timeout: 10000 })
srcA.interceptors.request.use((c) => { c.headers['Authorization'] = 'Bearer ' + tokenA; return c })

// 源 B:Token 放 Query
const srcB = axios.create({ baseURL: 'https://api.meishij.com', timeout: 10000 })
srcB.interceptors.request.use((c) => { c.params = { ...c.params, api_key: tokenB }; return c })

// 源 C:需要签名
const srcC = axios.create({ baseURL: 'https://api.douguo.com', timeout: 10000 })
srcC.interceptors.request.use((c) => { c.headers['X-Sign'] = sign(c.url); return c })

鉴权逻辑各写一次,之后再怎么加请求都不用管——这是拦截器最省心的地方。

并发拉取 + 容错

某个源挂了不该让整个页面白屏,用 allSettled:

async function fetchRecipes(category: string): Promise<Recipe[]> {
  const url = `/recipes?category=${category}`

  const results = await Promise.allSettled([
    srcA.get<Recipe[], AxiosResponse<Recipe[]>, null>(url),
    srcB.get<Recipe[], AxiosResponse<Recipe[]>, null>(url),
    srcC.get<Recipe[], AxiosResponse<Recipe[]>, null>(url),
  ])

  const all: Recipe[] = []
  results.forEach((r, i) => {
    if (r.status === 'fulfilled') all.push(...r.value.data)
    else console.warn(`源 ${i} 失败已跳过: ${r.reason}`)
  })

  // 按评分排序 + 同名去重
  const seen = new Set<string>()
  return all.sort((a, b) => b.rating - a.rating).filter((r) => {
    if (seen.has(r.title)) return false
    seen.add(r.title); return true
  })
}

封面图下载带进度

import fs from '@ohos.file.fs'

async function downloadCover(recipe: Recipe): Promise<string> {
  const filePath = getContext(this).cacheDir + `/cover_${recipe.id}.jpg`

  try { fs.accessSync(filePath); return filePath } catch (e) { /* 未缓存,继续下载 */ }

  const res = await axios.get<ArrayBuffer, AxiosResponse<ArrayBuffer>, null>(recipe.cover, {
    responseType: 'array_buffer',
    onDownloadProgress: (e) => {
      this.percent = e?.total ? Math.ceil(e.loaded / e.total * 100) : 0
    },
  })

  const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
  fs.writeSync(file.fd, res.data)
  fs.closeSync(file)
  return filePath
}

一个容易踩的点

设了 onDownloadProgress 之后,请求会自动切到流式模式,此时 response.performanceTiming 会是 undefined。

如果你既想要进度条又想要性能计时数据,两者拿不到一起——按业务优先级选一个。菜谱列表页要进度条,就别指望同时拿 DNS/TCP 耗时。


小结

聚合类 App 的痛点从来不是"发请求",而是多个数据源的差异化处理。@ohos/axios 的多实例拦截器把鉴权、错误、日志这些横切关注点全收敛掉,业务代码只剩下"拉数据、合并、渲染"。

再加上 allSettled 的容错和现成的下载进度回调,一个菜谱聚合页半天就能写完。