系統模型設計#
本文檔詳細說明水球軟體學院複刻專案的所有資料模型(Entity Models),包括核心課程系統和道館挑戰系統。
模型總覽#
系統共包含 16 個核心實體模型,分為以下模組:
| 模組 | Entity 數量 | 狀態 | 說明 |
|---|---|---|---|
| 🎓 基礎系統 | 6 | ✅ 已實作 | 用戶、課程、學習進度 |
| 🏋️ 道館系統 | 9 | ✅ 已實作 | 挑戰任務、作業批改、技能評級 |
| 📦 訂單系統 | 1 | ✅ 已實作 | 課程購買訂單 |
🎓 基礎系統模型#
1. User(用戶)#
用途: 用戶帳號、認證系統(含 OAuth)與遊戲化系統
實體類: tw.waterballsa.academy.entity.User
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 用戶 ID |
email | String(255) | Unique, NotNull | 登入信箱 |
password | String(255) | 加密密碼(OAuth 用戶可為空) | |
name | String(100) | NotNull | 用戶名稱 |
authProvider | Enum | NotNull, Default: LOCAL | 認證方式 (LOCAL/GOOGLE) |
googleId | String(255) | Google OAuth ID | |
role | Enum | NotNull, Default: STUDENT | 角色 (STUDENT/TEACHER) |
level | Integer | Default: 1 | 當前等級(1-36) |
exp | Integer | Default: 0 | 經驗值 |
title | String(50) | Default: 初級工程師 | 稱號 |
avatarUrl | String(500) | 頭像 URL | |
nickname | String(100) | 暱稱 | |
gender | String(20) | 性別 | |
profession | String(100) | 職業 | |
birthday | LocalDate | 生日 | |
region | String(100) | 地區 | |
githubUrl | String(500) | GitHub URL | |
createdAt | Timestamp | Auto | 帳號創建時間 |
updatedAt | Timestamp | Auto | 最後更新時間 |
核心業務邏輯#
// 經驗值增加時自動升級
public void addExp(int expToAdd) {
this.exp += expToAdd;
updateLevel(); // 自動重算等級
}
// 等級計算邏輯(基於固定門檻陣列)
private void updateLevel() {
int[] thresholds = {0, 200, 500, 1500, 3000, 5000, ...};
for (int i = thresholds.length - 1; i >= 0; i--) {
if (this.exp >= thresholds[i]) {
this.level = i + 1;
break;
}
}
}關聯關係#
- 1:N →
MissionProgress(學習進度) - 1:N →
CourseEnrollment(購課記錄) - 1:N →
Submission(作業提交) - 1:N →
UserSkill(技能等級)
索引#
UNIQUE INDEXonemailINDEXongoogleId
2. Course(課程)#
用途: 課程基本資訊(名稱、價格、講師等)
實體類: tw.waterballsa.academy.entity.Course
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 課程 ID |
name | String(200) | NotNull | 課程名稱 |
description | Text | 課程簡介 | |
imageUrl | String(500) | 課程封面圖 | |
price | BigDecimal(10,2) | 課程價格 | |
level | String(50) | 難度等級 | |
instructor | String(100) | 講師名稱 | |
totalDuration | Integer | 總時長(秒) | |
studentCount | Integer | Default: 0 | 學生人數 |
category | String(100) | 課程分類 | |
hasFreePreview | Boolean | Default: true | 是否有免費預覽 |
enabledFeatures | List | ElementCollection | 啟用功能列表 |
關聯關係#
- 1:N →
Chapter(章節,級聯刪除) - 1:N →
CourseEnrollment(購課記錄)
業務規則#
- 刪除課程時,所有章節和任務將被級聯刪除(
CascadeType.ALL) enabledFeatures儲存在關聯表course_features
3. Chapter(章節)#
用途: 課程章節(組織課程結構)
實體類: tw.waterballsa.academy.entity.Chapter
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 章節 ID |
courseId | Long | FK, NotNull | 所屬課程 |
title | String(200) | NotNull | 章節標題 |
orderIndex | Integer | NotNull | 排序索引 |
關聯關係#
- N:1 →
Course(所屬課程) - 1:N →
Mission(任務單元,級聯刪除) - 1:N →
Gym(道館,級聯刪除)
業務規則#
orderIndex決定章節顯示順序- 刪除章節時,所有任務和道館將被級聯刪除
4. Mission(任務單元)#
用途: 學習單元(影片/文章/問卷)
實體類: tw.waterballsa.academy.entity.Mission
相關枚舉: MissionType(VIDEO, ARTICLE, SURVEY)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 任務 ID |
chapterId | Long | FK, NotNull | 所屬章節 |
title | String(200) | NotNull | 任務標題 |
type | Enum | NotNull | 類型(VIDEO/ARTICLE/SURVEY) |
content | Text | 文章內容 | |
videoUrl | String(500) | 影片 URL | |
expReward | Integer | Default: 100 | 經驗值獎勵 |
orderIndex | Integer | 排序索引 | |
isFreePreview | Boolean | Default: false | 是否免費預覽 |
關聯關係#
- N:1 →
Chapter(所屬章節) - 1:N →
MissionProgress(用戶進度)
業務規則#
- 免費預覽規則:
isFreePreview=true的任務無需購課即可觀看 - 獎勵領取規則:進度達 80% 才可領取 exp 獎勵(參見
MissionService:130) - 類型限制:
VIDEO:通常有videoUrlARTICLE/SURVEY:使用content欄位
5. MissionProgress(學習進度)#
用途: 用戶學習進度追蹤
實體類: tw.waterballsa.academy.entity.MissionProgress
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 進度 ID |
userId | Long | FK, NotNull | 用戶 ID |
missionId | Long | FK, NotNull | 任務 ID |
progress | Integer | Default: 0 | 完成百分比 (0-100) |
completed | Boolean | Default: false | 是否已完成 |
rewardClaimed | Boolean | Default: false | 是否已領獎勵 |
lastWatchedPosition | Integer | Default: 0 | 最後觀看位置(秒) |
videoDuration | Integer | 影片總時長(秒) | |
watchedTime | Integer | Default: 0 | 累積觀看時間(秒,防作弊用) |
completedAt | Timestamp | 完成時間 | |
updatedAt | Timestamp | Auto | 最後更新時間 |
唯一性約束#
@UniqueConstraint(columnNames = {"user_id", "mission_id"})每個用戶對每個任務只有一條進度記錄。
核心業務邏輯#
// 更新進度(只能增加,不能減少)
public void updateProgress(int newProgress) {
int cappedProgress = Math.min(100, newProgress);
if (cappedProgress > this.progress) {
this.progress = cappedProgress;
}
if (this.progress >= 100 && !this.completed) {
this.completed = true;
this.completedAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}關聯關係#
- N:1 →
User(用戶) - N:1 →
Mission(任務)
6. CourseEnrollment(購課記錄)#
用途: 用戶購課記錄(含免費、付費、贈送)
實體類: tw.waterballsa.academy.entity.CourseEnrollment
相關枚舉:
EnrollmentType(FREE, PURCHASED, GIFTED)PaymentStatus(PENDING, COMPLETED, CANCELLED)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 報名 ID |
userId | Long | FK, NotNull | 用戶 ID |
courseId | Long | FK, NotNull | 課程 ID |
type | Enum | NotNull, Default: PURCHASED | 報名類型 |
status | Enum | NotNull, Default: PENDING | 支付狀態 |
enrolledAt | Timestamp | Auto | 報名時間 |
completedAt | Timestamp | 完成時間 |
唯一性約束#
@UniqueConstraint(columnNames = {"user_id", "course_id"})同一用戶不可重複購買同一課程。
業務規則#
- 免費課程:自動設為
FREE類型,無需支付 - 付費課程:須完成付款流程(測試模式下自動通過)
- 贈送課程:由管理員手動指定為
GIFTED
🏋️ 道館系統模型#
7. Gym(道館)#
用途: 道館(包含多個挑戰任務)
實體類: tw.waterballsa.academy.entity.Gym
相關枚舉:
GymCategory(WHITE, BLACK)GymDifficulty(BEGINNER, INTERMEDIATE, ADVANCED, EXPERT, MASTER)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 道館 ID |
chapterId | Long | FK, NotNull | 所屬章節 |
name | String(200) | NotNull | 道館名稱 |
category | Enum | NotNull | 類別(白/黑帶) |
difficulty | Enum | NotNull | 難度等級 |
description | Text | 道館描述 | |
imageUrl | String(500) | 道館圖片 | |
orderIndex | Integer | 排序索引 |
關聯關係#
- N:1 →
Chapter(所屬章節) - 1:N →
GymPrerequisite(前置課程要求) - 1:N →
Challenge(挑戰任務)
8. GymPrerequisite(前置課程)#
用途: 道館的前置課程要求
實體類: tw.waterballsa.academy.entity.GymPrerequisite
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | ID |
gymId | Long | FK, NotNull | 道館 ID |
missionId | Long | FK, NotNull | 前置任務 ID |
requireRewardClaimed | Boolean | Default: true | 是否需領取獎勵 |
業務規則#
- 用戶必須完成所有前置任務才能挑戰道館
- 如果
requireRewardClaimed=true,還需領取該任務的經驗值獎勵
9. Challenge(挑戰任務)#
用途: 挑戰任務(速戰速決/實戰演練)
實體類: tw.waterballsa.academy.entity.Challenge
相關枚舉: ChallengeType(INSTANT, PRACTICAL)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 挑戰 ID |
gymId | Long | FK, NotNull | 所屬道館 |
title | String(200) | NotNull | 挑戰標題 |
type | Enum | NotNull | 類型(速戰/實戰) |
description | Text | 挑戰描述 | |
repeatable | Boolean | Default: false | 是否可重複挑戰 |
關聯關係#
- N:1 →
Gym(所屬道館) - 1:N →
ChallengeField(提交欄位定義) - 1:N →
Submission(學員提交)
業務規則#
- 速戰速決(INSTANT):快速驗證概念理解
- 實戰演練(PRACTICAL):完整專案實作
- 重複挑戰:
repeatable=true允許多次提交
10. ChallengeField(提交欄位定義)#
用途: 挑戰需要提交的檔案欄位
實體類: tw.waterballsa.academy.entity.ChallengeField
相關枚舉: FieldType(FILE, URL, TEXT, CODE)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 欄位 ID |
challengeId | Long | FK, NotNull | 所屬挑戰 |
fieldKey | String(50) | NotNull | 欄位鍵值 |
fieldName | String(100) | NotNull | 欄位顯示名稱 |
fieldType | Enum | NotNull | 欄位類型 |
required | Boolean | Default: true | 是否必填 |
orderIndex | Integer | 顯示順序 |
業務規則#
- 定義學員需要提交的內容(如:原始碼檔案、GitHub URL、說明文字)
- 驗證提交時會檢查所有
required=true的欄位
11. Submission(作業提交)#
用途: 學員的作業提交記錄
實體類: tw.waterballsa.academy.entity.Submission
相關枚舉: SubmissionStatus(PENDING, GRADING, GRADED, REJECTED)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 提交 ID |
challengeId | Long | FK, NotNull | 挑戰 ID |
userId | Long | FK, NotNull | 用戶 ID |
status | Enum | NotNull | 提交狀態 |
submittedAt | Timestamp | Auto | 提交時間 |
gradedAt | Timestamp | 批改時間 |
關聯關係#
- N:1 →
Challenge(所屬挑戰) - N:1 →
User(提交用戶) - 1:N →
SubmissionFile(提交檔案) - 1:1 →
GradeResult(批改結果)
狀態流轉#
PENDING → GRADING → GRADED
↓
REJECTED12. SubmissionFile(提交檔案)#
用途: 提交的檔案(支援多檔案)
實體類: tw.waterballsa.academy.entity.SubmissionFile
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 檔案 ID |
submissionId | Long | FK, NotNull | 提交 ID |
fieldKey | String(50) | NotNull | 對應欄位鍵值 |
fileName | String(255) | NotNull | 檔案名稱 |
fileUrl | String(500) | NotNull | 檔案 URL(S3等) |
fileSize | Long | 檔案大小(bytes) | |
contentType | String(100) | MIME 類型 |
業務規則#
fieldKey對應ChallengeField.fieldKey- 支援一個欄位上傳多個檔案
13. GradeResult(批改結果)#
用途: 老師的批改結果
實體類: tw.waterballsa.academy.entity.GradeResult
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 結果 ID |
submissionId | Long | FK, Unique | 提交 ID |
score | Integer | 分數 | |
feedback | Text | 批改意見 | |
gradedBy | Long | FK | 批改教師 ID |
gradedAt | Timestamp | Auto | 批改時間 |
關聯關係#
- 1:1 →
Submission(對應提交) - N:1 →
User(批改教師) - 1:N →
SkillRating(技能評級)
14. SkillRating(技能評級)#
用途: 技能評級(6 個類別 × 34 種級別)
實體類: tw.waterballsa.academy.entity.SkillRating
相關枚舉:
SkillCategory(熟悉設計模式的Form、區分結構與行為、游刃有餘的開發能力、需求結構化分析、抽象萃取能力、建立Well-Defined Context)SkillLevel(F- 到 ACE,共 34 個等級:F-, F, F+, E-, E, E+, D-, D, D+, C-, C, C+, B-, B, B+, A-, A, A+, AA-, AA, AA+, AAA-, AAA, AAA+, S-, S, S+, SS-, SS, SS+, SSS-, SSS, SSS+, ACE)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 評級 ID |
gradeResultId | Long | FK, NotNull | 批改結果 ID |
category | Enum | NotNull | 技能類別 |
level | Enum | NotNull | 等級評價 |
comment | Text | 評語 |
業務規則#
- 每次批改可對多個技能類別進行評級
- 評級會影響用戶的
UserSkill紀錄
15. UserSkill(用戶技能等級)#
用途: 用戶的技能等級記錄
實體類: tw.waterballsa.academy.entity.UserSkill
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 記錄 ID |
userId | Long | FK, NotNull | 用戶 ID |
category | Enum | NotNull | 技能類別 |
level | Enum | NotNull | 當前等級 |
updatedAt | Timestamp | Auto | 最後更新時間 |
唯一性約束#
@UniqueConstraint(columnNames = {"user_id", "category"})業務規則#
- 用戶每個技能類別只有一條記錄
level為最近一次評級結果
📦 訂單系統模型#
16. Order(訂單)#
用途: 課程購買訂單
實體類: tw.waterballsa.academy.entity.Order
相關枚舉: OrderStatus(PENDING, PAID, CANCELLED, REFUNDED)
欄位說明#
| 欄位 | 類型 | 約束 | 說明 |
|---|---|---|---|
id | Long | PK, Auto | 訂單 ID |
userId | Long | FK, NotNull | 用戶 ID |
courseId | Long | FK, NotNull | 課程 ID |
amount | BigDecimal(10,2) | NotNull | 訂單金額 |
status | Enum | NotNull | 訂單狀態 |
paymentMethod | String(50) | 支付方式 | |
transactionId | String(100) | 交易流水號 | |
createdAt | Timestamp | Auto | 創建時間 |
paidAt | Timestamp | 支付時間 |
關聯關係#
- N:1 →
User(購買用戶) - N:1 →
Course(購買課程)
🔗 完整 ER 關係圖#
erDiagram
User ||--o{ MissionProgress : tracks
User ||--o{ CourseEnrollment : enrolls
User ||--o{ Submission : submits
User ||--o{ UserSkill : has
User ||--o{ Order : places
Course ||--o{ Chapter : contains
Course ||--o{ CourseEnrollment : enrolled_by
Course ||--o{ Order : purchased_in
Chapter ||--o{ Mission : contains
Chapter ||--o{ Gym : contains
Mission ||--o{ MissionProgress : tracked_by
Mission ||--o{ GymPrerequisite : required_by
Gym ||--o{ GymPrerequisite : requires
Gym ||--o{ Challenge : contains
Challenge ||--o{ ChallengeField : defines
Challenge ||--o{ Submission : receives
Submission ||--o{ SubmissionFile : includes
Submission ||--|| GradeResult : has
GradeResult ||--o{ SkillRating : rates💡 設計原則#
1. 資料一致性#
- 唯一性約束:防止重複記錄(如同一用戶重複購課)
- 外鍵約束:確保參照完整性
- 級聯刪除:避免孤兒資料(如刪除課程時自動刪除章節)
2. 效能優化#
- 延遲載入:使用
FetchType.LAZY避免 N+1 查詢 - 索引策略:在常查詢欄位加索引(email, googleId 等)
- 預設值:使用
@Builder.Default減少空值判斷
3. 擴展性#
- 枚舉管理:使用 Enum 管理有限狀態集合
- 彈性欄位:預留擴展欄位(如
metadataJSON 欄位) - 關聯表設計:便於未來新增關係
4. 安全性#
- 密碼加密:使用 BCrypt 加密儲存
- 時間戳記:
@PrePersist和@PreUpdate自動管理 - 權限檢查:業務邏輯分離到 Service 層
📚 相關文檔#
🔄 模型狀態總覽#
| 模組 | Entity | 狀態 | Release |
|---|---|---|---|
| 基礎系統 | User, Course, Chapter, Mission, MissionProgress, CourseEnrollment | ✅ 已實作 | R1 |
| 道館系統 | Gym, GymPrerequisite, Challenge, ChallengeField | ✅ 已實作 | R2 |
| 作業系統 | Submission, SubmissionFile, GradeResult | ✅ 已實作 | R2 |
| 技能系統 | SkillRating, UserSkill | ✅ 已實作 | R2 |
| 訂單系統 | Order | ✅ 已實作 | R1 |