Skip to content

分片上传使用说明 ​

管理端大文件(安装包 .ipa/.apk/.wgt、视频等)通过 admin/src/components/upload-chunk/ 上传,走标准 S3 multipart 协议:前端 @aws-sdk/lib-storage 直传,不经过后端"中转上传",天然带并发 / 重试 / 进度回调 / 中断。业务不要自己写 axios 上传。

普通图片 / 小文件请改用 单文件上传。 后端存储/接口详见 后端-存储引擎。业务表只保存 file_hash_id(统一文件哈希主键),访问地址通过 /api/uploads/<id> 动态拼装。

一、组件形态 ​

  • 外层是虚线拖拽区(点击 / 拖入均可),显示"文件名(大小)"。
  • 上方独立两段进度条:MD5 计算进度 + 上传进度(formatSize 显示 已传 / 总量)。
  • 底部按钮:开始上传 / 取消上传;额外具名插槽 #actions="{ file, uploading }" 允许自定义按钮组。
  • 秒传命中会走独立提示(instantHit),不再显示上传进度条。

二、上传流程 ​

选择文件 → SparkMD5(10MB 分块,进度实时反映)
        → POST {endpoint}/checkInstant 秒传预检
    ├─ 命中:直接 emit('change', { instant:true, fileHashId, location:url })
    └─ 未命中:
         POST {endpoint}/generateKey 拿 S3 Key
       → @aws-sdk/lib-storage Upload(Bucket=props.bucket, Key, Body=file, queueSize, partSize)
       → upload.done()
       → POST {endpoint}/registerHash 落 x_common_file_hash(供下次秒传)
       → emit('change', { fileHashId, key, location:url, instant:false })

任何一步失败或用户点"取消上传"(upload.abort())都会走 catch:AbortError 提示"已取消"、其它 emit('error', err)。

三、S3 客户端与鉴权 ​

admin/src/utils/s3Client.ts 提供单例:

ts
const s3Client = new S3Client({
    region: 'us-east-1',
    endpoint: `${location.protocol}//${location.host}/api/admin/upload_chunk`,
    forcePathStyle: true,                                 // 路径风格,不启用 virtual-host 风格
    credentials: { accessKeyId: 'dummy', secretAccessKey: 'dummy' }
})
  • 通过 middleware 在 finalizeRequest 阶段:注入 token: <JWT> 头、剥离 AWS SigV4 相关头(authorization / x-amz-date / x-amz-content-sha256: UNSIGNED-PAYLOAD),后端以 token 鉴权。
  • 切换 MinIO 或其他 S3 兼容服务时,前端代码零改动,只改 s3Client.ts 的 endpoint / credentials。

四、Props ​

属性类型默认说明
bucketstring'files'S3 Bucket 名(后端目前仅支持固定桶)
acceptstring''<input accept>,通常用 ext 代替
extstring[][]允许扩展名,如 ['ipa','apk']、['wgt'];非空时会覆盖 accept
endpointstring'/api/admin/upload_chunk'S3 兼容前缀,同时是管理接口前缀(checkInstant / generateKey / registerHash 拼在它下面)
partSizenumber5 * 1024 * 1024单片大小(5 MB)
queueSizenumber3分片并发数

五、Emits / Expose ​

change(result: ChunkUploadResult) ​

ts
// admin/src/components/upload-chunk/type.ts
interface ChunkUploadResult {
    md5: string
    key: string              // S3 Key(秒传命中时为空串)
    fileName: string
    fileSize: number
    fileHashId: string|null  // ⭐ 业务表要存的字段;秒传命中或注册失败都可能为 null
    instant: boolean          // 是否走了秒传
    location?: string         // 完整访问 URL(可直接展示)
}

error(err: Error) ​

上传任一阶段失败触发;AbortError 走"已取消"分支,不会 emit。

defineExpose({ startUpload, cancelUpload, triggerFileInput }) ​

外部可编程"开始 / 取消 / 弹文件选择"。

六、用法 ​

安装包发布(.ipa/.apk) ​

vue
<!-- views/fabu/components/upload.vue 内 -->
<upload-chunk :ext="['ipa', 'apk']" @change="handleUploaded" @error="handleError" />

<script setup lang="ts">
import UploadChunk from '@/components/upload-chunk'
import type { ChunkUploadResult } from '@/components/upload-chunk'

