代码之家  ›  专栏  ›  技术社区  ›  Hugo

如何将React原生应用上的本地图像文件上传到Rails api?

  •  0
  • Hugo  · 技术社区  · 8 年前

    我很难理解智能手机的本地文件路径如何通过Rails api上传到服务器端。

    我们发送到后端的文件路径对服务器没有任何意义?

    我从下面的响应中得到一个uri:

    file:///Users/.../Documents/images/5249F841-388B-478D-A0CB-2E1BF5511DA5.jpg):

    我曾尝试向服务器发送类似的内容:

      let apiUrl = 'https://vnjldf.ngrok.io/api/update_photo'
    
      let uriParts = uri.split('.');
      let fileType = uri[uri.length - 1];
    
      let formData = new FormData();
      formData.append('photo', {
        uri,
        name: `photo.${fileType}`,
        type: `image/${fileType}`,
      });
    
      let options = {
        method: 'POST',
        body: formData,
        headers: {
          Accept: 'application/json',
          'Content-Type': 'multipart/form-data',
        },
      };
    

    但我不确定它是什么,以及如何在后端对其进行解密。

    我也尝试过发送uri direclty,但我当然会遇到以下错误:

    Errno::ENOENT (No such file or directory @ rb_sysopen -...
    

    任何帮助/指导都将不胜感激。

    0 回复  |  直到 8 年前
        1
  •  8
  •   sandre89    6 年前

    我最近花了1个多小时调试类似的东西。

    我发现,如果您使用以下json从React原生应用程序向Rails后端发帖:

    let formData = new FormData();
      formData.append('photo', {
        uri,
        name: `photo.${fileName}`,
        type: `image/${fileType}`,
      });
    

    Rails会自动为您提供 ActionDispatch::Http::UploadedFile 在你的 params[:photo] ,您可以像 Photo.create(photo: params[:photo]) 而且它很有效。

    然而,如果你不传递文件名,一切都会中断,你会得到一个巨大的字符串,它会引发一个错误 ArgumentError (invalid byte sequence in UTF-8) .

    因此,根据您的代码,我可以发现错误:您正在通过 name 像 photo.${fileType} ,这是错误的,也是应该的 photo.${fileName} (相应更新以获取图像文件名。。。 console.log(photo) 在React中,本机代码将显示正确的代码。

        2
  •  0
  •   Shiva    5 年前

    维护删除和添加新文件的问题

    这就是我如何做到添加多个文件上传,并维护删除和添加新文件的问题

    class User < ApplicationRecord
      attribute :photos_urls # define it as an attribute so that seriallizer grabs it to generate JSON i.e. as_json method
    
      has_many_attached :photos
    
    
      def photos_urls
        photos.map do |ip|
          {url: Rails.application.routes.url_helpers.url_for(ip), signed_id: ip.signed_id}
        end
      end
    

    See about signed_id here 。它描述了如何处理多个文件上载。

    控制器看起来像

    
      def update
        user = User.find(params[:id])
        if user.update(user_params)
          render json: {
            user: user.as_json(except: [:otp, :otp_expiry])
          }, status: :ok
        else
          render json: { error: user.errors.full_messages.join(',') }, status: :bad_request
        end
      end
    
    ...
    
      private
    
      def user_params
        params.permit(
          :id, :name, :email, :username, :country, :address, :dob, :gender,
          photos: []
        )
      end
    
    

    反应原生部分

    我在用 react-native-image-crop-picker

      
    import ImagePicker from 'react-native-image-crop-picker';
    
    ...
    
      const photoHandler = index => {
        ImagePicker.openPicker({
          width: 300,
          height: 400,
          multiple: true,
        }).then(selImages => {
          if (selImages && selImages.length == 1) {
            // Make sure, changes apply to that image-placeholder only which receives 'onPress' event
            // Using 'index' to determine that
            let output = images.slice();
            output[index] = {
              url: selImages[0].path, // For <Image> component's 'source' field
              uri: selImages[0].path, // for FormData to upload
              type: selImages[0].mime,
              name: selImages[0].filename,
            };
            setImages(output);
          } else {
            setImages(
              selImages.map(image => ({
                url: image.path, // For <Image> component's 'source' field
                uri: image.path, // for FormData to upload
                type: image.mime,
                name: image.filename,
              })),
            );
          }
        });
      };
    
    ...
    
           <View style={style.imageGroup}>
            {images.map((item, index) => (
              <TouchableOpacity
                key={`img-${index}`}
                style={style.imageWrapper}
                onPress={() => photoHandler(index)}>
                <Image style={style.tileImage} source={item} />
              </TouchableOpacity>
            ))}
          </View>
    

    上传器看起来像

    // ../models/api/index.js
    
    // Update User
    export const updateUser = async ({ id, data }) => {
      // See https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
      let formData = new FormData(data);
    
      for (let key in data) {
        if (Array.isArray(data[key])) {
          // If it happens to be an Image field with multiple support
          for (let image in data[key]) {
            if (data[key][image]?.signed_id) {
              // if the data has not change and it is as it was downloaded from server then
              // it means you do not need to delete it
              // For perverving it in DB you need to send `signed_id`
              formData.append(`${key}[]`, data[key][image].signed_id);
            } else if (data[key][image]?.uri && data[key][image]?.url) {
              // if the data has change and it is as it has been replaced because user selected a different image in place
              // it means you need to delete it and replace it with new one
              // For deleting it in DB you should not send `signed_id`
              formData.append(`${key}[]`, data[key][image]);
            }
          }
        } else {
          formData.append(key, data[key]);
        }
      }
      return axios.patch(BASE_URL + "/users/" + data.id, formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      });
    };
    

    而传奇工作者看起来

    import * as Api from "../models/api";
    
    // worker Saga:
    function* updateUserSaga({ payload }) {
      console.log('updateUserSaga: payload', payload);
      try {
        const response = yield call(Api.updateUser, {
          id: payload.id,
          data: payload,
        });
        if (response.status == 200) {
          yield put(userActions.updateUserSuccess(response.data));
          RootNavigation.navigate('HomeScreen');
        } else {
          yield put(userActions.updateUserFailure({ error: response.data.error }));
        }
      } catch (e) {
        console.error('Error: ', e);
        yield put(
          userActions.updateUserFailure({
            error: "Network Error: Could not send OTP, Please try again.",
          })
        );
      }
    }
    
    推荐文章