上传文件需先保存至memorystream或临时文件再并发复制,避免流关闭导致失败;多区域上传须独立初始化客户端并校验凭证与endpoint;应控制并发度、统一路径格式、标准化文件名,并返回各区域上传结果。

上传后立刻触发多区域复制,别等 HTTP 请求结束
文件上传完成但还没写入磁盘时,HttpRequest.Form.Files 的流可能已关闭或不可重复读。直接拿这个流去并发上传到多个对象存储(如 AWS S3、Azure Blob、阿里云 OSS)会失败——常见报错是 ObjectDisposedException 或空内容。
实操建议:
- 用
MemoryStream或临时文件先完整保存上传的原始字节,确保所有下游复制任务都基于同一份稳定数据源 - 别在
IFormFile.CopyToAsync()后直接调stream.Position = 0就发给多个客户端——多数云 SDK 不支持复用已读完的流 - 如果文件超大(>100MB),优先选临时文件而非内存缓存,避免
OutOfMemoryException
Azure Blob Storage + AWS S3 并行上传要各自建 Client
同一个 BlobServiceClient 或 S3Client 实例可复用,但不能跨区域共享凭证或 endpoint。比如你往 eastus 和 westeurope 同时传,必须分别初始化两个 BlobServiceClient,各自带对应 region 的 Uri 和 TokenCredential。
常见错误现象:
- 所有副本都成功上传,但全落在同一个 region(比如全在 us-east-1),因为误用了统一 endpoint
- 某 region 报
InvalidEndpointException或AuthenticationFailed,其实是 credential scope 写死了某个 region - 并发上传时 CPU/内存飙升——没控制并发数,10 个 500MB 文件同时开 10 个
UploadAsync,线程池被打满
建议:用 Parallel.ForEachAsync + MaxDegreeOfParallelism = 3 控制节奏;每个 target region 封装成独立 UploadTarget 对象,含 client、container name、路径前缀。
复制失败时别静默吞掉异常,要标记“部分成功”
全球网络不稳定,某个 region 上传失败很常见。如果代码里只写 try/catch { log.Warn(...); } 然后继续,业务系统会以为“文件已就绪”,结果 CDN 回源时 404。
实操建议:
- 收集每个 region 的上传结果(
bool Success+string ErrorMessage),返回给调用方一个UploadResult对象 - 至少保证一个主 region(如
eastus)成功才返回 HTTP 200;其余 region 失败则记录并触发异步重试任务(用 Hangfire 或 Azure Durable Functions) - 别用
Task.WhenAll然后.Wait()—— 一个失败整个抛异常,掩盖了其他 region 的真实状态
路径一致性比速度更重要:别让不同 region 的 object key 对不上
各云厂商对路径分隔符、编码、大小写敏感性处理不一致。比如你在 Azure 用 "uploads/2024/06/photo.jpg",AWS 却生成了 "uploads%2F2024%2F06%2Fphoto.jpg",CDN 就找不到资源。
关键点:
- 统一用小写字母 + ASCII 字符命名 blob key;避免中文、空格、emoji
- 路径分隔符固定用
/(不是\或Path.DirectorySeparatorChar),所有 region 都走字符串拼接,不依赖Path.Combine - 上传前对原始文件名做标准化:
WebUtility.UrlEncode(filename).Replace("+", "%20"),再截断过长部分(S3 key 最长 1024 字符)
复杂点在于:有些 region 要求 prefix 必须以 / 结尾,有些又禁止结尾斜杠。这得查清每个目标 storage 的文档,不能靠猜。










