更新记录

1.6.6(2026-10-29)

  • 新增可选会话响应字段映射 sessionResponse,支持 result / data 等包装层和 uploadId、uploadedChunks、completed、result 字段路径;API、组件和运行期队列使用同一配置。
  • 修复接入统一响应包装时无法读取会话 ID、触发 9011032 的问题;未配置映射时继续兼容原顶层格式。
  • 完善 uni-app Vue 2 与 uni-app x 示例及 README 完整代码,按三个阶段分别标明前端操作与后端接收/保存/合并/校验职责,补齐三接口入参及成功/失败 JSON,并直接说明 session 的 POST JSON 参数、chunk 表单的 lzUploadId / file 字段、complete 的 fileHash / SHA-256、首次与续传会话 ID、两种响应格式,以及显示 100% 但服务器文件为 0 字节时的定位方法。

1.6.5(2026-10-29)

  • 新增 uni-app Vue 2 大视频分片上传完整页面,演示视频选择、上传进度、取消和当前页继续上传,默认每片 5 MiB、并发 2 片。
  • 修正文档中组件与 pickFiles() 的接入说明,补齐 Vue 2 的 data / methods、select 文件数组回调和 scene / accept 配置,避免方法未定义或误用图片场景选择视频。
  • 完善 README 内可直接复制的完整页面和 pages.json 注册代码,说明会话、分片、合并三个接口的配置,以及校验准备、进度与续传边界。

1.6.4(2026-10-27)

  • 修复示例页面和辅助模块使用相对引入路径的问题,统一通过 @/uni_modules/ 引用插件内文件,复制页面到其他目录时无需另行复制辅助代码。
查看更多

平台兼容性

uni-app(4.84)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
√ √ √ √ √ √ √ √ √
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
√ √ - - - - - - - - - -

uni-app x(4.84)

Chrome Safari Android iOS 鸿蒙 微信小程序
√ √ √ √ √ √

lizhao-choose-file

介绍

选择业务文件并上传,支持已有附件回显、只读查看、分批补选,以及大文件中断后继续上传。

功能特色

功能 能力与说明
附件回显与补选 合同、报销和资料表单:已有附件与新文件分别管理,取消补选保留原清单。
按业务类型选择 头像、图片和文档:按场景选择,图片缩略图与点击预览可选开启。
普通上传 普通上传:进度、取消、重试和并发控制,无需改造现有上传服务端。
任务持久化 可靠上传队列:保存待传文件和任务状态,在条件满足时显式继续。
续传与可信检查 大文件与安全检查:可选分片续传、真实类型检测、SHA-256 和同内容重复策略。
系统文件互通 本地文件联动:在 Android、iOS、Harmony 上将系统文件复制到沙盒或另存为。

适合哪些场景

场景 推荐能力 边界
合同与报销 附件回显、补选和确认后上传 取消补选保留原清单
头像与办公资料 按场景约束格式与数量 pickBusinessFiles
大文件与断网恢复 可靠队列、分片上传 需要相应服务端协议
SQLite备份 系统文件与沙盒互通 恢复前处理数据库占用

下载与导入

宿主 示例代码位置
uni-app uni_modules/lizhao-choose-file/example/uniapp/chooseFile.vue
uni-app x uni_modules/lizhao-choose-file/example/uniappx/index.uvue

基础使用

从插件市场导入并保留完整插件目录。App 使用包含本插件的自定义基座或正式安装包。

先确定页面实际使用哪个入口,再在该入口上配置文件类型:

接入方式 参数写在哪里 选择结果在哪里接收
uni-app Vue 2 / Vue 3 的 <lizhao-choose-file> 组件 组件标签的 scene / accept / multiple / max-count 等属性 @select="onImageSelected" 对应的页面方法
自己的按钮调用 pickFiles() 当前按钮执行的 pickFiles({ ... }) 参数 success(files)
uni-app x 的 .uvue 页面 公开 API 的参数 API 回调;使用上表中的 uni-app x 示例

组件有自己的选择流程。修改另一处 pickFiles() 的参数,不会改变组件自带按钮的行为。组件完整接入见内置界面与自定义页面。

下面是 uni-app Vue 2 的 API 最小页面示例;在已有页面中只合并对应模板、导入和 methods,保留原业务:

<template>
  <view><button @click="chooseAttachments">选择附件</button></view>
</template>

<script>
import { pickFiles } from '@/uni_modules/lizhao-choose-file'

export default {
  methods: {
    chooseAttachments() {
      pickFiles({
        accept: '.pdf,.docx,.xlsx,image/*,.avi,.mp4,video/avi',
        multiple: true,
        maxCount: 5,
        success(files) {
          console.log('已选择', files.length, '个文件', files)
        },
        fail(error) {
          if (error.errCode !== 9011002) console.error(error.errCode, error.errMsg)
        }
      })
    }
  }
}
</script>

单文件使用 pickFile,成功回调直接返回一个 PickedFile;pickFiles 返回数组。未配置 upload 时,这两个 API 只选择文件。

本文带类型声明的脚本片段适用于 uni-app x / UTS;<script setup> 示例适用于 Vue 3。Vue 2 页面使用本节及下方组件示例的 data / methods 写法。

从基础到进阶:按业务模块使用

先选择,再确认上传

用于用户先核对文件、保存表单时再提交。上传函数只接收本次选择的本地文件。

import { pickFiles, uploadFiles } from '@/uni_modules/lizhao-choose-file'
import type { PickedFile } from '@/uni_modules/lizhao-choose-file'

let localFiles : Array<PickedFile> = []
function choose() {
  pickFiles({ maxCount: 5, success(files) { localFiles = files } })
}
function submit() {
  if (localFiles.length == 0) return
  return uploadFiles({
    files: localFiles,
    url: 'https://api.example.com/upload',
    name: 'file',
    parseJson: true,
    progress(res) { console.log(res.progress) },
    success(res) { console.log(res.successCount, res.failCount) }
  })
}

uploadFile 用于单文件,uploadFiles 用于批量。返回的任务可调用 abort()。服务器响应保留在 data,启用 parseJson 时同时提供 parsedData;附件 ID 和 URL 由业务按自己的响应格式映射。

按业务场景选择并自动上传

用于头像、办公文件等确定后立即上传的入口;按场景自动填充类型和数量。

import { pickAndUploadBusinessFiles } from '@/uni_modules/lizhao-choose-file'

pickAndUploadBusinessFiles({
  scene: 'office',
  maxCount: 3,
  upload: { url: 'https://api.example.com/upload', name: 'file', parseJson: true },
  stateChange(state) { console.log(state.status, state.overallProgress) },
  success(result) { console.log(result) }
})

只选择使用 pickBusinessFiles;不需要场景预设时使用 pickAndUploadFile / pickAndUploadFiles。

已有附件回显、只读和分批追加(uni-app 组件)

用于编辑已保存表单。已有附件传给 attachments,新文件通过 attachments-change.files 交给业务;两类对象分开保存。

<template>
  <lizhao-choose-file
    mode="pick"
    :attachments="savedAttachments"
    :readonly="readonly"
    :append="true"
    :max-count="5"
    :show-progress="false"
    :confirm-remove="true"
    button-text="添加附件"
    @attachments-change="onChange"
  />
</template>
<script setup>
import { ref } from 'vue'
const readonly = ref(false)
const savedAttachments = ref([
  { id: 'contract-1', name: '合同.pdf', size: 204800,
    type: 'application/pdf', url: 'https://example.com/attachments/contract.pdf' }
])
const newFiles = ref([])
function onChange(payload) {
  savedAttachments.value = payload.attachments
  newFiles.value = payload.files
}
</script>

示例 URL 请替换为业务返回的地址。attachments 不进入本地检查、上传或可靠队列。保存时只上传 newFiles,并保留已有附件的业务 ID;移除远程项只通知业务,不自动请求服务器删除。

配置 业务含义
attachments 已保存附件数组;每项必须有稳定 id 和非空 url,支持 name / size / type;无效项忽略,重复 ID 保留首项,数字 ID 转字符串
readonly 隐藏选择和取消入口,限制移除、上传控制及队列操作;仍可预览
append 默认 false,成功后替换本地清单;true 时成功后追加,仅支持 mode="pick"
max-count 默认 9,按已有附件与本次保留的本地文件总数计算;选前计算剩余,选后复检
attachments-change 返回 { attachments, files } 两类清单;同步到业务时不要合并为上传数组

取消或选择失败保留原清单与状态;选择期间已有附件或只读发生变化,旧选择结果失效。追加用于分批收集文件,append 与 upload/queue 同用会明确失败,不会偷偷上传。trust 的批次去重范围仍按原合同执行,追加不会默认对全部历史附件计算摘要。

图片远程项自动使用 uni.previewImage;非图片项通过 preview 事件交给业务打开。远程事件携带 { attachment, remote: true },本地 remove / preview 原字段保持。远程移除可调用 removeAttachment(id),本地移除仍调用 remove(index);select / state-change 中的文件与数量仍只描述本地文件。

uni-app x 的附件编辑

用于 .uvue 页面。远程展示对象单独定义类型,本地列表继续使用公开 PickedFile,即可接入自己的表单 UI。

import { ref } from 'vue'
import { pickFiles } from '@/uni_modules/lizhao-choose-file'
import type { PickedFile } from '@/uni_modules/lizhao-choose-file'

type SavedAttachment = { id: string, name: string, url: string }
const saved = ref<Array<SavedAttachment>>([])
const local = ref<Array<PickedFile>>([])
const readonly = ref(false)
function chooseMore() {
  if (readonly.value) return
  const remaining = 5 - saved.value.length - local.value.length
  if (remaining <= 0) return
  pickFiles({
    maxCount: remaining,
    success(files) {
      if (readonly.value || saved.value.length + local.value.length + files.length > 5) return
      local.value = local.value.concat(files)
    }
  })
}

完整页面包含选择锁、模式切换与离页回调失效、容量复检,以及图片缩略图和数字索引预览。它是 API 示例,不会将 .vue 组件作为 uni-app x 原生组件使用。

真实类型检查与重复文件策略

独立预检 API

inspectFile 和 inspectFiles 接收已经由本插件选中的 PickedFile。它们只检查文件,不发起上传,也不把任务写入可靠队列;返回的控制器可调用 abort() 取消仍在进行的读取或 SHA-256 计算。

import { inspectFiles } from '@/uni_modules/lizhao-choose-file'

const inspectionTask = inspectFiles({
  files,
  calculateSha256: true,
  requireDetectedType: false,
  allowedDetectedTypes: ['image/*', 'application/pdf'],
  duplicatePolicy: 'allow',
  progress(res) {
    console.log('检查进度', res.file.name, res.progress)
  },
  success(checkedFiles) {
    checkedFiles.forEach((file) => {
      const result = file.inspection
      // 业务界面如需展示摘要,建议只显示短前缀;不要写入公开日志。
      const shortHash = result?.sha256 ? result.sha256.slice(0, 8) : ''
      console.log('检查状态', file.name, result?.status, shortHash)
    })
  },
  fail(err) {
    console.error('检查未完成', err.errCode, err.errMsg)
  }
})

// inspectionTask.abort()
参数 类型 必填 说明 默认值 可选参数
file — 按 API 单个待检查文件 必填 —
files — 按 API 多个待检查文件 必填 —
calculateSha256 — 按 API 是否分块计算完整 SHA-256 false —
requireDetectedType — 按 API 标记业务是否要求可识别类型;独立预检仍把 unknown 作为成功检测结论返回 false —
allowedDetectedTypes — 按 API 允许的真实 MIME / 主类型通配符列表,供调用方判断结果 不限制 —
duplicatePolicy — 按 API 批次重复策略;allow/skip 保留重复项并填写 duplicateOf,reject 遇到重复时返回 9011027 allow —
progress — 按 API 持续返回当前文件、序号、读取字节数与 0~100 进度 无 —
success / fail / complete — 按 API 一次性完成回调;取消后不会迟到触发 success 无 —

同一文件的 progress 与 bytesProcessed 单调递增(允许重复值),切换文件后重新计数。启用 SHA-256 时,头部/容器采样期间进度保持 0,随后按摘要读取推进;未启用摘要时,字节数为累计采样量并限制在文件长度内,采样区间可能重叠。100 表示检查流程完成,不代表有限采样覆盖了整个文件。严格策略已经确认类型不符时会直接失败,不再计算该文件的完整摘要。

