You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native通过S3预签名URL上传图片损坏或显示白块问题排查

React Native 上传图片到S3预签名URL文件损坏问题排查与解决方案

所有上传后文件损坏、仅显示小白块的核心原因,都是没有将真实的图片二进制内容正确传递给S3:要么传了路径字符串、元数据对象,要么传了未解码的base64文本、带冗余分隔符的FormData内容,和file URI前缀、Blob方法本身没有本质关系。

各失败方案的具体问题

  • 初始axios传file.uri:实际传递的是file:///xxx.jpg这段路径字符串,不是图片内容,修改URI前缀完全无效,因为根本没有读取文件本身。
  • 直接传选图返回的file对象、FormData:前者是选图库返回的元信息对象,不是二进制文件;后者是multipart格式,会自动拼接boundary分隔符,但生成预签名URL时没有校验这类格式头,S3会把分隔符计入文件内容直接导致损坏。
  • fetch(file.uri).blob()报错:是旧版React Native的fetch实现对本地文件URI的支持缺陷,不是blob方案逻辑错误。
  • 传base64显示小白块:直接把base64字符串塞进请求体,还错误添加Content-Encoding: base64配置,S3不会自动解码base64内容,存进去的是编码后的文本,自然无法正常显示。
  • 用axios上传:React Native环境下axios无法正确识别本地文件URI,会自动将传入参数序列化为JSON字符串,无论怎么调整配置都容易出内容错误。

可直接落地的修复方案

优先选第一种二进制上传方案,比base64省30%左右流量,兼容性最好。

方案1:原生XHR直接上传本地二进制(无需额外依赖)

后端预签名URL逻辑不需要修改,保持原有传ContentType参数的逻辑即可,不要加ContentEncoding配置,不要把ContentType改成application/octet-stream,保证前端上传时的Content-Type和预签名时传入的类型完全一致即可。
前端上传代码直接替换为以下内容:

/**
 * 上传图片到S3预签名URL
 * @param {string} preSignedUrl 后端返回的预签名PUT地址
 * @param {object} file react-native-image-picker返回的assets[0]对象
 */
export const uploadImageToS3 = (preSignedUrl, file) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function () {
      if (xhr.readyState !== 4) return
      if (xhr.status === 200) return resolve('上传成功')
      return reject(new Error(`上传失败,状态码:${xhr.status}`))
    }
    xhr.open('PUT', preSignedUrl)
    // 只设置Content-Type头,不要加其他自定义头、鉴权头,否则会导致预签名校验失败
    xhr.setRequestHeader('Content-Type', file.type)
    // 重点:只传带uri属性的对象,不要加name、type等其他属性,不要包装FormData
    xhr.send({ uri: file.uri })
  })
}

注意:iOS端默认返回的file:///前缀URI不需要修改,安卓端如果返回content://开头的URI,只要在react-native-image-picker的配置里固定mediaType: 'photo'即可直接使用,不需要做路径转换。

方案2:base64兼容上传(适配低版本安卓机型)

如果方案1在部分安卓低版本机型上有兼容问题,再使用base64方案,注意必须将base64解码为原始二进制再传递:

  1. 后端生成预签名URL的逻辑去掉ContentEncoding: 'base64'配置,ContentType保持和图片实际类型一致。
  2. 前端上传代码:
import RNFS from 'react-native-fs'

export const uploadBase64ImageToS3 = async (preSignedUrl, file) => {
  // 读取文件base64内容
  const base64Str = await RNFS.readFile(file.uri, 'base64')
  // 将base64解码为原始二进制ArrayBuffer
  const binaryStr = atob(base64Str)
  const byteLength = binaryStr.length
  const byteArray = new Uint8Array(byteLength)
  for (let i = 0; i < byteLength; i++) {
    byteArray[i] = binaryStr.charCodeAt(i)
  }

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function () {
      if (xhr.readyState !== 4) return
      if (xhr.status === 200) return resolve('上传成功')
      return reject(new Error(`上传失败,状态码:${xhr.status}`))
    }
    xhr.open('PUT', preSignedUrl)
    xhr.setRequestHeader('Content-Type', file.type)
    // 传递解码后的二进制数据,不要直接传base64字符串
    xhr.send(byteArray.buffer)
  })
}

问题排查步骤

如果按上述代码修改后仍有异常,按顺序校验:

  1. 先验证预签名URL有效性:拿到预签名URL后,用Postman选PUT请求,Body选binary模式,选本地一张同类型图片上传,如果上传后能正常打开,说明后端逻辑完全正常,问题仅出在前端。
  2. 上传完成后去S3控制台下载文件,对比本地原文件大小:如果S3上的文件大小远小于本地文件(比如本地2M,S3上只有几十/几百字节),说明还是传错了内容,没有传递正确的二进制。
  3. 检查是否有全局请求拦截器自动给上传请求加了额外头(比如Authorization、X-Requested-With等),S3预签名URL如果携带了签名时未包含的头,会直接校验失败或导致内容异常,上传S3时不要用封装过的带全局拦截器的请求实例。
  4. 不要用FormData传PUT请求到普通预签名URL,FormData仅适用于POST方式的分段上传场景,普通PUT上传直接传二进制即可。

内容的提问来源于stack exchange,提问作者user2465134

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 11:24:22