Network Kit+ArkTS:文搜图缩略图ETag再验证与过期结果隔离【鸿蒙心迹】
搜索结果里的小图并不起眼,却是最容易把“缓存命中”和“内容仍然有效”混为一谈的地方。列表回到前台,如果把旧缩略图原封不动展示,用户很难知道服务器是否替换过图片;如果每次都重新下载,又会增加流量、耗电和滚动等待。这里讨论一种可解释的折中:让服务端用 ETag 回答图片有没有变,同时让页面用查询序号判断这个回答还归不归当前搜索。本文采用构造数据和小规模接口契约示例,不宣称已经连通真实图片服务器。示意页面属于文搜图应用 Thumb304,任务 TH-1817,当前查询词“雨后栈桥”,候选图 IMG-207,缩略图尺寸 640×360,原有 ETag 为 "v3-207"。这组固定字段会在开发演示图和手机画面中保持一致。
## 一、304不是又收到了一张图片假设一张图片已经进入应用内缓存。再次查询时,客户端可以带 If-None-Match,把上次响应给的实体标签交给服务器判断。服务器选择返回 304 Not Modified,含义是该资源相对于条件未修改,不需要重发响应体;它并不是一份可直接解码的新图片数据。页面真正显示的仍是本地已经持有的二进制缩略图。还有一个被忽略的前提:缓存对象必须同时绑定资源 URL 和搜索上下文。旧查询“雨后栈桥”和新查询“河岸长椅”即使排在同一行,也不是同一张图。仅用列表位置或一个模糊的图片文件名作为键,等于让请求顺序替代资源身份。本文把键设计成查询标识、资源 ID 和请求 URL 的组合。查询变更后,上一轮网络响应可以完成,但没有权利改写新一轮页面。相反,假如服务器返回 304,而本地缓存已经被系统回收,客户端就没有可以展示的内容。正确处理是进入缺失分支,去掉条件头重新发起请求;不能把空响应当作一张透明图,更不能在 UI 上写“更新成功”。HTTP 缓存验证与业务状态更新是两回事,需要两个明确的判断点。## 二、先给缓存条目一个完整身份下面的类型不是 Network Kit 新增的接口,而是 Demo 自己管理的缓存契约。它解决一个问题:在收到 HTTP 状态码前,明确当前请求对应哪一份可复用的内容。querySeq 是当前搜索轮次,不是服务器返回的版本号;etag 必须使用服务端原文,不在客户端私自剥离双引号。tsinterface ThumbCache { cacheKey: string; querySeq: number; imageId: string; resourceUrl: string; etag: string; bytes: ArrayBuffer;}type CheckState = 'READY_304' | 'NEW_200' | 'STALE_DROP' | 'MISS_304' | 'HTTP_ERROR';function canReuse304(cache: ThumbCache | undefined, key: string, activeSeq: number, responseSeq: number): boolean { return cache !== undefined && cache.bytes.byteLength > 0 && cache.cacheKey === key && cache.querySeq === responseSeq && responseSeq === activeSeq;}这里的关键不是多加几个字段,而是把“文件仍在”和“请求仍属于我”拆开。缓存字节不为空只能证明本地有一份内容;序号相等才允许该内容出现在当前界面。CheckState 属于应用自身的状态常量,不是华为 SDK 的枚举。正式工程还要限制缓存大小、清理超期条目,并按照账号、清晰度和鉴权范围进一步细分键。### 1. 条件请求只携带可验证的标签请求逻辑要解决的是“如何发送条件头、如何处理返回值、如何释放 HTTP 对象”。下面使用 Network Kit 的 http.createHttp() 与 request();数据流采用二进制响应,方便缩略图后续交给图片解码模块。这里刻意不接入云端文搜图推理服务,以免把检索系统和缓存机制搅在一起。tsimport { http } from '@kit.NetworkKit';interface ThumbReply { code: number; data?: ArrayBuffer; etag: string;}async function fetchConditionalThumb(url: string, oldTag: string): Promise<ThumbReply> { const client = http.createHttp(); try { const resp = await client.request(url, { method: http.RequestMethod.GET, header: oldTag ? { 'If-None-Match': oldTag } : {}, connectTimeout: 5000, readTimeout: 8000, expectDataType: http.HttpDataType.ARRAY_BUFFER }); const h = resp.header as Record<string, string>; const etagKey = Object.keys(h).find(k => k.toLowerCase() === 'etag'); return { code: resp.responseCode, data: resp.result instanceof ArrayBuffer ? resp.result : undefined, etag: etagKey ? h[etagKey] : '' }; } finally { client.destroy(); }}````expectDataType`、超时值和状态处理要按目标 SDK 的声明再做编译确认。`finally` 不论请求成功或抛错都会调用 `destroy()`,避免页面连续搜索时积累网络任务对象。读取响应头时忽略键名大小写,是为了兼容常见服务端代理的写法;并不表示 ETag 字符串本身大小写无关。线上还应检查 HTTPS、返回的内容类型、最大字节数、HTTP 重定向目标与缓存控制头。若服务端不支持 ETag,就退回普通请求,不能凭空制造一个标签。## 三、网络结果先过查询门禁,再更新界面为了让边界更直观,考虑这样的顺序:搜索 A 已显示 IMG-207;用户立刻输入 B;A 的条件请求此时才返回 304。如果 UI 直接把 304 分支解释为“缓存可用”,旧图片就可能覆盖 B 的新搜索。正确顺序是先比对 `querySeq`,再比对缓存身份,最后考虑 HTTP 码。用户切换或离开页面时也要增加轮次,使未完成回调失效。下面的片段负责完成这个状态裁决。它不在回调里直接变更 PixelMap,而是先返回一个能记录、能测试的决策;调用层再把 `READY_304` 映射为“已验证复用”,把 `MISS_304` 映射为“去掉条件重试”。tsfunction decideThumb(reply: ThumbReply, cached: ThumbCache | undefined, cacheKey: string, activeSeq: number, requestSeq: number): CheckState { if (activeSeq !== requestSeq) return ‘STALE_DROP’; if (reply.code === 304) { return canReuse304(cached, cacheKey, activeSeq, requestSeq) ? ‘READY_304’ : ‘MISS_304’; } if (reply.code === 200 && reply.data && reply.data.byteLength > 0) { return ‘NEW_200’; } return ‘HTTP_ERROR’;}```这里容易误会的还有 NEW_200。它只表示新二进制已经收到,不意味着可以立刻放进长期缓存。正式保存前必须核对 URL、内容类型和容量,并将新 ETag 与新字节作为同一事务提交;ETag 缺失时不能沿用旧标签。MISS_304 必须建立新请求对象,去掉 If-None-Match 才能请求完整内容;不能复用已经 destroy() 的 HTTP 句柄。如果页面组件采用 @State 保存显示状态,异步闭包里需要再次读取当前轮次。aboutToDisappear() 时让活动序号递增,并取消不必要的动画更新;网络请求本身是否可以中断,要以实际 SDK 的任务机制为准,不能将“忽略旧响应”表述成“真实网络已经被取消”。这两者对流量和生命周期管理的作用不同。## 四、用四组可解释的样例反查代码第一组:有 IMG-207 缓存,键匹配、字节存在,服务器返回 304,当前查询轮次为 7,则期望 READY_304。本轮演示界面采用这个分支:任务 TH-1817,查询词“雨后栈桥”,尺寸 640×360,ETag "v3-207",来源显示 CACHE_VERIFIED。页面同时标出 HTTP 304,避免让“已验证”被误读成“重新下载完成”。第二组:服务器仍给 304,但缓存字节已清空,必须返回 MISS_304,再走无条件请求。第三组:用户已开启轮次 8,轮次 7 的回调即使携带正确 304,也应该返回 STALE_DROP,不允许改动现有图片。第四组:返回 200 且拥有非空数据,属于 NEW_200 候选;没有可信字节的 200 不能算成功。
调试时我更建议让日志解释“为什么选了这条分支”,而不是重复打印网络耗时。例如构造日志 [Thumb304] TH-1817 qSeq=7 image=IMG-207 http=304 与 [Thumb304] TH-1817 state=READY_304 source=CACHE_VERIFIED,可以让读者核对截图、返回码和状态机。日志中不要输出带鉴权令牌的完整图片地址。上面数字、图片和日志都是一致性样例,不是真实网络采样结果。## 五、不要让条件缓存替代内容安全ETag 不是图片文件的加密签名,也不保证某个资源永不被替换;它负责 HTTP 表示版本的条件判断,业务安全还需要传输层校验、来源鉴别和用户权限。特别是文搜图服务里,检索关键词属于可能敏感的数据,不应被拼入没有防护的公共缓存键或者调试输出。账号切换和退出时必须清理相应缓存命名空间。另一条边界与网络性能有关:304 减少的是资源正文传输,不保证服务端查验没有成本,更不能据此给出“节省了多少毫秒”的未经实测数字。若前端有多级缓存,还需对齐缓存策略与服务器 Cache-Control 的约束。实验室里的四个分支单测能够证明状态机按约定裁决,却不能替代真实服务端对 304、200、断网与缓存淘汰的集成测试。这个实现的价值不在于多用了一个请求头,而在于为“继续显示旧图”补上了证据链:资源身份匹配、查询轮次有效、本地字节仍在、服务器确认资源未修改。四个条件缺一项,页面都不应该悄悄把旧结果说成新结果。资料核对: 华为 Network Kit HTTP 数据请求接口(https://developer.huawei.com/consumer/en/doc/harmonyos-references-V5/js-apis-http-V5);MDN HTTP 304 语义(https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/304)。本文未实施 DevEco 编译、设备运行或真实远端缓存测试,接口选项以使用工程对应的官方 SDK 声明为准。
更多推荐




所有评论(0)