分類: 筆記x備忘

  • 使用 Nginx 基本認證保護動態內容。以一個舊網站為例。

    使用 Nginx 基本認證保護動態內容。以一個舊網站為例。

    最近處理一個舊網站,因為是 demo 用途所以帳號密碼基本上都隨便設定。但是我不想給陌生人掃進來 try error ,所以想到在 Nginx 上上添加 auth_basic 的功能。

    環境如下:

    1. 這個舊網站非常舊,是 ThinkPHP 的 5.x.x 版本
    2. 裡面會 php 設定網址後綴 .html 所以網址會變成 https://xxx.xxx/PATH/ooooo.html 這樣

    先上注意事項:

    1. 我最後選擇保護 .php 檔案,如果有其他需要保護的,甚至靜態檔案請確保 location 的設定。
    2. 因為是 location 選擇 php 檔案,所以驗證通過以後要走一樣的操作順序。
      • 得自首一下,這部分我 try error 很久,是試出來的結論。書到用時方恨少,以後有機會記得補上有系統的原理原則。
    3. 建議使用 HTTPS 避免 header 的帳密被中間人攻擊。
    4. auth_basic 幾個基本知識網路上滿多的,參考這裡這裡這裡

    補上程式碼:

    /PATH_AUTH_BASIC/auth_basic.conf

    # Directory protection rules
    location ~* \.php$ {
        auth_basic "Authorization";
        auth_basic_user_file /PATH/Need_Password.pass;
        # 如果驗證通過則走 PHP 動作
        include PHP-73.conf;
        # 如果是靜態的 .html 文件,則直接嘗試提供
        rewrite  ^(.*)$  /index.php?s=$1  last;   break;
    }
    

    而操作順序, 在 server 的 block 裡面如下:

    ...
    #Directory protection rules
    include /PATH_AUTH_BASIC/auth_basic.conf;
        
    #PHP reference configuration
    include PHP-73.conf;
    
    #REWRITE-START URL rewrite rule reference, any modification will invalidate the rewrite rules set by the panel
    include /PATH_REWRITE/rewrite.conf;
    ...

    補上 ThinkPHP 的 rewrite.conf 主要的設定:

    ...
    location / {
    	if (!-e $request_filename){
    		rewrite  ^(.*)$  /index.php?s=$1  last;   break;
    	}
    	try_files $uri $uri/ =404;
    }
    ...
  • 用 ChatGPT 生成 Codeigniter4 Model Class

    用 ChatGPT 生成 Codeigniter4 Model Class

    走正向工程的話,規劃 DB 資料庫會遇到要轉成程式結構的問題,基本上沒有技術但是 Han 麻煩。這次嘗試用 ChatGPT 生成。

    使用 ChatGPT 4o 處理。

    目標:

    1. Mysql DDL File(.sql) 匯出 Create 指令,將其轉換為 CI4 的 Model Class 的程式碼。
    2. Model Class 繼承 BaseModel Class,BaseModel 是一個把一些基礎的操作和變數寫好的 Class ,繼承 CodeIgniter\Model 。
    3. 使用 ChatGPT 生成,儘量不要還得手動調整。

    附上提示詞連結:

    操作過程也紀錄一下:

    1. 一開始拿一個 table 的 create 指令生成,最終請它產生提示內容。
    2. 將 .sql 檔案匯出,確保都是 create 指令。
    3. 匯入 ChatGPT 並且使用提示提請它提供生成檔案 zip 連結。
    4. 下載確認與修正 loop:
      • 確保 MySQL 檔案中的備註有放到 PHP 裡面
      • 移除 fk 會填入欄位的問題
      • 移除 index 等 key 會重複填入欄位的問題
      • 移除驗證規則(這個 case 不需要)
      • 添加 Class 註釋,包含日期、作者、 MySQL 中於 table 的註解。

    匯出了好多檔案

    看這版本,有種提案給客戶的設計稿的感覺。屠龍者終成惡龍阿。

    補一個操作畫面:

  • 使用 Cloudflare Workers 處理前端跨域問題

    使用 Cloudflare Workers 處理前端跨域問題

    在 Web Application 走前後端分離的開發時,常會遇到 CORS 的問題,先科普什麼是 CORS,請參考:

    除了使用 JSONP 的處理方式以外,較快的解決方法有:

    1. 前端架設一個 proxy server 提供給前端呼叫使用
    2. 後端 server 端透過 nginx 或其他方法允許跨域的 request

    兩個方法都存在著,正式環境和測試環境在環境設定上必然有所不同,就差在前端改還是後端改了。

    而架設 proxy server 的方法也有兩個常見的作法:

    1. 本地環境架設一個 porxy server ,這邊提供一個 vue+ vite 的範例,請參考
    2. 架設一個在網路上的 proxy server

    原則上來說第一種方式比較保險的,但可能有種種技術因素導致無法實現,於是第二種方法,我們透過 Cloudfalre 來實現了。

    原始碼和別人寫的說明附在最下方,直接講注意事項:

    1. 你需要一個 Cloudflare 帳號,並且有存取 Workers and Pages 這個服務的權限。
    2. 使用 Worker 服務實作,他是公開的,且免費版有 100k 的次數限制,記得要設定白名單避免被濫用
    3. 要記得前端呼叫的網域要透過環境區分,把這個放上正式環境,呼叫次數可能會爆掉。

    更新一些要注意的事項:

    1. 白名單是 request header 中的 Origin ,是來源網址不是 proxy 導向的網址。這如果是後端架設的話得和前端溝通是哪個網址來源。
    2. 針對 Worker 裡面的 fix() 方法我做了一些修改,如果有錯誤還請提醒,程式碼如下:
    function fix(myHeaders) {
      // myHeaders.set("Access-Control-Allow-Origin", "*");
      myHeaders.set("Access-Control-Allow-Headers", event.request.headers.get("access-control-request-headers"));
      myHeaders.set("Access-Control-Allow-Origin", event.request.headers.get("Origin"));
      if (isOPTIONS) {
        myHeaders.set("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS");
    
        //myHeaders.set("Access-Control-Allow-Credentials", "true");
    
        // myHeaders.delete("X-Content-Type-Options");
      }
      return myHeaders;
    }

    我 fork 專案,更新了一個在 header 帶 auth 的版本,應該對防止濫用更有幫助。 網址如下:

    用法在下面兩個連結分享中有說明了,簡單來說就是一個 proxy for api

    這是 cloudflare-cors-anywhere 來源:

    這是關於 cloudflare-cors-anywhere 的操作教學:

    同場加映:

    這裡有修改 response header 的方法,不太推薦,很容易忘記。

  • Nginx 中同一個網域設定不同應用程式/目錄

    Nginx 中同一個網域設定不同應用程式/目錄

    技能點還沒有點太多的領域一點點成果就會獲得大大的成就感。

    最近弄了一個可以在同一個網域中實作多的應用程式的設定,這邊紀錄一下。先講結論:

    1. 可以實現同一個網域前後端分離,讓版本控制的操作也分開來。
    2. 同理多個不同應用如果需要互相呼叫時,也可以使用 sub-path 不要 sub-domain 來處理。
    3. 強烈建議不要用套裝的應用程式,關於網域使用的設計需要特別處理。
    4. 個人認為,如果是不同功能的應用場景,還是應該用 sub-domain 處理,這應該是 UX 相關的設計,和技術較為無關。
    5. 系統層的設定較為複雜,包含 Nginx 的設定,如果沒有 full controll 的權限可能實現上較為困難。

    前提列一下:

    1. 主網域是 a.com.tw
    2. 第二個網域是 b.com.tw 作為 nginx 的 proxy 使用,如果有需要也會在 dns 上設定。

    目標有幾個:

    1. 用戶訪問 a.com.tw 時候,會跳轉至 /root-document/a.com.tw/ 路徑上的 web-app
    2. 用戶訪問 a.com.tw/admin 時候,會跳轉至 /root-document/b.com.tw/ 路徑上的 web-app
    3. 用戶訪問 a.com.tw/uploads 時候,會跳轉至 /root-document/b.com.tw/uploads 路徑上的靜態檔案

    實作步驟:

    設定 /etc/hosts 檔案,新增:

    127.0.0.1 b.com.tw

    如果有需要,可以在 DNS 上設定 CNAME 把 b.com.tw 設定為 a.com.tw 別名。

    設定 a.com.tw 的網站,Nginx 中添加:

    # 將 /admin/ 和 /admin/* 路徑指向後端應用程式
    location ^~ /admin/ {
      proxy_pass http://b.com.tw;
    }
    # 將 /uploads/ 和 /uploads/* 路徑的靜態檔案指向其他資料夾
    location ^~ /uploads/ {
      alias /root-document/b.com.tw/uploads/;
      try_files $uri $uri/ /index.html;
    }
    
    
    # Option 其他網站的配置,例如前端 
    location ^~ / {
      root /root-document/a.com.tw;
      ...
    }

    設定 b.com.tw 的網站,Nginx 按照一般的設定即可,不過在動態程式中需要特別注意幾項:

    1. b.com.tw 的顯示網域是主網域 a.com.tw,在畫面上如果顯示非相對域名,要注意顯示的是 a.com.tw 而不是 b.com.tw
    2. 尤其後端上傳行為與 form action 會需要特別注意。
    3. http://b.com.tw 作為後端服務 proxy_pass,要注意 ssl 設定,包含顯示網域的 ssl 是否寫死?或是 proxy_set_header 判斷處理。
    4. uploads 的路徑在範例中也是導向 /root-document/b.com.tw/ 裡面的路徑,如果單純只是靜態檔案也可以自行設定。建議事先規劃整理好。
    5. 需要請注意根目錄設定 .user.ini 檔案中的設定,建議把不同路徑都補進去。

    還有一些小細節:

    1. Nginx 中 alias 的路徑需要是 / 結尾。 alias 和 root 不同的地方,參考這裡
    2. Nginx 路徑匹配優先權問題,請參考這裡

    關於 Nginx 設定的注意事項,附上問 ChatGPT 生成回覆:

    配置意圖

    1. /admin//admin/* 路徑指向後端應用程式
      • location ^~ /admin/ { ... } 這個配置塊的意圖是將所有匹配 /admin/ 開頭的路徑(包括 /admin//admin/*)的請求,轉發到後端應用程式,其地址為 http://b.com.tw
      • proxy_pass http://b.com.tw; 這個指令表示將符合條件的請求轉發給 http://b.com.tw 這個後端服務處理。
    2. /uploads//uploads/* 路徑的靜態檔案指向其他資料夾
      • location ^~ /uploads/ { ... } 這個配置塊的意圖是處理匹配 /uploads/ 開頭的路徑(包括 /uploads//uploads/*)的請求,並將這些請求指向伺服器上的一個具體的資料夾來提供靜態檔案。
      • alias /root-document/b.com.tw/uploads/; 這個指令表示將 /uploads/ 路徑的請求映射到伺服器上 /root-document/b.com.tw/uploads/ 目錄中的對應檔案。例如,請求 /uploads/image.jpg 會映射到 /root-document/b.com.tw/uploads/image.jpg
      • try_files $uri $uri/ /index.html; 這個指令會嘗試按順序查找以下檔案:
        • $uri:請求的原始檔案或目錄(例如 /root-document/b.com.tw/uploads/image.jpg)。
        • $uri/:如果是目錄,則查找目錄下的檔案(例如 /root-document/b.com.tw/uploads/image.jpg/)。
        • 如果以上都未找到,則回退到 /index.html 這個檔案。

    注意事項

    1. ^~ 前綴
      • ^~ 表示如果路徑匹配 /admin//uploads/,那麼該配置塊將優先於其他正則表達式匹配(例如 location ~location /)來處理請求。這確保了 /admin//uploads/ 的請求被這些特定的配置塊處理。
    2. Proxy Pass 的上下文
      • 當使用 proxy_pass 指令時,Nginx 將請求轉發給後端服務(如 http://b.com.tw)。確保 b.com.tw 這個服務能夠正確處理接收到的 /admin/ 路徑,並且能夠返回正確的響應。
    3. Alias 的使用
      • alias 指令用於將 URL 路徑映射到文件系統上的實際路徑,注意它不同於 root 指令。使用 alias 時需要確保映射的目錄結構和權限設置正確,以便 Nginx 能夠讀取和提供靜態檔案。
    4. 靜態檔案的優先級
      • /uploads/ 路徑的靜態檔案配置中,try_files 的指令順序很重要。你需要確保 /index.html 是你希望用來處理靜態資源請求失敗時的回退頁面。
    5. 安全性考量
      • 在提供靜態資源時,應確保 /root-document/b.com.tw/uploads/ 資料夾內的檔案不包含敏感信息,且檔案權限設置得當,防止未經授權的訪問。

    這段配置主要是為了處理特定路徑的請求,將動態內容轉發給後端應用程式,並通過靜態資源提供功能來處理文件的提供。

  • Mac 使用 Nodejs 的管理工具 n 來切換版本

    Mac 使用 Nodejs 的管理工具 n 來切換版本

    Nodejs 在前端應用越來越廣泛,有時會遇到不同專案或是交接外包的 nodejs 版本不同,於是需要切換,以下筆記一下切換的方法。

    在 windows OS 上使用 nvm 做版本切換工具,參考。在 mac 上有 n 這個工具來處理。先資料來源,還有參考文章:

    安裝建議使用 brew

    brew install n

    基本操作

    sudo n latest // 升級至最新版本
    
    sudo n stable // 升級至穩定版本
    
    sudo n xx.xx // 升級至指定的版本號

    如果想要有互動介面版本管理,直接輸入 n 即可跳出介面。

  • 使用 Nginx 設定通過 Cloudflare 的真實 IP 與只允許 Cloudflare IP 來的流量。

    使用 Nginx 設定通過 Cloudflare 的真實 IP 與只允許 Cloudflare IP 來的流量。

    一次講兩件事,都是和 Cloudflare 的 IP 有關。

    首先是在Ngnix 中,設定只允許 Cloudflare IP 來的流量。幾點注意:

    1. 需確認 Nginx 設定的網域有沒有通過 Cloudflare 的 Proxy,也就是看 DNS 設定中,橘色的雲有沒有亮起。
    2. 一樣的設定可以套用在其他有 Proxy/VPN/CDN 等操作上。組合拳是:流量走我的,其他檔下來。
    3. 同樣的組合拳也可以設定在單一允許 ip 的 api service 上面,不過請注意邏輯層與系統層的拆分,別被自己的 code 搞死了。
    4. Cloudflare 的 ips 是會更新的,請參考這裡
    5. 如果你有用 crontab 執行 url 的話記得也要把 allow 補進去。例如: allow 127.0.0.1; 或是改成 cli 的操作(推薦)。
    allow 173.245.48.0/20;
    allow 103.21.244.0/22;
    allow 103.22.200.0/22;
    allow 103.31.4.0/22;
    allow 141.101.64.0/18;
    allow 108.162.192.0/18;
    allow 190.93.240.0/20;
    allow 188.114.96.0/20;
    allow 197.234.240.0/22;
    allow 198.41.128.0/17;
    allow 162.158.0.0/15;
    allow 104.16.0.0/13;
    allow 104.24.0.0/14;
    allow 172.64.0.0/13;
    allow 131.0.72.0/22;
    
    allow 2400:cb00::/32;
    allow 2606:4700::/32;
    allow 2803:f800::/32;
    allow 2405:b500::/32;
    allow 2405:8100::/32;
    allow 2a06:98c0::/29;
    allow 2c0f:f248::/32;
    
    deny all; # deny all remaining ips

    再來是設定讓網站在通過 Cloudflare 之後,可以獲取到真實 ip 的設定,這部分我是抄網路的,有些覺得怪的地方,如果有理解錯誤還煩請指正。

    1. set_real_ip_from 是指信任的 proxy ip 所以要設定為 cloudflare 的 ip 段,標示這些來源的 ip 才能正確取得真實的 client ip。
    2. real_ip_header 是修改真實 ip 的 header 字段,默認是 X-Real-IP。改為 CF-Connecting-IP 是 cloudflare 在邊緣到您的來源 Web 伺服器的流量上傳送的標頭。請參考 Cloudflare 文件
    3. 上面提到的流量來源限制和 set_real_ip_from 的設置會衝突,不能一起設置 🫠
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 131.0.72.0/22;
    
    set_real_ip_from 2400:cb00::/32;
    set_real_ip_from 2606:4700::/32;
    set_real_ip_from 2803:f800::/32;
    set_real_ip_from 2405:b500::/32;
    set_real_ip_from 2405:8100::/32;
    set_real_ip_from 2a06:98c0::/29;
    set_real_ip_from 2c0f:f248::/32;
    
    #use any of the following two
    
    real_ip_header CF-Connecting-IP;
    #real_ip_header X-Forwarded-For; # 滿多人用這個,不過 cf 有自己比較安全的標頭。

    同場加映 Cloudflare 的 ips 清單,更新頻率不高,不過設定上幾個注意:

    1. 相關 ips 的設定儘量統一管理。
    2. 有提供 api 可呼叫,參考這裡,如果要做自動化的人可以來弄弄。
  • WordPress 不用外掛置換網域

    WordPress 不用外掛置換網域

    雖然不是第一次做這件事了,不過還真的不常做。今天有朋友網域需要更換,剛好又是放我這管理,因為滿多人用外掛處理,但這個網站基於種種原因不太好用外掛,於是想乾脆改資料庫好了。 紀錄一下比較少處理的作法,某種程度上 wp 的外掛太方便惹,只要品管做的好,現在已經越來越少動程式。

    * 友情提醒:該操作時是離峰時間,所以沒有在顧忌考慮什麼可能會壞掉的。

    首先要先備份,主要是備份資料庫。如果資料過大的話要注意一下,我建議幾個方向:

    1. 優先把 sql 檔案備份下載。這次是使用這個方式處理。
    2. 如果檔案太大,複製一個新的資料庫。修改的話就只能用命令或網頁 phpMyAdmin / adminer 來處理。

    接下來是幾個系統環境設定的部分:

    1. 確認網域 DNS 是否正確:設定 A 紀錄到指定伺服器 ip
    2. 確認 HTTP Service 設定是否正確,可以新舊網域指向同一個 root path 沒問題。
    3. 確認新網域的 SSL 是否正常設置。建議如果使用 cloudflare 的話要先確認好 SSL 的配置是否和舊網域相同,如果使用「彈性」的選項滿容易遇到問題的。

    這時用新網域打開 wordpress,沒意外瀏覽器應該會跳轉至舊網域,正常展示。

    再來修改線上的資料庫 wp_options.siteurl 和 wp_options.home 欄位1,先改成新的網域,確認 SSL 是否正常/網頁正常展示。 如果正常的話開啟瀏覽器開發人員模式會看到網頁正常開啟,不過圖片等網址都還是指向舊的網域2

    再來要來改資料庫了,因為我這次是把資料庫備份下來的,所以記得複製一份以後來編輯,以下用 old-domain.com 和 new-domain.com 來區分新舊網域。

    首先要先注意 www 的子網域,以及是否有可恨的外掛/布景主題使用 PHP 序列化(參考)來做儲存的行為。如果遇到是序列化的欄位,請特別注意字元的數量。如果有要替換 www 的話,搜尋取代行為先替換比較好。

    1. 記得備份,開啟我們要編輯的 sql 檔案。
    2. 因為現在要改 SSL ,搜尋 http://(www)old-domain.com 替換成 https://(www)new-domain.com
    3. 搜尋 https://(www)old-domain.com 替換成 https://(www)new-domain.com
    4. 可能會有 email 或是單純網域的設定。搜尋 (www)old-domain.com 替換成 (www)new-domain.com

    再次提醒如果有遇到類似長這種的,不是 JSON ,要注意序列化的操作,可以使用線上的序列化(參考)和反序列化(參考)工具。這次沒遇到,就不管啦。3

    資料庫更新好以後覆蓋回去即可。如果會擔心的話,建議創一個新的資料庫匯入,從 wp-config.php 中修改資料庫路徑/名稱和帳號密碼。

    打開來檢查一下確認網域有沒有舊網域以及有沒有什麼錯誤。

    註解

    1. 呵呵看欄位名稱就知道網站多舊了。 ↩︎
    2. 這可能也是因為舊網站的關係。wp 很多設定可能有 CDN 或擴展的需求,資料庫紀錄絕對路徑,移站時,除了檔案路徑會有問題以外,常常網域的路徑也會需要注意。 ↩︎
    3. 我是覺得外掛/布景主題設計上,把網域包在序列化的開發者真的要判死刑 XD ↩︎

    同場加映, 如果是很舊的網站,遇到 css 和 js 沒有設定 https 而導致錯誤,這邊提供一個解法。寫在 function.php 裡面。

  • 全 12 活動紀錄

    全 12 活動紀錄

    全 12 是「中華民國童軍第12次全國大露營」簡稱。

    藉由中華民國童軍第12次全國大露營(以下簡稱大露營)的辦理,呈現我國新世代童軍的創意與活力,透過大露營的宣傳和活動,鼓勵全國青少年能親近大自然,讓童軍活動受到全國青少年的喜愛,提供國內外童軍交流互動平臺,以促進我國童軍和世界各國童軍的互動交流,強化童軍實作技能,提升童軍合作學習,以發揮童軍運動精神。

    在白天有提供給童軍伙伴跑站的活動,其中我的任務是在其中做一份簽到簽退系統。讓工作坊可以提供給學員打卡,除了回報給學員的分團支外,也提供紀錄、展示等用途(笑)。

    真的做了好久阿,去年年末到現在。這算是一份發願,看能不能給自己一個交代,於是最後產出了這份專案。

    內容包含:原始碼、技術結構、優化清單、部署介紹。

    無論如何也算是得償所願吧,接下來看有誰要接下去做了。

    其中有許多需要優化修改的地方,參考這裡

    可怕的是在活動前開始感冒,營隊幾天就咳嗽幾天,至今鼻涕和喉嚨還不太安分。而待在家參與活動這幾天,貢獻算是遠端提供協助並定時提供紀錄報告。於是趁這段時間寫了一份紀錄。

    — 寫於 2024-07-13 至 2024-07-16 , 板橋—

    Hi, this is Jerry.

    這個系統做得有點坎坷,需求和規劃並非一帆風順,而童軍運動也不是有給付薪水的工作(笑),針對吃程式設計這行飯的前後端人手似乎不太好找。這次的實作算是花費了 2024 上半年的很多時間,從一開始需求不清晰到開始找大流量的結構、研究比較服務、前端到後端實作在到配套規劃,中間也是吵吵鬧鬧的,所幸算是還拿的出來一個堪用的東西。未來如果童軍伙伴有需要使用,可以參考部署流程架設,或是想要自己實作的話,也可以抽換裡面的結構來做。 

    就希望這個作品當作一個拋磚引玉的作用吧。希望玩童軍的程式設計師都能有錢、有餘裕再回頭貢獻。

    規劃階段

    這系統原本是從 2023 年底以票務股開始,後來票務股給其他伙伴負責,轉變以數位系統實作為主。原本資訊混亂不清的時候,有滿多各種考慮和作法。

    這是開會筆記,滿多廢話的:https://docs.google.com/document/d/1htCP7UVHd2ug3Fjpj6QJhn2q84CZnRYljaCYXn-KxWo/edit?usp=sharing

    後來隨著資料明確,約 2024 年年初開始明確系統的用例,和大致上的需求規格,當時規劃的規格式:

    1. 參與工作坊的學員+其他人人數需滿足 8000 人規格
    2. 工作坊約 100 個以上。
    3. 4 天活動、一天活動 4 場。
    4. 場地(走馬賴)網路環境正常
    5. 配套規劃:網路壞掉、下雨、設備壞掉、操作有問題
    6. 工作坊有些不熟悉 3C 產品的伙伴,儘量減少操作錯誤的機會。

    於是開始規劃數位系統的結構和用法,因為預算有限,所以針對高併發的需求採用 cloudflare 的服務,後台系統使用現有的 production server 架設 LEMP 結構的前後端分離網站。製作有幾個目標:

    1. 降低未來前端、後端與第三方服務的耦合,使用 Restful API 溝通。
      1. 未來其他伙伴開發可以使用自己熟悉的語言替換。
      2. 服務有更好的替代方案也可以儘量以最少的修改替換。
    2. 不會與全 12 的活動過度綁定,更換為其他活動可以儘量最少的修改。
    3. 能夠防呆的 UX 設計就儘量做上。

    最後決定最佳解是:

    1. Cloudflare 付費方案(每月 5 美元),於六月底開始,預計七月中結束停用。
    2. Production Server 因原本內有現有服務運行,所以先調整規格,活動結束後再確認是否需調降。
    3. 現場運作後台的伙伴之外,配套規劃中安排

    最後除了程式之外,也產出一份包含配套方案的單項計畫書:

    該單項包含了針對活動前幾天可能天氣炎熱、下雨等配套再另外調整,例如考慮到紙本潮濕等規劃用手機拍攝照片,再另行處理等動作。

    題外話,有一件令人費解的事情: Cloudflare Worker Service 提供免費的即時監控功能,在活動前以及撰寫本文的當下(2024-07-13) ,除了伺服器發出的同步流量支外,會查到來自非台南地區的流量(高雄、台中、台北)。

    2024-07-13

    全 12 運作第三天,今天休息,結束前兩天活動。

    有幾個狀況:

    1. 後台系統在現場只有宗翰有權限,不過宗翰太忙了,後續請沈俊達沈校長協助操作後台。
    2. 第一天發現一個 bug:工作坊登入以後,簽到一次離開再回來,場次的選項會消失。
      1. 發現是尚未測試出來的 bug ,已修復。
      2. 這 bug 在開發時,和後續大家一起測試時都沒發現,會需要檢討以後開發與測試的流程。
    3. 工作坊工作人員訓練量不夠,有人並非工作人員,或是工作人員並非參與前期測試和教育訓練。導致訓練不足,第一天時操作狀況頻發。
    4. 「簽出」這個詞彙不是正確的,應該要是「簽退」;「營本部」這個詞彙應該要改成「組本部」。這也是測試的時候沒有發現的狀況,「簽出」有被工作人員誤會成「登出」的動作。
    5. 工作坊時間安排,學員進場時間不一定,導致如果只有 1 人的工作坊,簽到和活動會兼顧不來。
      1. 按原規劃:如果找不到人支援,前後放時作為緩衝在一起開始會比較好。
      2. 原則上有遇到簽到問題,可以先用紙本簽到,有空時再操作。
      3. 沈校長也提議:先把學員護照收起來,中間空檔在再掃描送出。結束時歸還護照。
    6. 老闆面板(展示簽到人數的網頁)有一度統計數字壞掉。
      1. 該網頁是活動組長委外請人協助,後續溝通完成修復。 按:此為因為添加了測試用工作坊的關係,測試工作坊不顯示於系統導致壞掉。
    7. 參觀旅行有一位工作坊工作人員無法掃描學員編號的 QR Code,但透過提供現場 QR Code 的照片來測試掃描正常。可能是手機問題,當下提供幾個作法:
      1. 換台手機掃描。
      2. 先紙本簽到,事後有空時輸入編號。
      3. 手動輸入編號。
    8. 會有需要定時匯出簽到簽退資料的需求。

    另外也有一些和規劃不同的改動:

    1. 榮譽卡和簽退的行為重疊,後續取消簽退動作。
      1. 影響到系統部分判斷的流程。但不難處理,統計上已調整不特別判斷指定單一條件。
    2. 改為榮譽卡作為領榮譽章標準,不從系統上撈資料。
    3. 有部分工作坊不願意配合簽到簽退,第一天晚上即改為不強制操作。
      1. 可能會影響到即時追蹤(包括老闆面板)的需求。
      2. 未來如果依然有統計需要,必須要透過規範讓工作坊確實執行。

    於是經過兩天活動,系統作了一些修改:

    1. 修復工作坊網頁場次的 bug。
    2. 修復工作坊紀錄可能會出現空的學員編號問題。
    3. 修復後台操作時發現的 bug。
    4. 修改老闆面板、學員場次統計的簽退條件,改為有紀錄(無論只有簽到還是簽退)就納入紀錄。
    5. 修改擷取頁面。
      1. 場次簽到簽退會有重複、簽錯的行為,必須要開啟指定四個場次時間的頁面。
      2. 改為不指定日期,依照當前日期判斷。
      3. 活動結束關閉,早上再開啟。
      4. 這操作必須要優化才行。

    大部分都是第一天活動出現的狀況,第二天活動運作較為正常。除掃描問題以外大多為偵測到簽錯場次的行為。簽錯場次問題可考慮以下修復步驟:

    1. 查找資料庫,找到錯誤的紀錄場次與工作坊編號。並且查找錯誤的紀錄應正常屬於哪些場次。
    2. 於資料庫中查找正確的場次,確認該工作坊是否有補輸入學員編號。
    3. 登入 Cloudflare 後台 KV 頁面,確認該筆 KV 欄位( key 值:signMWS_{{工作坊}}_{{場次}} ),點開 json 檔案確認要移除的資料。
      1. 只需移除不需添加,添加資料於後台動作即可。
    4. 如果工作坊於正確場次沒有輸入學員編號。
      1. 於資料庫中把錯誤場次的紀錄修改為正確場次即可。
      2. 後台操作中補輸入正確場次的學員資料。 並確認資料庫刪除錯誤場次的資料。
    5. 如果工作坊已經有補簽到(部分的)學員編號。
      1. 於資料庫中刪除錯誤的資料,修改沒有補簽的學員場次。
      2. 後台操作中補輸入正確場次的學員資料。 並確認資料庫刪除錯誤場次的資料。

    2024-07-14

    活動倒數第二天。明天就先收工。

    目前看起來結構算是穩定,吧?今天理解沒錯的話,應該工作人員操作也會上手了,先撇除掉操作錯誤的選項,針對數字有問題的內容做確認調整。

    滿有趣的是因為第一天晚上先取消了不強制簽退的動作,所以後面的工作坊單一場次的紀錄,有些會只有簽到,有些會只有簽退,也有些會想要兩個都做但是有一次簽錯場次。 這對統計並沒有很大的影響,但是對老闆面板的展示數量的 API 倒是有差,主要糾結在時間跨度很大的工作坊(參觀旅行)在展示數字上即便往後的場次也要照常顯示,而一般單純沒有該場次的工作坊,如果沒有簽出(或是取消簽出判斷),就很容易被當作是有跨度需求的工作坊,而顯示在其中。

    這個規劃可以看成是一個規則怪譚的遊戲,一個人玩真的不太好玩。嘗試把他列出來,邀請有興趣的伙伴可以試試看如果先規劃藍圖,能夠畫到多細,得以滿足所有規則和規格。

    場景(需求規格):

    1. 工作坊: 100-140 個。
    2. 場次數量:切分成 4 個場次,上午 2 場,中午休息,下午 2 場。
    3. 使用人數: 8000 人。
    4. 活動 5 天,中間休息 1 天。
    5. 目的:統計參與人數、展示正在活動(簽到)人數。
    6. 必要功能:
      1. 簽到、簽退。
      2. 後台匯出統計完成一定場次以上數量的學員。
      3. 需要有一個展示頁面展示當前於各個工作坊的活動中的學員人數。

    功能:

    1. 工作坊
      1. 簽到與簽退:輸入工作坊編號、場次編號、學員編號送出。
        1. 紀錄以下欄位:簽到/簽退、當前時間、工作坊編號、場次編號、學員編號。
      2. 讓工作坊自行檢查是否輸入正確。
    2. 後台
      1. 簽到簽出資料查詢。
        1. 查得到對應學員、工作坊資料。
      2. 指定條件的簽出簽到資料匯出。
        1. 必要搜尋欄位:場次編號、工作坊編號、學員編號。
      3. 編輯簽到簽出資料
        1. 必要可編輯的欄位:場次編號、工作坊編號、學員編號。
      4. 指定條件的學員匯出。
        1. 滿足滿 N 個活動完成的學員,做為獎章頒發依據之一。

    規則:

    1. 未必所有工作坊一天都是 4 個場次。以下列舉狀況:
      1. 30 個工作坊他們是整天的活動,所以只會有一個場次簽到。
      2. 有工作坊場次不會連續。
      3. 工作坊會有臨時關閉與臨時開啟的狀況。
    2. 簽到簽出速度 / 流程儘量快。
    3. 因腹地廣闊,學員移動速度有限,可能會陸陸續續到工作坊,差距最長約 30 分鐘。
    4. 需要解決/修復資料與確認配套的狀況:
      1. 發現某個工作坊應該有資料卻沒有資料。
      2. 發現某個工作坊數量異常多或異常少。
      3. 不熟悉操作(通常發生在第一天)導致速度太慢影響活動時長。
      4. 場次可能會輸入錯誤。(填錯場次)
      5. 學員可能會輸入錯誤。(學員跑錯場)
      6. 設備、載具有狀況等無法正常使用系統。
    5. 現場網絡環境與流量頻寬的承載量未知是否能承載學員上網。
    6. 活動的各項決議可能導致確認完整規格離第一次測試時間約 1-2 個月。
    7. 工作坊現場活動的人數需有一個展示頁面。
      1. 必要欄位:工作坊、工作坊活動人數、當前日期、天氣。

    有興趣可以可以規劃看看。

    2024-07-15

    活動倒數最後一天(對我來說)。營期幾天就感冒了幾天,結束我要去吃火鍋。

    這樣的系統目標是放在商業運作,我還是那個理念,無論怎麼想,有獲利的營運有機會是進步的動力。然而這套系統離商業運作還有一段路要走。觀察下來應該要克服幾點:

    1. 專業團隊組成

    我一直認為軟體設計一個人製作如果不是用時間和財力去堆積的話,成果有限。我認為技術團隊可以透過現有的服務或是外包來搭配,像是購買版型套版來減輕或是取代美術需要製作的工作時數、快取服務架設成本太高則找適合的雲端服務來頂著。但是其實這取代不了團隊最核心的優勢就是從不同角度的討論和溝通,以及互相 cover,我會認為如果團隊組建得宜,同樣的開發時間和同樣的需求,應該會做得更加細緻而且體驗更好。

    1. 使用者溝通訓練 vs. UX 規劃

    這應該不能算是一種糾結,簡單講是因為自己不是專業於 UX 設計的人,導致這塊表現平平甚至不佳,所以某種程度上只能寄情於希望給使用者的訓練能夠完善,這滿好笑的。總歸是能力不足,解決方式還是看能不能團隊中有企劃能夠針對活動內容做設計調整吧。

    1. 儲存結構調整

    這塊應該多花一些時間規劃的,像是把 KV 換成 D1,或是 KV 設計的結構上有成本考量、開發考量以及功能考量,但真的運用在活動時,覺得可能換成 D1 也許是用錢來換命的好方法。然而也可以考慮自架伺服器的方式,最大的難點應該是如何實作快取服務了,我想快取的資料還是用拉取的方式會好一點,無法主動掌控同步時機有點兒危險。

    1. 更多的應用

    我認為在推廣新的概念新事物上,賣方市場創造應用是很重要的。如果連應用都沒有,挖掘需求的目的就是從一開始就不存在的。可以用到的創意太多了,像是鼓勵工作坊的競爭、鼓勵學員回饋、提供給營報/媒體新聞素材等等,接不只直接的創造了統計分析的需求,甚至會需要因應適合的 KPI 設計,添加了一些新的欄位。而且以數據科學的角度,應該要從這些數據發現:「活動該怎麼改善」這是一個重中之重。

    其實很需要人阿。

    另外機會難得,我得把 Cloudflare Worker 的統計資料抓出來。 

    * 不同顏色表示不同版本,營期前半個月之後原則以修復 bug 為主不新增功能,所以看到顏色變化,即是當下有發現問題緊急修復。
    * 以小時為單位採樣。

    另外我得記得收尾的工作:

    1. 整理原始碼,打包開源(記得刪掉沒用的 code)
    2. 把 API 文件打出來,提供開發規格。
    3. 寫心得。
    4. 把 Cloudflare 付費停用。
    5. 把擷取頁面的 windows 主機停用。
    6. 調降 Production Server 規格。

    希望我感冒趕快好。

    這篇獻給李宗翰,他真的很辛苦,而且開會時一直被我嗆又要張羅一堆事情。
    也感謝沈俊達沈校長營期時大力幫忙,還有許多配合的工作坊伙伴和其他伙伴。


    希望軟體圈子文人相輕的文化這時可以發揮功用,看不下去的就出來做吧。

    2024-07-26 update

    整理了一個新解,紀錄一下:

    流程上修改

    1. 工作坊場次之間預留間格,約是最長路程的 3/2 的時間作為「路程時間」。例如這次活動路程最長約半小時,抓 20 分鐘作為場次與場次之間的緩衝。
    2. 訓練工作坊「必須最早簽到時間」為場次開始前 1/2 的路程時間,不得提早。
    3. 如無必要,取消簽退動作。

    系統上修改

    1. 工作坊介面移除場次選項,改為自動判斷。抓取時間作判斷。
    2. 工作坊介面移除確認動作,解開單一裝置登入行為(因為不會有循序的問題),即掃即輸入,加快掃描速度。
    3. 除了第一場以外,其他時間為場次開始前後 1/2 路程時間判斷。
    4. 如果該工作坊沒有上一場的場次,則計算為下一場。
    5. 如果超過 10 分鐘沒有連續打卡會跳場次提示的警告。
    6. 快取服務如果依然選用 Cloudflare 處理, key 值改為「前綴_場次_工作坊編號_學員編號」,顆粒切更細。
      • 需要修改工作坊的清單介面,採用 list 方式取值。
      • 注意使用上因為大量採用 list 取值,費用可能會增加,需精算。
    7. 如果有預算,考慮 Cloudflare 使用 D1 服務作為快取+後台的永久儲存機制,可以實作關連式資料庫,簡化快取服務實作。
      • 注意 D1 服務是付費服務,需確認預算與限制。
    8. 如果採用自架的伺服器,請務必考慮到工作坊介面 requests 的峰值數量,或是有需要能夠透過流程或系統實作分流的動作。

    判定上/同步處理

    1. 場次填錯的話,如果只是需要判定場次數量則不用特別去修復(頂多會填成上一個場次)
    2. 同步簽到紀錄行為上,添加一個只能由後台/開發人員動作的「該筆記錄作廢」的 flag 欄位,作為手動處理同步資料作廢用途。
      • 如果作廢的話,資料則不顯示在後台、API 上。
    3. 如果需要在意場次:同步簽到上著手,判斷學員編號有同一個場次、不同工作方的簽到時依照簽到時間自動修正
      • 修正使用的邏輯是「作廢舊的且錯誤的紀錄、添加新的正確紀錄」,避免同步錯誤。
    4. 同步行為如果依然選用 Cloudflare 實作快取服務,採用 list 操作,抓取「前綴_場次_工作坊編號」動作為佳(考慮 list 長度以及抓取時間的平衡);如果自架服務,則需考慮同步抓取的資料量大小與完整同步一次的時間做平衡。

    以上。

    最後放一些做封面圖時生成的圖片,滿好看的,有需要請自行取用。

  • 解決 Sublime text 4 上 Package Control 無法使用的問題

    解決 Sublime text 4 上 Package Control 無法使用的問題

    自從開始使用 Mac 時,Sublime text 就是自己愛用的文字編輯器,基本上 PHP 的網站製作從 Sublime text 2 用到了 4,前年發現更新 mac 之後突然無法使用 package contol 的功能了。最近才有辦法解決,筆記一下,附上解決邏輯:

    首先先更新一下,看是否有需要更新。

    備份設定檔案、外掛檔案:
    開啟 Top Bar 上的「Sublime Text」->「Settings」-> 「Browse Packages…」,會開啟 finder 得到一個路徑,返回上一頁把 Sublime Text 資料夾的東西先備份起來,可能會用到 Installed Packages 和 Packages 這兩個資料夾,放的是外掛檔案設定。

    查看 sublime text 上的 log:
    重新開啟,點開 Top Bar 上的 「View」,選擇「Show Console」,會顯示 runtime 的執行訊息。

    至此我發現錯誤訊息:

    AttributeError: dlsym(0x7f876fc44440, EVP_PKEY_size): symbol not found

    查資料找到這篇文章,看起來是因為更新 Mac 產生 OpenSSL 的問題:

    附上文章內提供的解決方法翻譯:

    簡單來說,在 console 中執行這段指令(使用 urllib 透過 sublime 自帶的 ssl )重新安裝 Package Control 即可:

    from urllib.request import urlretrieve;urlretrieve(url="https://github.com/wbond/package_control/releases/latest/download/Package.Control.sublime-package", filename=sublime.installed_packages_path() + '/Package Control.sublime-package')

    安裝以後依照提示,裝置會需要重啟兩三次。
    而我有因為有把 Sublime Text 重新安裝,如果操作上資料會消失的話,可使用備份把檔案放回去即可。

    以上。我認真覺得 Sublime Text 是一個輕便好用的編輯器,尤其是同時需要開其他 IDE 或是編輯器的時候,電腦不起飛阿。

  • 使用 Dropbox 實作異地備份

    使用 Dropbox 實作異地備份

    這邊是以線上運作中的 web 網站作為目標備份,備份包含運作中、不包含git內的上傳檔案、資料庫資料內容。不包含系統環境、環境設定檔。在講異地備份之前,提一下自己心目中的異地備份服務是從 VPS 廠商出發的:

    1. 如果資料庫是獨立服務,可以把資料庫當成一個獨立伺服器的運作方式,如果不是,則需要定期/手動操作把 sql 檔案轉換出來。
    2. 對於一個獨立伺服器,確定他是否使用額外硬碟。如果是額外硬碟責備份額外硬碟即可。
    3. 廠商提供備份與下載服務。如果沒有下載,應該也能夠提供備份至不同地區的資料中心的服務。
    4. 提供備援(備份且恢復資料)服務。

    如果廠商提供異地備援(備份+還原)的功能,這會是好的。需要考慮的有:

    1. 網站使用的狀態,會考慮到需要備份的頻率。
    2. 網站重要性。是否不能斷線?是否有資安問題、金流問題?是否實時有用戶在使用?
    3. 有沒有需要備份可編輯的原始碼?
    4. 能不能正確的抵擋天災、人禍?

    這次因為種種原因,以從系統環境的層級著手處理異地備份。

    異地備份如果要把檔案下載到電腦上再儲存起來實嫌麻煩,嘗試讓他跟自己常用的服務做雲端傳輸就好。於是我選了使用 Dropbox 做備份,筆記下來。

    實作的目標是:備份檔案、資料庫。
    使用的服務與環境是:debian 11, dropbox, Dropbox-Uploader, mysql_backup.sh

    這邊採用手動單次操作,如果需要用 cront job 動作的話要請配置好一個 script。

    手動操作的流程是:

    1. zip 要備份的檔案
    2. 透過 dropbox-uploader 上傳
    3. backup mysql 資料庫們
    4. 透過 dropbox-uploader 上傳

    幾個注意事項:

    1. 網站檔案記得先 zip ,要不檔案(尤其是 uploads/ )多到會瘋掉。
    2. 資料庫備份預設會被壓縮成 .sql.gz 。
    3. mysql 備份檔案內不會有對 database 的 use / drop / create 動作,要的話要自己加。
    4. dropbox-uploader 會執行多個會單個操作,其實沒啥影響,都是用同一個 upload 指令。
    5. 如果使用終端機 ssh 進去 server 的話,建議 keep session 操作避免長時間等待不小心離線,我是使用 screen 來實現,這是介紹

    再來關於兩個 shell script 的操作說明。

    關於 mysql_backup.sh 操作:

    請先下載 shell 或是創建以後複製貼上,記得更新權限

    chmod +x mysql_backup.sh

    設定其中的資料夾位置、帳號密碼,如果 mysql 路徑不一樣的話記得變更 PATH 變數, KEEP_BACKUPS_FOR 他會檢查路徑中舊的檔案來保留/移除,如果是用 cron job 動作的話好用。

    這定完成以後執行即可,會有資訊顯示。

    ./backup_mysql.sh

    關於 Dropbox-Uploader 的操作:

    首先先下載 script

    curl "https://raw.githubusercontent.com/andreafabrizi/Dropbox-Uploader/master/dropbox_uploader.sh" -o dropbox_uploader.sh

    設定權限並且執行他

    chmod +x dropbox_uploader.sh
    ./dropbox_uploader.sh

    系統會提示怎麼做,主要幾個步驟:

    1. 請開啟 dropbox developer 創建一個 app ,選擇 App folder 的 type 並且創建好名字。
    2. 創建完之後,在 Permissions 的 tab 中選擇 “files.metadata.read/write” 和 “files.content.read/write” 這四個權限。記得點擊 Submit 送出。
    3. 在 Settings 的 tab 中找到 App key 並且點擊 show 顯示 App secret ,記錄起來。
    4. 回到指令列上,依照要求輸入 key, secret
    5. 接著會跳出 access token 的請求網址,用瀏覽器開啟,會得到一組 token,依照要求把這組 token 輸入進去。
    6. 提示是否確認,輸入確認「y」之後就可以使用了。

    可用的指令可參考官網,這邊附上範例和中文翻譯:

    基本上會使用到的就是 upload 這個指令而已。

    另外,如果資料輸入錯誤,或是手動上傳完畢了不想留資料,除了移除 dropbox 的 app 之外,記得執行刪除設定檔的動作:

    rm ~/.dropbox_uploader

    以上。

    同場加映