徵求計畫批次匯入系統
太遙中心衛星影像徵求計畫的訂單,原本以 Excel 逐筆輸入、公式檢核與配號,再把 SQL 手動貼進資料庫執行,規則只存在於公式與承辦經驗中。本系統把規則逐條還原為領域規格與前後端共用的程式,做成試算表式網頁,可即時檢核、配號並直接寫入中心的 Oracle 資料庫。2026 年 6 月起於太遙中心實習,依中心需求主責完成並交付。
- 期間
- 2026-08-18 至 09-15(與 E-2 並行);已部署供中心內部使用
- 角色
- 依中心需求主責完成並交付:規則還原、領域規格與決策紀錄、前後端程式、測試、部署與交接文件;交付後持續參與中心的後續開發
- 技術
- TypeScript、pnpm monorepo;Fastify、Prisma、Zod、MySQL 8;Oracle(node-oracledb);Vue 3、Vite、Pinia;Vitest、Playwright;Docker Compose、nginx
- 規模
- 896 項自動化測試;25 則設計決策紀錄;188 筆歷史訂單全流程驗證;56 欄 Oracle 正式表鏡像編輯
書審對應:附件 E-3

本頁目錄
原本的流程與交接難題
中心的衛星影像徵求計畫受理學術研究與基礎教育的申請,提供的影像要在中心的 Oracle 資料庫建立訂單。原本以一份 Excel 活頁簿處理:
- 在「資料輸入」表逐筆填入客戶編號、影像編號與可選的範圍座標;
- 篩出要處理的客戶,把狀態設為 1、按 F9 重算,由公式配發流水號;
- 從「SQL 輸出」表複製整段 INSERT ALL,貼進資料庫工具執行,並手動去掉首尾多餘的引號;
- 完成後把狀態改為 2,資料留在「資料留存」表。
規則散在儲存格公式、資料驗證與設定格式裡:流水號以 NOW() 加上依列號遞增的秒數產生,靠自我參照與反覆運算維持不變;SQL 以 TEXTJOIN 串接固定範圍,註記可處理 999 筆,實際上限是 499 筆;TWD97 與經緯度同時填寫時,經緯度會被靜默忽略。規則沒有文件,也沒有自動化測試,交接只能從公式反推。難點在於把規則還原成可測試的邏輯,並證明輸出與原流程一致。
規則還原:領域規格與 25 則決策紀錄
逐條讀出公式、資料驗證與設定格式,寫成 10 章的領域規格,有疑義處與中心逐條確認(例如 Excel 的每一欄都要顯示、作廢列的畫面行為)。規格是唯一真相:規則要改先改規格,再改程式與測試;與 Excel 不同之處都寫明理由,例如兩種座標同時填寫改為明確錯誤、配號改由伺服器集中配發、超過筆數自動分段而不截斷。
設計取捨寫成 25 則決策紀錄(8 月 19 日至 9 月 15 日),每則記載背景、選項、決策、後果與刻意不做的事。需求在四週內多次改變形狀:8 月 25 日是批次工具,26 日改為直接編輯中心資料表,28 日再改為整張資料表的編輯器加 SQL 執行頁,29 日加入前台,之後陸續加入單一登入、統計與自動追蹤。
| 主題 | 決策紀錄 |
|---|---|
| 資料與寫入 | 自有資料存 MySQL(0001);直接寫入 Oracle、保留產生 SQL(0002);作廢不刪除(0003);SQL 執行頁(0009);中心資料頁改指正式表(0023) |
| 介面形狀 | 欄序照資料表(0004);原生核取方塊(0005);讀取並直接編輯中心資料表(0006 至 0008);Excel/CSV 匯入(0010);整表一次顯示(0011);導覽與版面(0015、0016、0019) |
| 權限與身分 | 前後台分離(0012);入口網單一登入(0017);帳號刪除條件(0018);權限收斂為管理員(0020) |
| 前台功能 | 追蹤表(0013);客戶影像紀錄(0014);統計只送加總(0021);已結案分頁(0022、0025);自動追蹤依原公式計算(0024) |
架構:pnpm monorepo,領域層前後端共用
| 套件 | 內容與邊界 |
|---|---|
| packages/core | 檢核、衍生欄位、流水號、SQL 產生、貼上與 CSV 解析、自動追蹤、統計加總;純函式,lint 禁止 import Vue、Fastify、Prisma 與 Node 內建模組,可直接在瀏覽器執行 |
| apps/api | Fastify;Prisma 存取 MySQL 8(系統自有的 9 張表);只有 Oracle 連線層可 import 驅動;授權集中於 policy 層 |
| apps/web | Vue 3 試算表介面;即時檢核直接呼叫 core;鍵盤與格線各只有一份實作 |
前端檢核是體驗,後端以同一份程式碼再驗一次才是權威。需求多次改變骨架,core 的領域邏輯都沒有改過形狀;新需求先問「這是規則還是版面」,規則一律進 core。lint 另限單檔 300 行、函式 50 行、巢狀 3 層。
規則設計
- 影像編號:格式為 SSS_TTT_YYYYMMDD_HHMMSS,去除空白、全形轉半形後檢核。長度不為 23、第 4、8、17 字元不是底線、日期段不合法或在未來為錯誤;應為數字處出現 I 或 O(既有 6 筆)、取像時間不合理(既有 2 筆)、同批重複只列警告,以免擋掉真實存在的編號。
- 座標:八格全空為全幅;TWD97 只輸入兩個對角、展開為四角;經緯度四格皆為數值;其餘為錯誤(188 筆歷史訂單為 159 筆全幅、29 筆經緯度)。另加 Excel 沒有的範圍警告,檢查座標是否落在臺灣附近、兩對角是否相同。資料庫存使用者輸入的原字串,正規化只在檢核與輸出時重算,因為數值型別會讓輸出對不上 Excel(121.3 會被補成 121.3000000000000)。
- 流水號:Excel 的「每列加一秒」在多人操作時會撞號,改為伺服器集中配號:以 SELECT … FOR UPDATE 鎖住配號表,下一號取「目前時刻」與「上一號加一」的較大者,原子配發連號;唯一索引為最後防線,撞號即回滾重試(上限 3 次),並有併發測試。
- 處理狀態與作廢:沿用 Excel 的 0、1、2(待確認、準備匯入、已完成),到 2 之後不得退回。已配號的列不可刪除、只能作廢;作廢以獨立欄位記錄、與狀態正交,事後仍看得出那個號是否真的進了中心資料庫;作廢不可逆並寫入稽核。
- SQL 產生:每筆訂單三段 INTO(主資料依座標型態三選一、DTM 檔、影像處理方式)。逐字元細節寫入規格:INTO 前兩個空白、VALUES 前四個、欄位清單後一個尾隨空白(最後一行除外)、客戶編號加引號而取像日期不加、座標照原始精度、換行一律 LF。超過設定筆數(預設 500)自動分段並標明「第 n 段/共 m 段」。core 的 SQL 模組是唯一組字串之處:單引號加倍、數值須為有限數、拒絕控制字元,並以注入樣本測試。
試算表式前端
中心要求從 Excel 換到網頁後,使用體驗不能差太多。操作規格逐鍵對照 Excel,18 個按鍵逐一實作並各有元件測試:
- 編輯與選取:Enter 往下一列,F2 或直接打字進入編輯,離開儲存格即確認、只有 Esc 放棄;點列號或欄標題選整列整欄,⌘/Ctrl 加選、⇧ 連選;⌘+、⌘- 插入與刪除列;複製出去是 TSV,可直接貼回 Excel;復原 100 步;所有改動按「儲存」才在一個交易內寫入。
- 貼上與匯入:有標題列依欄名對應,沒有時依欄數推斷版面並提醒,寫入前列出逐欄對應供核對;八成以上的列在同一欄報同一錯時判定為整欄錯位,只回一句提示;500 列貼上實測 3.6 秒。Excel 與 CSV 檔在瀏覽器內解析,不上傳伺服器。
- 整表與呈現:整張表一次載入(上限 5,000 列),只把看得見的列畫進 DOM;錯誤不用顏色,改用側槽的校對符號;深淺兩套主題各量測 24 個位置,文字對比皆達 WCAG AA;主線流程有純鍵盤的端對端測試。
兩條匯入路徑、表名後綴與 Oracle 替身
中心於 8 月 25 日決定由系統直接寫入中心資料庫,產生 SQL 保留為並存的退路。同一份已驗證的資料列產生兩種輸出:給人複製的文字版逐字元照規格,給系統執行的版本以繫結變數傳值。繫結值一律以字串攜帶,因為 121.3 經過浮點數可能變成 121.30000000000000426。
| 直接寫入(預設) | 產生 SQL(退路) | |
|---|---|---|
| 送出前 | 重新檢核,有錯即整批拒絕並指出列號 | 同左 |
| 判重 | 本系統的寫入紀錄,加上在同一個 Oracle 交易內查中心的流水號 | 不需要 |
| 完成 | 單一交易,commit 成功自動轉為已完成;失敗整批回滾並指出哪一列 | 承辦人員確認後手動標記 |
| 留底 | SQL 全文與 SHA-256 檢核碼 | 同左 |
- 表名後綴由一個設定同時套用在直接寫入與產生的 SQL:留空為正式表,填入後綴則整組指向結構相同的測試表,不會出現「寫進測試表、貼出去的 SQL 卻打正式表」。目標 schema 以連線後設定 CURRENT_SCHEMA 決定,給人複製的文字因此不必加前綴;兩個設定都走嚴格白名單,格式不符即啟動失敗。
- 中心資料庫版本較舊,驅動須用 thick mode 並打包 Instant Client 19.x;未設定連線時直接寫入自動停用、系統照常運作。流水號欄為定長 CHAR,判重先去除尾端空白再比對;NUMBER 一律以十進位字串取回,避免經緯度多出浮點雜訊。
- Oracle 替身:開發環境的 Oracle XE 容器照抄 8 月 26 日取得的真實建表語法,連測試表組與附屬表主鍵停用的狀態一併重現,免得開發環境擋下正式環境擋不下的東西;8 月 28 日以只下 SELECT 的腳本逐欄比對中心三張表,零差異(主表 56 欄)。替身只綁本機與容器橋接位址;整合測試若連線字串不是本機即直接失敗。
登入、權杖與稽核
- 帳號與權杖:密碼以 Argon2id 雜湊(64 MiB、迭代 3、平行度 4),首次登入強制改密碼。access 為 15 分鐘的 JWT,refresh 為 7 天的隨機字串、雜湊後存資料庫,兩者都放在 httpOnly cookie;refresh 每次換發即輪替,偵測到重用就撤銷該使用者的全部工作階段。登入失敗 5 次鎖定 15 分鐘,另以來源 IP 限制速率。
- 單一登入:實作中央大學入口網的 OAuth 2.0 授權碼流程。回呼指向後端,前端看不到授權碼;state 為 32 位元組亂數;登入後的落點只收站內路徑,避免開放轉址;以入口網的 identifier 對應帳號(數字 id 跨系統會變);自動開通者一律為一般使用者,且不以同名帳號自動接上,避免權限提升;保留本機密碼作為入口網故障時的退路。以 25 項整合測試對真的 HTTP 伺服器驗證。
- 授權:判斷集中在 policy 層;前端路由守衛為純函式,並以不變式測試要求除少數公開頁外,每一頁都標為管理員專用;後端每個端點再檢查一次。
- 稽核只寫不改:登入、帳號異動、訂單列增改刪、配號與匯出全部記錄;中心資料表每一列異動記下操作者、時間、來源 IP 與改前改後;SQL 執行頁與客戶影像查詢也都記錄。帳號只有未留下營運資料者刪得掉。
鏡像編輯與統計的安全設計
中心於 8 月 28 日把需求改為:網站要和資料庫一樣完整,但具備 Excel 的操作。管理員的中心資料頁因此鏡像整張資料表:
- 欄位定義向資料庫取得,欄名逐一過白名單才拼進 SQL;主鍵(流水號)只出現在 WHERE、不可修改,其餘欄位皆可編輯。
- 檢核對齊 Oracle 收不收:型別、精度、長度、必填不符即整批擋下;影像編號與座標等領域規則降為提醒,因為中心既有資料本來就有這類列。
- 一次儲存一個交易,全成或全敗。中心的資料表不只本系統在寫、又沒有版本欄位,因此改或刪之前以 SELECT … FOR UPDATE 鎖列,比對這次要寫的欄位與使用者載入時的值,對不上就整批中止;刪除前先把整列內容寫入稽核(本系統的訂單列則只作廢、不刪除)。
- SQL 執行頁只接受查詢與資料異動,結構變更、權限與交易控制一律拒絕;一次一句,查詢自動加上筆數上限並偵測截斷,每分鐘 60 句,每次執行都寫稽核。
- 統計頁是唯一對一般使用者開放的資料頁,只送出加總,不含申請人姓名、計畫名稱、客戶編號與單筆影像編號。時間軸為計畫年度(6 月 1 日至翌年 5 月 31 日),對應中心成果報告的統計表;加總在 core 以 bigint 精確計算,金額與面積全程以字串攜帶;圖表手寫 SVG、單一色相、不畫雙軸。客戶影像紀錄頁同樣只送出四欄、一律唯讀。
測試:896 項自動化測試
| 套件 | 測試數 | 範圍 |
|---|---|---|
| 領域層 core | 346 項 | 規則正反例、SQL 逐字元樣本、自動追蹤對照、統計加總 |
| 後端 api | 389 項 | 連真實 MySQL 與 Oracle 替身;每個端點正常、未登入、越權、輸入錯誤四條路徑;配號併發;直接寫入的成功、回滾與重複寫入 |
| 前端 web | 161 項 | 鍵盤、選取、貼上解析、列虛擬化、錯誤呈現 |
2026 年 9 月 25 日於中心伺服器實跑,896 項全數通過;core 的驗收標準含分支覆蓋率 100%。另以 Playwright 開真的瀏覽器、打真的後端做端對端測試,涵蓋主線(登入、建批次、貼上、修錯、配號、產生 SQL、標記完成)、帳號管理、純鍵盤操作與前台各頁,也對正式 Compose 堆疊跑過;登入的速率限制不為測試調鬆,改為整份測試共用一次登入。
等價驗證:逐字元比對與 188 筆歷史訂單
- SQL 逐字元樣本:依規格獨立推導四份預期輸出(三種座標型態各一、混合一),推導時不參考實作程式碼;測試逐字元比對,行尾空白與檔尾無換行都算數。
- 兩種輸出等價:從文字版剖析出值,與繫結參數逐項比對,確認兩者寫入相同的值。
- 188 筆歷史訂單:以中心實際的 188 筆歷史訂單跑完整流程;歷史資料匯入指令完成後校驗筆數、座標型態分布與客戶數,不符即中止;實跑兩次,第一次新增 188 筆、第二次全數略過。
- 自動追蹤:以追蹤表 128 列的三個日期與 Excel 自己算出的結果為對照組,固定「今天」為原工作簿最後重算的 2026 年 8 月 1 日,128 列全數相符;Excel 的比較語意(數字恆小於文字、空白等於 0)一併照抄。
部署與交接
- 部署:Docker Compose 三個容器,只有 nginx 對外,MySQL 與 api 不開埠;映像多階段建置、以非 root 身分執行並設健康檢查;HTML 的 CSP 於建置時由行內 script 的雜湊算出。已部署於中心伺服器供內部使用。
- 交接文件:交接說明(閱讀順序、現況、環境建置、逐條記錄症狀與解法的環境紀錄)、領域規格、架構、操作規格、設計語言、部署文件(含上線前檢查清單與中心資料庫的實際結構)、25 則決策紀錄,以及每項附驗收標準的工作清單。

