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文本本身,而非解码后的原始二进制图片数据。
修复步骤
替换客户端的二进制转换逻辑:
不要手动拆分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); }更简洁的写法:
用Uint8Array.from配合解码逻辑一步完成:const fileData = await FileSystem.readAsStringAsync(imageUri, { encoding: FileSystem.EncodingType.Base64 }); const binaryData = Uint8Array.from(atob(fileData), c => c.charCodeAt(0));可选优化:直接读取原始二进制:
如果你使用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
相关产品推荐
相关产品推荐

