如何将 React Native 应用程序上的本地图像文件上传到 Rails api?
Posted
技术标签:
【中文标题】如何将 React Native 应用程序上的本地图像文件上传到 Rails api?【英文标题】:How to upload local image file on React Native app to Rails api? 【发布时间】:2019-02-19 10:25:44 【问题描述】:我很难理解来自智能手机的本地文件路径是如何通过 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,但我当然会收到以下错误:
Errno::ENOENT (No such file or directory @ rb_sysopen -...
任何帮助/指导将不胜感激。
【问题讨论】:
您在哪里看到此错误?在后端? 我看到您正在使用 Cloudinary。我建议将图像直接从客户端上传给他们,然后将 cloudinary_id 发布到服务器。 支持@BenToogood 请您提供您的控制器代码和错误的堆栈跟踪。 【参考方案1】:我最近花了 1 个多小时调试类似的东西。
我发现如果你使用这个 json 从你的 React Native 应用程序向你的 Rails 后端进行 POST:
let formData = new FormData();
formData.append('photo',
uri,
name: `photo.$fileName`,
type: `image/$fileType`,
);
Rails 会自动在您的 params[:photo]
中为您提供一个 ActionDispatch::Http::UploadedFile
,您可以像 Photo.create(photo: params[:photo])
一样将其直接附加到您的模型上,而且它很简单。
但是,如果你不传递文件名,一切都会中断,你会得到一个巨大的字符串,它会引发ArgumentError (invalid byte sequence in UTF-8)
。
因此,根据您的代码,我可以发现错误:您将name
传递为photo.$fileType
,这是错误的,应该是photo.$fileName
(相应地更新以获取您的图像文件名...您的 React Native 代码中的 console.log(photo)
会显示正确的。
【讨论】:
你救了我的命。我的文件名是null
,我在控制器中收到了一个超长字符串。通过设置一些文件名,我解决了这个问题!非常感谢
我花了几个小时才找到解决方案,谢谢您的回答【参考方案2】:
维护删除和添加新文件的问题
这就是我设法添加多个文件上传并维护删除和添加新文件的问题
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 部分
我正在使用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',
,
);
;
Saga 工人看起来像
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.",
)
);
【讨论】:
以上是关于如何将 React Native 应用程序上的本地图像文件上传到 Rails api?的主要内容,如果未能解决你的问题,请参考以下文章
如何创建 POST 请求以从本地路径将文件存储在服务器上 - React Native
Android上的react-native-webview无法使用ERR_ACCESS_DENIED加载保存在缓存目录中的本地文件[重复]
如何将库的本地副本添加和编译到 Android React Native 模块中?