HarmonyOS 大文件断点续传:Range、流式落盘与完整性校验
HarmonyOS 大文件断点续传Range、流式落盘与完整性校验大文件下载不能简单地把普通HTTP请求换成更长超时。一次把响应读进内存会抬高峰值网络中断后从头开始浪费流量已有半个文件时若服务端忽略Range并返回200继续追加会直接生成损坏文件下载完成后若不校验用户只能在打开时才发现内容不可用。HarmonyOS的requestInStream适合流式响应。本文设计一个可恢复协议临时文件记录已写长度与ETag请求使用Range和If-Range只有206才允许续写200代表资源已变化或服务端不支持续传必须清空临时文件重新开始最终通过长度与摘要后再原子替换正式文件。1. 续传依赖服务端HTTP语义客户端发出Range: bytes1048576-后服务端若接受范围请求应返回206和Content-Range若返回200响应体通常是完整文件不能追加到已有1MB后面。GET /packages/map.bin HTTP/1.1 Range: bytes1048576- If-Range: etag-v7 HTTP/1.1 206 Partial Content Content-Range: bytes 1048576-5242879/5242880 ETag: etag-v7服务端返回416时可能是本地偏移超过远端长度也可能文件已完整。先读取Content-Range: bytes */total再决定校验或清空不能无条件认定成功。2. 临时文件与元数据必须成对存在正式路径旁保存.part文件另存一份小型元数据。元数据至少包含URL、已写长度、ETag、期望总长度和摘要算法。应用重启后先核对实际文件长度不能只相信元数据。interfaceResumeMeta{url:string;downloadedBytes:number;totalBytes?:number;etag?:string;sha256?:string;updatedAt:number;}functionnormalizeMeta(meta:ResumeMeta,actualSize:number):ResumeMeta{return{...meta,downloadedBytes:Math.max(0,Math.min(meta.downloadedBytes,actualSize)),updatedAt:Date.now()};}若元数据URL与当前任务不一致应创建新任务不能复用旧临时文件。3. 大响应使用requestInStream官方FAQ建议普通request用于不超过5MB的响应更大内容使用requestInStream。流式接口通过dataReceive连续提供ArrayBuffer应用边收边写不保存完整响应。import{http}fromkit.NetworkKit;functionbuildHeaders(meta:ResumeMeta):Recordstring,string{constheaders:Recordstring,string{};if(meta.downloadedBytes0){headers[Range]bytes${meta.downloadedBytes}-;if(meta.etag){headers[If-Range]meta.etag;}}returnheaders;}If-Range很重要资源未变化时继续返回剩余范围资源变化时服务端可返回完整新文件客户端据200状态重启下载。4. 断点下载闭环先判断状态码读取断点只是第一步。收到状态码后才能决定追加还是清空数据进入临时文件结束后校验长度与摘要全部通过才替换正式文件。任何错误都保留一致的临时状态供下次恢复。5. 文件写入器只负责顺序落盘下面使用应用沙箱路径打开临时文件。续传时写入位置从当前文件长度开始重新下载时先截断为0。import{fileIoasfs}fromkit.CoreFileKit;classPartFileWriter{privatefile:fs.File;privateoffset:number;constructor(path:string,offset:number){this.filefs.openSync(path,fs.OpenMode.CREATE|fs.OpenMode.READ_WRITE);this.offsetoffset;}reset():void{fs.truncateSync(this.file.fd,0);this.offset0;}append(buffer:ArrayBuffer):number{constwrittenfs.writeSync(this.file.fd,buffer,{offset:0,length:buffer.byteLength,position:this.offset});this.offsetwritten;returnwritten;}getlength():number{returnthis.offset;}close():void{fs.closeSync(this.file);}}不同SDK版本的文件导入入口可能不同请以项目SDK声明为准。核心约束是检查实际写入字节数若少于buffer.byteLength应继续写剩余部分或终止任务不能直接增加完整长度。6. 状态码必须在写入前确定requestInStream返回HTTP响应码。事件回调可能很快到达因此实现中应在业务层串行化首个数据块确保状态码规则已经确定。下面突出206与200的分支。asyncfunctiondecideWriteMode(status:number,localBytes:number):Promiseappend|reset{if(localBytes0(status200||status206)){returnappend;}if(localBytes0status206){returnappend;}if(localBytes0status200){returnreset;}thrownewError(unexpected download status:${status});}若已有部分文件却收到200最安全的实现是销毁当前请求、截断文件后重新发起一个不带Range的新请求而不是在同一事件流中边重置边接收避免前几个数据块时序不清。7. 下载器要固定回调引用并统一销毁classStreamDownloadSession{privaterequest:http.HttpRequesthttp.createHttp();privatereceivedBytes:number0;constructor(privatewriter:PartFileWriter){}privatereadonlyonData(chunk:ArrayBuffer):void{constwrittenthis.writer.append(chunk);if(written!chunk.byteLength){thrownewError(partial file write);}this.receivedByteswritten;};asyncstart(url:string,headers:Recordstring,string):Promisenumber{this.request.on(dataReceive,this.onData);returnthis.request.requestInStream(url,{method:http.RequestMethod.GET,header:headers,connectTimeout:15000,readTimeout:60000});}close():void{this.request.off(dataReceive,this.onData);this.request.destroy();this.writer.close();}}回调中抛错不应成为唯一取消方式。完整工程要把写入失败传给会话状态机主动销毁请求并落盘最新元数据。8. 续传责任边界避免正式文件半成品下载任务负责重试与状态HTTP层只提供响应码、头与数据流临时文件允许中断正式文件只接收校验通过的完整结果。播放器、解压器或地图引擎只能读取正式路径不能窥探.part文件。typeDownloadState|idle|requesting|streaming|verifying|completed|paused|failed;interfaceDownloadSnapshot{state:DownloadState;receivedBytes:number;totalBytes?:number;failureReason?:string;}状态迁移应单向且可记录例如streaming - verifying - completed。失败后回到paused还是failed取决于错误是否可重试。9. Content-Range必须与本地偏移吻合续传响应为206时解析起始位置并与本地长度比较。服务端若从其他位置返回继续写入会产生缝隙或重叠。interfaceContentRange{start:number;end:number;total:number;}functionparseContentRange(value:string):ContentRange|undefined{constmatch/^bytes (\d)-(\d)\/(\d)$/.exec(value.trim());if(!match)returnundefined;conststartNumber(match[1]);constendNumber(match[2]);consttotalNumber(match[3]);if(start0||endstart||totalend)returnundefined;return{start,end,total};}头字段名大小写不应硬编码到单一形式读取响应头时先做统一归一化。10. 完整性至少校验长度重要文件再校验摘要下载结束先比较临时文件实际长度与Content-Length或Content-Range总长度。安装包、离线地图和模型文件还应使用服务端提供的SHA-256。摘要必须来自可信元数据不能来自同一不受信下载通道的可修改字段。functionverifyLength(actual:number,expected?:number):void{if(expectedundefined||expected0){thrownewError(missing expected file length);}if(actual!expected){thrownewError(file length mismatch:${actual}/${expected});}}摘要计算也要流式读取文件不能为了校验再次把大文件全部载入内存。11. 校验成功后再替换正式路径临时文件与正式文件最好位于同一文件系统校验通过后使用rename替换。若目标已存在可先保存旧版本或按产品策略删除任何时候都不要让正式路径指向半成品。asyncfunctioncommitPartFile(partPath:string,finalPath:string):Promisevoid{conststatfs.statSync(partPath);if(stat.size0){thrownewError(empty part file);}awaitfs.rename(partPath,finalPath);}元数据清理放在rename成功之后。应用崩溃在rename之前仍可续传崩溃在rename之后下次通过正式文件校验恢复完成状态。12. 重试策略按错误类别区分网络断开、超时 - 保留断点退避后续传 HTTP 401/403 - 刷新授权不自动高频重试 HTTP 404 - 任务失败提示资源不存在 已有断点却返回200 - 清空临时文件重新完整下载 Content-Range不一致 - 停止追加重新建立任务 磁盘空间不足 - 保留可解释状态用户处理后再试 摘要不一致 - 删除不可信临时文件从头下载13. 断点续传验收清单[ ] 大于5MB响应使用requestInStream [ ] 本地偏移来自真实临时文件长度 [ ] 续传只接受起点一致的206响应 [ ] 200响应不会追加到旧临时文件 [ ] 数据块写入后核对实际字节数 [ ] 退出时解除事件并destroy HttpRequest [ ] 完成前校验总长度与可信摘要 [ ] 校验通过后才替换正式文件 [ ] 杀进程重启后可从一致断点恢复14. 流式下载资料索引Network Kit大文件请求FAQHttpRequest.requestInStream、dataReceive与文件接口以本机HarmonyOS SDK API 23声明为准。断点续传的难点不在Range字符串而在每个边界都要可证明响应码证明能否追加Content-Range证明起点一致临时文件证明中断可恢复长度与摘要证明内容完整最终rename证明正式路径只暴露完成结果。

