LoginSignup
3
2

More than 5 years have passed since last update.

RailsとiOS(Swift)とAWS間をAPIモードで、データファイル保存呼び出しさせるまで④

Last updated at Posted at 2018-09-24

前書き

初心者向けに自分なりの言葉で柔らかく書いたつもりです。非エンジニアですが、メモ目的とアウトプットの恩恵のために記事にしました。間違っているところなどありましたら、コメントをください。
あとコードにコメントが多く可読性が悪いかもしれません。そこは申し訳なく…

iosからAWSまでの一連の流れを記事にしているためかなり長くなっているので、記事を分けて書いています。

③までのものがこちら
https://github.com/ToruMizuno/railswiftapi
④までのものがこちら
https://github.com/ToruMizuno/railswiftapi2

carrierwave編

参考記事

事前準備

(メールアドレス作成と)AWSアカウント作成

AWS アカウント作成の流れ
からAWSアカウントを作成するが、先にメールアドレス(Googleアカウント)の作成からしていく。
実際、複数のメールアドレスで複数のAWSアカウントを作成できてしまうので、練習でAWSを使用する場合などを考えて、メールアドレスを複数取得しておけば便利。
画像では、スマホからGoogleアカウント(メールアドレス)を取得しているが好きな方法で取得すれば良いと思う。

最初の画面
自分のアイコンから
google1.png.png

アカウントを追加を押す
google2.png.png

アカウントを作成を押す
google3.png.png

適当にサクサクっと
google4.png.png

同じ電話番号でOK
google5.png.png

ショートメールで送られてくる数字を記入して確認を押す
!
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3234353435382f32623939306533392d306338642d653262622d396534352d3930653632363764376434642e706e67.png

生年月日も同じものでOK
google7.png.png

スキップでOK
google8.png.png

