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

React Native Expo+Node.js通过Cognito上传S3图片损坏问题求助

问题:React Native(Expo)上传头像至AWS S3后文件损坏无法读取

我用React Native(Expo)搭配Node.js后端开发应用,想实现用户从手机上传头像到AWS S3存储桶,但上传后的图片一直损坏无法读取。流程看似正常,文件能以正确文件名上传,显示为2Mb的Jpeg格式并返回URL,但就是打不开。

操作流程

  • 已配置AWS Cognito/IAM用户角色权限及CORS,允许put、get、delete操作
  • 从后端获取预签名URL(pre-signedUrl),此环节无异常
  • 将图片转换为base64格式后再转为二进制,上传至S3存储桶

后端预签名URL代码

router.get("/", async (req, res) => {
  const fileName = req.query["file-name"];
  const fileType = req.query["file-type"];
  const operation = req.query["operation"]; // determine if asking for get or push command

  console.log("filename input to preSignedUrl", fileName);

  try {
    let command;

    if (operation === "put") {
      command = new PutObjectCommand({
        Bucket: "test",
        Key: fileName,
        ContentType: fileType,
        
      });
    } else if (operation === "get") {
      command = new GetObjectCommand({
        Bucket: "test",
        Key: fileName,
      });
    } else {
      return res.status(400).json({ error: "Invalid operation." });
    }

    const signedUrl = await getSignedUrl(s3Client, command, {
      expiresIn: 360, 
    });

    // Construct the return data
    const returnData = {
      signedRequest: signedUrl,
      url: `https://test.s3.amazonaws.com/${fileName}`,
    };

    console.log("data returned by presignedUrl", returnData); // debug returnedData
    res.json(returnData);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: "Failed to generate signed URL: ${error.message}" });
  }
});

React Native客户端上传代码

const getPresignedUrlAndUpload = async (fileName, fileType, imageUri) => {
  try {
    // Read the file as binary data
    const fileData = await FileSystem.readAsStringAsync(imageUri, {
      encoding: FileSystem.EncodingType.Base64,
    });

    const binaryData = new Uint8Array(
      fileData.split("").map((char) => char.charCodeAt(0))
    );
    // get presigned Url for upload to S3
    const response = await client.get(getPresignedUrlEndpoint, {
      "file-name": fileName,
      "file-type": fileType,
      operation: "put",
    });

    if (!response.ok) {
      throw new Error("Error getting presigned URL");
    }

    // Once you have the presigned URL, use it to upload the file
    const { signedRequest } = response.data;

    const uploadResponse = await fetch(signedRequest, {
      method: "PUT",
      body: binaryData,
      headers: {
        "Content-Type": fileType,
      },
    });

    if (!uploadResponse.ok) {
      throw new Error("Error uploading file to S3");
    }

    // If everything went well, return the Object URL of the image
    return response.data.url;
  } catch (error) {
    console.error("Error in getPresignedUrlAndUpload: ", error);
    return null;
  }
};

问题根源与解决方案

问题出在客户端对base64数据的转换逻辑上:你把base64字符串直接按字符转成Uint8Array,这相当于上传了base64文本本身,而非解码后的原始二进制图片数据。

修复步骤

  1. 替换客户端的二进制转换逻辑:
    不要手动拆分base64字符串转Uint8Array,改用atob解码base64,再将解码后的字符串转成Uint8Array:

    // 替换原有的binaryData生成代码
    const decodedData = atob(fileData);
    const binaryData = new Uint8Array(decodedData.length);
    for (let i = 0; i < decodedData.length; i++) {
      binaryData[i] = decodedData.charCodeAt(i);
    }
    
  2. 更简洁的写法:
    用Uint8Array.from配合解码逻辑一步完成:

    const fileData = await FileSystem.readAsStringAsync(imageUri, { encoding: FileSystem.EncodingType.Base64 });
    const binaryData = Uint8Array.from(atob(fileData), c => c.charCodeAt(0));
    
  3. 可选优化:直接读取原始二进制:
    如果你使用Expo SDK 48及以上版本,可以跳过base64转换,直接读取原始二进制数据:

    const fileInfo = await FileSystem.getInfoAsync(imageUri);
    const fileData = await FileSystem.readAsync(imageUri, { length: fileInfo.size });
    // 直接将fileData作为body传入fetch请求
    

验证要点

  • 确保后端生成预签名URL时的ContentType,和上传请求里的Content-Type完全一致(比如统一为image/jpeg,不要出现大小写或格式差异)
  • 上传时不要添加预签名URL未包含的额外请求头,避免触发签名校验失败

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 19:52:40