文件服务(二)

概括

  • 1)客户端调用 InitUploadFileUrl(fileName, fileSize, contentType)
    服务端向 MinIO 发起分片会话(拿到 UploadId 和最终对象 Key),按固定分片大小(6MB)生成每一片的预签名 PUT URL 列表并返回(含每片的 PartNumber/Url/StartByte/EndByte)。

  • 2)客户端按返回的分片信息切文件并直接 PUT 到 MinIO
    每片用对应的预签名 URL 上传;上传成功后从响应里取到该片的 ETag,收集成 PartETagList = [{PartNumber, ETag}, ...]

  • 3)客户端调用 ChunkMultipartUploadUrl 提交合并
    把每个文件的 Key + UploadId + PartETagList 传回服务端;服务端调用 MinIO 的 CompleteMultipartUpload(会按 PartNumber 排序)生成最终对象,然后写入业务数据库(记录 Path=Key 等),返回业务结果。

  • 4)如需取消上传,客户端调用 CancelMultipartUploadUrls
    key + uploadId,服务端调用 AbortMultipartUpload,MinIO 清理该上传会话的临时分片。

知识点:MinIO 分片上传(预签名 URL 模式)在本项目里的实现

  • 关联入口(Controller)
    • 初始化并返回预签名 URL:[KnowController.InitUploadFileUrl](file:///d:/ceshi/MedicTechServer/MedicTechServer/Controllers/KnowController.cs#L794-L819)
    • 合并分片(支持多文件):[KnowController.ChunkMultipartUploadUrls](file:///d:/ceshi/MedicTechServer/MedicTechServer/Controllers/KnowController.cs#L821-L855)
    • 取消上传(Abort):[KnowController.CancelMultipartUploadUrl](file:///d:/ceshi/MedicTechServer/MedicTechServer/Controllers/KnowController.cs#L858-L878)
  • 关联实现(Service)
    • 生成预签名 URL:[MinioService.GeneratePresignedUrl](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L402-L419)
    • 初始化分片会话 + 批量生成 URL:[MinioService.InitMultipartUploadForURL](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L421-L495)
    • 合并分片(会排序 PartNumber):[MinioService.CompleteMultipartUploadForURL](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L497-L522)
    • 取消分片会话:[MinioService.AbortUploadForURL](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L524-L542)

1) 这套模式解决什么问题?

  • 与“服务端中转上传分片”不同:这里服务端只负责“签发上传 URL + 最终合并”,真正的分片数据由客户端直接 PUT 到 MinIO。
  • 优点:服务端带宽/CPU 压力小;分片可并发 PUT;上传更接近对象存储的标准用法。

2) 初始化:签发 UploadId + Key + 每片的预签名 PUT URL

对应:[InitUploadFileUrl](file:///d:/ceshi/MedicTechServer/MedicTechServer/Controllers/KnowController.cs#L794-L819) → [InitMultipartUploadForURL](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L421-L495)

关键点:

  • 服务端先调用 InitiateMultipartUpload 得到 UploadId,同时生成对象 Key = Guid + 文件名(防重名)。
  • 固定分片大小:chunkSize = 6MB6 * 1024 * 1024)。
  • 每个分片生成一个预签名 URL:HTTP PUT、有效期 2 小时、强制 HTTPS、带上 UploadId + PartNumber
    • 见 [GeneratePresignedUrl](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L402-L419)

伪代码:

InitUploadFileUrl(fileName, fileSize, contentType):
    init = minio.initiateMultipart(
        bucket="medic-tech",
        key=Guid + "_" + fileName,
        contentType=contentType
    )

    chunkSize = 6MB
    totalChunks = 根据 fileSize 和 chunkSize 计算
    urls = []

    for partNumber in 1..totalChunks:
        url = minio.presignPutPartUrl(
            bucket, key=init.key, uploadId=init.uploadId, partNumber,
            expires=2h, protocol=https
        )
        urls.add({partNumber, url, startByte, endByte})

    return { uploadId: init.uploadId, key: init.key, chunkSize, totalChunks, chunkUrls: urls }

客户端要做的事(核心知识点):

  • startByte/endByte 切片后,对每片的 url 执行 HTTP PUT 上传。
  • 每次 PUT 成功后,从响应头拿到 ETag,记录成 { PartNumber, ETag },用于最终合并。

3) 合并:客户端带着 PartETagList 回来,服务端 Complete 并写库

对应:[ChunkMultipartUploadUrls](file:///d:/ceshi/MedicTechServer/MedicTechServer/Controllers/KnowController.cs#L821-L855) → [CompleteMultipartUploadForURL](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L497-L522)

关键点:

  • 入参 uploadFormUrl files 支持多个文件同时合并:files.mutipartFormItems
  • 每个文件都必须携带:Key + UploadId + PartETagList
  • Service 里会按 PartNumber 排序再 Complete(避免乱序导致合并失败/不稳定):
    • [MinioService.cs:L511-L517](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L511-L517)
  • 合并成功后,用 compResponse.Key 作为文件在 MinIO 的最终路径,并调用 _knowSe.AddSingleKnow(...) 写入业务数据。

伪代码:

ChunkMultipartUploadUrls(files):
    resData = []

    for item in files.mutipartFormItems:
        completeResp = minio.completeMultipart(
            bucket="medic-tech",
            key=item.Key,
            uploadId=item.UploadId,
            partETags=sortByPartNumber(item.PartETagList)
        )

        know = business.addSingleKnow(
            code=files.code, orgId, userId, role,
            uploadMeta=item,
            path=completeResp.Key
        )

        resData.add({ id: know.id, name: know.name })

    return resData

4) 取消:AbortMultipartUpload 清理 MinIO 侧临时分片

对应:[CancelMultipartUploadUrl](file:///d:/ceshi/MedicTechServer/MedicTechServer/Controllers/KnowController.cs#L858-L878) → [AbortUploadForURL](file:///d:/ceshi/MedicTechServer/MedicTechServer/Services/MinioService.cs#L524-L542)

关键点:

  • 客户端只要提供 key + uploadId,服务端调用 Abort 即可让 MinIO 清掉该上传会话的临时分片。

伪代码:

CancelMultipartUploadUrl(cancelFormList):
    for each c in cancelFormList:
        minio.abortMultipart(bucket="medic-tech", key=c.key, uploadId=c.uploadId)
    return OK

5) 最小注意点(以后排查问题用)

  • 预签名 URL 有效期 2 小时:过期后 PUT 会失败,需要重新 Init。
  • 合并必须提供完整且正确的 PartETagList(PartNumber 对应的 ETag 不能错/不能漏)。
  • 这套模式不依赖服务端内存缓存(与另一套 UploadFiles/UploadSubmit 不同),上传状态主要由客户端维护并回传。
posted @ 2026-05-03 23:10  爱晒太阳的懒猫。。  阅读(3)  评论(0)    收藏  举报