拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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证明正式路径只暴露完成结果。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门