[Nodejs]透過Google Cloud Storage輕鬆展示圖片:打造屬於自己的圖庫
前言
本篇文章是講述如何在後端是Nodejs 的情況下,透過nodejs-storage套件,已Google Cloud Storage作為圖床,使用本地檔案上傳的方式,產生出可以回傳至前端並鑲嵌於html中的URL。
本文主要是講述Google Cloud Storage如何設定,程式碼的部份我假設您使用 formidable、multer 等會先將前端傳過來的圖片先存在本地端 /tmp資料夾的套件,這樣才可以使用本地檔案上傳圖片。此外程式碼應該各框架都可以使用,但本文是使用nextjs 後端實做。
撰寫本文的原因是在實做公司官網部落格後台的markdown編輯器時,主管開了Google Cloud Platform(GCP)權限讓我練習,於是我變將練習的過程紀錄下來變成本文。
那就讓我們開始巴!
目錄
⊙ 官方文件參考
⊙ Google Cloud Platform操作
1. 新增專案
2. IAM 權限管理
3. 新建一個值區(Bucket)
4. 生成Service Account 金鑰
⊙ 後端Nodejs實作上傳功能
1. google_setting.ts
2. google_drive_upload.ts
⊙ CORS 設定
⊙ 結語
官方文件參考:
- nodejs-storage(Google官方Google Cloud Storage上傳套件)
- Upload objects from a filesystem
- Make data public
- Cross-origin resource sharing (CORS)
Google Cloud Platform操作
1. 新增專案
在使用Cloud Storage之前,我們需要先在Google Cloud Platform建立一個專案(由於這邊已經存在一個專案,畫面可能不太一樣)
點左上角的專案區
然後選擇專案名稱,如果公司有付費可以選擇機構
進入這個畫面就成功了
2. IAM 權限管理
要從nodejs 上傳圖片,我們需要有一個service account(服務帳戶),他可以賦予程式特定幾組GCP的權限,並使用金鑰或其他方式認證。
點選下面的
IAM與管理
點選左邊的服務帳戶 => 建立服務帳戶
給這個帳號一個名字
給這個帳號 Cloud Storage往下的
Storage管理員
、Storage物件使用者
這兩個權限其中一個
Storage管理員
: 權限比較大,可以上傳檔案,還可以將檔案存取狀態設定成 Public
,如果之後要在儲存用的值區(Bucket)中選擇 精細調整存取權限的話,建議選此(後面會談到此部份)
Storage物件使用者
:權限不包含將檔案存取狀態設定成Public
,因此在值區(Bucket)中需要統一把整個Bucket的存取權限設定為公開,外部人員才看得到圖片。
接著按下完成(角色只要選其中一個就好)
3. 新建一個值區(Bucket)
接著要建立一個值區(Bucket),它和 Amazon S3很像,都是一個存放檔案的地方。
回到主畫面,點擊左下角
Cloud Storage
進入值區,點擊建立
取一個名字
選一個儲存位置,可以單選台灣,速度很快,如果有減碳需求可以選擇低二氧化碳標示的地區
預設級別有四種,依照多常存取而區分不同的收費,standard的最貴。如果不確定可以選擇 Autoclass,如果有資料不常被存取,可以允許物件切換到 Coldline 和 Archive
設定存取權,先取消禁止公開存取(因為我的檔案是要供外界讀取),並建議選擇精細,我們可以用 google 提供的nodejs-storage逐一修改檔案的公開狀態(需要搭配擁有
Storage管理員
角色的Service Account)
下面的資料保護可以自己選擇,然後按下建立
4. 生成Service Account 金鑰
我們需要Service Account的金鑰才能讓 Nodejs 與 Bucket溝通
回到主畫面=>點選
IAM與管理
點選剛剛建立的服務帳戶=>管理金鑰
點選新增金鑰(下面的金要是我的實驗結果)
點選
JSON
格式
建立之後會下載一個金鑰如:專案id-xxxxxxxxxxxx.json
,內容如下,接著會在nodejs連接Cloud Storage的時候使用。
{
"type": "service_account",
"project_id": "",
"private_key_id": "",
"private_key": "",
"client_email": "",
"client_id": "",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "",
"universe_domain": "googleapis.com"
}
後端Nodejs實作上傳功能
以下解說參考下兩個google官網
這裡使用直接將本地檔案上傳的方法,因此建議使用如 formidable、multer 等會先將前端傳過來的圖片先存在本地端 /tmp資料夾的套件。我在這邊是使用formidable,但其實你只要知道你想要上傳的圖片的路徑與檔案名稱就可以操作。
先安裝 nodejs-storage 套件
npm install @google-cloud/storage
1. google_setting.ts
建立一個
google_setting.ts
檔案
先列出完整程式碼,於後面解說。
// Ref: https://cloud.google.com/storage/docs/uploading-objects#storage-upload-object-client-libraries
import { Storage } from "@google-cloud/storage";
export const googleStorage = new Storage({
projectId: process.env.GOOGLE_PROJECT_ID,
credentials: JSON.parse(process.env.GOOGLE_SERVICE_KEY || '{}'),
});
export const googleBucket = googleStorage.bucket(process.env.GOOGLE_STORAGE_BUCKET_NAME || '');
export function uploadGoogleFile(filePath: string, destFileName: string, generationMatchPrecondition: number) {
const options = {
destination: destFileName,
// Optional:
// Set a generation-match precondition to avoid potential race conditions
// and data corruptions. The request to upload is aborted if the object's
// generation number does not match your precondition. For a destination
// object that does not yet exist, set the ifGenerationMatch precondition to 0
// If the destination object already exists in your bucket, set instead a
// generation-match precondition using its generation number.
preconditionOpts: {ifGenerationMatch: generationMatchPrecondition},
};
const url = `https://storage.googleapis.com/${process.env.GOOGLE_STORAGE_BUCKET_NAME ? process.env.GOOGLE_STORAGE_BUCKET_NAME + '/': ''}${destFileName}`;
return async function uploadFile() {
await googleBucket.upload(filePath, options);
await googleBucket.file(destFileName).makePublic()
return url;
}
}
首先先建立 storage object
import { Storage } from "@google-cloud/storage";
export const googleStorage = new Storage({
projectId: process.env.GOOGLE_PROJECT_ID,
credentials: JSON.parse(process.env.GOOGLE_SERVICE_KEY || '{}'),
});
projectId
可以在 主畫面中得到,放在 .env
中可以保護你的id(也可以直接寫在Code裡)
Storage
認證的方法有兩種
keyFilename
:直接讀取前面下載的金鑰json的存放位置來取得金鑰credential
:當程式中有金鑰json可以取用時,可直接傳入
使用 keyFilename
認證,需要先把金鑰json檔案放在專案內,然後把路徑傳給Storage
export const googleStorage = new Storage({
projectId: process.env.GOOGLE_PROJECT_ID,
keyFilename:('/path/to/your/keyfile.json')
});
使用credential
的方法,可以先將金鑰用 JSON.stringfy()
的方法轉成字串,並手動複製到.env
裡面,字串前後可加引號 GOOGLE_SERVICE="{JSON 文字串}"
(但是部署之後建議把部署環境內的文字串的引號刪除)
export const googleStorage = new Storage({
projectId: process.env.GOOGLE_PROJECT_ID,
credentials: JSON.parse(process.env.GOOGLE_SERVICE_KEY || '{}'),
});
建立 Bucket
export const googleBucket = googleStorage.bucket(process.env.GOOGLE_STORAGE_BUCKET_NAME || '');
用 Storage
向下建立值區(Bucket),並傳入你想上傳的值區(Bucket)的名稱,名稱可以從Cloud storage中找到,我將名稱存放在 .env
裏面
進入Cloud storage,點擊你的Bucket
複製Bucket 名稱,另外可以先建立資料夾(不一定需要建立)
產生上傳用function
這邊我使用閉包寫法,輸入argument如下:
filePath
:要上傳檔案的本地存放pathdestFileName
:要上傳的檔案位置與名稱,像是我想要上傳到km
這個資料夾,就寫成km/檔案名稱
如果直接存放在Bucket的根資料夾,檔案名稱
就可以了generationMatchPrecondition
:處理同時上傳檔案時會發生的race conditions,可以看這裡的說明,不一定要加,如果加了,第一次上傳時設為0就好
export function uploadGoogleFile(filePath: string, destFileName: string, generationMatchPrecondition: number) {
const options = {
destination: destFileName,
preconditionOpts: {ifGenerationMatch: generationMatchPrecondition},
};
const url = `https://storage.googleapis.com/${process.env.GOOGLE_STORAGE_BUCKET_NAME ? process.env.GOOGLE_STORAGE_BUCKET_NAME + '/': ''}${destFileName}`;
return async function uploadFile() {
await googleBucket.upload(filePath, options);
await googleBucket.file(destFileName).makePublic()
return url;
}
}
上傳完成之後,要從外界觀看上傳的檔案的話,Google提供固定的URL如下,因此可以寫死並回傳
const url = `https://storage.googleapis.com/${Bucket 名稱}/${檔案路徑+檔案名稱}`;
最後會產生一個我們真正會使用的function
upload
:把檔案上傳
makePublic
:將檔案設成公開,這裡需要Bucket的公開存取設定為精確才可以使用
return async function uploadFile() {
await googleBucket.upload(filePath, options);
await googleBucket.file(destFileName).makePublic()
return url;
}
2.
google_drive_upload.ts
新增
google_drive_upload.ts
接著實作上傳功能,這邊假定是使用 formidable
傳入一個 type為formidable.file
的 file檔(注意並不是node 原生的 File
type),但其實只要能夠取得想要上傳檔案的存放位置,就可以使用。
程式碼如下:
fileName
:我使用uuid
產生唯一值uuid名稱避免撞名,並使用mime-types
產生File
的副檔名,但這些不是必須,可以隨意給一個名稱storePath
:將fileName
與我想上傳的資料夾名稱結合,如果存放在根目錄,直接使用fileName
就好uploadGoogleFile
:產生一個可上傳的function, 傳入
- 本地端檔案位置
- 要存放到哪裡
- 0 (在可以確定不會有race conditions時使用)uploadToGoogle
:上傳圖片並回傳上傳後的URL,將這個URL傳給前端,再經過下方CORS的設定就可以正常顯示在畫面了
CORS 設定
如果剛剛成功將URL產出,你會發現前端網頁無法正常顯示,這是因為Google會限制可以存取他的資源的網站,我們可以用Cloud Shell設定 (注意:這裡的CORS並不是本地專案後端的問題,不需要在你的專案內修改CORS設定)
以下的設定是可以用Google Cloud 指令列介面 (gcloud CLI),但是因為指令比較少,我使用Google線上的Coding 環境 Cloud Shell執行
在主畫面搜尋 Cloud Shell
新增
cors.json
檔案,並輸入下方資訊(檔案位置可以隨意,只要cd進該資料夾就可以)
[
{
"origin": ["*"]
}
]
輸入以下指令
gsutil cors set ./cors.json gs://你的Bucket名稱
這樣就能正確展示檔案了
結語
不曉得大家有沒有成功呢?我目前前後端都用Next可以實做成功。也希望我們下次可以再相見!