系統架構
公開內容預設公開,以預先產生的靜態網頁提供;帳號、進度、評量與教師、管理功能等需要信任的操作,一律經過 Worker API。
- 預估時間
- 約 37 分鐘
- 最後驗證
瀏覽所有說明主題
跳至文章內容架構一覽
NTPU AI Learn 把系統分成兩條路徑:閱讀公開內容時,瀏覽器直接取得建置時預先產生的網頁;需要信任的操作,例如送出評量、同步進度、登入或使用教師與管理功能,則交給 Worker API 驗證、授權後再讀寫資料庫。
讀取路徑不需要登入、不呼叫 API,也不碰資料庫,所以公開內容快速、可被搜尋引擎索引。互動路徑則是唯一能讀寫學習資料的地方,每個請求都在 Worker 裡驗證與授權。評量答案與教師專用內容只存在伺服器端,從不出現在公開路徑上。
有些互動操作除了通過驗證與授權的請求之外,還需要更多條件。像結業證書或能力證書這類項目,在核發之前,Worker 會另外執行伺服器端的資格規則,檢查真實的證據(例如已完成的課程單元與通過的評量,或教師對提交內容審閱後判定通過)才會核發,絕不會只因為送出了表單就自動取得。
系統情境 System Context
這張圖聚焦一件事:誰在使用 NTPU AI Learn,以及平台與外部世界如何互動。
讓第一次接觸這個平台的人,也能快速理解系統邊界與整體脈絡。
學員
閱讀課程、完成學習與評量
- 閱讀課程、完成學習與評量,前往 → NTPU AI Learn
教師
教學、查看場次與學習結果
- 教學、查看場次與學習結果,前往 → NTPU AI Learn
管理員
管理平台、場次與權限
- 管理平台、場次與權限,前往 → NTPU AI Learn
NTPU AI Learn
AI 學習與實作平台
- 確認登入身分,前往 → Google 帳號登入
- 核對是否為真人,前往 → Cloudflare Turnstile
Google 帳號登入
選用的登入方式之一
Cloudflare Turnstile
公開表單的機器人驗證,可依表單啟用
Container 架構
這張圖把 System Context 裡「NTPU AI Learn」這一整個方塊,攤開成實際部署與執行的技術元件,回答的問題是:主要的執行與資料元件有哪些、各自負責什麼、彼此怎麼溝通。
瀏覽器
學員、教師或管理員
- 閱讀公開內容,前往 → Static Assets
- 互動操作(/api/),前往 → Worker API
Static Assets
預先產生的 HTML、JS、CSS
所屬區域:公開
Worker API
結構驗證、身分確認、授權、評分
所屬區域:僅限伺服器
- 讀取與寫入,前往 → D1 資料庫
- 上傳與簽章下載,前往 → R2 檔案儲存
D1 資料庫
帳號、登入工作階段、進度、提交內容、評量結果
所屬區域:持久資料
R2 檔案儲存
認證佐證檔案與受保護的教師投影片匯出檔;檔案中繼資料存在 D1
所屬區域:持久資料
版本控管的內容
課程、說明與文件,兩種語言共用穩定 ID
- 建置時產生,前往 → Static Assets
伺服器專用邏輯
評量答案、講者備註、角色檢查
所屬區域:僅限伺服器
- 只隨 Worker 部署,前往 → Worker API
一次操作如何在系統中流動
以下是兩個實際的例子:一個只讀取公開內容,一個需要伺服器評分並寫入資料。
學員打開一門課的單元頁面,沒有登入。
- 1瀏覽器請求頁面
例如 /courses/… 之下的單元網址。 - 2Worker 處理網址
舊網址在這一步直接永久轉址;每個回應都加上安全標頭。 - 3回傳預先產生的網頁
從 Static Assets 取得建置時產生的完整 HTML、CSS 與 JS。不呼叫 API、不讀取資料庫、不需要登入。 - 4瀏覽器顯示並接手互動
內容立即可讀;JavaScript 載入後才加上語言切換、自我檢測與「標記為完成」等互動,進度預設只存在這個瀏覽器。
學員在課程場次中以場次代碼與學員代碼作答後測。
- 1開始作答
瀏覽器以 POST 呼叫 /api/ 之下的評量 API,送出場次代碼與學員代碼(不需要登入)。Worker 驗證格式、確認場次開放中,在 D1 建立一筆作答紀錄,回傳不含答案的題目。 - 2送出答案
作答完成後,瀏覽器再以 POST 送出每一題選擇的選項。 - 3結構驗證
Worker 先以結構描述檢查請求內容,格式不符的請求直接被拒絕。 - 4確認作答與場次
找到這次作答並確認場次仍開放中。已經送出過的作答直接回傳原本的結果,不會重新評分。啟用機器人驗證時,也在這一步由伺服器端核對。 - 5伺服器端評分
確認每一題都已作答、題目都存在後,以只存在伺服器端的答案計分,並整理各能力面向的表現。 - 6寫入 D1
分數與每一題的作答以參數綁定的查詢寫入資料庫。 - 7回傳結果並更新進度
回應包含分數與能力分析(後測另附自己的前測分數)。頁面顯示結果,並在瀏覽器的進度中把這項評量標記為完成。
元件
| 元件 | 職責 | 技術 | 信任層級 |
|---|---|---|---|
| 前端(瀏覽器端) | 呈現頁面、處理互動、在本機保存學習進度 | React、TypeScript、Vite | 不受信任:在使用者的裝置上執行,任何人都能檢視與修改 |
| Static Assets | 提供預先產生的 HTML、JS、CSS 與練習檔案 | Cloudflare Static Assets,建置時預先產生頁面 | 公開:不需登入,只包含可以公開的內容 |
| Worker API | 轉址與 404、結構驗證、身分與角色檢查、評分、讀寫資料 | Cloudflare Workers、Hono、Zod | 信任邊界:所有授權與評分都在這裡決定 |
| D1 資料庫 | 保存帳號、登入工作階段、場次、進度、提交內容與評量結果 | Cloudflare D1,版本化的 migration | 受信任的持久資料:只能經由 Worker 存取 |
| R2 檔案儲存 | 兩個私有儲存桶:認證佐證檔案(Evidence,檔案中繼資料另存在 D1)與預先建置的教師投影片匯出檔 | Cloudflare R2 | 受信任的持久資料:只能經由 Worker 存取;私人與審核者層級的佐證檔案以時效簽章網址下載,教師匯出檔只會串流給具權限的教師,且每次嘗試都留下稽核紀錄 |
| 郵件寄送 | 寄送帳號驗證、重設密碼、認證政策變更通知等交易性 Email | Cloudflare Workers 內建的 Email 傳送功能 | 只有 Worker 能觸發寄送;寄送內容由伺服器決定,不接受前端指定收件內容 |
| 結構化內容 | 課程、說明中心與文件的文字與結構 | 版本控管中的 TypeScript 內容模組 | 公開內容在建置時進入網站;伺服器專用內容只隨 Worker 部署 |
設計原則
上面的技術選擇背後,是這六個在程式與建置流程中實際執行的原則;下表列出每個原則為什麼重要、它帶來什麼影響,以及採用它必須付出的取捨。
| 原則 | 為什麼 | 影響 | 取捨 |
|---|---|---|---|
| 預設公開(Public by default) | 課程內容、說明與文件不需要登入或執行 JavaScript 就能被閱讀,也才能被搜尋引擎索引,對第一次接觸平台、還沒註冊的人最友善。 | 公開頁面必須在建置時就能產生完整的 HTML,內容與版面必須在沒有互動狀態下也講得通。 | 任何設計成公開的內容都必須假設任何人可以直接看到;需要保護的內容必須從一開始就放進伺服器專用的來源,不能事後才靠前端隱藏。 |
| 由伺服器決定(Server-authoritative) | 瀏覽器裡的程式碼任何人都能檢視與修改,只有使用者無法竄改的地方,才能承載真正有權威性的判斷,例如評分與授權。 | 每一個需要授權或評分的操作,都在 Worker 收到請求後重新檢查一次,不只信任前端已經檢查過。 | 每個受保護的操作都多一趟往返 Worker 的請求;前端無法只靠本機狀態顯示最終結果,必須等伺服器回應。 |
| 單一真實來源(Single source of truth) | 同一項資訊如果有兩份定義,遲早會兜不起來,例如網址、內容 ID 或課程目錄。 | 路由、轉址、404、預先產生的頁面與 sitemap 都從同一份路由表產生;課程與內容也各自只有一份定義。 | 修改這份共用定義時影響範圍比較大,需要更謹慎地驗證,不能只改一個使用的地方就結束。 |
| 可重現(Reproducible) | 維護者需要能從原始碼重建整個網站與資料庫結構,而不是依賴某一次手動操作留下的狀態。 | 內容、資料結構描述與資料庫 migration 都放在版本控管中,每一次變更都能追溯並重新建置。 | 資料庫只能透過新增的 migration 變更,不能直接修改正式環境的資料表結構,需要多一個步驟。 |
| 漸進增強(Progressive enhancement) | 確保即使 JavaScript 載入失敗或被關閉,公開內容仍然可以閱讀,對可用性與可靠度都有幫助。 | 公開頁面需要有能在建置時期產生完整 HTML 的流程;互動功能必須設計成疊加在可讀內容之上,而不是取代它。 | 建置流程因此變得更複雜,需要同時維護「可以直接讀」與「載入後才互動」兩種狀態;部分互動體驗需要另外走一條路徑實作。 |
| 最小暴露(Least exposure) | 公開輸出裡多留一份不該公開的資料,就多一分外洩風險,尤其是評量答案與教師專用內容。 | 評量答案、講者備註與管理邏輯只存在伺服器專用的原始碼裡,從不打包進公開的前端程式;每次建置都會掃描公開輸出。 | 一旦建置掃描發現外洩,建置就會失敗,開發者必須先處理外洩問題才能繼續部署,即使趕時間也一樣。 |
品質屬性 Quality Attributes
以下用具體情境,而不是抽象的形容詞,說明平台在幾個品質面向上實際的行為;每一項都能對照程式或測試驗證,這裡不列出任何未經量測的數字目標。
- 1無障礙(Accessibility)
就算瀏覽器的 JavaScript 執行失敗,公開課程內容仍然完整可讀,因為它本來就是建置時就產生好的 HTML(見「漸進增強」原則)。 - 2安全(Security)
修改瀏覽器裡的程式碼,不能讓學員改變具有權威性的授權判斷或評量分數;這些判斷只在 Worker 裡決定,見「信任邊界」一節。 - 3隱私(Privacy)
公開頁面與前端程式包不會包含受保護的評量答案、教師專用備註或不必要的使用者資料;每次建置都會掃描所有公開輸出檔案,一旦偵測到外洩就讓建置失敗。 - 4可靠性(Reliability)
資料庫無法使用時,已經預先產生的公開學習內容仍然可以透過 Static Assets 提供;只有需要讀寫資料庫的互動功能(例如送出評量)會受影響,見下方「失效與降級」一節。 - 5可維護性(Maintainability)
網址、路由與資料庫結構的變更都在版本控制中審查;一份路由表同時產生前端路由、轉址、404 與 sitemap,避免正式行為與文件各自對不上。 - 6可重現性(Reproducibility)
只要有相同的原始碼版本與支援的工具鏈,維護者就能重新建置整個公開網站,並依序套用版本化的資料庫 migration 重建資料庫結構。 - 7國際化(Internationalization)
切換語言時會停留在同一個邏輯頁面;因為兩種語言的課程、文件與測驗共用同一組穩定 ID,自動化測試會檢查兩邊的章節結構、連結與 ID 是否一致。
內容與資料
「內容」和「使用者資料」是兩件不同的事,分開存放、分開管理。修改課程文字不會動到任何學員資料,學員的操作也不會寫回內容。
結構化內容
- 課程、說明中心與這些文件
- 存放在版本控管中,不是另一套內容管理系統
- 經過審查後隨建置發布
- 兩種語言共用穩定 ID,自動化測試檢查結構一致
- 網頁是唯一的正式版本,列印或簡報檔只是衍生輸出
使用者資料
- 帳號、登入工作階段、場次與學員代碼
- 同步的進度、評量作答與結果
- Use Case、Canvas 與 Exit Ticket 等提交內容
- 存放在 D1,只能經由 Worker API 讀寫
- 資料結構只透過版本化的 migration 變更
瀏覽器本機的學習進度是第三種:它只存在你的裝置上,除非你選擇同步到課程場次。
課後回饋的元件
課後回饋由前端表單、四組 Worker API、D1 資料表、排程與郵件寄送組成,全部沿用既有的信任邊界:授權、遮罩、門檻與稽核都在 Worker 中完成。
| 部分 | 內容 |
|---|---|
| 表單頁 | /feedback/:course,一頁三區、進度條、草稿只存在瀏覽器;教師簡報結尾頁與 QR code 在瀏覽器中產生 |
| 學員 API | /api/feedback:讀寫自己的回饋、待填課程、匿名的表單事件、梯次代碼的即時檢查(session-check) |
| 教師與人資 API | /api/teacher-feedback(依場次的統計)、/api/org-feedback(單位統計、留言、CSV,依單位授權) |
| 管理員 API | /api/feedback-admin:留言審核、表單成效;保存期限由既有的 /api/retention-admin 提供;梯次的課程綁定由 /api/admin/sessions(含影響預覽)提供 |
| 投影用 QR 頁 | /teacher/feedback-qr:教師選場次,全螢幕顯示該梯次專屬的 QR code、倒數與 A4 列印版面;連結帶 ?session= 讓掃描後自動填好梯次 |
| D1 資料表 | 回饋、留言(原文與遮罩後文字)、一次性提醒紀錄、匿名的表單事件計數;人資角色存在單位成員資料表 |
| 排程 | 每月的保存期限報告(只報告)與每日的一次性提醒信(正式環境預設關閉) |
| 簡報檔 | 教師版簡報最後一個模組的結尾頁由離線匯出腳本產生,發佈到教師匯出的 R2 儲存桶 |
信任邊界
| 區域 | 包含 | 誰能取得 | 如何保護 |
|---|---|---|---|
| 公開 | HTML、JS、CSS、公開的課程內容、練習檔案、說明與文件 | 任何人,不需要登入 | 只放可以公開的內容;每次建置都掃描是否有外洩 |
| 僅限伺服器 | 評量答案與評分邏輯、角色與授權檢查、講者備註與教師專用內容 | 只有 Worker 的程式 | 不打包進網站;只透過受保護的 API 提供給被授權的角色 |
| 持久資料 | D1:帳號、登入工作階段、進度、提交內容、評量結果 | 只能經由 Worker API | 輸入先經過結構驗證、查詢一律使用參數綁定、權限預設拒絕 |
注意
部署 Deployment
NTPU AI Learn 分為三個部署環境,各自承擔不同階段的開發與發布工作。
使用者瀏覽器
- 公開頁面請求,前往 → Static Assets
- API 請求(/api/),前往 → Worker Runtime
Static Assets
預先產生的公開頁面
Worker Runtime
執行 API、驗證與授權
- 上傳與簽章下載,前往 → R2 檔案儲存
- 讀取與寫入,前往 → D1 資料庫
R2 檔案儲存
佐證檔案與教師匯出檔
D1 資料庫
| 環境 | 用途 |
|---|---|
| 正式環境 Production | 提供正式服務 |
| 預備環境 Staging | 正式發布前進行整合與驗證 |
| 本機開發 Development | 開發、測試與除錯 |
Production 與 Staging 使用彼此獨立的部署與資料環境;本機開發則使用本機模擬資源,不會直接操作正式環境的資料。
失效與降級 Failure & Degradation
以下只涵蓋已經可以在架構與程式行為中驗證的情境;存放庫裡目前沒有找到 migration 失敗,或身分驗證服務本身降級時的正式回滾與事故處理程序文件,所以這裡不列出未經驗證的處理步驟。
- 1靜態內容可用、Worker API 無法使用
公開課程內容持續可以透過 Static Assets 閱讀,不受影響;需要 Worker 的互動操作(登入、送出評量、同步進度等)會失敗,並顯示明確的錯誤,而不是假裝成功。 - 2Worker API 可用、D1 資料庫無法使用
公開內容不受影響,因為它由 Static Assets 提供,不會讀取資料庫;需要讀寫資料庫的互動操作會失敗,不會回傳假的成功結果。 - 3Worker API 可用、R2 檔案儲存無法使用
公開內容與其他不涉及檔案的互動操作不受影響;認證佐證檔案的上傳與下載、教師投影片匯出檔的下載會失敗並顯示明確的錯誤,不會回傳假的成功結果。 - 4JavaScript 無法執行
公開課程內容仍然可讀,因為它是建置時就產生好的 HTML;只有 React 載入後才加上的互動(例如語言切換、自我檢測、標記完成)無法使用。
網址與語言
平台只有一份路由表:前端路由、Worker 的轉址與 404、預先產生的頁面與 sitemap 都從同一份定義產生。路由表也把每個網址分成三類:公開內容(預先產生、可被索引)、公開的互動流程(不預先產生、不被索引),以及需要授權的受保護頁面。
| 網址 | 語言 | 例子 |
|---|---|---|
| 沒有語言前綴 | 繁體中文(預設) | /courses、/docs/architecture |
| /en/ 之下 | English | /en/courses、/en/docs/architecture |
| 舊的 /zh/ 網址 | 永久轉址到沒有前綴的中文網址 | /zh/courses → /courses |
語言由網址決定,切換語言時會停留在同一個邏輯頁面;因為兩種語言共用同一組 ID,進度與連結不會因切換語言而失效。
架構決策 Architecture Decisions
重要的架構決策會以 Architecture Decision Record(ADR)記錄,包含決策背景、採用方案與後續影響。
ADR 屬於存放庫內的工程文件,供需要進一步理解實作或參與開發時查閱。
相關文件
這篇說明對你有幫助嗎?
回饋是匿名的:只會以每日次數記錄文章、語言與你的選擇。