通过React经由Rails上传文件到S3的方法
你好,我是小菜一枚。
最近我尝试使用React来创建一个文件上传功能,但意外地遇到了一些困扰,所以我写了一篇文章来分享经验。
后端我使用Rails将文件存储在S3上。
将文件数据从React发送到Rails。
首先创建一个附加文件的按钮。
有很多选择,但这次我们选择了Material-UI。
它既时尚又具备到达痒处的功能。
首先安装Material-UI。
yarn add @mui/material @emotion/react @emotion/styled
请将以下代码复制并粘贴。
import React,{useMemo} from "react";
import Input from "@mui/material/Input";
export default function APP() {
const [file, setFile] = React.useState<File | string>();
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
return (
<label htmlFor="contained-button-file">
<Input
id="contained-button-file"
type="file"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
/>
</label>
);
}
代码沙盒:示例
通过查看上方,你会发现出现了一个文件附件按钮。
简单来解释一下,当你附加文件时,它将会出现。
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
↑点燃
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
执行↑操作,并将选择的第一个文件存储在file变量中。
然后将file发送到Rails,以便进行下一步操作,但是目前Rails无法识别该文件。
需要将file转换为文件格式才行。
添加以下代码。
import React,{useMemo} from "react";
import Input from "@mui/material/Input";
export default function APP() {
const [file, setFile] = React.useState<File | string>();
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
+ const createFormData = () => {
+ const formData = new FormData();
+ formData.append("user[file]", file!);
+ return formData;
+ };
return (
<label htmlFor="contained-button-file">
<Input
id="contained-button-file"
type="file"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
/>
</label>
);
}
formData.append被转换为文件格式。
formData.append的参数是
formData.append("Railsのテーブル名[カラム名]", ファイルが格納された変数);
我决定成这样。
请注意表名没有“s”。
接下来,使用axios将数据发送到Rails。
yarn add axios
请安装 Axios。
请添加以下代码。
import React, { useMemo } from "react";
import Input from "@mui/material/Input";
+ import Button from "@mui/material/Button";
+ import axios from "axios";
export default function APP() {
const [file, setFile] = React.useState<File | string>();
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
const createFormData = () => {
const formData = new FormData();
formData.append("user[file]", file!);
return formData;
};
+ const postFile = async () => {
+ const data = createFormData();
+ await axios
+ .post(`http://localhost:3000/post_file/`, data)
+ .then((response) => {
+ //成功したときの処理
+ })
+ .catch(() => {
+ //失敗したときの処理
+ });
+ };
return (
<label htmlFor="contained-button-file">
<Input
id="contained-button-file"
type="file"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
/>
+ <Button
+ size="medium"
+ onClick={() => {
+ postFile();
+ }}
+ >
+ Railsにファイルを送信
+ </Button>
</label>
);
}
代码沙盒:示例
我认为当您添加代码时,会得到与上述相同的结果。
当点击按钮后,postFile函数会被触发,axios会开始运行。
const data = createFormData();
将在此处转换的文件存储到data中
.post(`http://localhost:3000/post_file/`, data)
以这种方式向Rails发送数据。
当然,在正式环境下,http://localhost:3000的部分会改变。
请将您自己的正式环境URL放入。
有关详细的axios使用方法,请参考其他文章。
在Rails中接收数据
默认情况下,Rails不会接收来自其他端口的post请求。因此,我们可以使用rack-cors来进行授权。
+ gem 'rack-cors'
请在Gemfile中添加↑并执行bundle install。
然后在config/initializers文件夹下创建cors.rb文件。
配置文件的内容应该如↓所示。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3001'
resource '*',
headers: :any,
expose: %w[access-token expiry token-type uid client],
methods: %i[get post put patch delete options head],
credentials: true
end
end
原点部分是Rails使用的端口部分。
默认是3000,但是React设置为3000,所以Rails是3001。
在生产环境中,也是相同的,将域名放入原点。
接下来安装carrierwave和fog-aws。
gem 'rack-cors'
+ gem 'carrierwave'
+ gem 'fog-aws'
Carrierwave是一个方便的工具,可以帮助处理上传、下载等操作。
运行”bundle exec rails g uploader File”命令,创建app/uploaders/file_uploader.rb文件。
class FileUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
# if Rails.env.production?
# storage :fog
# else
# storage :file
# end
if Rails.env.development?
storage :file
else
storage :fog
end
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
# def default_url(*args)
# # For Rails 3.1+ asset pipeline compatibility:
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
#
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
# end
# Process files as they are uploaded:
# process scale: [200, 300]
#
# def scale(width, height)
# # do something
# end
# Create different versions of your uploaded files:
# version :thumb do
# process resize_to_fit: [50, 50]
# end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
# def extension_whitelist
# %w(jpg jpeg gif png)
# end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
# "something.jpg" if original_filename
# end
end
我会做得像↑那样的感觉。
if Rails.env.development?
storage :file
else
storage :fog
end
存储:当使用文件系统(file)时,上传的文件将保存在public/uploads文件夹下。
存储:当使用云存储(fog)时,会储存在S3上,借助fog-aws实现。
以上是设置,本地环境下保存在public/uploads文件夹下,生产环境下保存在S3上。
环境变量的设置如下。
放在Rails根目录下。
RAILS_ENV = "development"
如果是正式的话,只需要把这个改为Production。
为了保存到S3,我们将在config/initializers文件夹下创建一个carrierwave.rb文件。
require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'
CarrierWave.configure do |config|
config.storage :fog
config.fog_provider = 'fog/aws'
config.fog_directory = 'バケット名' # 作成したバケット名を記述
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], # 環境変数
aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], # 環境変数
region: 'ap-northeast-1', # アジアパシフィック(東京)を選択した場合
path_style: true
}
end
请参考【Rails】CarrierWave教程,按照上述的设置进行配置。
在迁移中,我会创建User表和File列。
rails g migration User
class User < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string 'file'
t.timestamps
end
end
end
rails db:migrate
接下来,我们需要创建一个模型,并将其与CarrierWave进行关联。
rails g model User
class User < ApplicationRecord
mount_uploader :file, FileUploader
end
这将与file_uploader.rb文件相关联。
接下来设置路由。
Rails.application.routes.draw do
post '/post_file' => 'users#post_file'
end
创建控制器。
rails g controller user
class UsersController < ApplicationController
def post_file
User.create(file: params[:user][:file])
render status: 200
end
end
当使用axios进行.post(“http://localhost:3000/post_file/”, data)请求时,user_controller会调用post_file动作,将file参数保存到file列中,并创建用户。
file列与CarrierWave相关联,它会再次将文件格式转换,并根据carrierwave.rb的设置保存到指定位置。
如果有任何错误,请在评论栏告知。
以上为草率的解释。