Techvenience

Technology × Convenience - Vue / React / Next / Nuxt / ChatGPTなどのIT技術がもたらす便利さをお伝えします。最近はChatGPTなどのAI技術を使ってブログを書いています。

Vue.jsでFirebase Storageを使う

Vue.jsでFirebase Storageを使う

f:id:duo-taro100:20160218004611p:plain

今回はVue.jsで開発しているアプリケーションでVue.jsでFirebase Storageを使う方法を解説します。
今回の開発環境は以下のページで解説している環境を使いますが、他の環境を使用しても大差ありません。
www.sky-limit-future.com

コンテンツ

Firebase側の設定

Firebaseコンソール上でのプロジェクト作成・アプリ追加は以下のページを参考にしてください。
www.sky-limit-future.com


アプリの追加までができたら、Storageを有効化します。
Firebaseコンソールから該当プロジェクトに入り、画面左メニューの「Storage」を選択します

storage設定
storage設定

画面上部に「始める」というボタンがありますのでクリックしてください。
以下の画面が表示されますが、今は無視でOKです。Storageは権限(閲覧/書き込みなど)の設定ができますが、デフォルトではこのようになっているという説明になります。

権限の説明
権限の説明

次に進んでロケーション を設定します。ここでは東京を示すasia-northeast1を選択します。

ロケーション
ロケーション

完了を押すとデフォルトバケットが作成されます。

デフォルトバケット
デフォルトバケット

ここでテスト用のバケットを作成しておきます。Filesタブ選択すると右側に追加ボタンがあります。

f:id:duo-taro100:20200930135307p:plain
バケット追加
名称入力
名称入力

名前を入力して追加しましょう。今回は「test」という名前で作りました。
ここの画面の「Rules」タブを後で使うので覚えておいてください!

これでStorageを使用する準備ができました。

最後に該当アプリで使用するAPIキーなどを取得します。

Firebaseを使うためにVue.js側で設定

プロジェクトの上部に作成したアプリが表示されていると思うので、アプリを選択してください。

アプリ選択
アプリ選択

選択すると歯車マーク(設定)へのリンクが表示されます。

設定画面へ
設定画面へ

設定画面の「全般」タブの一番下に、「マイアプリ」というエリアがあります。その中の「Firebase SDK snippet」という項目で「構成」を選択します。

アプリ設定
アプリ設定

すると以下のような形で、設定値が記載されています。(内容はサンプルです)

const firebaseConfig = {
  apiKey: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  authDomain: "project_id.firebase.com",
  databaseURL: "https://project_id.firebase.com",
  projectId: "project_id",
  storageBucket: "project_id.appsss.com",
  messagingSenderId: "111111111111",
  appId: "9:99999999999:app:7777777777777777",
  measurementId: "A-88888888888"
};

firebaseとの接続で必要になるのでコピーしておきましょう。

ここで、実際のソースコードに変更を加えていきます。
いくつか設定方法があるのですが、私の場合は役割をはっきりとさせたいタイプなので、firebaseの設定だけが記載されているjsを作成します。

/src/firebase/firebase.js

こちらに作成しました。
中身は以下の通りです。

import firebase from "firebase/app";
import "firebase/storage";

// 以下に先ほどコピーしたものを貼り付け
const firebaseConfig = {
    apiKey: process.env.FIREBASE_API_KEY,
    authDomain: process.env.FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.FIREBASE_DATABASE_URL,
    projectId: process.env.FIREBASE_PROJECT_ID,
    storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.FIREBASE_APP_ID,
    measurementId: process.env.FIREBASE_MEASUREMENT_ID,
};

firebase.initializeApp(firebaseConfig);

export default firebase;

先ほどFirebaseコンソールでコピーしたAPIキーなどの設定値をここに記載します。
私は環境変数(configファイル)を使って記載していますが、Firebaseコンソールでコピーしたものをそのまま貼り付けても問題ないです。
今回はFirebase Stoargeの使い方なので、2行目の

import "firebase/storage";

これの追加も忘れないようにしましょう。
これで実装準備は完了です。次にサンプルページを作成し動作確認を行いましょう。

サンプル画面の作成

サンプルページはデフォルトで用意されているファイルを編集していきます。

/src/components/HelloWorld.vue

まずはこのファイルの中身を以下のように上書きします。

<template>
  <div class="hello">
    <div>ここを編集していきます。</div>
  </div>
</template>

<script>

export default {
  name: 'HelloWorld',
}
</script>

文字列が表示されるだけの画面になりました。
サンプルとして、選択した画像がStorageにアップロードされること。
アップロードされた画像を指定して取得できること、指定バケットに格納されている画像全てを取得できることを確認します。

