React Native S3 预签名 URL 损坏或白盒

问题描述 投票:0回答:1

过去 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 中产生一个损坏的文件
  • 尝试将文件中的所有数据附加到 formData 对象会给我一个损坏的文件
  • 尝试创建一个 blob 执行
    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 请求时,我的文件不再损坏,而是一个小白框。

我觉得我已经阅读了所有讨论预签名上传的帖子并访问了每个博客。每个人似乎都能够轻松地做到这一点,但我真的不确定并且沮丧,因为我的代码无法成功地做到这一点。

amazon-web-services react-native amazon-s3 pre-signed-url
1个回答
0
投票

我使用 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,
  },
});

© www.soinside.com 2019 - 2024. All rights reserved.