使用Google Cloud Storage预签名URL上传图片异常(MERN+React Native)
I see exactly what's going on here—when you send the image object directly through Axios, React Native can’t automatically convert that asset URI into the raw binary data Google Cloud Storage (GCS) needs. That’s why your uploaded images end up corrupted. Let’s fix this step by step.
Step 1: Tweak Server-Side Pre-Signed URL Settings
Your current server code is almost there, but we can add a small safeguard to ensure GCS handles the upload correctly:
const { Storage } = require("@google-cloud/storage"); const storage = new Storage({keyFilename: "keys.json"}); const bucketName = "bucket"; const bucket = storage.bucket(bucketName); const { v4 } = require("uuid"); async upload(req, res, next) { const options = { action: "write", expires: Date.now() + 15 * 60 * 1000, // 15 minutes contentType: "image/jpeg", // Ensure GCS treats the file as a direct upload, not an attachment contentDisposition: "inline" }; const fileName = `1234/${v4()}.jpg`; const [url] = await bucket.file(fileName).getSignedUrl(options); res.send(url); }
The contentDisposition: "inline" flag isn’t mandatory, but it helps GCS avoid misinterpreting the file as a downloadable attachment.
Step 2: Convert React Native Asset URI to Binary Data
React Native’s asset URIs (like assets-library://...) can’t be sent directly as a request body. We need to convert the URI into a Blob first. If you’re using Expo, the expo-file-system library makes this simple:
First, install the library if you haven’t already:
npx expo install expo-file-system
Then update your client-side code to read the URI, convert it to binary, and send it properly:
import axios from "axios"; import * as FileSystem from 'expo-file-system'; async function uploadImageToGCS(signedUrl, imageUri) { // Read the image from the asset URI as base64 const base64Data = await FileSystem.readAsStringAsync(imageUri, { encoding: FileSystem.EncodingType.Base64, }); // Convert base64 to a Blob (raw binary data) const blob = new Blob( [Uint8Array.from(atob(base64Data), char => char.charCodeAt(0))], { type: "image/jpeg" } ); // Send the PUT request with the Blob await axios.put(signedUrl, blob, { headers: { "Content-Type": "image/jpeg", "Content-Length": blob.size.toString() } }); } // Usage example: // const image = {uri: "assets-library://...", type: "image/jpeg", name: "IMG_4831.JPG"}; // uploadImageToGCS(yourSignedUrlFromServer, image.uri);
Why This Works:
FileSystem.readAsStringAsyncpulls the raw image data from the asset URI as a base64 string.- We convert that base64 string into a Uint8Array (binary data) and wrap it in a Blob—this is the format GCS expects for PUT uploads.
- The
Content-Lengthheader helps GCS verify it received the full file, preventing partial/corrupted uploads.
Step 3: Verify Critical Details
- Content Type Consistency: Make sure the
contentTypein your server’s pre-signed URL options matches exactly what you send in the client’sContent-Typeheader (both should beimage/jpeg). Mismatches will cause GCS to misinterpret the file. - Expiry Time: 15 minutes is usually enough, but if you’re uploading large images, extend the expiry to 30 minutes to avoid mid-upload timeouts.
- Permissions: Confirm your service account (from
keys.json) has thestorage.objects.createpermission on your GCS bucket.
Alternative: Use fetch Instead of Axios
If you still run into issues with Axios, try the native fetch API—it sometimes handles Blobs more reliably in React Native:
async function uploadImageToGCS(signedUrl, imageUri) { const base64Data = await FileSystem.readAsStringAsync(imageUri, { encoding: FileSystem.EncodingType.Base64, }); const blob = new Blob( [Uint8Array.from(atob(base64Data), char => char.charCodeAt(0))], { type: "image/jpeg" } ); const response = await fetch(signedUrl, { method: "PUT", body: blob, headers: { "Content-Type": "image/jpeg", "Content-Length": blob.size.toString() } }); if (!response.ok) { throw new Error(`Upload failed: ${response.statusText}`); } }
内容的提问来源于stack exchange,提问作者agaveazul

