HarmonyOS 7 / API 26 ArkWeb 文件上传适配:内核差异、权限边界和失败兜底一次验清
HarmonyOS 7 / API 26 里用 ArkWeb 承载 H5 页面时文件上传是一个很容易被低估的适配点。H5 页面里一个普通的 input typefile在桌面浏览器里基本不会出问题但放进应用之后就会遇到权限、文件类型、回调生命周期、页面恢复和上架审核这些边界。我一般不会只问“能不能选文件”。更稳的检查方式是能不能选到正确类型取消选择有没有兜底页面切后台后回调还在不在上传失败后用户能不能重试应用权限声明和实际触发时机是否一致。问题先拆开ArkWeb 文件上传至少有四个风险点H5 允许的文件类型和应用实际能提供的文件类型不一致用户取消选择以后页面没有收到明确结果选择文件时应用进入后台回来后回调对象已经失效页面要求上传敏感文件但应用权限声明和说明不清楚。这些问题单独看都不复杂放到一起就会变成“线上偶现上传失败”。所以我会先把上传流程当成一个独立能力来验而不是等业务页面写完再补。案例一文件类型没有收口H5 里常见写法是这样input typefile acceptimage/*,.pdf /这段代码只是在 H5 侧表达意图不等于应用侧一定正确处理了类型。比如页面只想要图片和 PDF但应用侧没有做白名单最后可能把视频、压缩包或者不支持的文件也传进来。我会在应用侧先定义一个类型白名单type UploadScene avatar | feedback | document type UploadRule { scene: UploadScene mimeTypes: string[] maxSizeMb: number } const uploadRules: UploadRule[] [ { scene: avatar, mimeTypes: [image/png, image/jpeg], maxSizeMb: 5 }, { scene: feedback, mimeTypes: [image/png, image/jpeg, video/mp4], maxSizeMb: 30 }, { scene: document, mimeTypes: [application/pdf], maxSizeMb: 20 }, ]然后统一检查class UploadRuleChecker { check(scene: UploadScene, file: { mimeType: string; sizeMb: number }): string[] { const rule uploadRules.find(item item.scene scene) const errors: string[] [] if (!rule) { return [上传场景没有配置规则] } if (!rule.mimeTypes.includes(file.mimeType)) { errors.push(文件类型 ${file.mimeType} 不在白名单内) } if (file.sizeMb rule.maxSizeMb) { errors.push(文件大小 ${file.sizeMb}MB 超过 ${rule.maxSizeMb}MB) } return errors } }这一步不是为了把逻辑写复杂而是为了让 H5、ArkWeb 回调和应用上传策略对齐。文件类型不对越早挡住越好。案例二取消选择和后台恢复没有兜底另一个常见问题是用户点了上传入口但是没有真正选文件。可能是用户取消也可能是切后台后流程被打断。如果页面一直等回调就会卡在“上传中”。我会给每一次选择分配一个请求 idclass WebFileSelectGuard { private activeRequestId start(): string { this.activeRequestId ${Date.now()}-${Math.random()} return this.activeRequestId } isActive(id: string): boolean { return this.activeRequestId id } cancel(id: string) { if (this.isActive(id)) { this.activeRequestId } } }回调处理时先判断请求是否还有效async handleFileResult(requestId: string, files: UploadFile[]) { if (!this.fileSelectGuard.isActive(requestId)) { return } if (files.length 0) { this.showUploadState(cancelled) return } const errors this.ruleChecker.check(feedback, files[0]) if (errors.length 0) { this.showUploadError(errors.join(\n)) return } await this.upload(files[0]) }这样用户取消、页面销毁、后台恢复后的旧回调都不会继续改当前页面状态。用脚本检查上传规则下面这个脚本可以直接跑用来检查上传配置有没有明显漏洞const rules [ { scene: avatar, mimeTypes: [image/png, image/jpeg], maxSizeMb: 5 }, { scene: feedback, mimeTypes: [image/png, image/jpeg, video/mp4], maxSizeMb: 30 }, { scene: document, mimeTypes: [application/pdf], maxSizeMb: 20 }, ] const cases [ { scene: avatar, file: { mimeType: image/png, sizeMb: 2 } }, { scene: avatar, file: { mimeType: video/mp4, sizeMb: 2 } }, { scene: document, file: { mimeType: application/pdf, sizeMb: 28 } }, ] function checkUploadCase(item) { const rule rules.find(rule rule.scene item.scene) const errors [] if (!rule) { errors.push(缺少场景规则) return { ...item, passed: false, errors } } if (!rule.mimeTypes.includes(item.file.mimeType)) { errors.push(文件类型不允许) } if (item.file.sizeMb rule.maxSizeMb) { errors.push(文件大小超限) } return { ...item, passed: errors.length 0, errors } } const result cases.map(checkUploadCase) console.log(JSON.stringify({ total: result.length, failed: result.filter(item !item.passed).length, result, }, null, 2))输出里应该有两个失败用例{ total: 3, failed: 2 }一个是头像场景不允许视频一个是 PDF 文件超过大小限制。这个脚本能提前把配置问题拦住不要等用户上传失败以后才发现。几种兜底方式对比方案好处问题适合场景H5 自己处理 accept接入快应用侧不可控边界不清楚简单内部页面应用侧统一白名单类型和大小明确需要维护规则生产应用场景化上传规则最清楚便于审核和排查前期设计成本更高多页面、多文件类型上传我更倾向第三种。上传不是一个按钮而是一组规则场景、类型、大小、取消、失败、重试、权限说明都应该放到同一个模型里。发布前检查清单检查项合格标准文件类型H5 accept 和应用白名单一致文件大小每个场景都有明确上限取消选择用户取消后页面不会一直 loading后台恢复旧回调不会覆盖新状态失败兜底上传失败能重试错误提示清楚审核说明权限触发时机和使用目的能解释清楚后面怎么避免我会把 ArkWeb 文件上传当成应用能力来做而不是当成 H5 的一个输入框每个上传入口先定义场景每个场景配置文件类型和大小上限每次选择文件都有 requestId用户取消和页面销毁都要有明确状态上传失败保留重试入口上架前检查权限说明和实际触发是否一致。这样处理以后ArkWeb 上传问题会从“偶现不好复现”变成“有规则、有日志、有兜底”的普通问题。后面无论是图片、反馈附件还是文档上传都可以复用同一套检查方式。