过去 10 个小时左右,我一直在尝试成功地将图像从我的 React Native 应用程序上传到 S3。
React Native 应用程序向我的无服务器 lambda 端点发出请求:
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')
const BUCKET_NAME = process.env.BUCKET_NAME
const REGION = process.env.AWS_REGION
const expirationTime = 60
const s3Client = new S3Client({
region: REGION,
})
module.exports = App => {
App.controllers.putPresignedUrl = async (event, context, cb) => {
const body = JSON.parse(event.body)
const { fileName, type } = body
const params = {
Bucket: BUCKET_NAME,
Key: fileName,
ContentType: type
}
try {
const command = new PutObjectCommand(params)
const signedUrl = await getSignedUrl(s3Client, command, {
expiresIn: expirationTime
})
return cb(null, utils.res(200, { url: signedUrl }))
} catch (err) {
console.log('ERROR putPresignedUrl : ', err)
return cb(null, utils.res(400, { message: 'Failed to pre-sign url' }))
}
}
}
在我的 RN 应用程序中,我有:
import React from 'react'
import { Button, SafeAreaView } from 'react-native'
import { useDispatch } from 'react-redux'
import { launchImageLibrary } from 'react-native-image-picker'
import { createPicture } from '../store/pictures/Picture.reducer'
const Home = () => {
const dispatch = useDispatch()
const getImageFromLibrary = async () => {
const options = { noData: true }
const result = await launchImageLibrary(options)
const file = result.assets[0]
dispatch(createPicture({ file }))
}
return (
<SafeAreaView>
<Button onPress={getImageFromLibrary} title="Get from library" />
</SafeAreaView>
)
}
export default Home
运行 createPicture 将通过我的 API 文件调用我的 lambda 函数:
export async function saveImageToS3(preSignedUrl, file) {
const awsAxios = axios.create({
transformRequest: (data, headers) => {
// Remove all shared headers
delete headers.common
return data
}
})
const data = await awsAxios.put(
preSignedUrl,
file.uri,
{
'Content-Type': file.type
}
)
}
我一直能够上传到我的 s3 存储桶,但文件要么已损坏,要么打开时只是一个小白框
result.assets[0]
(文件)将会在 S3 中产生一个损坏的文件const result = fetch(file.uri)
会起作用,然后当我尝试执行 result.blob()
时,我得到 blob 不是一个函数file.uri
以file:///
开头,我尝试将其更改为file://
,file:/
并完全摆脱它。文件最终损坏然后我转而尝试上传一个base64字符串。我正在使用
react-native-fs
创建 base64。我还尝试使用 xhr 请求而不是 axios:
const fileString = await RNFS.readFile(file.uri, 'base64')
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// success
} else {
// failure
}
}
}
xhr.open('PUT', preSignedUrl)
xhr.setRequestHeader('Content-Type', pictureData.type)
xhr.send({ uri: fileString, type: pictureData.type, name: pictureData.fileName })
我尝试将后端和前端的标题更改为
ContentType: 'application/octet-stream'
,但这仍然不起作用。我还在 lambda 方面添加了 ContentEncoding: 'base64'
到我的参数中。然而,当我开始使用 XHR 请求时,我的文件不再损坏,而是一个小白框。
我觉得我已经阅读了所有讨论预签名上传的帖子并访问了每个博客。每个人似乎都能够轻松地做到这一点,但我真的不确定并且沮丧,因为我的代码无法成功地做到这一点。
我使用 RNFetchBlob 及其作品
await RNFetchBlob.fetch('PUT', presignedUrl, {
'Content-Type': 'image/jpeg', // Change to your image type if needed
}, RNFetchBlob.wrap(imageUri));
import { ActivityIndicator, StyleSheet, Text, View, TouchableOpacity, Image, Button } from 'react-native';
import React, { useState, useEffect, useRef } from 'react';
import { Camera, Templates, useCameraDevice, useCameraFormat, useCameraPermission } from 'react-native-vision-camera';
import { Image as f } from 'react-native-compressor';
import RNFetchBlob from 'rn-fetch-blob';
const App = () => {
const { hasPermission, requestPermission } = useCameraPermission();
const camera = useRef(null);
const [flash, setFlash] = useState(false);
const [onFlash, setOnFlash] = useState("");
const [PHoto, setPHOTO] = useState(null);
const [imageUri, setImageUri] = useState(null);
useEffect(() => {
if (!hasPermission) {
requestPermission();
}
}, [hasPermission]);
console.log("flash img ", onFlash)
console.log(flash)
const TakePicture = async () => {
const photo = await camera.current.takePhoto({
flash: flash ? "on" : "off"
});
setPHOTO(photo);
console.log(photo.path);
const resultCompressed = await f.compress(photo.path);
console.log("compressed", resultCompressed);
setOnFlash(resultCompressed)
console.log("FLSH", onFlash)
};
console.log("FLSH2", onFlash)
const uploadImage = async (imageUri) => {
try {
// Fetch pre-signed URL from your server
const presignedUrl = 'presignurl';
// Local image path on device
// const imagePath = 'file:///data/user/0/com/cache/0e3685b3-a9b1-49f3-9186-4326f1a0d4b7.jpg';
// Upload image to S3 using pre-signed URL
await RNFetchBlob.fetch('PUT', presignedUrl, {
'Content-Type': 'image/jpeg', // Change to your image type if needed
}, RNFetchBlob.wrap(imageUri));
// Display uploaded image after successful upload
// setImageUri(imageUri);
} catch (error) {
console.error('Error uploading image to S3:', error);
}
};
// Call the uploadImage function with the URI of the image
const SubmitUpload = async (url) => {
console.log("Url", url)
const ImgName = onFlash.split('/')
console.log(ImgName[ImgName.length - 1])
uploadImage(url);
}
const device = useCameraDevice('back');
if (device == null) {
return <Text>No camera device</Text>;
}
if (!hasPermission) {
return <ActivityIndicator />;
}
return (
<View style={{ flex: 1 }}>
{PHoto ?
(<><Image source={{ uri: onFlash }} style={{ flex: 1 }} />
{console.log("fff",onFlash)}
<Button title='submit' onPress={() => SubmitUpload(onFlash)} />
<Button title="retake" onPress={() => setPHOTO(null)} />
</>)
: (
<>
<Camera
ref={camera}
device={device}
isActive={true}
// format={useCameraFormat(device, Templates.Instagram)}
style={{ flex: 1 }}
photo={true}
/>
<TouchableOpacity style={styles.button} onPress={TakePicture}>
{/* Your camera icon or any content inside the button */}
</TouchableOpacity>
<TouchableOpacity style={styles.button2} onPress={() => setFlash(!flash)}>
<Text>On Flash</Text>
</TouchableOpacity>
</>
)}
</View>
);
};
export default App;
const styles = StyleSheet.create({
button: {
position: 'absolute',
bottom: 20,
right: '50%',
width: 60,
height: 60,
borderRadius: 30, // Half of the width and height to make it round
backgroundColor: 'white', // Instagram camera button color
justifyContent: 'center',
alignItems: 'center',
shadowColor: 'black',
shadowOffset: {
width: 0,
height: 5,
},
shadowOpacity: 0.5,
shadowRadius: 5.84,
elevation: 10,
},
button2: {
position: 'absolute',
bottom: 20,
right: 0,
width: 60,
height: 60,
borderRadius: 30, // Half of the width and height to make it round
backgroundColor: 'pink', // Instagram camera button color
justifyContent: 'center',
alignItems: 'center',
shadowColor: 'black',
shadowOffset: {
width: 0,
height: 5,
},
shadowOpacity: 0.5,
shadowRadius: 5.84,
elevation: 10,
},
});