const handleUploaded = async (r: ChunkUploadResult) => {
    if (!r.fileHashId) { feedback.msgError('上传异常,未获取到文件标识'); return }
    // 通知后端按 file_hash_id 解析安装包元信息(BundleId/版本/图标…)
    await fabuVersionUpload({ file_hash_id: r.fileHashId, file_name: r.fileName })
}
</script>

热更包(.wgt) ​

vue
<!-- views/fabu/version/wgt.vue -->
<upload-chunk :ext="['wgt']" @change="handleUploaded" @error="handleUploadError" />

调试页 + 自定义 actions 插槽 ​

views/dev_tools/uploadChunk.vue 演示了 bucket="files" 显式写法与 #actions 插槽:

vue
<UploadChunk ref="uploadChunkRef" bucket="files" @change="handleUploadResult" @error="handleError">
    <template #actions="{ file }">
        <span v-if="file" class="text-info mr-2">已选:{{ file.name }}</span>
    </template>
</UploadChunk>

七、后端接口 ​

用途方法路径请求
秒传预检POST/api/admin/upload_chunk/checkInstant{ file_md5, file_name }
生成 S3 KeyPOST/api/admin/upload_chunk/generateKey{ file_name } → { key }
上传完注册哈希POST/api/admin/upload_chunk/registerHash{ file_md5, file_key, file_name, file_size } → { file_hash_id, url }
S3 multipart 全套(Init / UploadPart / Complete / Abort / ListParts)PUT / POST / GET / DELETE/api/admin/upload_chunk/{bucket}/{key}标准 S3 RESTful,forcePathStyle 路径风格
文件流访问GET/api/uploads/<file_hash_id>[/<fileName>]自动 MIME + 长缓存

鉴权统一走请求头 token: <JWT>(由 s3Client middleware 注入)。

分片临时目录由 storage/cron_cleanup.go 每小时清理一次,超过 ChunkExpireHour(默认 24h)的未完成任务会被删除。

八、常见坑 ​

  1. 业务表存 file_hash_id,别存 URL:URL 由 ossDomain + /api/uploads/<id> 动态拼(见 stores/modules/app.ts),换域名/协议时不用回补数据。
  2. fileHashId 可能为 null:秒传命中但后端未返 id、或 registerHash 失败都会为空;业务侧必须显式判空再走后续接口(fabu/components/upload.vue 就是这么处理的)。
  3. 秒传的前提是历史曾成功 registerHash 同 MD5:首次上传完不调 registerHash,下次同 MD5 也不会秒传;组件已在成功路径里内置调用,业务无需重复。
  4. 走的是真 S3 协议:s3Client.ts 通过 middleware 剥离 AWS 签名头并注入 token;不要给请求加自定义业务头(后端只识别 token),也不要启用 SigV4 校验。
  5. ext 会覆盖 accept:ext.length > 0 时 acceptValue 用 ext 拼 .png,.jpg 形式;只设 accept 才会走原生属性。
  6. partSize 与浏览器内存:单片过小 → 分片数暴涨;过大 → 一片读入 ArrayBuffer 占内存。默认 5 MB / 并发 3 是常见甜点,几百 MB ~ 数 GB 场景无需改。
  7. 中断是 upload.abort():与 单文件上传 的 uploadSeq++ 不同,这里是真中断(AWS SDK 内部会 AbortController 取消后续分片),已 in-flight 的分片会由后端 AbortMultipartUpload 清理。
  8. MD5 计算占用主线程:SparkMD5 走 FileReader.readAsArrayBuffer,GB 级文件会明显卡顿;如需极限优化可迁到 Web Worker(upload-chunk/index.vue::calcMD5 一处即可)。

九、相关代码位置 ​

  • 前端组件与客户端:
    • admin/src/components/upload-chunk/index.vue(组件实现)
    • admin/src/components/upload-chunk/index.ts(barrel 出口)
    • admin/src/components/upload-chunk/type.ts(ChunkUploadResult)
    • admin/src/utils/s3Client.ts(S3 客户端与 token 注入)
    • admin/src/utils/file.ts(formatSize)
  • 后端接口:server/app/controller/admin_ctl/common_controller/upload_chunk.go;驱动与存储:server/plugin/storage/{storage_interface.go,storage_factory.go,storage_local.go,storage_oss.go,presign.go,cron_cleanup.go}。
  • 现网调用点:
    • admin/src/views/fabu/components/upload.vue(.ipa/.apk 发布)
    • admin/src/views/fabu/version/wgt.vue(.wgt 热更)
    • admin/src/views/dev_tools/uploadChunk.vue(分片调试页)