Apps Script用Sheet生成動態網頁(5): 上傳檔案

前言

看到上一篇,Apps Script用Sheet生成動態網頁(4):上傳HTML檔案,很容易就會聯想到,既然可以上傳HTML檔案,那應該也可以上傳一般檔案吧? 用來上傳像是圖片、影片等等的檔案。

只是很不幸的,除了HTML和文字檔案,其他類型檔案用相同的上傳方法,再下載回來都會變成無法辨識的格式。而在上一年的時候,我也曾實測並彙整在Apps Script上傳檔案到Google Drive,關於常見的2種表單上傳檔案的方法,也是以失敗作收。

不過感謝tanaikech的文章,除了表單方法之外,我們還有方法可以突破平台限制。以下就針對這種上傳方法做說明。

承上,說明施作要點

  • 能夠突破限制的簡單方法就是使用BASE64編碼,這個編碼已經內建在瀏覽器功能中。
    這裡選擇使用FileReader來協助處理BASE64編碼。
  • Sheet的儲存格不支援儲存其他類型檔案,所以我們選擇將檔案上傳到Google Drive中。

接下來就馬上開始吧!

1. Apps Script的部份(後端/伺服端)

因為要使用到Google Drive,這裡是需要先去開一個資料夾,把資料夾的ID取出來,命名為FOLDER_ID變數

舉例:  如果Drive的資料夾的URL是https://drive.google.com/drive/folders/1YULb...K0kOu?usp=sharing

程式碼就寫
const FOLDER_ID = '1YULb...K0kOu';

剩下的code就可以直接貼上

function getTypeAndData(dataURL, name) {
const target = ';base64,';
let offset = dataURL.indexOf(target);
if (offset < 0) throw new Error(`not supported DataURL starts with:${dataURL.substring(0, 30)}`);
let mimeType = dataURL.substring(0, target);
let base64 = dataURL.substring(offset + target.length);
let data = Utilities.base64Decode(base64, Utilities.Charset.UTF_8);
return {
type: mimeType,
blob: Utilities.newBlob(data, mimeType, name),
}
}

function uploadEncodedFileToDrive(form) {
validateUploadPermission();
let name = form['the-name'];
let dataURL = form['the-data'];
if (!name || !dataURL || typeof dataURL !== 'string')
throw new Error(`名稱或檔案有問題:${name},${typeof dataURL}`);
let { type, blob } = getTypeAndData(dataURL, name);
let folder = DriveApp.getFolderById(FOLDER_ID);
let file = folder.createFile(blob);
return {
name: name,
type: type,
id: file.getId(),
url: file.getDownloadUrl(), // file.getUrl()
}
}

在之前的code裡面增加上面的uploadEncodedFileToDrive()和getTypeAndData(),

  • uploadEncodedFileToDrive 用來接收使用者端傳送的檔案資料,這裡只取用了表單的the-name和the-data,沒有像上傳HTML檔案時使用表單的the-file,是因為裡面沒有我們需要DataURL資料。
  • getTypeAndData 用來處理DataURL並轉換成Blob,一種儲存資料的結構,裡頭可以儲存資料本身、MimeType以及檔案名稱。
上傳成功後,會將檔案名稱、MimeType、Google Drive、檔案下載URL返回給使用者端

2. HTML部份(前端/使用者端)

前段部份基於前一篇上傳HTML檔案的網頁做小改。首先改一下onSubmit的部份,因為後端部份用另一個function了,所以要改成uploadEncodedFileToDrive

google.script.run
.withSuccessHandler(onCompleted)
.withFailureHandler(onFailure)
.uploadEncodedFileToDrive(event.target)

接著,我們調整HTML在選檔案清單的部份,多加一個不顯示的textarea元件,準備是將編碼資料放在這個欄位中一起被送出去。

然後在檔案選擇的部份加上onchange處理,當使用者選擇了檔案後,會將處理的dataURL放到不顯示的textarea元件中。

<label for="file_chooser">請選檔案</label>
<input id="file_chooser" type="file" name="the-file" onchange="loadFile(event)" />
<textarea id="file_data" name="the-data" style="display:none;"></textarea>

javascript部份對應上面onchange所需loadFile,使用FileReader的readAsDataURL()轉成BASE64編碼字串

function loadFile(event) {
var data = document.getElementById('file_data')
var file = event.target.files[0]
var reader = new FileReader()
data.value = ''
reader.onerror = function (error) {
onFailure(error)
}
reader.onload = function () {
data.value = this.result
}
reader.readAsDataURL(file)
}

3. 展示

4. 結語

這裡也要提醒一下,Google方面也很聰明的,在Apps Script免費額度上設定有執行最多6分鐘的限制,所以如果你要直接傳送大型檔案也會被這個限制擋住。不過聰明的你應該已經想到可能怎模做了吧!(把檔案分割傳上去阿)

5. 額外問題

提問: 為什麼上傳的檔案URL傳給其他人沒辦法下載?

因為Google Drive預設權限是只有自己可以看到檔案,要分享給其他人可以透過setSharing(),讓要知道URL就能查看檔案的設定,要添加的設定如下:

file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);


留言