本篇要解決的問題
Firebase Cloud Functions 是可以把自己寫的函式,放到 Firebase 上後當後端來使用。
平常用的是 Google Apps Script,但知道可以拿 Cloud Functions 來寫 Node.js 後,就打算把之前寫在 GCP 上的 LINE、Telegram 的二隻機器人移到 Functions,看能不能統一把資料庫跟程式碼都收在 Firebase 中。
本篇是一些 Cloud Functions 的基本使用,包含:安裝、本機測試、觸發、更新 Firestore。
將來有機會會再新增其他 Functions 的相關筆記文。
要注意的是,Cloud Functions 是一項收費的功能,但有提供免費的額度,收費的部份麻煩自行看定價說明:https://cloud.google.com/functions/pricing
參考教學:Firebase Functions
安裝 Firebase CLI
文件:https://firebase.google.com/docs/cli#mac-linux-npm
開啟終端機,輸入:
npm install -g firebase-tools
如果安裝過程中看見很多 ERR!,代表權限不足,Mac 的話就輸入:
sudo npm install -g firebase-tools
輸入密碼後再按下 enter,就會執行安裝。
要看有沒有安裝成功,可以輸入:
firebase -V
如果有出現版本號,代表安裝成功。
登錄
安裝完 Firebase CLI,接著必須登入帳號,輸入:
firebase login
按下 enter,會問要不要將錯誤訊息回報給 Firebase,這個看個人。
接著會開啟網頁,是 Google 在問要不要授權給 Firebase,按下允許後,就會看見成功訊息:

終端機上也會出現一行: Success! Logged in as 你的Google帳號。告知我們是登入了哪個帳號。
要測試是不是真的登入了 Firebase,輸入:
firebase projects:list
有成功登入,就會出現終端機上秀出我們所擁有的 Firebase 專案。

更改成 Blaze 付費方案
開頭有說 Cloud Functions 超過免費用量後是會收費的,是屬於要付費的方案。
在開始用 Functions 之前,必須把我們的專案改成付費。
進到 Firebase 的後台,並進到我們要用 Functions 的專案,會看見專案名字的右邊有個寫著「Spark 方案」的按鈕,點擊它:

接著會出現方案選擇,預設是免費方案,為了用 Functions 必須改成付費,點擊 Blaze 下的「選取方案」:

之後 Firebase 會再次確認是否真要換付費方案,再次確認即可。
方案換成功後,Firebase 還會提醒你可以設定一個預算,要超過預算了就發通知信來:

在本機案裝 Firebase Cloud Functions
建立一個資料夾後,用終端機開機,接著輸入:
firebase init
按下 enter 後,會出現選單,選擇要在本地安裝 Firebase 的哪些功能。
因為未來的目標是可以把機器人搬到 Cloud Functions 上,所以就安裝 Firestore、Functions 這二個:

選好後,接著會出現下一個選單,選擇是要新增一個專案,還是連到現有的 Firebase 專案,August 已在 Firebase 有專案可以用,因此選擇「Use an existing project」,是要新增或是用現有,就看各人:

選擇好專案後,有可能會出現一個錯誤訊息:
「Error: It looks like you haven’t used Cloud Firestore in this project before.」
因為我們在第一步有選 Firestore,如果第二步選擇了用現有專案,而那個現有專案本身在 Cloud Firestore 上沒有資料的話,就會出現這個錯誤。
解決這個錯誤很簡單,就是隨便塞幾個資料到 Cloud Firestore 上就好。
用 Javascript 塞資料的方式可以參考這篇:Firebase Cloud Firestore 常用功能筆記
那當然,也可以直接進 Firebase 後台去塞。
Init 的過程中,Firebase 還會問一些問題,全部都用預設值就行(就是一直無腦地按 enter),init 結束,會看見資料夾中多了很多檔案:

在 functions > index.js 的檔案中,還可以看到貼心的給了範例的程式碼。
關於各個檔案的說明可以看文件,這邊就不一一列出。
部署 Functions
我們的資料夾裡,「functions > index.js」已經寫好了一個「helloWorld」的 function,解開註解後就可以部署上 Firebase,可以直接拿來看部署上去後的改變。

在資料夾內打開終端機,輸入:
firebase deploy --only functions
終端機就會只部署「functions」這個資料夾的檔案上去。
部署過程中終端機都會顯示訊息,部署完整個訊息是這樣:

訊息裡會看到一句:
Function URL (addMessage): https://us-central1-xxxxxxxxxxx.cloudfunctions.net/helloWorld
這一句就是觸發這個 function 的 URL,開啟瀏覽器,網址列貼上這行網址,我們剛傳上去的「helloWorld」這個 function 就會被執行。
我們這邊把網址貼到瀏覽器上,看會發生什麼:

可以看見頁面上顯示了「Hello from Firebase!」。
我們看一下這個「functions > index.js」的這個檔案,在 helloWorld 裡最後寫著:
response.send("Hello from Firebase!");
沒錯,最後 Functions 會回傳「Hello from Firebase!」來。
我們進到 Firebase 的後台,然後點擊「Functions」,可以看到在資訊主頁的部份,多了一條 helloWorld 的函式,上面也有寫著觸發的 URL:

部署的 Command Line
文件:https://firebase.google.com/docs/functions/manage-functions
這邊整理一下部署檔案到 Firebase 的命令。
部署全部的 Functions:
firebase deploy --only functions
只部署 Function 裡的指定 function:
firebase deploy --only functions:helloWord,functions:helloWorld2
刪除指定的 function:
firebase functions:delete helloWorld
一次刪除多個 function:
firebase functions:delete helloWorld helloWorld2
如果是要修改 function 的名稱,就要先部署新的名稱上去,接著再刪除舊的名稱:
firebase deploy --only functions:hellowWorld_new_name
firebase functions:delete hellowWorld_old_name
本機測試 Functions
如果我們在本機開發完,可以直接在本機測試,畢竟我們可能每次都部署到 Firebase 上後才來測,部署跟測試都會耗掉免費的額度,一個不小心就會超過。
看一下資料夾的內容:

可以看到有「package.json」檔,Good,代表可以在本機把 Node.js 給運行起來,打開 package.json 檔,會看到「scripts」有以下內容:
"scripts": { "lint": "eslint .", "serve": "firebase emulators:start --only functions", "shell": "firebase functions:shell", "start": "npm run shell", "deploy": "firebase deploy --only functions", "logs": "firebase functions:log" }
這些就是我們可以輸入 npm run XXXXX
的部份。
npm run serve
就會看見終端機開始執行,並把每一個流程都寫了出來,從終端機中我們就可以看見本機測試的 URL:

觸發 Functions:HTTP request
這段會寫用 HTTP request 來觸發 Functions。
其實這段在預設給的 helloWorld 就是了,這邊只是多做點補充。
Functions 是用 Node.js 在寫,因此如果本來就會 Node.js 的大大們會上手地很快。
像我們是用 Node.js + express,一般 http request 會像這樣:
app.get('/request_url', (request, response) => { response.send('hello World') })
到了 Functions,就是換成它們的規則,但一樣有 request、response:
exports.functionName = functions.https.onRequest((request, response) => { response.send('hello World'); });
因為都是 Node.js,所以 request、response 的物件就跟 Node.js 相同,在官方文件裡放的連結也是 express 的連結。
Request:https://expressjs.com/en/api.html#req
Response:https://expressjs.com/en/api.html#res
觸發 Functions:排程
文件:https://firebase.google.com/docs/functions/schedule-functions
除了藉由 HTTP request 來觸發外,也可以寫排程來自動觸發。
要注意的是,文件上說「每個 Cloud Scheduler 作業每月的費用為 $ 0.10(USD)」、「每個 Google 帳戶可以提供三個免費作業」,所以想要免費使用的話,排程的部份不要設定超過三個。
另外,排程這段 August 一直想在本機測,卻一直失敗,PubSub emulator 這功能一直 On 不起來,還請高手們有成功在本機測試排程這段的,留言提供解法,感謝~
寫排程的 Functions 如下:
exports.scheduledFunction = functions.pubsub.schedule('*/5 * * * *').onRun((context) => { console.log('每 5 分鐘觸發一次'); return null; });
functions.pubsub.schedule
要包的是時間設定,這部份的教學可看這篇:Nodejs 定時執行(node-cron)
部署上去後,就會看見 Firebase 後台多了一個觸發條件是時間:

那要看有沒有運行成功,可以在「紀錄」那邊觀看。
觸發 Functions:Callable
Callable 是我們在前端的頁面上主動去觸發 Fucntions。
要觸發 Functions,在前端頁面要引用 Firebase SDK。
Callable Functions 的範例程式碼:
exports.callable_name = functions.https.onCall((data, context) => { // data:前端傳來的資料 // context:非必填,為使用者的資料 });
如果前端頁面上,使用者有用 Firebase Auth 登入,context 就會是使用者的登入資料,如果未登入 context 就會是 null。
另外,因為引用 Firebase SDK 時,initialize 的 config 設定全部指定的都是 Firebase 的 URL,所以在呼叫 Callable Functions 時,也會是觸發 Firebase 上的,無法在本機上做測試。
前端頁面上觸發 Callable 的程式碼,這邊包含用 Firebase 中,用 Google 登入的部份,Firebase 後台中的 Authentication 要打開 Google 登入的功能,如果沒開也沒關係,把下方程式碼中有關「登入」、「登出」的部份給刪掉就好。
觸發 Functions:Firebase Auth
文件:https://firebase.google.com/docs/functions/auth-events
頁面有使用 Firebase Auth 的話,當使用者註冊帳號、刪除帳號,也可以觸發 Functions。
註冊帳號
刪除帳號
這二個 callback 回來的 user,有的資料如文件:https://firebase.google.com/docs/reference/admin/node/admin.auth.UserRecord.html
Functions 讀寫刪 Firestore 資料
「functions > index.js」檔案中,再引用 firebase-admin 後就可以讀寫 Firestore 的資料。
關於 Firestore 讀、寫、刪的方法,請見這篇筆記:Firebase Cloud Firestore 常用功能筆記
Functions 回傳錯誤訊息
當遇到錯誤時,可以指定遇到什麼錯誤就回傳什麼錯誤訊息,報錯方式如下:
new functions.https.HttpsError(錯誤碼, 要回的訊息)
錯誤碼請看文件:https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
使用範例:

