こんにちは!
普段Rails + Reactを書いていますkannachiです。
Rails + Reactを使った自作アプリで画像データをAmazon S3にuploadすることがあったのですが、 せっかくなので、その時の実装を簡略化して紹介したいと思います。
githubに今回のコードを残しているので良ければ参考にしてみてください!
今回は以下のようなことをやっていきます。
- 1. rails newとcreate-react-appを使って簡単なAppを作成
- 2. フロントで画像を選択と描写
- 3. 画像をリサイズしてエンコード化
- 4. バックエンドにデータを送信
- 5. S3に画像データをupload
それでよろしくお願いします!
1. rails newとcreate-react-appを使って簡単なAppを作成
まずプロジェクトを作成しましょう。
$ rails new sample-app-with-aws -d mysql --api
gemfileでrack-corsのコメントアウトを外してbundle install
してください。
gem 'rack-cors'
$ bundle install
railsでは3001ポートを利用します。
以下のコマンドでサーバーが立ち上がることを確認してください。
$ rails s -p 3001
application.rbを変更します。
module SampleAppWithAws class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.2 config.api_only = true config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3000' resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :delete, :options] end end end end
つぎにcreate-react-appを使ってviewを作成していきます。
$ create-react-app front_end
以下のコマンドで、reactサーバーが起動されるか確認してください。
cd front_end npm start
App.jsを編集します。
import React from "react"; import logo from "./logo.svg"; import "./App.css"; import User from "./Views/User"; function App() { return ( <div className="App"> <User /> </div> ); } export default App;
2. フロントで画像を選択と描写
formikというライブラリを使うと簡単にユーザー登録ができるようになります。
$ yarn add formik
今回はわかりやすいようにバリデーションは考慮せずに、cssも記述しません。
users componetを作成し、編集していきます。
import React from "react"; import { Formik, Form, Field } from "formik"; export class Users extends React.Component { constructor(props) { super(props); this.state = { // 画像を表示するためにstateを作成します. profileImage: "" }; } // 後ほど記述 createUser = payload => {}; setImage = (e, setFieldValue) => { let files = e.target.files; let reader = new FileReader(); // 画像をbase64にエンコードします. reader.readAsDataURL(files[0]); reader.onload = () => { // stateに画像を入れることで描写させます. this.setState({ profileImage: reader.result }); // formikで送信できるようにsetFieldValue()を呼び出します. setFieldValue("profile_image", reader.result); }; }; render() { return ( <Formik initialValues={{ name: "", profile_image: "" }} onSubmit={this.CreateUser} > {({ setFieldValue, isSubmitting }) => { return ( <Form> <label>プロフィール画像</label> <img className="profile-image" src={!this.state.profileImage ? "" : this.state.profileImage} /> <React.Fragment> <Field id="select_profile_image" type="file" name="profile_image2" onChange={e => this.setImage(e, setFieldValue)} /> <Field type="hidden" name="profile_image" /> </React.Fragment> <label>名前</label> <Field className="input" type="text" name="name" /> <button className="submit-button" type="submit" disabled={isSubmitting} > 送信 </button> </Form> ); }} </Formik> ); } } export default Users;
これで画像の描写ができるようになりました。
3. 画像をリサイズしてエンコード化
しかし、このままでは大きな画像データをバックエンド及びS3へ、際限無く送ることになってしまいます。
そのため、フロント側で画像をリサイズしてからバックエンドに送るようにします。
修正後
. . //canvasにresizeした画像を描写した後にエンコード setImage = (e, setFieldValue) => { let canvas = document.getElementById("canvas"); let ctx = canvas.getContext("2d"); let maxW = 250; let maxH = 250; let img = new Image(); img.onload = () => { let iw = img.width; let ih = img.height; let scale = Math.min(maxW / iw, maxH / ih); let iwScaled = iw * scale; let ihScaled = ih * scale; canvas.width = iwScaled; canvas.height = ihScaled; ctx.drawImage(img, 0, 0, iwScaled, ihScaled); const resizeData = canvas.toDataURL("image/jpeg", 0.5); this.setState({ profileImage: resizeData }); setFieldValue("profile_image", resizeData); }; img.src = URL.createObjectURL(e.target.files[0]); }; render() { return ( <Formik initialValues={{ profile_image: "", name: "" }} onSubmit={updateUser} > {({ setFieldValue, isSubmitting }) => { return ( <Form> <label>プロフィール画像</label> <img src={!this.state.profileImage ? "" : this.state.profileImage} /> <React.Fragment> <Field type="file" onChange={e => this.setImage(e, setFieldValue)} /> <Field type="hidden" name="profile_image" /> </React.Fragment> {/* resizeした画像を描写するためのcanvasを作成 */} <canvas id="canvas" style={{ display: "none" }} width="64" height="64" /> <label>名前</label> <Field className="input" type="text" name="name" /> <button className="submit-button" type="submit" disabled={isSubmitting} > 送信 </button> </Form> ); }} </Formik> ); } }
これで画像データをresizeして送ることができるようになりました。
4. バックエンドにデータを送信
次にバックエンドにデータを送信します。
axiosを使えば簡単にHTTP通信が扱えるのでREST-API を簡単に実装できます。
$ npm install axios --save
以下のメソッドを作成して追加してください。
import axios from "axios"; . . createUser = payload => { axios .post("http://localhost:3001/users", payload) .then(({ data, message }) => { if (data) { this.setState({ user: data }); } else { throw new Error(message); } }) .catch(e => alert(e.message)); };
Databaseを作成しましょう。
$ mysql -u root -p mysql> CREATE DATABASE sample_app_with_aws;
database.ymlの内容を変更しておきます。
development:
<<: *default
database: sample_app_with_aws
Userテーブルを作成します。
$ rails g migration CreateUsers
class CreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :name, null: true, comment: '名前' t.text :image_data, comment: '画像データの名前' t.timestamps end end end
migrateを行います。
$ rails db:migrate
controllerを作成します。
$ rails g controller users
users_controllerの中はこんな感じです。
class UsersController < ApplicationController def create user = User.create!(user_params) render json: user end private def user_params params.permit(:name, :image_data) end end
config/routesを設定します。
Rails.application.routes.draw do resources :users end
これでバックエンドにデータを送ることができるようになりました。
5. S3に画像データをupload
S3にアクセスできるように以下のgem file以下を記述してbundle instal
lします。
gem 'aws-sdk'
次にaccess_key, secret_access_keyを取得します。
取得方法についてはたくさん記事がありますので割愛します。
keyを.envファイルに保管してくれるgemがありますのでこちら利用します。
https://github.com/bkeepers/dotenv/
gem 'dotenv-rails'
.envに以下を記述します。 (gitignoreに.envを追加するのを忘れないでください。)
AWS_ACCCES_KEY='######' AWS_ACCCES_SECRET_KEY='######'
自分のS3にsample_bucket
という名前のbucketを作成してください。
controllerを編集してにAWSにuploadできるようにします。
(今回の実装だとmysqlにも同時に保存しています。)
class UsersController < ApplicationController def create user = User.create!(user_params) # bucketを設定 bucket = Aws::S3::Resource.new( :region => 'ap-northeast-1', # keyは.envファイルに補完しています. :access_key_id => ENV['AWS_ACCCES_KEY'], :secret_access_key => ENV['AWS_ACCCES_SECRET_KEY'], ).bucket('sample_bucket') # sample_bucketにencodeされた画像データをupload bucket.object("user_id_#{user.id}_profile_image").put(:body => params[:profile_image]) render json: user end private def user_params params.permit(:name, :image_data) end end
S3を確認すると、encodeされた画像データがuplaodされていることを確認できると思います。