PayPool 是一套團購記帳管理系統的後端 API,整個專案的核心流程用一句話概括就是:一人開團、多人下單、截止時間到自動從各人帳戶餘額扣款、結果即時通知。
這次使用的技術棧為:Java 25、Spring Boot 3.5、MySQL、MyBatis-Plus、Spring Security + JWT(RS256)、Redis、WebSocket(STOMP)、Flyway。
後面30天會頻繁出現以下七個詞,先在這邊解釋一下。
| 名詞 | 定義 |
|---|---|
| 團主 | 開團者,即建立訂單的帳號 |
| 團購訂單 | 一次開團的整體,含名稱、截止時間、狀態 |
| 品項 | 某人在某張訂單裡點的東西(小美的大杯珍奶 ×1) |
| 截止時間 | 開團時決定,到期自動關單並結算 |
| 結算 | 逐一從各人餘額扣款並寫入流水帳 |
| 餘額 | 帳戶實際金額 |
| 可用餘額 | 餘額減去未結算的凍結金額,不存 DB,即時計算 |
小明餘額 500,在三張未結算訂單中各點了 200。若下單檢查只看實際餘額,三次都會通過(每次都看到 500),如果三張訂單同時到期,那小明的帳戶就會爆掉了。
因此兩者用途不同:下單時檢查的是"可用餘額",結算時實際扣款扣的是"餘額"。
可用餘額 = balance - SUM(OPEN 與 FAILED 訂單中該使用者的品項小計)

FAILED 既非成功也非結束:錢沒扣到,但補足餘額之後仍可回到主流程,只是回到主流程並不是自動觸發的——訂單會停在 FAILED,要有人再呼叫一次結算api,對同一張訂單重跑整段扣款流程。
相反的,如果訂單上全部的人都足額就轉 SETTLED。
開團、下單、扣款,每一步都是 CRUD。
服務本身是單體架構,但設計上假設會有多個實例同時在跑,MySQL 與 Redis 是所有實例共用的外部服務。難的是這五個問題:
這次專案選擇以 AI 輔助開發,流程採用 **SDD(Spec-Driven Development)**模式,使用OpenSpec進行開發:先有規格,人審過規格後 AI 才照規格實作,實作完再對照規格審查。
下面這四個Skill串起了整個開發流程,從討論規格定義以及實作的邊界,根據討論結果寫出proposal(規格提案,其中包含測試的規格),再開始按照task.md的任務清單進行實作。規格先定義清楚,AI 再動手開發。
| 指令 | 做什麼 |
|---|---|
/opsx:explore |
釐清需求,只討論不動手 |
/opsx:propose |
產出四份規劃文件(design.md, proposal.md, tasks.md, spec.md),不碰程式碼 |
/opsx:apply |
照 tasks.md 逐項實作 |
/opsx:archive |
歸檔,並可把這次的 spec 同步進 openspec/specs/ 主規格 |
每次的propose會先產出四份規劃文件:
| 文件 | 回答什麼 |
|---|---|
proposal.md |
要做什麼、為什麼做 |
specs/*/spec.md |
系統必須有什麼行為,寫成 WHEN / THEN 情境 |
design.md |
怎麼做,每個設計決策編號記錄(D1、D2⋯) |
tasks.md |
實作步驟,每項依 TDD 拆成 RED / GREEN |
以權限系統為例,spec 的情境:
#### Scenario: 無權限的操作被拒絕
- **WHEN** 角色「團長」未被允許「刪除其他人帳號」,且僅擁有團長角色的使用者呼叫該功能
- **THEN** 系統回覆 HTTP 403,錯誤訊息明確表達「權限不足」(而非「未登入」)
- **AND** 沒有任何資料被異動
對應的 tasks,測試先於實作:
- [x] 3.1 RED:`DynamicAuthorizationManager` 單元測試——未認證 deny、SUPER_ADMIN 放行、URL pattern 命中且有權限放行、命中但無權限 deny、未登記端點放行、多角色取聯集、method 比對(含 ALL)
- [x] 3.2 GREEN:實作 `DynamicAuthorizationManager`(PathPatternParser 比對,依 design.md D2 判斷順序)
到目前為止,SDD不只幫忙提前想到我沒想到的邊界問題,也讓我可以掌控AI開發出來最後的結果,避免AI自由發揮XD