独立预检以交付真实检测结论为目标,因此 mismatch 和 unknown 会进入 success;参数无效、平台不支持、文件不可读、哈希失败、主动取消,或 duplicatePolicy: 'reject' 命中重复时进入 fail。要在选择或上传流程中阻断类型风险,请使用下方 trust.mode: 'strict'。

检查结果

每个检查后的文件会新增 inspection,原 PickedFile 其他字段保持不变。

字段 说明
status verified / mismatch / unknown / unsupported / unreadable / aborted
declaredType 选择器或平台声明的 MIME
extensionType 文件扩展名推断的 MIME
detectedType 内容特征确认的 MIME;无法确认时为空字符串
containerType 容器补充分型,如 zip / ooxml-word / ooxml-excel / ooxml-powerpoint / ofd / apk / ole / iso-bmff
confidence confirmed / probable / unknown
matchStatus match / mismatch / unknown
risk none / extension-mismatch / executable-disguised / unreadable / unsupported
sha256 / hashStatus SHA-256 小写十六进制摘要及 not-requested / verified / failed / aborted 状态
bytesProcessed / totalBytes 已处理与总字节数
duplicateOf 重复时指向本批次或同业务队列中的首次文件标识

内容识别覆盖常用 JPEG、PNG、GIF、WebP、BMP、HEIC/HEIF/AVIF、MP4/MOV/AVI、MP3/M4A/WAV/AAC/FLAC、PDF、OLE、ZIP/RAR/7z/GZip,以及 OOXML、OFD、APK 的有限容器分型。仅识别出通用 OLE 或 ISO BMFF 容器时,会对兼容的旧版 Office / M4A 扩展保留 unknown 结论而不是误报伪装;这不等于确认了具体子类型。纯文本、SVG、未知二进制等无法仅凭有限特征可靠确认时同样返回 unknown,不会把扩展名匹配伪装成“已验证”。未知格式不等于恶意文件,只有集成策略使用 strict 且 requireDetectedType: true 时才会拒绝。

在选择、上传、业务 API 与队列中启用

现有 pickFile / pickFiles / uploadFile / uploadFiles、选择并上传 API、业务场景 API 和 enqueueUploadFiles 都可传同一份 trust。选择类 API 未配置 allowedDetectedTypes 时会沿用当前 accept 作为真实类型白名单;手动上传未配置时只检查内容与声明是否一致。

uploadFiles({
  files,
  url: uploadUrl,
  trust: {
    mode: 'strict',
    calculateSha256: true,
    requireDetectedType: true,
    allowedDetectedTypes: ['image/*', 'application/pdf'],
    duplicatePolicy: 'skip'
  },
  trustCallbacks: {
    progress(res) {
      console.log('上传前检查进度', res.file.name, res.progress)
    },
    inspect(file) {
      console.log('上传前检查状态', file.name, file.inspection?.status)
    },
    duplicate(event) {
      console.log('发现重复文件', event.file.name, event.duplicateOf)
    }
  },
  success(res) {
    console.log('上传完成', res.successCount, res.skippedCount)
  }
})
策略 普通选择/上传 可靠上传队列
mode: 'warn' 返回检查结论但继续原流程;读取失败会成为明确状态 继续入队,检查结果随文件快照保存,运行期回调不持久化
mode: 'strict' 已确认类型不一致、不可读、不支持、禁止类型,以及 requireDetectedType 下的未知类型会在第一条网络请求前失败 在创建任务、复制文件和写 metadata 前失败
duplicatePolicy: 'allow' 报告重复事件并保留所有文件 允许重复文件形成正常队列项
duplicatePolicy: 'skip' 重复项不进入上传并计入 skippedCount 重复项不复制、不占并发、不发请求,以 skipped 终态保留
duplicatePolicy: 'reject' 整批在网络请求前以 9011027 失败 在任务创建和持久化前失败,不留下队列目录或 metadata

skip 和 reject 为建立稳定内容身份会自动计算 SHA-256。队列跨任务判重只在相同且非空的 businessKey 内进行;空 businessKey 或不同业务键不会互相判重。已过期、已删除或不再保留的历史任务不参与判重。

组件可配置 trust-mode、calculate-sha256、require-detected-type、duplicate-policy 与 show-trust-status。show-trust-status="false" 时不增加视觉元素;headless 模式始终只转发 inspect-progress、inspect、duplicate 事件,不渲染可信状态 DOM。状态中文固定为“校验中、已验证、类型不一致、类型未知、重复文件、无法读取”。

保存并继续上传任务

队列必须先配置运行期请求解析器,再恢复旧任务或加入新文件。上传地址、请求头和签名不会持久化;建议在 resolveRequest 中读取当前登录态并生成本次请求。

import {
  configureUploadQueue,
  enqueueUploadFiles,
  restoreUploadQueue,
  onUploadQueueChange
} from '@/uni_modules/lizhao-choose-file'

configureUploadQueue({
  concurrency: 2,
  retryCount: 2,
  retryDelay: 1500,
  resumeOnNetworkRestored: true,
  autoResumeOnLaunch: false,
  wifiOnly: false,
  resolveRequest(task, success, fail) {
    const url = getCurrentUploadUrl()
    if (!url) {
      fail({ errSubject: 'lizhao-choose-file', errCode: 9011017, errMsg: 'queue request unavailable' })
      return
    }
    // 每次实际上传前动态读取最新登录态,不把鉴权信息写入队列文件。
    success({ url, name: 'file', header: {} })
  }
})

onUploadQueueChange((snapshot) => {
  console.log('队列状态', snapshot)
})

// 应用启动时默认只恢复任务,避免未取得新凭据前自动上传。
restoreUploadQueue({ autoResume: false })

// files 来自 pickFile / pickFiles 成功回调。
enqueueUploadFiles({
  files,
  businessKey: 'order-10001',
  autoStart: true
})

已有任务可用 pauseUploadQueueTask(taskId)、resumeUploadQueueTask(taskId)、retryUploadQueueTask(taskId) 和 cancelUploadQueueTask(taskId) 控制。retryUploadQueueTask 会跳过成功文件;源文件副本丢失时状态为 file-missing,需要重新选择文件。

大文件中断后补传缺失部分

用于 200MB、1GB 视频等较大文件。给 uploadFile 配置 resumable.enabled: true 后,插件负责计算文件 SHA-256、按范围创建分片、并发上传、查询已确认分片和提交合并。前端保留选中的完整文件对象,不需要把整个视频转成 Base64 或自己切数组。

uni-app Vue 2 完整页面

下面直接给出可复制的完整页面,包括视频选择、准备状态、整体进度、已确认分片数、取消、当前页继续/重试和离页取消。页面使用 uni-app Vue 2 的 data / methods,本节代码可以直接复制到自己的页面。

代码注释按下面三个阶段逐步说明前后端职责。前端业务只调用一次 uploadFile(),三个网络阶段由插件自动执行,后端分别实现对应接口:

阶段 前端及插件做什么 后端做什么 什么时候进入下一步
1. session 选择文件,配置地址/请求头;插件计算 SHA-256 和分片参数,POST JSON 按文件身份创建或查询会话,保存文件信息,返回真实 ID 和已落盘片号 HTTP 2xx,能按默认字段或映射读到有效 ID
2. chunk 插件自动切缺失片、并发发送 multipart;页面保存续传状态、展示进度 绑定 lzUploadId / file,校验范围及实收字节数,写盘成功后记录片号并返回 2xx 缺失分片全部得到成功响应
3. complete 插件 POST JSON,等待期间进度上限 99%;页面在 success 后显示完成 核对会话和所有分片,顺序合并,校验总大小及 SHA-256,通过后返回真实业务结果 完成接口 HTTP 2xx 后前端进入成功回调

接入步骤:

  1. 新建 pages/video-upload/video-upload.vue,复制下方完整的 <template>、<script>、<style> 三部分。
  2. 在现有 pages.json 的 pages 数组中添加下面的注册项,保留原有页面。
  3. 在下方代码顶部的 UPLOAD_API 填写以下三个地址;默认留空,填齐后才会发起上传。需要登录时,在 uploadFile 的 header 中传入应用当前有效的请求头。
  4. 根据会话接口实际返回值设置 SESSION_RESPONSE:完整页面默认演示 result 包装;后端直接返回顶层 uploadId / uploadedChunks / completed 时,将它改为 null。完整代码内同时解释了会话请求参数和两种返回格式。
  5. 打开这个页面,点击“选择视频”,再点“开始分片上传”。选择阶段只返回文件,上传由后一个按钮触发。
  6. 需要中断时点击“取消本次上传”;在当前页保留该文件并再次点击“继续或重试上传”,会携带保存的状态向服务端重新核对并补传缺失部分。

pages.json 的 pages 数组添加一项:

{
  "path": "pages/video-upload/video-upload",
  "style": { "navigationBarTitleText": "视频分片上传" }
}
示例配置 接口职责 请求形式
UPLOAD_API.sessionUrl 创建或查询上传会话,返回会话 ID 和已确认分片 JSON POST
UPLOAD_API.chunkUrl 接收一个分片;传给 uploadFile.url multipart/form-data POST
UPLOAD_API.completeUrl 检查分片、校验并合并视频,返回最终业务结果 JSON POST

lizhao-resumable-v1 是下方三个请求的调用顺序和字段约定,后端可自行选择语言和路由。三个接口需要实现该分片流程;已有的普通整文件上传接口不能只替换一个地址就当作分片接口使用。会话响应存在 result / data 包装或字段改名时,可通过 sessionResponse 配置适配。header 会用于该流程的请求,表单文件字段名默认是 file。

完整页面代码(复制到 pages/video-upload/video-upload.vue):

<template>
  <view class="video-upload-page">
    <lizhao-choose-file
      scene="media"
      mode="pick"
      ui-mode="simple"
      accept="video/*"
      :multiple="false"
      :max-count="1"
      :max-size="maxFileSize"
      :disabled="uploading"
      :show-progress="false"
      :show-image-preview="false"
      :preview-on-click="false"
      button-text="选择视频"
      @select="onVideoSelected"
      @fail="onChooseFail"
    />

    <view v-if="videoFile" class="video-file-info">
      <text>{{ videoFile.name }} · {{ formatSize(videoFile.size) }}</text>
    </view>
    <view class="video-status"><text>{{ phaseText }}</text></view>
    <progress :percent="percentage" :show-info="true" />
    <view v-if="chunkCount > 0" class="video-status">
      <text>服务端已确认 {{ confirmedChunks }} / {{ chunkCount }} 片</text>
    </view>

    <view class="video-actions">
      <button type="primary" :disabled="!videoFile || uploading" @click="startUpload">
        {{ savedState ? '继续或重试上传' : '开始分片上传' }}
      </button>
      <button :disabled="!uploading" @click="cancelUpload">取消本次上传</button>
    </view>
    <view v-if="resultText" class="video-result"><text>{{ resultText }}</text></view>
  </view>
</template>

<script>
import LizhaoChooseFile from '@/uni_modules/lizhao-choose-file/components/lizhao-choose-file/lizhao-choose-file.vue'
import { uploadFile } from '@/uni_modules/lizhao-choose-file'

// 填入支持 lizhao-resumable-v1 的三个接口地址。
const UPLOAD_API = {
  sessionUrl: '', // 创建或查询上传会话,JSON POST。
  chunkUrl: '', // 接收单个分片,multipart/form-data POST。
  completeUrl: '' // 校验并合并文件,JSON POST。
}

/*
 * 三步 JSON 入参与响应对照:以下仅示意,真实文件值由插件自动生成,不要写死哈希或ID。
 * 第1步 session:POST application/json,请求:
 * {
 *   "protocol": "lizhao-resumable-v1",
 *   "uploadId": "",
 *   "fileHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
 *   "fileName": "video.mp4",
 *   "fileSize": 209715200,
 *   "chunkSize": 5242880,
 *   "chunkCount": 40,
 *   "metadata": {}
 * }
 * 后端成功返回 HTTP 200(默认顶层格式;result包装见下方映射配置):
 * {
 *   "uploadId": "upload-demo-001",
 *   "uploadedChunks": [],
 *   "completed": false
 * }
 * 第2步 chunk:实际是multipart/form-data,下面JSON仅展示表单结构,file必须是真实二进制:
 * {
 *   "lzProtocol": "lizhao-resumable-v1",
 *   "lzUploadId": "upload-demo-001",
 *   "lzFileHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
 *   "lzChunkIndex": 0,
 *   "lzChunkCount": 40,
 *   "lzChunkOffset": 0,
 *   "lzChunkSize": 5242880,
 *   "lzFileSize": 209715200,
 *   "lzFileName": "video.mp4",
 *   "file": "当前分片的二进制文件,此处仅作说明"
 * }
 * 保存分片成功后返回HTTP 200,响应体字段不强制固定,示例:
 * {
 *   "received": true,
 *   "uploadId": "upload-demo-001",
 *   "chunkIndex": 0,
 *   "receivedBytes": 5242880
 * }
 * 第3步 complete:POST application/json,请求:
 * {
 *   "protocol": "lizhao-resumable-v1",
 *   "uploadId": "upload-demo-001",
 *   "fileHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
 *   "fileName": "video.mp4",
 *   "fileSize": 209715200,
 *   "chunkSize": 5242880,
 *   "chunkCount": 40,
 *   "metadata": {}
 * }
 * 后端验证合并大小及SHA-256通过后返回HTTP 200,业务字段可自定义,示例:
 * {
 *   "fileId": "file-demo-001",
 *   "url": "/upload/files/video.mp4",
 *   "fileSize": 209715200
 * }
 * 失败时返回非2xx,例如HTTP 409及以下JSON,不要只在HTTP 200正文写失败码:
 * {
 *   "code": "UPLOAD_CHUNKS_MISSING",
 *   "message": "缺少分片:2"
 * }
 * 下面继续按阶段说明前端/插件/后端各自要做什么。
 * 阅读顺序:先填三个 UPLOAD_API 地址,再按自己的 session 响应设置 SESSION_RESPONSE。
 * 业务页只调用下面的 uploadFile(),不要再手动重复调用 session、chunk 或 complete。
 *
 * 第一步 session:创建或恢复会话,POST JSON 到 UPLOAD_API.sessionUrl。
 * 【前端开发者】选择视频后保留完整 videoFile;填写地址、header 和 formData。
 * 【插件自动做】计算实际文件 SHA-256、片数,首次发空 uploadId,续传发匹配的旧ID。
 * 以 200 MiB 视频、每片 5 MiB 为例,后端收到的请求体是:
 * {
 *   "protocol": "lizhao-resumable-v1", "uploadId": "",
 *   "fileHash": "插件计算的SHA-256(64 个十六进制字符)值", "fileName": "video.mp4",
 *   "fileSize": 209715200, "chunkSize": 5242880, "chunkCount": 40,
 *   "metadata": {}
 * }
 * 上述文件名、大小、哈希、片数都是插件按实际文件生成的,业务页不用手写请求。
 * 请求字段叫 protocol;protocolName 只是下面的前端配置项名称。
 * 首次无续传状态时 uploadId 为 "";携带匹配的 savedState 继续时传回旧会话 ID。
 * 后端要接收 JSON 请求体;这一请求还没有文件,视频分片会在会话有效后发给 chunkUrl。
 * 【后端开发者】按 protocol/fileHash/fileSize/chunkSize/chunkCount 创建或查询会话,
 * 持久保存真实 uploadId 和文件信息;恢复时只返回已实际保存的分片编号。
 * 后端成功后返回下面A或B的JSON(HTTP 2xx),客户端读取有效ID后才进入第二步。
 *
 * 第一步的响应适配:看自己的 session 接口返回哪一种格式,再配置 SESSION_RESPONSE。
 * A. 顶层格式:{ "uploadId": "真实会话ID", "uploadedChunks": [], "completed": false }
 *    将下方 SESSION_RESPONSE 整段改为 null,插件会读默认字段。
 * B. 统一包装:{ "code": 200, "type": "success", "result": {
 *      "uploadId": "真实会话ID", "uploadedChunks": [], "isFinished": false
 *    } }
 *    保留下方映射;路径从响应正文根对象开始,不要再加 uni.request 回调的 data 前缀。
 * completed 对应的实际值要是布尔值;"false" 字符串不能代替 false。
 * uploadId 必须由后端维护真实会话身份,uploadedChunks 使用从 0 开始的片序号。
 * HTTP 2xx 但 ID 路径不存在/为空会报 9011032,此时还不会调用 chunkUrl。
 * 这个映射只读取 session 响应;完整视频成功仍以 success 回调为准。
 *
 * 第二步 chunk:上传缺失分片,POST multipart/form-data 到 UPLOAD_API.chunkUrl。
 * 【插件自动做】按范围生成临时分片、并发上传,下面的表单字段会自动填入:
 *   lzUploadId = session 返回的 uploadId(不是名为 UploadId 的字段)
 *   lzChunkIndex = 0 / 1 / 2 ...(零基序号)
 *   lzChunkSize = 当前片实际声明字节数,lzChunkOffset = 在原文件中的起点
 *   lzChunkCount / lzFileSize / lzFileName / lzFileHash / lzProtocol 随片一起发送
 *   file = 当前分片二进制(对应下方 name: 'file')
 * 后端按表单绑定 lzUploadId 和文件字段 file,不能只读 JSON 或 UploadId。
 * 【后端开发者】1. 用 lzUploadId 查第一步的会话,核对原文件身份与分片范围;
 * 2. 从 file 读取真实二进制,检查实收字节数等于 lzChunkSize,写入临时分片文件;
 * 3. 写入关闭成功后,记录该零基序号已完成,再返回HTTP 2xx;重复分片须幂等。
 * 不能只记“收到第几片”就返回成功,也不能忽略空文件或写盘失败。
 * 【前端开发者】用 resumableStateChange 保存公开续传状态,progress 显示整体百分比。
 * 所有片成功后 completeUrl 再接收 JSON,其中会话字段又叫 uploadId。
 * sessionResponse 只映射响应,不能用它改 chunk 的表单字段名称。
 *
 * 第三步 complete:所有缺失分片收到成功响应后,插件 POST JSON 到 UPLOAD_API.completeUrl。
 * 【插件自动做】发送 protocol / uploadId / fileHash / fileName / fileSize /
 * chunkSize / chunkCount / metadata;uploadId 为第一步的真实ID,等待期间进度上限99%。
 * 文件校验字段叫 fileHash,算法是 SHA-256(64 个十六进制字符),插件不发送 FileMd5,也不计算MD5。
 * 后端不能只用 { UploadId, FileMd5 } 接收并要求非空MD5;应读取 fileHash 并按SHA-256校验。
 * 【后端开发者】1. 按 uploadId 查会话,核对 fileHash、大小及切片配置;
 * 2. 检查0到chunkCount-1每片都存在、落盘大小和预期相符;
 * 3. 按片号顺序合并到临时目标,关闭写入后校验总字节数等于fileSize、SHA-256等于fileHash;
 * 4. 校验通过再发布最终文件并返回HTTP 2xx及真实业务结果,失败返回合适的非2xx和原因。
 * 重复complete应返回同一业务结果,不能重新覆盖已成功的文件。
 * 【前端开发者】只在 success 回调显示最终完成;fail 保留savedState供当前页继续重试。
 * 停在99%并报9011034表示complete返回非HTTP 2xx,或请求超时/连接失败等网络错误。
 * 此时记录complete原始JSON、HTTP状态码、响应正文和后端异常;后端绑定后的DTO不是原始请求。
 * 不要把complete的JSON uploadId也改成chunk的表单lzUploadId。
 *
 * 若页面100%而服务器文件0字节:记录每片 lzChunkIndex / lzChunkSize、文件字段 file
 * 实收字节数及落盘大小;三片正确但合并为0,排查后端合并/路径/流处理;请求非空但
 * 实收为0,排查后端绑定/保存;请求本身为空,再排查前端文件来源/切片/传输。
 * 后端合并接口应在总大小等于 fileSize、整文件SHA-256匹配后返回成功,
 * 不能只生成空文件或返回一个路径。HTTP成功响应本身不是落盘内容的校验证明。
 */
const SESSION_RESPONSE = {
  uploadId: 'result.uploadId',
  uploadedChunks: 'result.uploadedChunks',
  completed: 'result.isFinished'
}

export default {
  components: { LizhaoChooseFile },
  data() {
    return {
      maxFileSize: 1024 * 1024 * 1024, // 本示例最多选择 1 GiB。
      videoFile: null,
      savedState: null,
      uploading: false,
      percentage: 0,
      confirmedChunks: 0,
      chunkCount: 0,
      phaseText: '请先选择视频',
      resultText: ''
    }
  },
  created() {
    // 原生任务句柄只用于方法调用,不参与模板响应式处理。
    this._uploadTask = null
    this._uploadRun = 0
    this._leaving = false
  },
  onUnload() {
    this._leaving = true
    this._uploadRun += 1
    const task = this._uploadTask
    this._uploadTask = null
    if (task) {
      try { task.abort() } catch (error) { console.error('取消上传失败', error) }
    }
  },
  methods: {
    formatSize(bytes) {
      return (bytes / 1024 / 1024).toFixed(1) + ' MiB'
    },
    onVideoSelected(files) {
      if (this._leaving || this.uploading || !files.length) return
      this.videoFile = files[0] // 保留完整文件对象,上传时直接交给插件。
      this.savedState = null
      this.percentage = 0
      this.confirmedChunks = 0
      this.chunkCount = 0
      this.resultText = ''
      this.phaseText = '视频已选择,点击开始分片上传'
    },
    onChooseFail(error) {
      if (this._leaving || error.errCode === 9011002) return
      this.phaseText = '选择失败:' + error.errMsg
      console.error('选择失败', error.errCode, error.errMsg, error.details)
    },
    startUpload() {
      if (this._leaving || this.uploading || !this.videoFile) return
      if (!UPLOAD_API.sessionUrl || !UPLOAD_API.chunkUrl || !UPLOAD_API.completeUrl) {
        uni.showToast({ title: '请先配置上传地址', icon: 'none' })
        return
      }

      const run = ++this._uploadRun
      let settled = false
      const active = () => !settled && !this._leaving && this._uploadRun === run
      const finish = () => {
        settled = true
        this.uploading = false
        this._uploadTask = null
      }
      this.uploading = true
      this.percentage = 0
      this.resultText = ''
      this.phaseText = '准备文件并计算校验值,大视频可能需要等待'

      try {
        const task = uploadFile({
          file: this.videoFile,
          url: UPLOAD_API.chunkUrl,
          name: 'file',
          header: {}, // 需要登录时,在这里传入应用当前有效的请求头。
          formData: {}, // 业务参数会作为 session/complete 请求的 metadata;可填 { businessType: 'video' }。
          timeout: 120000,
          parseJson: true,
          retryCount: 2,
          retryDelay: 1000,
          resumable: {
            enabled: true,
            protocolName: 'lizhao-resumable-v1',
            sessionUrl: UPLOAD_API.sessionUrl,
            completeUrl: UPLOAD_API.completeUrl,
            sessionResponse: SESSION_RESPONSE,
            chunkSize: 5 * 1024 * 1024, // 每片 5 MiB。
            concurrency: 2 // 同时上传两个分片。
          },
          resumableState: this.savedState,
          resumableStateChange: (state) => {
            if (!active()) return
            this.savedState = state
            this.confirmedChunks = state.uploadedChunks.length
            this.chunkCount = state.chunkCount
            this.phaseText = this.confirmedChunks === this.chunkCount
              ? '分片已确认,等待服务端合并结果'
              : '正在分片上传'
          },
          progress: (res) => {
            if (!active()) return
            this.percentage = res.progress
          },
          success: (res) => {
            if (!active()) return
            this.percentage = 100
            this.phaseText = '上传完成'
            this.resultText = res.parsedData == null ? res.data : JSON.stringify(res.parsedData)
            this.savedState = null
            finish()
          },
          fail: (error) => {
            if (!active()) return
            this.phaseText = error.errCode === 9011010
              ? '已取消本次上传,可在当前页继续'
              : '上传失败:' + error.errMsg + ',可在当前页重试'
            console.error('上传结束', error.errCode, error.errMsg, error.data, error.details)
            finish()
          }
        })
        // 参数校验可能同步回调,已结算的请求不再保存旧句柄。
        if (active()) this._uploadTask = task
      } catch (error) {
        if (!active()) return
        this.phaseText = '上传调用失败,请检查日志后重试'
        console.error('上传调用失败', error)
        finish()
      }
    },
    cancelUpload() {
      const task = this._uploadTask
      if (!this.uploading || !task) return
      try {
        task.abort()
      } catch (error) {
        console.error('取消上传失败', error)
      }
    }
  }
}
</script>

