通过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的设置保存到指定位置。
如果有任何错误,请在评论栏告知。
以上为草率的解释。

bannerAds