星期六, 5月 16, 2026

[GAS] OTP 登錄

在了解 [GAS] CacheService 如何使用後,該筆記範例是 OTP 登錄範例,OTP (One Time Password) 相關操作都會在 CacheService 上進行
gs Code
/**
 * 處理 Web App 的 HTTP GET 請求,回傳使用者介面。
 * @param {Object} e - 事件物件 (GET 參數)。
 * @return {HtmlOutput} 渲染後的 HTML 畫面。
 */
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile('Index')
      .setTitle('OTP 登入')
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * 產生並發送 OTP 至指定 Email,同時將 OTP 寫入 CacheService。
 * @param {string} email - 使用者的電子郵件地址。
 * @return {Object} 包含執行結果的狀態物件 {success: boolean, message: string}。
 */
function generateAndSendOTP(email) {
  try {
    if (!email || !email.includes('@')) {
      return { success: false, message: '請提供有效的 Email 地址格式' };
    }

    // 產生 6 位數隨機 OTP
    const otp = Math.floor(100000 + Math.random() * 900000).toString();

    // 取得 Script 層級的 Cache,將 email 當作 Key,OTP 當作 Value
    const cache = CacheService.getScriptCache();
    // 設定快取保留時間為 300 秒 (5 分鐘)
    cache.put(email, otp, 300);

    // 寄送包含 OTP 的電子郵件
    const subject = '您的系統登入一次性密碼 (OTP)';
    const body = '您好,\n\n您的登入驗證碼為:' + otp + '\n此驗證碼將於 5 分鐘後失效。\n\n若非本人操作請忽略此信件。';
    GmailApp.sendEmail(email, subject, body);

    return { success: true, message: 'OTP 已發送至您的信箱,請於 5 分鐘內輸入。' };
  } catch (error) {
    return { success: false, message: '系統錯誤:' + error.message };
  }
}

/**
 * 驗證使用者輸入的 OTP 是否與 CacheService 中的記錄相符。
 * @param {string} email - 使用者的電子郵件地址。
 * @param {string} otp - 使用者輸入的 OTP。
 * @return {Object} 包含驗證結果的狀態物件 {success: boolean, message: string}。
 */
function verifyOTP(email, otp) {
  try {
    const cache = CacheService.getScriptCache();
    const cachedOtp = cache.get(email);

    if (!cachedOtp) {
      return { success: false, message: 'OTP 已過期或不存在,請重新獲取。' };
    }

    if (cachedOtp === otp) {
      // 驗證成功後,基於安全性應立即移除 Cache 中的 OTP
      cache.remove(email);
      return { success: true, message: '驗證成功!歡迎登入系統。' };
    } else {
      return { success: false, message: 'OTP 錯誤,請重新輸入。' };
    }
  } catch (error) {
    return { success: false, message: '驗證時發生系統錯誤:' + error.message };
  }
}
執行結果

收到 OTP email

使用 OTP 密碼登錄

Index.html 不是該筆記重點,就不放在文章上囉

星期二, 5月 12, 2026

[SQL] 轉型為 bit

無意中在自家系統內看見下述 TSQL 語法,整數 2 轉型 bit 竟然不會出現轉型錯誤
SELECT CAST(2 as bit)
在官方文件 - bit 內有這段說明
The bit data type can be used to store Boolean values. The string values TRUE and FALSE can be converted to bit values: TRUE is converted to 1, and FALSE is converted to 0.

Converting to bit promotes any nonzero value to 1.

The bit data type supports the COUNT function. However, other standard aggregate functions, like SUM, AVG, MIN, and MAX, don't directly support the bit data type.
基本上只要不是 0 就是轉型為 1 就是,測試範例如下
SELECT 
    source as 整數 ,
    CAST(source as bit) AS 整數轉型bit
FROM
    (
        SELECT 0 AS source UNION ALL
        SELECT 1 UNION ALL
        SELECT 2 UNION ALL 
        SELECT 100 UNION ALL
        SELECT -1 
    ) AS T

星期六, 5月 09, 2026

[GAS] clasp deploy

A release that makes a specific version of your script available for users. A deployment has a unique URL or ID.
GAS IDE 部屬有三種模式,分別為
  • 新增部屬作業:詳見 [GAS] 部屬網頁程式
  • 管理部屬作業:詳見 [GAS] 管理部屬作業
  • 測試部屬作業:未部屬狀態下可以執行 Code,會得到一個 dev 結尾網址,該位址不會變化,Code 有進行任何更改的話,可以在瀏覽器上按 F5 refresh 執行程式

而 claps deploy 有兩種語法應用
  • clasp deploy 對應 [新增部屬作業],部屬後每次都會取得一個新的網址
  • clasp deploy --deploymentId <deployment-id> 對應 [管理部屬作業],部屬後網址不變

星期二, 5月 05, 2026

[GAS] 專案紀錄

學習 clasp 時發現有 clasp version 語法可以使用,但是腦海裡對應不上是 GAS IDE 上哪一個功能

官方文件 - 部署作業與版本 說明
A static snapshot of your script project's code. Once created, a version is immutable. Think of a version as a "save point" in your development history.
仔細去查發現,原來在 GAS IDE 上繁體中文是 [專案紀錄],想說會不會是翻譯過來時差太多,特別切成英語版本來查看,還真的是 Project History

星期一, 5月 04, 2026

[GAS] Template 解析異常

請 AI 寫小範例驗證功能時,發生下述錯誤訊息
SyntaxError: Unexpected token ';' (第 18 行,檔案名稱:程式碼)
該範例就單純在 code.gs 內的參數傳遞至 Index.html 而已,因為錯誤訊息一直指向 code.gs 內,所以第一時間也沒有意識到錯誤是在 Index.html 內,都修錯方向

部屬執行後都出現下面錯誤訊息
錯誤竟然在 Index.html 內的註解,傻眼


正確應該是下圖才是,這樣才會正確解析 < 和 > 符號,但這是註解說,本來就沒有要顯示,真的打在有趣的點