画面を作成する前に、Firebase Storageの処理を記載するファイルを作成します。
HelloWorld.vue内に記載してもいいのですが、個人的な考えでファイルごとに役割を持たせたいということでこのようにします。
実際はどこに記載していただいてもOKです。
処理の内容はドキュメントを参考にして作成しました。

firebase.google.com


作成ファイルは以下の通りです。

/src/firebase/storage.js

中身は以下のようになります

import firebase from "./firebase.js";

export const STORAGE = firebase.storage();

export const STORAGE_REF = STORAGE.ref();

/**
 * 指定したファイルのダウンロードurlを取得します
 * @param {*} childName
 */
export function  findStorage(childName){
    const downRef = STORAGE_REF.child(childName)
    downRef.getDownloadURL()
    .then((url) => {
       return url;
    })
    .catch(function(error) {
        
    });
};

/**
 * 指定ディレクトリ内にあるファイルのダウンロードurlを取得します
 * @param {*} childName 
 */
export async function findListStorage(childName){
    var listRef = STORAGE_REF.child(childName);
    return listRef.listAll()
}

/**
 * 画像をアップロードします
 * @param inputFile ファイル
 * @param directory バケット名
 */
export function attachImage(inputFile, bucket) {
    const file = inputFile
    if(!file || !bucket) {
      return;
    }

    const uploadTask = STORAGE_REF.child(`${bucket}${file.name}`).put(file)
    uploadTask.on('state_changed',
      (snapshot) => {
        // 成功時の処理
      },
      (error) => {
        // エラー
        console.log('err', error)
      },
      () => {
        uploadTask.snapshot.ref.getDownloadURL().then(function(downloadURL) {
          // ファイルアップロードして使えるようになったときの処理
        })
      }
    )
};

HelloWorld.vueも以下のように変更します。

<template>
  <div class="hello">
    
  
    <b-form-file
      v-model="file1"
      :state="Boolean(file1)"
      placeholder="Choose a file or drop it here..."
      drop-placeholder="Drop file here..."
    ></b-form-file>
    <button v-on:click="addImage()">アップロード</button>

  </div>
</template>

<script>
import {attachImage, findListStorage} from "../firebase/storage";
export default {
  name: 'HelloWorld',
  // 追加
  data () {
    return {
      file1 : null,
    }
  },
  // 追加
  methods : {
    /**
     * 指定画像を追加します
     */
    addImage: function() {
      const file = this.file1;
      if(!file) {
        alert("画像を選択してください");
        return;
      }

      attachImage(file, "test/");

    }
  }
}
</script>

この段階で以下のように表示されます。

画面作成
画面作成


簡単に説明すると

<b-form-file v-model="file1" ....省略></b-form-file>

上記でinputを作成し、画像選択を実行できるようになりました。選択されたファイルをfile1として、v-modelに指定しました。
そのため、dataも用意する必要がありましたので、以下のようにしています。

data () {
    return {
      file1 : null,
    }
  }

また、アップロードボタンを用意しました

    <button v-on:click="addImage()">アップロード</button>

クリック時に呼ばれるメソッドも追加しました。

  methods : {
    /**
     * 指定画像を追加します
     */
    addImage: function() {
      const file = this.file1;
      if(!file) {
        alert("画像を選択してください");
        return;
      }

      attachImage(file, "test/");

    }
  }

さて、実際に動かしてみましょう!
結論からいうとこのままでは正しくアップロードができません。
アップロード時にエラーがあった場合にはログが出るようにしていたので確認してみると以下のように出ています。

FirebaseStorageError {
code_: "storage/unauthorized", message_: "Firebase Storage: User does not have permission to access 'test/スクリーンショット 2019-04-24 16.30.58.png'.", serverResponse_: "{"error": {"code": 403,  ... 省略 .... FirebaseError"}

要するに権限がないとうことにになります。

ここで権限の設定を行います。

権限の設定

再びFirebaseコンソールに戻ります。
Firebaseコンソールから該当プロジェクトに入り、画面左メニューの「Storage」を選択します

storage設定
storage設定

「Rules」タブがあるので移動してください。
ここで権限の設定が可能です。

権限の設定
権限の設定

デフォルトでは以下のようになっています。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

これを変更します。使い方に関する詳細は以下のドキュメントを参考にしてください。
firebase.google.com

今回は

: if request.auth != null;

上記の箇所を削除し、以下のようにします。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write
    }
  }
}

変更を加えると、変更を反映させるかどうかのボタンが出てくるので「公開」を選択します

公開
公開

ルール変更には多少時間がかかることがありますが、これで完了です。

画像のアップロード

先ほど失敗した画像アップロードですが今度は成功するはずですので、もう一度試してみてください。
今の実装では成功した場合でも何も起きないので、成功したか分かりません。
実際にFirebaseコンソールのtestバケットに画像がアップロードされているか確認してみましょう。