<style scoped>
.video-upload-page { padding: 24rpx; }
.video-file-info { margin-top: 24rpx; word-break: break-all; }
.video-status { margin: 20rpx 0; color: #555; }
.video-actions { margin-top: 24rpx; }
.video-actions button { margin-bottom: 16rpx; }
.video-result { margin-top: 24rpx; word-break: break-all; }
</style>

常用参数怎么调整

在上方完整代码中修改以下配置即可:

配置 本示例值 使用说明
组件 accept video/* 参数直接写在组件上,选择系统识别的视频
组件 max-size 1024 * 1024 * 1024 本示例限制为 1 GiB,即 1024 MiB;单位是字节,可按业务调整
resumable.chunkSize 5 * 1024 * 1024 每片 5 MiB;200 MiB 为 40 片,1 GiB 为 205 片
resumable.concurrency 2 同一个视频同时上传两个分片,不是同时上传两个视频
timeout 120000 单次请求超时,单位毫秒;不表示整个大视频必须在两分钟内完成
retryCount / retryDelay 2 / 1000 分片传输失败的自动重试设置;用户取消不自动重试

chunkSize 默认 5 MiB,允许 256 KiB~64 MiB;concurrency 默认 2,允许 1~4。App 需要为临时分片及平台缓存保留可用空间,实际速度取决于文件来源、网络和服务器。

进度、成功与取消

大视频开始时会先读取并计算 SHA-256,此时上传百分比可能仍为 0,完整示例显示“准备文件并计算校验值”。progress(res) 中的 res.progress 是上传百分比,totalBytesSent / totalBytesExpectedToSend 是上传字节统计;resumableStateChange(state) 中的 uploadedChunks 是服务端已确认的分片。

页面以 success(res) 进入完成状态。分片全部确认后仍需等待合并接口返回成功;后端应先验证实际落盘分片、合并文件字节数和 SHA-256,再返回成功。百分比、已确认片数和成功回调表示接口按约定回应成功,不能代替服务器文件内容校验。res.parsedData 是启用 parseJson 后解析的结果,res.data 保留原文。视频业务 ID、访问 URL 等字段由自己的后端响应决定。

完整示例将 resumableStateChange 返回的状态保存在页面的 savedState 中,下次调用通过 resumableState 传回;服务端返回的已确认列表仍是最终依据。task.abort() 取消当前请求,取消码为 9011010。当前页继续要求源文件仍然可访问、会话仍有效;示例离页会取消上传,页面状态只保存在内存,不承诺关闭页面后自动续传或系统后台常驻上传。需要跨重启恢复时,使用上方可靠队列并提供有效的运行期请求配置。

分片上传支持 Android、iOS、Harmony、Web;微信和支付宝小程序返回 9011030,不会退回整文件上传并伪装成分片成功。

可靠队列接入同一协议

在 resolveRequest 中返回 resumable;URL、请求头和 Token 只存在当前运行期,队列只保存非敏感续传状态:

configureUploadQueue({
  concurrency: 2,
  resolveRequest(_task, success, fail) {
    success({
      url: 'https://api.example.com/upload/chunk',
      name: 'file',
      header: {},
      resumable: {
        enabled: true,
        protocolName: 'lizhao-resumable-v1',
        sessionUrl: 'https://api.example.com/upload/session',
        completeUrl: 'https://api.example.com/upload/complete',
        chunkSize: 5 * 1024 * 1024,
        concurrency: 2
      }
    })
  }
})

服务端协议要求

  1. 会话接口接收 protocol / uploadId / fileHash / fileName / fileSize / chunkSize / chunkCount / metadata,默认成功返回 { uploadId, uploadedChunks, completed?, result? };有包装或字段改名时配置 resumable.sessionResponse。即使已完成也必须返回非空字符串 uploadId。
  2. 分片接口接收文件字段及 lzProtocol / lzUploadId / lzFileHash / lzChunkIndex / lzChunkCount / lzChunkOffset / lzChunkSize / lzFileSize / lzFileName。同一会话、同一序号重复提交必须幂等。
  3. 完成接口接收与会话相同的文件身份字段,校验分片数量、偏移、大小和整文件 SHA-256 后再原子合并;重复完成请求必须返回同一业务结果。
  4. uploadedChunks 是恢复依据。客户端保存的分片序号只用于加速会话查询,不能覆盖服务端状态。

第一步:session 接口实际接收什么

插件先计算文件 SHA-256,再以 POST JSON 调用 sessionUrl。以 200 MiB 视频、每片 5 MiB 为例:

{
  "protocol": "lizhao-resumable-v1",
  "uploadId": "",
  "fileHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "fileName": "video.mp4",
  "fileSize": 209715200,
  "chunkSize": 5242880,
  "chunkCount": 40,
  "metadata": {}
}

后端成功返回 HTTP 200:

{
  "uploadId": "upload-demo-001",
  "uploadedChunks": [],
  "completed": false
}

uploadedChunks 返回真实落盘的零基片号;completed 只有最终文件合并并校验通过才为 true。上面是顶层响应格式,使用上方完整 Vue 2 页时将 SESSION_RESPONSE 设为 null;统一 result 包装则保留映射。

示意中的文件名、大小、哈希和片数会按实际文件生成,业务页不需要另发一次请求,也不要写死示意哈希。请求字段叫 protocol,前端配置项才叫 protocolName。首次没有本地续传状态时 uploadId 是空字符串,后端需要按文件身份创建或查询真实会话;携带匹配的续传状态时会带上原 ID。metadata 来自上传参数 formData,默认 {};大小单位是字节,片号从 0 开始。

后端应按 JSON 请求体接收这一步的数据;此时不含视频文件。得到有效会话后,插件才向 uploadFile.url 发 multipart 分片,默认文件字段名为 file。

第二步:chunk 收到的 ID 为什么不是 UploadId

三个阶段的会话 ID 字段有明确区别:

请求阶段 请求形式 会话 ID 字段
session POST JSON;首次可以为空 uploadId,响应也默认返回 uploadId
chunk POST multipart/form-data lzUploadId,取自有效的 session 响应
complete POST JSON uploadId

chunk 表单中的字段如下,插件自动填入,业务页不用自行拼装:

下面 JSON 仅说明表单字段结构;实际请求是 multipart,file 必须是真实二进制,不是说明字符串,也不需要转 Base64。以第 0 片为例:

{
  "lzProtocol": "lizhao-resumable-v1",
  "lzUploadId": "upload-demo-001",
  "lzFileHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "lzChunkIndex": 0,
  "lzChunkCount": 40,
  "lzChunkOffset": 0,
  "lzChunkSize": 5242880,
  "lzFileSize": 209715200,
  "lzFileName": "video.mp4",
  "file": "当前分片的二进制文件,此处仅作说明"
}

分片实际保存成功后返回 HTTP 200,业务 JSON 示例:

{
  "received": true,
  "uploadId": "upload-demo-001",
  "chunkIndex": 0,
  "receivedBytes": 5242880
}

分片响应体没有固定必填字段,插件按成功 HTTP 状态确认该片;后端须先验证实收字节数和落盘成功再返回 2xx。普通 multipart 字段按表单文本传输,后端按需要绑定为字符串或整数。末片的 lzChunkSize 可能小于配置的 chunkSize。

如果后端方法里的 UploadId 为空,先核对原始表单的 lzUploadId 及其值;后端应将这个表单字段绑定到自己的会话 ID 变量。文件从上传参数 name 对应的文件字段接收,不能把整个 chunk 请求按 JSON 解析。sessionResponse 只改变 session 响应的读取路径,不会改名这些请求字段。

第三步:complete 的请求与成功响应

所有缺失分片收到成功响应后,插件 POST JSON 到 completeUrl,请求例如:

{
  "protocol": "lizhao-resumable-v1",
  "uploadId": "upload-demo-001",
  "fileHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "fileName": "video.mp4",
  "fileSize": 209715200,
  "chunkSize": 5242880,
  "chunkCount": 40,
  "metadata": {}
}

后端按 uploadId 查询会话,检查每片和字节数,顺序合并、校验总大小和 SHA-256,通过后返回 HTTP 200,业务 JSON 例如:

{
  "fileId": "file-demo-001",
  "url": "/upload/files/video.mp4",
  "fileSize": 209715200
}

完成响应的业务字段可以自行定义,插件通过 data / parsedData 交给页面。不要在文件为空或校验失败时返回成功;这里发送的是 uploadId / fileHash,不是 lzUploadId / FileMd5。

失败响应示例:HTTP 409,缺少分片。

{
  "code": "UPLOAD_CHUNKS_MISSING",
  "message": "缺少分片:2"
}

失败必须使用合适的非 2xx HTTP 状态;仅在 HTTP 200 的正文中写失败业务码,不会被插件自动当作 HTTP 失败。

会话响应有 result / data 包装时如何配置

不配置映射时,后端可以直接返回:

{ "uploadId": "后端生成的真实会话ID", "uploadedChunks": [], "completed": false }

如果业务统一返回下面的格式,也可以保留:

{
  "code": 200,
  "type": "success",
  "message": "",
  "result": {
    "uploadId": "后端生成的真实会话ID",
    "uploadedChunks": [],
    "isFinished": false
  }
}

在现有 uploadFile() 调用的 resumable 中增加以下配置;上方完整 Vue 2 页已将它提取为 SESSION_RESPONSE:

sessionResponse: {
  uploadId: 'result.uploadId',
  uploadedChunks: 'result.uploadedChunks',
  completed: 'result.isFinished'
}

例如后端改为 { data: { session: { id, chunks, finished } } },则分别配置为 data.session.id、data.session.chunks、data.session.finished。路径以 HTTP 响应正文为起点,不能额外添加 uni.request 回调对象外层的 data。

映射项 未配置时读取 响应值要求
uploadId uploadId 非空字符串,会话身份由后端维护
uploadedChunks uploadedChunks 已确认的零基分片序号数组
completed completed 布尔值;false、true 不要写成字符串
result result 可选;会话已完成时直接返回的业务结果路径

可以只配置需要改名的字段,其余仍使用默认字段。路径每一段以字母、_ 或 $ 开头,后续可有数字;不支持独立的 $ 段、数组下标、空段、表达式或 __proto__ / prototype / constructor。显式无效路径返回 9011029;配置的 ID 路径取不到非空字符串时返回 9011032,此时分片接口尚未调用。

组件自行上传时,在组件标签上使用 :resumable-session-response="sessionResponse",页面 data() 中声明上述映射对象。mode="pick" 只选择文件,若由页面调用 uploadFile(),应在该 API 的 resumable 内配置。可靠队列在 resolveRequest 返回的 resumable 内使用相同映射,恢复时需重新提供。

映射只用于 session 响应,不改变请求字段;合并接口的 data / parsedData 仍保留后端原响应。控制接口要求 HTTP 2xx,正文中的业务 code: 200 不会替代 HTTP 状态,也不会自动增加业务码判断。

最后一步:99% 并提示 9011034

3 / 3 片、99% 表示分片已收到成功响应,但还在等待 completeUrl 成功。完成接口接收 POST JSON,文件身份字段与 session 相同:protocol / uploadId / fileHash / fileName / fileSize / chunkSize / chunkCount / metadata,其中 uploadId 是本次真实会话 ID,不能改成 chunk 的表单字段 lzUploadId。

文件校验值叫 fileHash,是插件计算的 SHA-256(64 个十六进制字符)。插件不发送 FileMd5,也不会把 SHA-256 转成 MD5;后端只用 { UploadId, FileMd5 } 接收,就取不到约定的文件校验字段。后端应绑定 fileHash,并计算合并文件的 SHA-256 与之比较。若日志只打印绑定后的 DTO,不能据此断定原始请求也只有两个字段,应同时记录原始 JSON。

当前 9011034 由 complete 返回非 HTTP 2xx 或请求失败(如超时、连接中断)触发;响应正文有 result 包装本身不会触发该错误。排查时记录 complete 原始请求、HTTP 状态码、响应正文和服务端异常。示例同时打印错误对象的 data / details;其中可能有 HTTP 状态信息,具体响应和异常仍应结合服务端或网络日志确认。若后端因缺片、校验字段未绑定或哈希不匹配拒绝合并,应先解决对应原因。

显示 100%,服务器文件却是 0 字节

先核对同一 uploadId 的每片 lzChunkIndex / lzChunkSize、实际收到的文件字段字节数,以及保存到磁盘后的分片大小。例如约 11.5 MiB 文件分三片时,编号为 0 / 1 / 2,前两片各 5242880 字节,末片是 fileSize - 10485760 字节。

核查结果 继续排查的位置
分片落盘大小都正确,合并文件为 0 后端合并顺序、路径、读写流与关闭时机
上传请求的文件内容非空,后端接收或落盘为 0 后端文件字段绑定、接收和保存
请求发出的分片本身为空 前端文件来源、切片或传输

lzChunkSize 是声明的本片字节数,不能代替实际收到并保存的文件字节数。后端只有在分片齐全、合并大小等于 fileSize、整文件 SHA-256 匹配后才应返回成功;只返回 HTTP 200 或一个文件路径不能证明文件有效。

内置界面与自定义页面

用于 uni-app Vue 2 / Vue 3 的表单附件选择。组件的 business 是默认业务面板,simple 是轻量按钮和文件列表;headless 由页面绘制按钮,通过组件实例的 choose() / open() 和事件复用流程。ui-mode 只控制界面形式,文件类型由 scene / accept 等配置决定。

uni-app Vue 2:图片、文档和视频混选

下面是完整的最小页面。已有页面只合并组件标签、components、data 和 methods,不要重复声明第二个 export default。

<template>
  <view>
    <lizhao-choose-file
      scene="attachment"
      mode="pick"
      ui-mode="simple"
      accept=".pdf,.docx,.xlsx,image/*,.avi,.mp4,video/avi"
      :multiple="true"
      :max-count="5"
      :show-progress="false"
      :show-image-preview="true"
      :preview-on-click="true"
      @select="onImageSelected"
      @fail="onChooseFail"
    />
  </view>
</template>

<script>
import LizhaoChooseFile from '@/uni_modules/lizhao-choose-file/components/lizhao-choose-file/lizhao-choose-file.vue'

export default {
  components: { LizhaoChooseFile },
  data() {
    return { selectedFiles: [] }
  },
  methods: {
    onImageSelected(files) {
      this.selectedFiles = files
      files.forEach(file => {
        console.log('已选择', file.name, file.size, file.type)
      })
    },
    onChooseFail(error) {
      if (error.errCode === 9011002) return
      console.error('选择失败', error.errCode, error.errMsg, error.details)
    }
  }
}
</script>

点击组件自带按钮后,@select 直接把本地文件数组传给 onImageSelected(files)。该名称是页面自己定义的处理函数,必须在 methods 中实现并与标签一致;它不需要从插件导入,也不按 event.detail 取值。selectedFiles 提前在 data 中声明,便于表单保存和页面更新。文件字段见PickedFile。

mode="pick" 只负责选择,图片预览开关只影响图片显示;选择视频不等于已上传视频。手动上传时,把选中的完整文件对象交给 uploadFile / uploadFiles。大视频的完整前端代码直接见本文大文件上传。

文件类型怎么配置

scene="image" 未指定 accept 时默认只接受 image/*,视频会被过滤。混合附件使用上方 scene="attachment" 并明确设置 accept;只选视频可参考下表。

业务目标 组件 scene 组件 accept
只选图片 image image/*
只选 AVI、MP4 attachment .avi,.mp4,video/avi
接受系统识别的任意视频 media video/*
临时取消类型过滤排查 attachment */*

accept 使用英文逗号分隔。扩展名写成 .mp4、.avi,图片或视频通配写成 image/*、video/*;不要写 .mp4/* 或裸 image。video/avi 用于兼容部分文件来源对 AVI 的 MIME 标记。*/* 会允许所有类型,排查后按自己的业务恢复限制。

过滤优先级见组件配置与事件。已有页面同时配置 file-type / extensions / mime-types 时,也应检查这些值。uni-app Vue 2 的组件接入与大视频上传完整代码已在本节和上方给出;uni-app x 使用公开 API 接入。

系统文件与 SQLite 备份、恢复

连接处理仍以 SQLite 插件规则为准:Android/iOS 恢复前先关闭目标数据库,成功后重新打开;Harmony 恢复时保持数据库打开。完整可运行流程已同时加入两个插件的 uni-app 与 uni-app x 示例页。

SQLite 文件备份与恢复业务流程

lizhao-sqlite-pro 的 backupDatabase / restoreDatabase 只接受应用沙盒中的普通本地路径。Android 系统选择器常返回 content://,iOS 和 Harmony 也可能返回带临时授权的 URI;这些值不能直接当作 SQLite 文件路径使用。

正确顺序是:

  1. 备份:SQLite 先把数据库备份到应用沙盒,saveSandboxFile 再把该文件写到用户选择的系统位置。
  2. 恢复:pickFileToSandbox 先把系统文件完整复制到应用沙盒,SQLite 再用返回的 file.path 恢复。
import { pickFileToSandbox, saveSandboxFile } from '@/uni_modules/lizhao-choose-file'
import {
  openDatabase,
  closeDatabase,
  backupDatabase,
  restoreDatabase,
  getDatabasePath
} from '@/uni_modules/lizhao-sqlite-pro'

const databaseName = 'business'
const sandboxBackupPath = getDatabasePath(databaseName) + '.system-backup.db'

// 备份并另存为:必须等待数据库备份成功后再打开系统保存选择器。
export function backupAndSaveToSystem() {
  backupDatabase({
    name: databaseName,
    path: sandboxBackupPath,
    success(backupResult) {
      saveSandboxFile({
        path: backupResult.targetPath,
        filename: databaseName + '-backup.db',
        mimeType: 'application/vnd.sqlite3',
        success(saveResult) {
          console.log('系统文件已保存', saveResult.name, saveResult.size)
        },
        fail(err) {
          console.error('系统另存为失败', err)
        }
      })
    }
  })
}

// 选择备份并恢复:SQLite 只接收复制完成后的沙盒 path。
function restoreFromSandboxPath(path) {
  const restoreNow = () => {
    restoreDatabase({
      name: databaseName,
      path,
      success() {
        // #ifdef APP-PLUS
        openDatabase({
          name: databaseName,
          success() {
            console.log('恢复成功,数据库已重新打开')
          }
        })
        return
        // #endif
        console.log('恢复成功')
      },
      fail(err) {
        console.error('恢复失败', err)
      }
    })
  }

  // Android / iOS 必须先关闭目标数据库;Harmony 保持打开并直接恢复。
  // #ifdef APP-PLUS
  closeDatabase({
    name: databaseName,
    success() {
      restoreNow()
    },
    fail(err) {
      console.error('恢复前关闭数据库失败', err)
    }
  })
  return
  // #endif
  restoreNow()
}

export function pickBackupAndRestore() {
  pickFileToSandbox({
    accept: '.db,.sqlite,.sqlite3,.bak',
    directory: 'cache',
    success(file) {
      restoreFromSandboxPath(file.path)
    }
  })
}

API 用途速查

模块 适用业务 常用方法 使用结果
文件选择 单文件或批量资料 pickFile / pickFiles PickedFile 或文件数组
业务场景 头像、图片、Office pickBusinessFiles / pickAndUploadBusinessFiles 场景预设与流程状态
上传 确认后上传或选择后上传 uploadFile / uploadFiles / pickAndUploadFile / pickAndUploadFiles 进度、逐项结果与取消控制器
附件编辑 已存记录回显、只读、补选 Vue 组件 attachments / readonly / append 独立已有记录与新文件列表
可信检查 格式一致性与同内容策略 inspectFile / inspectFiles,或 trust 检查结论及可选 SHA-256
可靠上传队列 保留任务、恢复和重试 configureUploadQueue / enqueueUploadFiles / restoreUploadQueue 公开任务快照
大文件续传 网络中断后补传 上传配置 resumable 服务端确认的会话与分片状态
系统文件桥 沙盒导入、另存为 pickFileToSandbox / saveSandboxFile 普通本地路径或系统保存结果
调试 业务接入排障 getDebugTrace / clearDebugTrace 当前轨迹

详细参数、默认值、回调与返回字段见本节下方的参数表和返回值说明。

队列管理API 用途
getUploadQueueSnapshot() 读取队列独立快照
pauseUploadQueueTask / resumeUploadQueueTask / retryUploadQueueTask / cancelUploadQueueTask 按任务ID暂停、继续、重试或取消
removeUploadQueueTask / clearCompletedUploadQueueTasks 删除指定任务或清理已完成任务
pauseAllUploadQueueTasks / resumeAllUploadQueueTasks 批量暂停或继续队列
onUploadQueueChange / offUploadQueueChange 订阅或解除同一回调的队列变化监听

业务配置

业务场景默认值

场景 默认 accept 默认多选 默认数量 说明
attachment */* 是 9 通用附件
avatar image/* 否 1 头像或单图上传
image image/* 是 9 图片选择或图片上传
document .pdf,.txt,.ofd 是 9 PDF、文本、OFD 文档
office .doc,.docx,.xls,.xlsx,.ppt,.pptx 是 9 Office 文件
media image/*,video/*,audio/* 是 9 图片、视频、音频
archive .zip,.rar,.ofd,application/zip,application/vnd.rar,application/ofd 是 9 压缩包与 OFD
custom */* 是 9 优先使用业务传入的 accept

组件配置与事件

说明 可选业务组件,适合快速放到页面中触发选择或选择并上传。核心文件能力仍由 UTS API 提供,组件按 ui-mode 提供三种接入方式:business 使用内置业务上传 UI,simple 使用轻量按钮列表 UI,headless 不渲染内置 UI,仅通过组件方法和事件交给业务页面自定义。

属性

参数 类型 必填 说明 默认值 可选参数
scene string 否 业务场景 attachment attachment / avatar / image / document / office / media / archive / custom
file-type string 否 组件文件类型别名,合法值会优先覆盖 scene 无 attachment / avatar / image / document / office / media / archive / custom
mode string 否 工作模式 pick pick / upload / queue
ui-mode string 否 内置 UI 模式,business 为业务上传面板,simple 为轻量按钮列表,headless 不渲染内置 UI business business / simple / headless
attachments array 否 已有附件展示记录,独立于本地文件 [] id/name/size/type/url
readonly boolean 否 只读查看,保留预览并禁止编辑与任务控制 false true / false
append boolean 否 本地选择成功后追加,仅 mode=pick false true / false
trust-mode string 否 可信检查模式;空字符串表示关闭 空字符串 warn / strict
calculate-sha256 boolean 否 启用可信检查时是否计算完整 SHA-256 false true / false
require-detected-type boolean 否 strict 模式是否拒绝无法识别真实类型的文件 false true / false
duplicate-policy string 否 同内容文件处理策略 allow allow / skip / reject
show-trust-status boolean 否 business/simple 是否展示可信检查中文状态;headless 始终不渲染 false true / false
accept string 否 自定义文件类型过滤,优先级最高 无 无
extensions array/string 否 常用扩展名过滤,可传数组或逗号字符串 [] 例如 .pdf,.docx,.xlsx
mime-types array/string 否 MIME 类型过滤,可传数组或逗号字符串 [] 例如 image/*,application/pdf
multiple boolean 否 是否允许多选 true true / false
max-count number 否 已有附件与本次保留本地文件的最大总数 9 无
max-size number 否 单文件最大体积,单位字节 0 无
filename string 否 返回文件名模板,多选时底层会按现有规则追加序号 按场景预设 无
upload-url string 否 上传服务器地址,mode=upload 时必填 无 无
upload-name string 否 multipart 文件字段名 file 无
header object 否 上传请求头 无 无
form-data object 否 上传表单字段 无 无
timeout number 否 上传超时时间,单位毫秒 0 无
parse-json boolean 否 是否把服务器响应字符串尝试解析到 parsedData false true / false
success-status-codes array/string 否 自定义成功 HTTP 状态码;为空时 200-299 均视为成功 [] 例如 200,201,204
retry-count number 否 单文件上传失败后的重试次数 0 大于等于 0
retry-delay number 否 每次重试前等待毫秒数 0 大于等于 0
concurrency number 否 批量普通上传并发限制,也是队列默认并发数 0 大于等于 0
stop-on-fail boolean 否 多文件失败后是否停止 true true / false
sequential boolean 否 多文件是否按顺序上传 true true / false
resumable-enabled boolean 否 是否显式启用 lizhao-resumable-v1 真正断点续传 false true / false
resumable-session-url string 否 创建或查询分片会话的 JSON POST 地址 无 开启分片时必填
resumable-complete-url string 否 校验、合并并完成文件的 JSON POST 地址 无 开启分片时必填
resumable-session-response object 否 会话响应字段路径映射,与 API 的 resumable.sessionResponse 相同 null 可选 uploadId / uploadedChunks / completed / result
resumable-chunk-size number 否 分片字节数 5242880 256 KiB~64 MiB
resumable-concurrency number 否 单文件内部同时上传的分片数 2 1~4
queue-auto-start boolean 否 mode=queue 入队后是否立即上传 true true / false
queue-wifi-only boolean 否 队列是否只在 Wi-Fi 下上传 false true / false
queue-business-key string 否 队列业务键,可用于同业务内容判重 空字符串 无
queue-business-data object 否 队列可持久化的非敏感业务字段 无 不要放 Token 或密钥
show-progress boolean 否 是否显示上传进度条 true true / false
show-file-count boolean 否 是否显示文件数量 true true / false
show-selected-list boolean 否 是否显示已选文件列表 true true / false
show-cancel boolean 否 是否显示取消上传按钮 true true / false
show-remove boolean 否 是否显示文件删除入口,上传中不显示删除入口,避免 UI 列表和底层上传队列不一致 true true / false
show-image-preview boolean 否 图片文件是否显示图片缩略图;非图片仍显示文件类型图标 true true / false
preview-on-click boolean 否 点击图片缩略图时是否调用 uni.previewImage 预览同批图片 true true / false
button-text string 否 自定义按钮文案 按模式生成 无
title string 否 自定义内置业务 UI 标题 按场景预设 无
description string 否 自定义内置业务 UI 描述 按场景预设 无
empty-text string 否 自定义空态标题 尚未选择文件 无
empty-hint string 否 自定义空态说明 按标题描述生成 无
remove-text string 否 删除文件按钮文案 删除 无
confirm-remove boolean 否 删除前是否弹出确认框 false true / false
confirm-remove-title string 否 删除确认框标题 删除文件 无
confirm-remove-content string 否 删除确认框内容 确定删除该文件吗? 无
disabled boolean 否 是否禁用 false true / false
busy-cooldown number 否 系统选择器忙碌时的点击保护时长,单位毫秒 1800 无

文件类型优先级:accept 明确传入时直接使用;否则组件会把 extensions 与 mime-types 合并为过滤条件;仍未配置时再使用 file-type 或 scene 对应的业务预设。

UI 模式

模式 说明 适用场景
business 渲染内置业务上传面板,包含场景图标、文件图标、文件名、文件大小、整体进度、当前文件进度、文件数量和取消按钮 想快速获得完整上传体验,不希望页面重复写业务 UI
simple 渲染轻量按钮、数量、进度条和简洁文件列表 只需要基础交互,页面已有自己的卡片或表单布局
headless 不渲染内置 UI,组件只保留选择、上传、进度聚合、取消和事件能力 页面需要完全自定义视觉,但希望复用组件封装的流程状态

事件

参数 类型 必填 说明 默认值 可选参数
@attachments-change function 否 附件清单变化,返回 { attachments, files } 不适用 —
@select function 否 直接传入本次选中的 PickedFile 数组,页面需定义同名处理方法 不适用 —
@progress function 否 上传进度变化时触发 不适用 —
@success function 否 上传成功时触发 不适用 —
@fail function 否 选择或上传失败时触发 不适用 —
@complete function 否 选择或上传完成时触发 不适用 —
@cancel function 否 用户取消选择或取消上传时触发 不适用 —
@remove function 否 本地移除返回 { file, index, files, state };远程移除返回 { attachment, remote: true },服务端删除由业务处理 不适用 —
@state-change function 否 统一流程状态变化时触发 不适用 —
@inspect-progress function 否 可信检查读取进度变化时触发,只含文件名和进度等运行期信息,不展示完整 hash 不适用 —
@inspect function 否 单个文件检查完成时触发,返回 FileInspectionResult 不适用 —
@duplicate function 否 发现重复内容时触发,返回文件与 duplicateOf 不适用 —

方法

参数 类型 必填 说明 默认值 可选参数
choose function 否 通过组件实例主动触发选择或选择并上传流程,适合 headless 模式自定义按钮调用 不适用 —
open function 否 choose 的语义化别名,便于兼容常见上传组件调用习惯 不适用 —
abort function 否 取消当前上传任务,取消上传错误码为 9011010 不适用 —
remove function 否 通过组件实例删除指定索引文件,等价于调用内置删除入口 不适用 —
removeAttachment function 否 按业务 ID 移除远程展示项;只读时不执行 不适用 —
reset function 否 程序化重置本地清单和流程,保留远程记录;不取消底层任务,只读时也可由业务调用 不适用 —

可靠上传队列配置

configureUploadQueue(options)

配置全局并发、重试、联网恢复和运行期请求解析器。应在恢复或入队前调用,resolveRequest 每次真正上传一个文件前触发。

参数 类型 必填 说明 默认值 可选参数
options ConfigureUploadQueueOptions 是 队列配置 无 concurrency / retryCount / retryDelay / resumeOnNetworkRestored / autoResumeOnLaunch / wifiOnly / maxPersistedBytes / completedRetention / taskTtl / resolveRequest
options.concurrency number 否 全局最大并发文件数 2 大于等于 1
options.retryCount number 否 普通上传失败自动重试次数 2 大于等于 0
options.retryDelay number 否 重试间隔,单位毫秒 1500 大于等于 0
options.resumeOnNetworkRestored boolean 否 当前会话网络恢复后是否继续 true true / false
options.autoResumeOnLaunch boolean 否 恢复持久任务后是否自动继续 false true / false
options.wifiOnly boolean 否 是否只允许 Wi-Fi 上传 false true / false
options.maxPersistedBytes number 否 插件持久副本容量上限 536870912 大于 0
options.completedRetention number 否 完成任务保留时间,单位毫秒 86400000 大于等于 0
options.taskTtl number 否 未完成任务有效期,单位毫秒 604800000 大于 0
options.resolveRequest UploadQueueResolveRequest 是 动态返回当前上传请求,失败时任务进入 waiting-config 无 无

resolveRequest 返回的 UploadQueueRequest 可包含 resumable。该配置与 url / header / formData 一样只在本次运行期使用;启用后,队列文件项会额外保存协议、会话 ID、文件摘要、分片大小、分片总数、服务端已确认序号、确认字节数和更新时间,以便跨重启继续缺失分片。

enqueueUploadFiles(options) / restoreUploadQueue(options)

enqueueUploadFiles 会先保存源文件,再确认任务入队;restoreUploadQueue 会恢复元数据并检查文件副本。应用启动建议先配置队列,再以 autoResume: false 恢复,由用户确认后继续。

参数 类型 必填 说明 默认值 可选参数
options.files Array<PickedFile> 入队时是 待保存并上传的文件 无 无
options.businessKey string 否 非敏感业务标识 空字符串 无
options.businessData UTSJSONObject 否 非敏感业务扩展字段 无 无
options.autoStart boolean 否 保存成功后是否开始调度 true true / false
options.autoResume boolean 恢复时否 恢复后是否立即继续 false true / false
options.success function 否 入队返回任务,恢复返回队列快照 无 无
options.fail function 否 持久化、文件缺失或配置失败回调 无 无
options.complete function 否 成功或失败后触发 无 无

其他控制 API:getUploadQueueSnapshot()、pauseUploadQueueTask(taskId)、resumeUploadQueueTask(taskId)、retryUploadQueueTask(taskId)、cancelUploadQueueTask(taskId)、removeUploadQueueTask(taskId)、pauseAllUploadQueueTasks()、resumeAllUploadQueueTasks()、clearCompletedUploadQueueTasks()、onUploadQueueChange(callback)、offUploadQueueChange(callback)。

安全说明:队列只持久化任务状态、非敏感业务字段、文件显示信息、插件内部文件键和上述续传状态;不持久化上传 URL、Authorization、Cookie、Token、签名、完整请求头、响应正文、响应头、临时分片路径或 App 私有绝对路径。业务侧也不要把账号、密钥或生产配置放进 businessData。

系统文件桥配置

pickFileToSandbox(options)

打开系统单文件选择器,并在系统授权仍有效时把文件完整复制到应用沙盒。成功回调返回普通本地 path,可交给 SQLite、解压、离线模型等只接收沙盒路径的能力。

参数 类型 必填 说明 默认值 可选参数
accept string 否 扩展名、MIME 或通配符过滤 */* —
maxSize number 否 最大字节数,0 表示不限制 0 —
filename string 否 沙盒目标文件名;非法字符会被清理,同名时自动追加序号 原文件名 —
directory cache / userData 否 写入临时缓存或持久用户目录 cache —
success function 否 文件完整复制后返回 PickedFile 无 —
fail function 否 用户取消、限制不符或复制失败 无 —
complete function 否 成功或失败后触发 无 —

cache 适合“选择后立即恢复/解析”,系统可能清理;需要长期保留时选择 userData,并由业务自行提供清理入口。插件不会把 content:// 或短期授权 URI 伪装成普通路径返回。

saveSandboxFile(options)

校验源文件属于当前应用沙盒后,打开系统保存选择器,并把文件完整写入用户指定位置。

参数 类型 必填 说明 默认值 可选参数
path string 是 应用沙盒内的可读普通文件路径 无 —
filename string 否 系统保存界面的建议文件名 源文件名 —
mimeType string 否 Android 保存选择器使用的 MIME application/octet-stream —
success function 否 写入完成后返回 SaveSandboxFileResult 无 —
fail function 否 用户取消、源路径越界或写入失败 无 —
complete function 否 成功或失败后触发 无 —

SaveSandboxFileResult 包含 name / size / sourcePath / targetUri / platform。targetUri 只用于展示或诊断,不保证是可长期访问的普通路径,更不能直接传给 restoreDatabase;再次恢复时仍应通过 pickFileToSandbox 重新选择并复制到沙盒。

选择与上传常用参数

pickFiles(options)

说明 选择多个文件。未配置 upload 时只返回文件选择结果;配置 upload 时选择成功后自动逐个上传。

支持平台 Android / iOS / Harmony / Web / 微信小程序 / 支付宝小程序

参数

参数 类型 必填 说明 默认值 可选参数
options PickFilesOptions 是 多文件选择参数对象 无 accept / multiple / maxCount / maxSize / filename / upload / success / fail / complete
options.accept string 否 文件类型过滤规则,支持扩展名、MIME、image/*、*/* */* 无
options.multiple boolean 否 是否允许多选 false true / false
options.maxCount number 否 最大可选数量,单选固定按 1 处理 多选默认 9,单选默认 1 无
options.maxSize number 否 单文件最大体积,单位字节,0 表示不限制 0 无
options.filename string 否 返回结果重命名模板,多选时自动追加序号 无 无
options.upload UploadConfig 否 选择成功后自动上传配置 无 url / name / header / formData / timeout / parseJson / successStatusCodes / resumable / sequential / stopOnFail / progress / success / fail / complete
options.success function 否 选择成功回调,返回文件数组 无 无
options.fail function 否 失败回调,返回统一错误对象 无 无
options.complete function 否 完成回调,成功返回文件数组,失败返回错误对象 无 无

pickFile(options)

说明 选择单个文件。未配置 upload 时只返回文件选择结果;配置 upload 时选择成功后自动上传。

支持平台 Android / iOS / Harmony / Web / 微信小程序 / 支付宝小程序

参数

参数 类型 必填 说明 默认值 可选参数
options PickFileOptions 是 单文件选择参数对象 无 accept / maxSize / filename / upload / success / fail / complete
options.accept string 否 文件类型过滤规则 */* 无
options.maxSize number 否 单文件最大体积,单位字节,0 表示不限制 0 无
options.filename string 否 返回结果重命名模板 无 无
options.upload UploadConfig 否 选择成功后自动上传配置 无 url / name / header / formData / timeout / parseJson / successStatusCodes / resumable / progress / success / fail / complete
options.success function 否 选择成功回调,返回单个文件对象 无 无
options.fail function 否 失败回调,返回统一错误对象 无 无
options.complete function 否 完成回调,成功返回文件对象,失败返回错误对象 无 无

uploadFile(options)

说明 手动上传单个已选择文件。默认使用通用 HTTP multipart/form-data;传入 resumable.enabled: true 后切换为 lizhao-resumable-v1 分片流程。服务器响应默认原样放入 data 字段;传 parseJson: true 时会额外尝试写入 parsedData。

支持平台 普通上传:Android / iOS / Harmony / Web / 微信小程序 / 支付宝小程序。分片上传:Android / iOS / Harmony / Web。

参数

参数 类型 必填 说明 默认值 可选参数
options UploadFileOptions 是 单文件上传参数对象 无 file / url / name / header / formData / timeout / parseJson / successStatusCodes / resumable / resumableState / resumableStateChange / retryCount / retryDelay / success / fail / complete / progress
options.file PickedFile 是 pickFile 或 pickFiles 返回的文件对象 无 无
options.url string 是 上传服务器地址 无 无
options.name string 否 multipart 文件字段名 file 无
options.header UTSJSONObject 否 请求头配置 无 无
options.formData UTSJSONObject 否 表单附加字段 无 无
options.timeout number 否 上传超时时间,单位毫秒,0 表示平台默认值 0 无
options.parseJson boolean 否 是否把服务器响应字符串尝试解析到 parsedData;解析失败不影响 data 原文 false true / false
options.successStatusCodes Array 否 自定义成功 HTTP 状态码;为空时 200-299 均视为成功 [] 例如 [200, 201, 204]
options.resumable ResumableUploadConfig 否 真正断点续传配置;未传或 enabled=false 时保持普通上传 无 见下方协议参数
options.resumableState ResumableUploadState 否 上次保存的非敏感状态提示;服务端返回仍是最终依据 无 单文件手动恢复时使用
options.resumableStateChange function 否 每个分片被服务端确认后返回最新状态 无 可持久化后用于下一次调用
options.retryCount number 否 上传失败后自动重试次数,用户取消不会重试 0 无
options.retryDelay number 否 每次重试前等待时间,单位毫秒 0 无
options.success function 否 上传成功回调,返回 UploadSuccessItem 无 无
options.fail function 否 上传失败回调 无 无
options.complete function 否 上传完成回调 无 无
options.progress function 否 上传进度回调 无 无

uploadFiles(options)

说明 手动上传多个已选择文件。默认按顺序上传,失败后停止;传 sequential: false 时会并发上传,传 concurrency 可限制最大并发数量;传 stopOnFail: false 时会在成功结果中额外返回 failResults 便于定位部分失败文件。

支持平台 普通上传:Android / iOS / Harmony / Web / 微信小程序 / 支付宝小程序。分片上传:Android / iOS / Harmony / Web。

参数

参数 类型 必填 说明 默认值 可选参数
options UploadFilesOptions 是 多文件上传参数对象 无 files / url / name / header / formData / timeout / parseJson / successStatusCodes / resumable / resumableStateChange / retryCount / retryDelay / concurrency / sequential / stopOnFail / success / fail / complete / progress
options.files Array 是 pickFiles 返回的文件数组 无 无
options.url string 是 上传服务器地址 无 无
options.name string 否 multipart 文件字段名 file 无
options.header UTSJSONObject 否 请求头配置 无 无
options.formData UTSJSONObject 否 表单附加字段 无 无
options.timeout number 否 上传超时时间,单位毫秒 0 无
options.parseJson boolean 否 是否把服务器响应字符串尝试解析到 parsedData;解析失败不影响 data 原文 false true / false
options.successStatusCodes Array 否 自定义成功 HTTP 状态码;为空时 200-299 均视为成功 [] 例如 [200, 201, 204]
options.resumable ResumableUploadConfig 否 为每个文件建立独立分片会话 无 Android / iOS / Harmony / Web
options.resumableStateChange function 否 返回 { file, index, state },其中 index 保持原批次序号 无 无
options.retryCount number 否 单个文件上传失败后自动重试次数,用户取消不会重试 0 无
options.retryDelay number 否 每次重试前等待时间,单位毫秒 0 无
options.concurrency number 否 并发上传数量限制,仅在 sequential: false 时生效;0 表示不限制 0 无
options.sequential boolean 否 是否逐个上传;传 false 会并发上传多个文件 true true / false
options.stopOnFail boolean 否 某个文件失败后是否停止后续上传 true true / false
options.success function 否 全部上传成功回调,返回 UploadFilesResult 无 无
options.fail function 否 上传失败回调 无 无
options.complete function 否 上传完成回调 无 无
options.progress function 否 上传进度回调 无 无

ResumableUploadConfig 与 ResumableUploadState

参数 类型 必填 说明 默认值 可选参数
enabled boolean 是 只有 true 才切换到分片上传 false —
protocolName string 否 当前仅支持 lizhao-resumable-v1 lizhao-resumable-v1 —
sessionUrl string 是 创建或查询会话的 JSON POST 地址 无 —
completeUrl string 是 校验、合并并完成上传的 JSON POST 地址 无 —
sessionResponse ResumableSessionResponseMap 否 会话响应字段路径映射;包装或改名时配置 未配置字段读顶层同名键 uploadId / uploadedChunks / completed / result
chunkSize number 否 每片字节数,必须为安全整数 5 MiB —
concurrency number 否 单个文件内部的分片并发数 2 —

状态回调包含 protocolName / uploadId / fileHash / chunkSize / chunkCount / uploadedChunks / confirmedBytes / updatedAt。服务端会话与完成接口仍使用 JSON 键 protocol。状态不包含 URL、请求头、Token、响应正文或临时文件路径。业务自行落盘时仍应保护 uploadId,并设置合理有效期;恢复调用兼容候选早期保存的 protocol 键,并会重新查询服务端,不会仅凭本地数组跳过分片。

pickAndUploadFile(options) / pickAndUploadFiles(options)

说明 先选择文件,再根据 options.upload 自动上传。选择成功回调仍返回原始文件对象或文件数组,上传成功回调通过 upload.success 返回。

参数

参数 类型 必填 说明 默认值 可选参数
options.upload.url string 是 上传服务器地址 无 无
options.upload.name string 否 multipart 文件字段名 file 无
options.upload.header UTSJSONObject 否 请求头配置 无 无
options.upload.formData UTSJSONObject 否 表单附加字段 无 无
options.upload.timeout number 否 上传超时时间,单位毫秒 0 无
options.upload.parseJson boolean 否 是否把服务器响应字符串尝试解析到 parsedData false true / false
options.upload.successStatusCodes Array 否 自定义成功 HTTP 状态码;为空时 200-299 均视为成功 [] 例如 [200, 201, 204]
options.upload.resumable ResumableUploadConfig 否 选择完成后使用真正断点续传 无 Android / iOS / Harmony / Web
options.upload.retryCount number 否 上传失败后自动重试次数,用户取消不会重试 0 无
options.upload.retryDelay number 否 每次重试前等待时间,单位毫秒 0 无
options.upload.concurrency number 否 多文件并发上传数量限制,仅在 sequential: false 时生效 0 无
options.upload.progress function 否 上传进度回调 无 无
options.upload.success function 否 上传成功回调 无 无
options.upload.fail function 否 上传失败回调 无 无
options.upload.complete function 否 上传完成回调 无 无

返回值说明

PickedFile

字段 类型 说明
name string 文件名
size number 文件大小,单位字节
type string MIME 类型
path string 交给本插件上传的文件路径或授权 URI;需要普通沙盒路径时使用 pickFileToSandbox
url string 与 path 保持一致的兼容字段
lastModified number 最后修改时间,平台不支持时为 0
source string 文件来源平台标识
ext string 文件扩展名

UploadSuccessItem

字段 类型 说明
file PickedFile 本次上传的文件对象
index number 文件在本批次中的下标,单文件固定为 0
statusCode number HTTP 状态码
data string 服务器原始响应字符串
parsedData any/null 开启 parseJson 且解析成功时返回 JSON 对象或数组,否则为 null
header any 响应头信息,平台不返回时为 null

UploadFilesResult

字段 类型 说明
files Array 本批次上传的文件数组
results Array 每个成功上传文件的服务器返回
failResults Array stopOnFail: false 或部分失败场景下的失败明细,包含 file / index / error
successCount number 上传成功数量
failCount number 上传失败数量

错误码说明

错误主题 errSubject 为 lizhao-choose-file;失败时保留 errCode 和 errMsg,按下表处理。

错误码 含义 说明
9011001 platform unsupported 当前平台不支持
9011002 user cancelled 用户取消选择
9011003 permission denied 平台权限不足
9011004 count exceeded 选择数量超过 maxCount
9011005 invalid file type 文件类型不符合 accept
9011006 file size exceeded 文件超过 maxSize 限制
9011007 system error 平台选择器异常或返回结构异常
9011008 invalid upload options 上传参数错误,例如 url 为空或文件路径为空
9011009 upload failed 上传失败,例如网络错误或 HTTP 非 2xx
9011010 upload aborted 上传任务已取消
9011011 upload file path convert failed 上传前文件路径转换失败,例如 Android content:// 或 Harmony picker URI 无法复制到缓存/临时目录
9011012 queue not configured 队列尚未配置;先调用 configureUploadQueue,再添加或恢复任务
9011013 queue persist failed 队列持久化失败;保留错误详情,检查可用存储空间、目录访问和平台存储能力后重试
9011014 queue storage limit exceeded 队列本地存储达到配置上限;释放不再需要的任务和文件,或调整存储预算后重试
9011015 queue task not found 指定任务不存在;刷新 getUploadQueueSnapshot() 并使用仍有效的任务 ID
9011016 queue file missing 队列依赖的本地文件丢失或不可读;重新选择可访问文件并添加任务
9011017 queue request unavailable 无法取得当前上传请求;检查 resolveRequest 提供的地址、鉴权和失败回调,修复后显式重试
9011018 queue task expired 队列任务已过期;重新确认业务数据、选择文件并建立新任务

文件桥新增错误

错误码 说明 处理建议
9011019 沙盒文件桥参数或源路径无效 确认 path 是当前应用沙盒内的普通文件
9011020 系统文件复制到沙盒失败 检查文件是否仍可访问、空间是否充足,并重新选择
9011021 沙盒文件保存到系统目标失败 重新选择可写位置,确认目标存储空间充足

可信文件管线新增错误

错误码 含义 触发场景
9011022 invalid file inspection options 文件、文件数组或检查选项无效
9011023 file inspection unsupported 当前平台或当前文件来源不支持内容读取
9011024 file read failed 文件已失效、授权过期或分块读取失败
9011025 file content type mismatch strict 策略确认类型不一致、禁止类型,或要求真实类型但结果未知
9011026 file hash failed SHA-256 未能完整计算或结果校验失败
9011027 duplicate file duplicatePolicy: 'reject' 检测到重复内容
9011028 file inspection aborted 调用独立预检任务的 abort() 主动取消

系统选择器取消仍为 9011002,上传阶段取消仍为 9011010,两者不会与预检取消码混用。

分片上传新增错误

错误码 含义 处理建议
9011029 invalid resumable upload options 检查协议名、三个 URL、分片大小、并发数、文件大小及响应映射路径格式
9011030 resumable upload unsupported 当前平台不支持真正断点续传;改用普通上传或切换到 Android、iOS、Harmony、Web
9011031 upload session request failed 检查会话地址、网络、鉴权和 HTTP 状态码
9011032 invalid upload session response 会话响应中必须能从默认字段或 sessionResponse.uploadId 指定路径取得非空字符串 ID;核对包装层和字段名
9011033 file chunk read failed 源文件已失效、授权过期、分片越界或临时目录不可写,需重新选择文件
9011034 upload completion failed complete 返回非 HTTP 2xx 或请求失败;核对 JSON 的 uploadId / fileHash、SHA-256、分片完整性及状态/响应/异常日志

注意事项

平台差异与使用边界

平台 是否支持 说明
uni-app 是 Vue2/Vue3 可用
uni-app x 是 多端可用
Android 是 文件桥使用系统打开/保存文档选择器,在授权有效期内通过 URI 流复制
iOS 是 文件桥使用安全作用域读取和系统文档导出选择器
Harmony 是 使用系统文件选择器;文件桥通过 DocumentViewPicker 和文件描述符复制
Web 是 当前会话可继续;页面重载后普通 File 引用失效会标记 file-missing,需要重新选择
微信小程序 是 通过平台持久文件能力尽力恢复,受单文件、总配额和平台清理策略限制
支付宝小程序 是 通过平台持久文件能力尽力恢复,受文件系统配额和平台生命周期限制

说明:原有选择、上传和队列 API 的平台范围为 Android / iOS / Harmony / Web / 微信小程序 / 支付宝小程序;pickFileToSandbox / saveSandboxFile 只支持 Android、iOS、Harmony,Web 和小程序会明确返回不支持。Harmony 端通过系统文件选择器返回 URI。file://media/Photo/...、file://docs... 等系统选择器 URI 会先通过 fs.openSync 打开授权文件,再复制到应用临时目录后上传;若 Harmony picker URI 无法复制到缓存/临时目录,会返回 9011011 明确错误,不会伪造成功。

真正断点续传的平台范围单独计算:Android、iOS、Harmony、Web 支持 lizhao-resumable-v1;微信和支付宝小程序的普通上传与可靠队列仍可用,但分片模式明确返回 9011030。Web 的分片源文件只在当前页面会话有效,页面刷新后必须重新选择同一文件,服务端会根据 SHA-256 继续已有会话。

六端边界、隐私与安全

平台 可信检查边界
Android 通过 URI 或普通路径分块读取;content:// 访问期限由系统授权决定
iOS 在安全作用域内分块读取并及时释放;过期临时 URL 需重新选择
Harmony 通过 URI、普通路径或文件描述符分块读取,并在完成或取消时关闭 fd
Web 使用当前页面会话中的 Blob.slice();刷新后 File/Blob 引用失效,需重新选择
微信小程序 使用平台文件系统的 offset/length 能力;临时文件过期后需重新选择
支付宝小程序 当前宿主 API 缺少可靠的 position/length 区间读取能力;可信内容预检与 SHA-256 明确返回 9011023,原有选择、上传和队列能力不受影响

因此,可信内容读取当前支持 Android、iOS、Harmony、Web 和微信小程序。支付宝小程序保留相同的公开调用入口,但会在读取前明确报“不支持”,不会整文件读入内存或伪造检查成功。

插件只在当前设备读取必要的头部、尾部、有限 ZIP 中央目录和可选完整哈希,不上传检查证据;队列只持久化公开检查结论,不持久化原始文件字节、头尾十六进制或运行期回调。SHA-256 可用于内容身份和完整性对比,但不是病毒扫描。客户端检测不能替代服务端安全校验,服务端仍应限制体积与类型、重新计算摘要、隔离存储并按业务风险执行恶意内容检测。

可靠上传队列自身仍是文件级恢复:未配置 resumable 时,中断中的单个文件会从头上传;在运行期请求中配置 lizhao-resumable-v1 后,同一队列会保存服务端已确认分片,并在重启后只补传缺失部分。

  • 文件选择使用平台系统选择器,通常无需额外申请存储权限;上传能力需要网络访问,正式项目还需配置服务器地址、域名白名单和小程序后台合法域名。
  • Android 选择到 content:// 文件时会临时复制到应用缓存目录,不会写入公共存储;系统 URI 不能直接当作普通本地路径交给 SQLite 等插件。
  • App 文件选择与上传请在包含此插件的自定义基座或正式包中使用;Web 和小程序重新编译后使用。
  • 上传目标只支持通用 HTTP 服务器,不内置 uniCloud 上传。
  • 可靠队列默认不要求分片接口,按文件恢复;只有显式传入 resumable 且服务端实现 lizhao-resumable-v1 后,才会在单个文件内只补传缺失分片。两种模式都不代表系统后台常驻上传。
  • 分片会话、分片写入和完成接口必须按文件摘要与分片序号实现幂等;客户端状态不能替代服务端校验,完成时应重新校验分片长度、顺序和整文件 SHA-256。
  • 队列恢复前先重新提供有效的 resolveRequest;HTTP 401/403 会进入 waiting-config,更新凭据并重新配置后再继续。
  • method 固定为 POST,底层按平台上传 API 的 multipart/form-data 语义执行。
  • name 默认值为 file,如果后端使用其他字段名,请通过 options.name 或 upload.name 指定。
  • header 和 formData 都是普通对象;不要把 token、密钥、账号等敏感信息写死在示例代码或仓库中。
  • 发布版运行日志会保留中文阶段、错误码、文件名与来源平台,但不会输出完整上传 URL、本地路径、临时路径或 picker URI;业务侧需要完整链路信息时,请在自己的回调里按需记录。
  • accept 支持扩展名与 MIME;对于 application/zip、application/x-rar-compressed、application/vnd.rar、application/ofd、application/x-ofd 等常见归档/OFD 类型,平台未返回 MIME 时会按扩展名兜底校验,避免误报 9011005。
  • Web / H5 调试公开上传接口时,默认不要传 X-Demo 等自定义请求头;部分公开接口不会在 CORS 预检响应中允许这些 header,浏览器会直接拦截上传。
  • 服务器响应默认不会自动 JSON 解析;需要结构化响应时传 parseJson: true,插件会把解析成功的结果放入 UploadSuccessItem.parsedData,同时保留 data 原文。
  • 上传默认以 HTTP 200-299 视为成功;后端使用 201、204 或其他固定成功码时,可通过 successStatusCodes 明确指定,传空数组则保持默认 2xx 规则。
  • 多文件默认逐个上传,失败后停止;如需并发上传可传 sequential: false,如需跳过失败继续上传可传 stopOnFail: false,最终通过 UploadFilesResult.failResults 查看失败明细。
  • progress 是持续回调,App 和插件根公开上传入口通过 @UTSJS.keepAlive 按 UTS 要求保活;Web / H5 与小程序平台胶水层不重复使用装饰器,避免目标编译器报 Decorators are not valid here。
  • Android 选择到 content:// 文件时,插件会先复制到应用缓存目录再上传;如果复制失败会返回 9011011。
  • iOS 选择到 file:// 路径时,插件会去掉前缀并做 decodeURI 后再上传。
  • Harmony 选择到 file://media/Photo/...、file://docs... 等系统 URI 时,插件会先打开授权文件并复制到应用临时目录再上传;如果复制失败会返回 9011011。
  • 示例中的公开测试接口只用于联调,不建议作为线上业务接口。

常见问题

Vue 2 提示 onImageSelected 未定义或 select handler 为 undefined

模板中的 @select="onImageSelected" 是页面方法绑定。按上方完整 Vue 2 示例在同一页面的 methods 定义该函数,并在 data 声明需要保存的数组。只复制组件标签、没有复制对应脚本,或把普通函数写在页面实例之外,都会导致绑定不到;参数直接是文件数组。

图片能选,AVI 或 MP4 不显示或灰显

先检查实际点击入口:组件自带按钮使用组件标签上的参数,另一处 pickFiles() 配置不会影响它。scene="image" 默认只选图片;混选或视频选择按文件类型配置修改组件的 scene / accept。若仍不可选,可在实际入口临时设置 accept="*/*",进入文件实际所在目录再测;继续灰显时检查系统文件提供方和文件是否可用,不以同名扩展名推断手机返回的 MIME。

大视频点击上传后为什么暂时还是 0%

分片流程先读取视频并计算文件 SHA-256,再查询会话及传输分片。校验阶段不会伪造上传进度;完整示例会显示准备状态。以 success 确认最终合并完成,取消和当前页继续的处理见大文件上传。

SQLite 备份和恢复为什么不能直接使用系统选择器返回的 URI

系统 URI 依赖临时授权,不是 SQLite 可直接访问的普通路径。备份时先调用 backupDatabase,再用 saveSandboxFile 另存;恢复时先用 pickFileToSandbox 复制进沙盒,再把返回的 file.path 传给 restoreDatabase。

Android 系统选择器的“下载内容”提示暂时无法加载怎么办

这通常是手机系统文件提供者自身异常,不代表插件没有收到选择请求。可打开选择器左上角菜单,进入“手机名称 → Download”选择同一个文件;如果其他目录也无法加载,再检查系统文件管理器、下载管理器是否被停用。

SQLite 恢复为什么提示数据库仍有日志文件

先确认目标数据库已经关闭,并停止其他线程或进程读写。新版 lizhao-sqlite-pro 会处理 Android 正常关闭后留下的全零 PERSIST journal;WAL、SHM、非零 journal 或源备份伴随文件仍会被拒绝,以免覆盖活跃数据库。

可靠上传队列是不是字节级断点续传

取决于运行期请求。未配置 resumable 时,队列只记住批次与已成功文件,中断中的单个文件从头上传;配置 lizhao-resumable-v1 后,队列会保存非敏感分片状态,恢复时先查询服务端并只补传缺失分片,这才是真正断点续传。

为什么重新选择同一个 Web 文件后仍能续传

浏览器刷新会丢失原 File/Blob 引用,所以必须让用户重新选择文件。插件会重新计算 SHA-256,会话接口按文件摘要返回已有 uploadId 和 uploadedChunks,因此可以继续服务端尚未完成的会话。若内容不同,服务端必须创建新会话,不能只按文件名续传。

作者系列 UTS 插件

以下为作者已上架的 UTS 插件,详情见各市场页面或作者主页。

插件 能力方向 插件市场
lizhao-app-keepalive 应用保活、唤醒、自愈与报告 查看插件
lizhao-bg-audio 背景音频播放、队列、倍速与事件 查看插件
lizhao-ble BLE 扫描、连接、读写、通知与自动重连 查看插件
lizhao-call-kit 电话、短信、通讯录原生能力 查看插件
lizhao-camera-pro 原生相机、拍照录像、水印与媒体保存 查看插件
lizhao-cast-screen DLNA 投屏、AirPlay 路由入口 查看插件
lizhao-choose-file 原生文件选择、上传、进度与取消 查看插件
lizhao-device-id 设备标识、隐私策略与诊断 查看插件
lizhao-doc-corrector 文档扫描、矫正、增强与识别 查看插件
lizhao-emu-detect 模拟器环境检测、风险评分与证据 查看插件
lizhao-float-window 悬浮窗、画中画、权限与诊断 查看插件
lizhao-gallery-pro 相册媒体分页、筛选、缩略图与导出 查看插件
lizhao-hce-card NFC 虚拟卡、APDU 交互与 NDEF 标签模拟 查看插件
lizhao-health 健康数据授权、查询、统计与写入 查看插件
lizhao-http-server 本地 HTTP 服务、文件传输与实时通信 查看插件
lizhao-icon-pro SVG 图标组件、多主题与缓存 查看插件
lizhao-id-photo 证件照制作、尺寸换算、换底色与模板换装 查看插件
lizhao-img-eraser 图片去水印、局部修复与原生编辑器 查看插件
lizhao-live-activity 实时活动、系统胶囊与普通通知降级 查看插件
lizhao-nfc-pro NFC 标签读写、NDEF、IsoDep 与诊断 查看插件
lizhao-notify-pro 本地通知、点击动作、进度与定时提醒 查看插件
lizhao-novel-reader 原生小说阅读、仿真翻页与段落评论 查看插件
lizhao-office-view Office 与 PDF 文件预览、进度与缓存管理 查看插件
lizhao-pdf-pro PDF 阅读、签批、真实写回与页面处理 查看插件
lizhao-scan-pro 原生扫码、连续扫码、相册识别 查看插件
lizhao-serial-port 路径串口、USB 串口、多会话收发与诊断 查看插件
lizhao-share-plus 系统分享、远程文件下载后分享 查看插件
lizhao-smart-tts 系统 TTS、云端合成、听书方案 查看插件
lizhao-sqlite-pro 原生 SQLite、迁移、备份与诊断 查看插件
lizhao-sse-pro SSE、Line、JSONL 与 Raw 流式请求 查看插件
lizhao-tcp-pro TCP 客户端、服务端、多连接与诊断 查看插件
lizhao-usb-storage U 盘、移动硬盘文件浏览、读写与批量复制 查看插件
lizhao-video-editor 视频裁剪、压缩、取帧与 FFmpeg/FFprobe 查看插件
lizhao-video-feed 短视频上下滑、短剧选集与分页加载 查看插件
lizhao-video-player 原生视频播放器、清晰度与播放交互 查看插件
lizhao-video-thumb 视频封面、批量取帧与 Base64 返回 查看插件
lizhao-vpn-pro 企业 VPN、IKEv2、安全接入与脱敏诊断 查看插件
lizhao-wechat-kit 微信登录、分享、支付、小程序与客服 查看插件

隐私、权限声明

1. 本插件需要申请的系统权限列表:

按平台系统文件选择器规则

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

无

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无