HarmonyOS APP开发---"菜谱汇"美食菜谱App,需要用到这个库
菜谱数据分散在三个平台,要同时拉取、合并、按评分排序。麻烦的是三个源的鉴权方式还不一样:一个用 Header Token,一个用 Query 参数,一个要签名。
@ohos/axios的多实例 + 拦截器,让每个源的鉴权各管各的,业务代码一行都不用重复。
📦 仓库地址 |ohpm install @ohos/axios
场景
"菜谱汇"把下厨房、美食杰、豆果的菜谱聚到一处,用户搜"川菜"能一次看到全网结果。
三件事要做:
- 三个源并发请求,合并去重后按评分排序
- 每个源的鉴权方式不同,得隔离管理
- 菜谱封面图下载到本地缓存,要显示进度条
用原生 @ohos.net.http 的话,三套鉴权逻辑、三次回调嵌套、三次文件流处理------全是重复代码。
为什么是 axios
| 需求 | 原生 http | @ohos/axios |
|---|---|---|
| 三源并发 | 手写 Promise.all + 回调 | axios.all 一行 |
| 各源独立鉴权 | 每次手动加 header | 多实例拦截器 |
| 错误兜底 | 每个请求各写 catch | 响应拦截器统一 |
| 下载进度 | 手写流式处理 | onDownloadProgress |
| 某源挂了要容错 | 手写 | Promise.allSettled |
关键实现
每个源一个实例,鉴权互不干扰
typescript
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:
typescript
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
})
}
封面图下载带进度
typescript
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 的容错和现成的下载进度回调,一个菜谱聚合页半天就能写完。