画像アップロード確認
画像アップロード確認

正常に動いていますね。

画像の取得

続いてアップロードした画像を取得します。

HelloWorld.vueを以下のように変更します。

<template>
  <div class="hello">
    <h3>アップロード</h3>
    <b-form-file
      v-model="file1"
      :state="Boolean(file1)"
      placeholder="Choose a file or drop it here..."
      drop-placeholder="Drop file here..."
    ></b-form-file>
    <button v-on:click="addImage()">アップロード</button>

    <h3>指定画像取得</h3>
    <b-form-group
      description="バケット名を入力してください."
      label="バケット名:"
      label-for="bucketName"
    >
      <b-form-input id="bucketName" v-model="bucketName" trim></b-form-input>
    </b-form-group>

    <b-form-group
      description="ファイル名を入力してください."
      label="ファイル名:"
      label-for="fileName"
    >
      <b-form-input id="fileName" v-model="fileName" trim></b-form-input>
    </b-form-group>
    <button v-on:click="findImage()">取得</button>


    <div v-if="sampleImage">
      <img :src="sampleImage" />
    </div>


  </div>
</template>

<script>
import {attachImage, findListStorage, findStorage} from "../firebase/storage";
export default {
  name: 'HelloWorld',
  // 追加
  data () {
    return {
      file1 : null,
      bucketName : null,
      fileName: null,
      sampleImage : null,
    }
  },
  // 追加
  methods : {
    /**
     * 指定した画像を取得します
     */
    findImage: async function(){
      if(!this.bucketName) {
        alert("バケット名を指定してください。")
        return;
      }
      if(!this.fileName) {
        alert("ファイル名を指定してください。")
        return;
      }
      const image = findStorage(this.bucketName + "/" + this.fileName);
      image.getDownloadURL()
      .then((url) => {
        this.sampleImage = url;
      })
      .catch(function(error) {
          alert("画像ダウンロードURLの取得に失敗しました。")
      });
    },
    /**
     * 指定画像を追加します
     */
    addImage: function() {
      const file = this.file1;
      if(!file) {
        alert("画像を選択してください");
        return;
      }

      attachImage(file, "test/");

    }
  }
}
</script>


こちらも簡単に解説すると

<h3>指定画像取得</h3>
    <b-form-group
      description="バケット名を入力してください."
      label="バケット名:"
      label-for="bucketName"
    >
      <b-form-input id="bucketName" v-model="bucketName" trim></b-form-input>
    </b-form-group>

    <b-form-group
      description="ファイル名を入力してください."
      label="ファイル名:"
      label-for="fileName"
    >
      <b-form-input id="fileName" v-model="fileName" trim></b-form-input>
    </b-form-group>
    <button v-on:click="findImage()">取得</button>

ここで取得したいバケット名とファイル名を入力できるようにしています。
それぞれを保持するためにdataにも変更を加え以下のようにしています。

  // 追加
  data () {
    return {
      file1 : null,
      bucketName : null,
      fileName: null,
      sampleImage : null,
    }
  }

sampleImageは取得した画像のダウンロードURLを保持するために使いますので、ここで作成しておきます。
「取得」ボタンを押すと実際の取得処理が走ります。

その処理を以下のように定義しました。

    /**
     * 指定した画像を取得します
     */
    findImage: async function(){
      if(!this.bucketName) {
        alert("バケット名を指定してください。")
        return;
      }
      if(!this.fileName) {
        alert("ファイル名を指定してください。")
        return;
      }
      const image = findStorage(this.bucketName + "/" + this.fileName);
      image.getDownloadURL()
      .then((url) => {
        this.sampleImage = url;
      })
      .catch(function(error) {
          alert("画像ダウンロードURLの取得に失敗しました。")
      });
    }

bucketNameやfileNameが空の場合はエラーになります。

/src/firebase/storage.js

に定義したfindStorageを利用して、指定した画像を取得する処理になっています。
取得に成功したらsampleImageにダウンロードURLを詰め込み、失敗したらalertダイアログが上がるようになっています。
sampleImageに値が入ると、以下の実装により画像が表示されます。

    <div v-if="sampleImage">
      <img :src="sampleImage" />
    </div>

実際に動かしてみます。
まずは存在しないファイルを指定してみます。
エラーとなりました。
次に正常系。私の場合は、testバケット内に「ハートのマーク.png」という画像があるのでそのように指定してみます。

存在するファイルの入力
存在するファイルの入力
画像の取得
画像の取得

画像が表示されました。
以下は実際にStorageに存在する画像です。

Storageにある画像情報
Storageにある画像情報

今回はここまでです。
画像リストの取得に関しては別の機会にやりたいと思います!