DAY18 — 將檔案上傳到 firebase storage

Jason Z
jason-read-code
Published in
8 min readMar 28, 2022

什麼是 firebase storage

storage 是 firebase 所提供的儲存服務,可以想像是 firebase 版本的雲端硬碟,只不過還加上許多實用的功能。

免費有提供一定的容量和流量,可以放心使用與放心玩

設定 firebase storage

首先在 app.module.ts 引入 AngularFireStorageModule

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularFireModule } from '@angular/fire/compat';
// 引入
import { AngularFireStorageModule } from '@angular/fire/compat/storage';
import { environment } from '../environments/environment';
@NgModule({
imports: [
BrowserModule,
AngularFireModule.initializeApp(environment.firebase),
// 引入
AngularFireStorageModule
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {}

再來設定你的 BUCKET

providers: [
{ provide: BUCKET, useValue: environment.firebase.storageBucket },
],

這樣就設定完成囉

回到服務,撰寫上傳圖片的功能

再來回到 checkinService 上傳圖片的功能實作部分

uploadFile(
imageFiles: File[],
filePath: string,
docPath: string,
message: string,
name: string
): Observable<any> {
const nowTimestamp = +new Date();
const fullFilePath = `checkin/${filePath}`;
let fileArray$ = [];
for (const [i, imageFile] of Object.entries(imageFiles)) {
const task = this.storage.upload(
`${fullFilePath}${nowTimestamp}${i}`,
imageFile
);
fileArray$.push(task);
}
return forkJoin(fileArray$).pipe(
finalize(() => {
fileArray$.forEach((e, i) => {
const fileRef = this.storage.ref(
`${fullFilePath}${nowTimestamp}${i}`
);
const downloadURL$ = fileRef.getDownloadURL();
downloadURL$.subscribe((imageUrl) => {
if (Number(i) === 0) {
this.sendMessageToLineChatbot(message, name, imageUrl, filePath);
}
this.firestore.doc(docPath).update({
imgFile: firebase.firestore.FieldValue.arrayUnion(imageUrl),
docPath: filePath,
});
});
});
})
);
}

總共會使用到5個參數

  1. imageFiles: File[] : 上傳的檔案
  2. filePath: string, :上傳的檔案放在firebase storage的路徑,路徑已存在firestore 建立的id 為主
  3. docPath: string, : firestore上傳的路徑位址,圖片上傳成功後,要將圖片的URL 更新回 firestore 的資料中,記錄下來
  4. message: string, : 圖片上傳成功後,要將打卡人的打卡內容作為訊息傳送到 line message api
  5. name: string : 圖片上傳成功後,要將打卡人的名字做為訊息傳送到 line message api

準備的工作

const fullFilePath = `checkin/${filePath}`;
const nowTimestamp = +new Date();
let fileArray$ = [];
for (const [i, imageFile] of Object.entries(imageFiles)) {
const task = this.storage.upload(
`${fullFilePath}${nowTimestamp}${i}`,
imageFile
);
fileArray$.push(task);
}

一開始先定義三個變數

  1. fullFilePath :很好懂,就是要上傳到 firbase storage 的路徑
  2. nowTimestamp: 在上傳的時候,除了以使用者上傳的檔案名稱為主之外,另在檔案名稱後面加上時間戳記,這是為了避免不同使用者上傳圖片時,使用的圖片檔名都一樣(例如: img001),這樣在上傳的時候,會因為同樣的檔名而互相覆蓋,所以加上時間戳記做為區別
  3. fileArray: 將上傳的圖片分別塞到陣列當中,可以看到緊接著就跑了迴圈,將檔案上傳的位置、檔案、通通以可被觀察的對象 (observable) 的形式,放到物件當中

執行階段

準備好之後,就開始執行上傳的工作

return forkJoin(fileArray$).pipe(
finalize(() => {
fileArray$.forEach((e, i) => {
const fileRef = this.storage.ref(`${fullFilePath}${nowTimestamp}${i}`);
const downloadURL$ = fileRef.getDownloadURL();
downloadURL$.subscribe((imageUrl) => {
if (Number(i) === 0) {
this.sendMessageToLineChatbot(message, name, imageUrl, filePath);
}
this.firestore.doc(docPath).update({
imgFile: firebase.firestore.FieldValue.arrayUnion(imageUrl),
docPath: filePath,
});
});
});
})
);

最最最最精華的地方就是使用rxjs的 forkJoin ,解決所有非同步的困擾。forkJoin 代表要陣列裡面所有的可被觀察對象都執行成功後,才會繼續執行下一個動作,也就是所有的圖片都上傳成功之後,才會執行下一動。也就不必煩惱哪個會先上傳完,要做甚麼處理等等。

再來使用 finalize 的運算子,顧名思義,也就是前面都完成之後,最後要做什麼。

上傳完畢之後透過 fileRef 取得上傳位址,再呼叫 getDownloadURL 取得圖片的URL

取得之後,再呼叫 sendMessageToLineChatbot 將打卡訊息、名稱、圖片位置、路徑等參數送出去,傳送到 line message api

最後呼叫firestore 更新的功能,將圖片的資訊更新上去,其中一個比較特殊的功能是 firebase.firestore.FieldValue.arrayUnion ,這個意思是說,檢查陣列裡面的內容,如果重複就不動,沒有的就插入,約等於javascript 的 push 的加強版

好了,這樣上傳圖片的功能就完成了

檢討

最後檢討一下,這樣的寫法是否有問題。

想信眼尖的人都會感到不對勁,為什麼上傳的圖片裡面還會包含傳送line message api的訊息呢?這樣讓功能名稱誤導人又違反單一職責原理。

沒錯,這樣的寫法的確不好,有改善的空間,要快快樂樂地寫 side project,但不是製造麻煩給未來的自己,這一段要改進!

--

--

Jason Z
jason-read-code

哲學系畢業的前端工程師,大部分時間都在搞鐵路系統,喜歡寫程式外,更喜歡鐵道,欣賞路上每個平凡的風景