相关新闻

Zynq 7020 SPI读写数据:SDK驱动实现与调试全解析

Zynq 7020 SPI读写数据:SDK驱动实现与调试全解析

简介:本资源是面向嵌入式开发工程师与FPGA软硬件协同设计学习者的ZYNQ 7020平台SPI外设驱动实战项目,聚焦于基于Xilinx SDK的C语言底层驱动开发,解决ARM端与SPI从设备(如Flash、传感器等)高效通信的核心问题。压缩包共…

2026/11/11 17:05:54 阅读更多 →
Python之鸭子类型:for循环的本质,进一步解析迭代协议

Python之鸭子类型:for循环的本质,进一步解析迭代协议

引言在先前的文章里头, 我们简要介绍了其中的可迭代对象, 还有迭代器, 以及迭代协议的概念。于今日, 那就选这个使用频次特别繁多, 让人觉着极为熟悉且十分简易的语法结构for, 略微予以展开, 进而能够更为良好地加深对于迭代协议的理解。本文的主要内容有:1、迭代协…

2026/11/12 6:32:40 阅读更多 →
平面型与沟槽型MOS管:结构差异、性能对比及选型指南

平面型与沟槽型MOS管:结构差异、性能对比及选型指南

你很可能遇到过这种场景:同一个电源项目里,两只 MOS 管标称耐压都是 60V,额定电流都在 50A 左右,封装也一样,放在同一块板子上替换使用,结果一只管子温升正常,另一只却烫到不敢摸。更奇怪的是&a…

2026/11/7 17:57:57 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/12 5:00:00 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/12 5:13:20 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/12 5:13:20 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/11 17:16:37 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/11 17:16:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/12 5:13:20 阅读更多 →

日新闻

周新闻

月新闻