Skip to content

Commit 29faea6

Browse files
Merge pull request #6 from alvin000009238/codex/provide-project-architecture-diagram-231d5w
新增 ARCHITECTURE 文件並在 README 加入連結
2 parents 040913e + cdbb0bc commit 29faea6

2 files changed

Lines changed: 287 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [Background](#background)
1515
- [Install](#install)
1616
- [Usage](#usage)
17+
- [Architecture](docs/ARCHITECTURE.md)
1718
- [Maintainers](#maintainers)
1819
- [License](#license)
1920

docs/ARCHITECTURE.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# 專案架構與前後端溝通說明
2+
3+
本文件補充本專案的完整系統架構、API 規格,以及登入/查詢/分享三大流程的時序圖。
4+
5+
## 1) 系統架構圖(Mermaid)
6+
7+
```mermaid
8+
flowchart TD
9+
10+
U[使用者瀏覽器<br/>index.html]
11+
FE[Frontend SPA<br/>public/app.js]
12+
13+
BE[Flask API<br/>server.py]
14+
15+
GF[GradeFetcher<br/>fetcher.py<br/>requests]
16+
17+
SCH[School System API<br/>shcloud2.k12ea.gov.tw]
18+
19+
TS[Cloudflare Turnstile<br/>siteverify]
20+
21+
SF[(shared_grades<br/>JSON Cache)]
22+
23+
U --> FE
24+
25+
FE -->|POST /api/login| BE
26+
FE -->|GET /api/structure| BE
27+
FE -->|POST /api/fetch| BE
28+
FE -->|POST /api/share| BE
29+
FE -->|GET /api/share/:id| BE
30+
31+
BE -->|fetch grades| GF
32+
GF --> SCH
33+
34+
BE -->|verify token| TS
35+
36+
BE <--> SF
37+
```
38+
39+
## 2) 元件分層與責任
40+
41+
| 層級 | 元件 | 責任 |
42+
|---|---|---|
43+
| 前端呈現層 | `public/index.html`, `public/style.css` | 提供儀表板、Modal、分享 UI。 |
44+
| 前端邏輯層 | `public/app.js` | API 呼叫、表單互動、localStorage、圖表渲染、分享頁唯讀模式。 |
45+
| API 層 | `server.py` | 路由、Session、Turnstile 驗證、分享檔案讀寫、靜態檔案服務。 |
46+
| 整合層 | `fetcher.py` | 以 requests 登入學校系統並抓取結構/成績。 |
47+
| 外部服務 | 學校系統、Cloudflare | 資料來源與人機驗證。 |
48+
| 佈署層 | `Dockerfile`, `docker-compose.yml` | Gunicorn 啟動、健康檢查、cloudflared tunnel。 |
49+
50+
## 3) 前後端 API 規格
51+
52+
> Base URL: https://score.clhs.dev
53+
54+
### 3.1 安全驗證與登入
55+
56+
#### `GET /api/turnstile-site-key`
57+
- **用途**:取得前端初始化 Turnstile 的 site key。
58+
- **回應 200**
59+
```json
60+
{ "siteKey": "<TURNSTILE_SITE_KEY or empty>" }
61+
```
62+
63+
#### `POST /api/login`
64+
- **用途**:登入學校系統,建立後端 session。
65+
- **Request JSON**
66+
```json
67+
{
68+
"username": "學號",
69+
"password": "密碼",
70+
"cf-turnstile-response": "token"
71+
}
72+
```
73+
- **回應 200**
74+
```json
75+
{ "success": true, "message": "登入成功" }
76+
```
77+
- **回應 400/401/403/500**
78+
```json
79+
{ "success": false, "message": "錯誤訊息" }
80+
```
81+
82+
#### `GET /api/check_login`
83+
- **用途**:確認當前 session 是否已登入。
84+
- **回應 200**
85+
```json
86+
{ "logged_in": true }
87+
```
88+
- **回應 401**
89+
```json
90+
{ "logged_in": false }
91+
```
92+
93+
#### `POST /api/logout`
94+
- **用途**:清除 session。
95+
- **回應 200**
96+
```json
97+
{ "success": true, "message": "已登出" }
98+
```
99+
100+
### 3.2 成績查詢
101+
102+
#### `GET /api/structure`
103+
- **用途**:取得「學年度/學期」與可查詢考試清單。
104+
- **Query**`reload=true`(可選,強制重抓)
105+
- **回應 200**
106+
```json
107+
{
108+
"structure": {
109+
"113學年度第2學期": {
110+
"year_value": "113_2",
111+
"exams": [
112+
{ "text": "第一次段考", "value": "1" },
113+
{ "text": "第二次段考", "value": "2" }
114+
]
115+
}
116+
}
117+
}
118+
```
119+
- **回應 401**
120+
```json
121+
{ "error": "錯誤訊息" }
122+
```
123+
124+
#### `POST /api/fetch`
125+
- **用途**:查詢指定學期與考次的成績資料。
126+
- **Request JSON**
127+
```json
128+
{
129+
"year_value": "113_2",
130+
"exam_value": "2"
131+
}
132+
```
133+
- **回應 200**
134+
```json
135+
{
136+
"success": true,
137+
"message": "成績已更新",
138+
"data": {
139+
"Result": {
140+
"StudentName": "王小明",
141+
"StudentNo": "123456",
142+
"StudentClassName": "三年甲班",
143+
"SubjectExamInfoList": []
144+
}
145+
}
146+
}
147+
```
148+
- **回應 401/500**
149+
```json
150+
{ "success": false, "error": "錯誤訊息" }
151+
```
152+
153+
### 3.3 分享機制
154+
155+
#### `POST /api/share`
156+
- **用途**:將前端成績 JSON 產生分享連結。
157+
- **Request JSON**:任意成績資料(需包含前端使用資料),且附帶 `cf-turnstile-response`
158+
```json
159+
{
160+
"Result": {},
161+
"cf-turnstile-response": "token"
162+
}
163+
```
164+
- **回應 200**
165+
```json
166+
{ "success": true, "id": "Abc12-Def34_Gh~" }
167+
```
168+
- **回應 400/403/500**
169+
```json
170+
{ "error": "錯誤訊息" }
171+
```
172+
173+
#### `GET /api/share/:share_id`
174+
- **用途**:讀取分享資料。
175+
- **回應 200**
176+
```json
177+
{ "success": true, "data": { "Result": {} } }
178+
```
179+
- **回應 400/404/500**
180+
```json
181+
{ "error": "Invalid ID format or Link expired or not found" }
182+
```
183+
184+
#### `GET /share/:share_id`
185+
- **用途**:回傳前端頁面(由前端再呼叫 `/api/share/:share_id` 載入唯讀資料)。
186+
187+
### 3.4 其他
188+
189+
#### `GET /health`
190+
- **用途**:健康檢查。
191+
- **回應 200**
192+
```json
193+
{ "status": "ok" }
194+
```
195+
196+
## 4) 核心流程時序圖(Mermaid)
197+
198+
### 4.1 登入 + 預抓結構
199+
200+
```mermaid
201+
sequenceDiagram
202+
participant Browser as Browser(app.js)
203+
participant Server as Flask(server.py)
204+
participant Turnstile as Cloudflare Turnstile
205+
participant Fetcher as GradeFetcher(fetcher.py)
206+
participant School as School API
207+
208+
Browser->>Server: GET /api/turnstile-site-key
209+
Server-->>Browser: {siteKey}
210+
211+
Browser->>Server: POST /api/login (username/password/token)
212+
Server->>Turnstile: siteverify(token)
213+
Turnstile-->>Server: success/fail
214+
alt 驗證成功
215+
Server->>Fetcher: login_and_get_tokens()
216+
Fetcher->>School: GET login page + token
217+
Fetcher->>School: POST DoCloudLoginCheck
218+
Fetcher->>School: GET grades page + API token
219+
Fetcher-->>Server: cookies + student_no + api_token
220+
Server->>Fetcher: get_structure_via_api()
221+
Fetcher->>School: 取年期 + 平行取考試
222+
Fetcher-->>Server: structure
223+
Server-->>Browser: {success:true}
224+
else 驗證失敗
225+
Server-->>Browser: 403
226+
end
227+
```
228+
229+
### 4.2 查詢成績
230+
231+
```mermaid
232+
sequenceDiagram
233+
participant Browser as Browser(app.js)
234+
participant Server as Flask(server.py)
235+
participant Fetcher as GradeFetcher(fetcher.py)
236+
participant School as School API
237+
238+
Browser->>Server: GET /api/structure
239+
Server-->>Browser: {structure}
240+
241+
Browser->>Server: POST /api/fetch (year_value, exam_value)
242+
Server->>Fetcher: fetch_grades_via_api(...)
243+
Fetcher->>School: POST GetScoreForStudentExamContent
244+
School-->>Fetcher: grades JSON
245+
Fetcher-->>Server: grades JSON
246+
Server-->>Browser: {success:true,data}
247+
Browser->>Browser: store localStorage + update dashboard
248+
```
249+
250+
### 4.3 分享與唯讀檢視
251+
252+
```mermaid
253+
sequenceDiagram
254+
participant Browser as Browser(app.js)
255+
participant Server as Flask(server.py)
256+
participant Turnstile as Cloudflare Turnstile
257+
participant FS as shared_grades folder
258+
259+
Browser->>Server: POST /api/share (grades + token)
260+
Server->>Turnstile: siteverify(token)
261+
Turnstile-->>Server: success
262+
Server->>FS: write <share_id>.json
263+
Server-->>Browser: {success:true,id}
264+
265+
Browser->>Server: GET /share/:id
266+
Server-->>Browser: index.html
267+
Browser->>Server: GET /api/share/:id
268+
Server->>FS: read <share_id>.json
269+
Server-->>Browser: {success:true,data}
270+
Browser->>Browser: 進入唯讀模式
271+
```
272+
273+
## 5) 佈署與執行摘要
274+
275+
- `Dockerfile` 使用 `python:3.11-slim`,安裝依賴後以 `gunicorn` 啟動 `server:app`(5000 port)。
276+
- `docker-compose.yml`
277+
- `app` 服務掛載 `./shared_grades:/app/shared_grades`
278+
- 健康檢查透過 `GET /health`
279+
- `tunnel` 服務使用 `cloudflare/cloudflared` + `TUNNEL_TOKEN`
280+
281+
## 6) 重要注意事項
282+
283+
- 後端將登入狀態保存在 Flask session(cookie-based session + server-side secure key)。
284+
- `SESSION_COOKIE_SECURE=True`,部署需 HTTPS。
285+
- `shared_grades` 檔案有背景清理機制:每 10 分鐘掃描,2 小時過期刪除。
286+
- 前端會將最近一次查詢成績存放在 `localStorage.gradesData`,以提升重開頁面體驗。

0 commit comments

Comments
 (0)