( ´Д`)y━・~~
google9.png.png

これらを繰り返して複数アドレスを作成しておくと、色々と便利
次にAWSアカウントを作成

最初の画面
作成したメールアドレスと考えたパスワードを記入。AWSアカウントはプロジェクト名など好きなものを記入すると良い。
aws1.png.png

個人で使用する場合などはパーソナルを選んで住所などの必要情報を記入。
複数回アカウントを作成する場合でも同じ情報を使用してもアカウントは作成できる。
aws2.png.png

クレジットカード情報を記入。
複数回アカウントを作成する場合でも同じ情報を使用してもアカウントは作成できる。
aws3.png.png

電話番号を記入して送信を押す。
電話番号に電話が鳴るので電話番号の確認がとれ、ブラウザに表示された4桁の番号を記入欄に記入
aws4.png.png

ベーシックプランを選択で、1年間無料でAWSのサービスが使用できる
aws5.png.png

次の画面で完了画面になり、コンソールへログインで登録したメールアドレスとパスワードでログインできる
aws6.png.png

IAMの設定とアクセスキー、シークレットアクセスキーの取得

AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ
こちらの記事を参考に必須事項のIAMの設定をする
スクリーンショット 2018-09-20 23.53.47.png.png

  • ルートアカウントのMAFを有効化
  • 個々のIAMユーザーの作成
  • グループを使用したアクセス許可の割り当て
  • IAMパスワードポリシーの適用

わかりやすく言うと

  • AWSアカウントを二段階認証化(通常のAWSアカウントに二段階認証アプリとか使ってサインインできるようにする)
  • IAMユーザ作成(通常のAWSアカウントは使わず、このユーザーでパスワードを設定、二段階認証を設定(MFA)して、そのパスワードや二段階認証でサインインするようにする)
  • IAMグループの作成(グループごとに権限を設定して、そこにユーザーを所属させる)

    • AdministratorAccess(AWSアカウントに次ぐ強力な権限)
    • AmazonS3FullAccess(S3をフルに使える)
  • IAMパスワードポリシーの適用(パスワードのルールを決める)

まとめると、作成したIAMユーザーと二段階認証でサインイン(ユーザー名、パスワード、二段階認証番号)できるようにして、そのサインインしたユーザーは限定された機能しか使えないグループに所属させることにより、このIAMユーザーでAWSを使用していくように設定する。という言い方で間違いないと思う。
ここのIAMユーザーをグループに所属させた時に、アクセスキーとシークレットアクセスキーを取得しておく

S3のバケットの作成

AWSコンソールから、サービス、S3を選択
スクリーンショット 2018-09-20 12.17.10.png.png

バケットを作成する
スクリーンショット 2018-09-20 15.05.19.png.png

バケット名に好きな名前をつける、リージョンはTokyo、作成を押すと作成される
スクリーンショット 2018-09-20 15.05.47.png.png

Rails側の実装

ImageMagickの導入

$ brew install imagemagick@6

gem編集

gemfile
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'# コメントアウトを外す
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'# コメントアウトを外す


###############################
# ユーザー認証をするために追加したgem
gem 'omniauth'
gem 'devise'
gem 'devise_token_auth'
# 画像をS3に保存をするために追加したgem
# brew install imagemagick@6でimagemagickのバージョン6をインストールしないとrmagickが使えない模様
gem 'carrierwave'# , github: 'carrierwaveuploader/carrierwave'
gem 'fog-aws'
gem 'rmagick'
###############################

gemのインストール

$ bundle install

アップローダーを作成

名前を変えて幾つでもアップローダーを作成できる

$ rails g uploader Image

アップローダーを編集

名前の違うアップローダーを作成した場合は、設定の違うアップの仕方ができる。
store_dirメソッド内で好きなフォルダー名を設定する

app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # リサイズしたり画像形式を変更するのに必要
  include CarrierWave::RMagick# コメントを外す
  # include CarrierWave::MiniMagick

  # ストレージの設定
  # storage :file# コメント
  # storage :fog# コメントを外す
  if Rails.env.development?# 開発環境だった場合
    storage :fog# 開発が終わったらfileに戻す
  elsif Rails.env.test?# テスト環境だった場合
    storage :file
  else# それ以外の環境だった場合
    storage :fog
  end

  # iPhoneから画像投稿した際に、画像の向きがおかしい場合があるので、
  # Rmagickのauto_orientメソッドで向きを正す。
  process :fix_rotate
  def fix_rotate
    manipulate! do |img|
      img = img.auto_orient
      img = yield(img) if block_given?
      img
    end
  end

  # S3のディレクトリ名(フォルダー名)# 格納するディレクトリを指定
  def store_dir
    # #{}で文字列挿入
    # "uploads/#①{model.class.to_s.underscore}/#②{mounted_as}/#③{model.id}"# uploads/①モデル名/②カラム名/③モデルID
    "ディレクトリ名/#{model.id}"
    # "#{model.id}"# コメント
  end

  # キャッシュを格納ディレクトリを指定
  # def cache_dir
  #   "cache"
  # end

  # デフォルト画像は1200x5000に収まるようリサイズ
  process resize_to_limit: [1200, 5000]

  # サムネイル画像だった場合
  # version :thumb do
  #   process resize_to_fill: [100, 100]
  # end

  # 許可する画像の拡張子
  def extension_whitelist
    %w(jpg jpeg gif png)
  end

  # 保存するファイルの命名規則。上のディレクトリー+このファイル名で保存される
  def filename
    # 下のトークンを作成するメソッドでファイル名を作るパターンのコード
    # "#{secure_token(10)}.#{file.extension}" if original_filename.present?
    original_filename if original_filename# 送られて来たファイル名をダイレクトに使う
  end

  # 一意となるトークンを作成。特に使わないかも
  # ファイル名で使うメソッド
  # protected
  # def secure_token(length=16)
  #   var = :"@#{mounted_as}_secure_token"
  #   model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.hex(length/2))
  # end
end

ModelにUploaderを紐つける

Postのpost_imageカラムにImageUploaderを紐つける

app/models/api/post.rb
class Api::Post < ApplicationRecord

  # どのカラムにどのアップローダーを使うか
  # Postのpost_imageカラムに対して、アップローダーのImageUploaderの設定でアップする
  mount_uploader :post_image, ImageUploader

  # S3の画像を削除
  before_destroy :clean_s3

  private
  # S3の画像を削除
  def clean_s3
    image.remove!       #オリジナルの画像を削除
    image.thumb.remove! #thumb画像を削除
  rescue Excon::Errors::Error => error
    puts "Something gone wrong"
    false
  end
end

carrierwave.rbの作成

IAMユーザー設定時に取得してきたアクセスキーとシークレットアクセスキーをここで使用
S3で作成したバケット名もここで使用する

config/initializers/carrierwave.rb
CarrierWave.configure do |config|
  config.fog_provider = 'fog/aws'
  config.fog_credentials = {
    #AWSに保存するために必要
    #  IAMの設定
    # アクセスキー
    # シークレットアクセスキー
    provider:              'AWS',
    # アクセスキー
    aws_access_key_id:     'アクセスキー',
    # シークレットキー
    aws_secret_access_key: 'シークレットキー',
    # Tokyo
    region:                'ap-northeast-1'
    # host: 's3.example.com',
    # endpoint: 'https://s3.example.com:8080'
  }

  # 公開・非公開の切り替え# S3のURLに直アクセスしていいか
  config.fog_public     = true

  # キャッシュをS3に保存
  # config.cache_storage = :fog
  # キャッシュの保存期間(1年)
  # config.fog_attributes = { 'Cache-Control' => "max-age=#{365.day.to_i}" }
  # キャッシュの保存場所
  # config.cache_dir = 'tmp/image-cache'

  # S3のURLに有効期限を60秒で設定する
  # config.fog_authenticated_url_expiration = 60

  # S3バケットを指定
  # config.fog_directory  = 'BUCKET_NAME(ENV)'


  # 環境ごとにS3のバケットを指定
  # 
  # 保存先URL
  # asset_host名/fog_directory(バケット)名/uploaderに定義したディレクトリー名/postID/送られてくるファイル名
  case Rails.env
    when 'production'
      config.fog_directory = 'バケット名'# バケット名
      config.asset_host = 'https://バケット名.s3-ap-northeast-1.amazonaws.com'

    when 'development'
      config.fog_directory = 'バケット名'# バケット名
      config.asset_host = 'https://バケット名.s3-ap-northeast-1.amazonaws.com'

    when 'test'
      config.fog_directory = 'バケット名'# バケット名
      config.asset_host = 'https://バケット名.s3-ap-northeast-1.amazonaws.com'
  end
end

# 日本語ファイル名の設定# 日本語入力を可能にするため。
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/

Swift側の実装

ストーリーボードとの接続と、各種変数

画像表示用のUIImageViewと、画像関係の変数を追加

PostViewController
    @IBOutlet weak var idTextField: UITextField!
    @IBOutlet weak var imageView: UIImageView!

    var accesstoken: String!
    var client: String!
    var uid:String!
    var webuser: Webuser!

    //画像を保存する際に取得する画像名を入れる変数
    fileprivate var postImagerandomString: String!
    //画像を保存する際に取得するタイトルを入れる変数
    fileprivate var titleNamerandomString: String!

    var posts = [Post]()//全てのPostデータを入れる変数
    var myposts = [Post]()//自分のPostデータを入れる変数

送信部分

画像送信用のボタンを追加
PostViewController画面から各種データの送信を行う

PostViewController
//画像とpostのパラメーターを保存
    @IBAction func postImageSaveButton_Clicked(_ sender: AnyObject) {

        //画像名を作る
        postImagerandomString = generate(length: 30) as String
        //タイトルを作る
        titleNamerandomString = generate(length: 30) as String

        //画像を用意してね!
        let image = UIImage(named: "IMG_0002.JPG")
        let data = UIImageJPEGRepresentation(image!, 1.0)

        //画像を保存する関数を実行する
        self.createImage(postimagedata: data!, fileName: postImagerandomString)

    }

画像保存関数

PostViewController
    //dataには画像、wihtNameには保存するカラム名、fileNameにはランダムに作った画像名を入れる
    func createImage(postimagedata: Data, fileName: String) {

        //ログインやサインアップでこれらの情報を取ってこれていた場合は
        if self.accesstoken != nil && self.client != nil && self.uid != nil {

            let accesstoken: String = self.accesstoken//optionalをStringに変換
            let client: String = self.client//optionalをStringに変換
            let uid: String = self.uid//optionalをStringに変換

            //取り出したトークン情報をヘッダーに格納
            var headers: [String : String] = [:]
            headers["access-token"] = accesstoken
            headers["client"] = client
            headers["uid"] = uid

            print("accesstoken: \(String(describing: accesstoken))")
            print("client: \(String(describing: client))")
            print("uid: \(String(describing: uid))")

            Alamofire.upload(multipartFormData: { (multipartFormData) in
                //画像を送りたい場合はこれを使用*******************************
                //複数個指定可
                //dataにはData型でUIImageJPEGRepresentationなどでUIImageから変換する
                //withNameはサーバーで受け取る際、使用する名前。つまりはRails側のカラム名
                //fileNameはファイルの名前。
                //mimeTypeはファイルのフォーマット
                multipartFormData.append(postimagedata, withName: "post_image", fileName: "\(fileName).jpeg", mimeType: "image/jpeg")

                /*
                 //送りたいテキストがある場合はこれを使用*************************
                 //テキストをdata型に変換
                 if let data = "文字列".data(using: String.Encoding.utf8) {
                 //withNameはサーバーで受け取る際、使用する名前。つまりはRails側のカラム名
                 multipartFormData.append(data, withName: "name")
                 }
                 */

                //ここにRailsのコントローラーアクションへのルートを入れる
            }, to: "http://localhost:3000/api/posts", method: .post, headers: headers) { (encodingResult) in
                switch encodingResult {
                case .success(request: let upload, streamingFromDisk: _, streamFileURL: _):
                    upload.responseJSON { response in
                        //debugPrint(response)
                        print("Request: \(String(describing: response.request))")
                        print("Response: \(String(describing: response.response))")
                        print("Error: \(String(describing: response.error))")

                        let json:JSON = JSON(response.result.value ?? kill)

                        //保存した画像のpostのIDを取り出す。次に保存(更新)するpostデータのために必要
                        let postid = json["id"].intValue
                        print("id: \(String(describing: postid))")

                        //データを保存
                        self.createNewPost(id: postid)

                    }
                case .failure(let encodingError):
                    print("エラーだよ")
                    print(encodingError)

                }
            }

        }
    }

画像を新規で保存した後なので、データの保存は更新で保存する

データの保存後、Postのパラメータセット時に画像保存先のURLをpostImageで保持させるのだが、URL作成にRails側で設定したバケット名やディレクトリー名を使用するので注意

PostViewController
    //postデータを保存する関数
    func createNewPost(id: Int) {

        print("画像は\(postImagerandomString)")
        print("タイトルは\(titleNamerandomString)")

        let accesstoken: String = self.accesstoken//optionalをStringに変換
        let client: String = self.client//optionalをStringに変換
        let uid: String = self.uid//optionalをStringに変換

        //取り出したトークン情報をヘッダーに格納
        var headers: [String : String] = [:]
        headers["access-token"] = accesstoken
        headers["client"] = client
        headers["uid"] = uid

        //パラメーターを辞書で入れていく//左辺がカラム名、右辺が値
        var params: [String: Any] = [:]
        params["web_user_id"] = self.webuser.web_user_id
        params["title_name"] = self.postImagerandomString
        //画像アップロードでcreateをしたのでデータを作成する場合はcreateしたレコードのIDにアップデートする

        //PUT /api/posts/:id/update_post_id(.:format)
        Alamofire.request("http://localhost:3000/api/posts/\(id)/update_post_id", method: .put, parameters: params, headers: headers).responseJSON { response in
            print("Request: \(String(describing: response.request))")
            print("Response: \(String(describing: response.response))")
            print("Error: \(String(describing: response.error))")

            //型を指定した定数にいれないと比較演算子が使えない
            let res: Int = (response.response?.statusCode)!

            //アクセスできた場合(更新ができた場合)
            if res >= 200 && res < 300 {
                print("通信に成功しました")
                print("post saved")

                print("Request: \(String(describing: response.request))")
                print("Response: \(String(describing: response.response))")
                print("Error: \(String(describing: response.error))")
                print("アップデートなので、レスポンスデータはありません")

                let newPost = Post()

                //パラメーターセット
                newPost.id = id
                newPost.web_user_id = self.webuser.web_user_id
                newPost.postImage = "https://バケット名.s3-ap-northeast-1.amazonaws.com/fog_directory(バケット)名/uploaderに定義したディレクトリー名/\(id)/\(self.postImagerandomString).jpeg"
                newPost.titlename = self.titleNamerandomString

                self.myposts.append(newPost)

                //アクセスできなかった場合
            } else {

                print("アクセスできませんでした")

            }

        }

    }

受信部分

画像の取得をパラメーターのpostImageのURLで取得する
index、show、mypostshowボタンで取得したpostの最後のpostのpostImageで画像を取得している

PostViewController
//画像の取得
    @IBAction func postImagegetButton_Clicked(_ sender: AnyObject) {

        // タイムアウトの設定
        let TIME_OUT: TimeInterval = 1 * 300

        let post = myposts.last!

        let url: String = post.postImage

        print("url:\(url)")
        // URLRequestの生成
        let req = URLRequest(url: NSURL(string: url)! as URL,
                             cachePolicy: .returnCacheDataElseLoad,
                             timeoutInterval: TIME_OUT)
        let conf =  URLSessionConfiguration.default
        // URLSessionの生成
        let session = URLSession(configuration: conf, delegate: nil, delegateQueue: OperationQueue.main)
        session.dataTask(with: req, completionHandler:
            { imageData, resp, err in
                // 通信結果
                if err == nil {
                    // Success
                    // 画像表示
                    self.imageView.image = UIImage(data: imageData!)!
                    self.view.addSubview(self.imageView)

                }else{
                    // Error

                    print("エラーです")
                    //self.imageView.image = UIImage(named: "代わりの画像")
                }

        }).resume()

    }

スクリーンショット 2018-09-23 12.02.23.png.png

AWS解約(おまけ)

最後にAWSの無料利用枠が1年なので、練習としてAWSを利用する場合などは利用料を徴収されては割に合わないので、利用料を徴収されてしまわないように停止しておきたい。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2