技術對照表#
本章節幫助你將熟悉的 Django/Angular 概念對應到 Spring Boot/Next.js。
Backend 對照#
整體架構對比#
| 層級 | Django | Spring Boot | 本專案位置 |
|---|---|---|---|
| 資料模型 | models.py | @Entity 類別 | entity/*.java |
| 資料存取 | Django ORM / Manager | JpaRepository | repository/*.java |
| 業務邏輯 | services.py 或 views 內 | @Service 類別 | service/*.java |
| API 端點 | views.py + urls.py | @RestController | controller/*.java |
| 序列化 | serializers.py | DTO 類別 | dto/*.java |
| 設定檔 | settings.py | application.properties | resources/ |
| 資料遷移 | migrations/ | JPA 自動 / Flyway | 自動產生 |
具體語法對照#
定義資料模型#
Django:
# models.py
class User(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
level = models.IntegerField(default=1)
exp = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'users'Spring Boot:
// entity/User.java
@Entity
@Table(name = "users")
@Getter @Setter // Lombok 自動產生 getter/setter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String name;
private Integer level = 1;
private Integer exp = 0;
@Column(nullable = false)
private LocalDateTime createdAt;
@PrePersist // 等同 auto_now_add
protected void onCreate() {
createdAt = LocalDateTime.now();
}
}資料庫查詢#
Django:
# 查詢單一物件
user = User.objects.get(email='test@example.com')
# 過濾查詢
users = User.objects.filter(level__gte=10)
# 關聯查詢
enrollments = user.enrollments.all()Spring Boot:
// repository/UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
// 方法命名自動產生 SQL
Optional<User> findByEmail(String email);
List<User> findByLevelGreaterThanEqual(int level);
}
// 使用方式
User user = userRepository.findByEmail("test@example.com")
.orElseThrow(() -> new RuntimeException("User not found"));定義 API 端點#
Django REST Framework:
# views.py
class MissionViewSet(viewsets.ViewSet):
@action(detail=True, methods=['post'])
def claim_reward(self, request, pk=None):
mission = get_object_or_404(Mission, pk=pk)
# 業務邏輯...
return Response({'exp_gained': 100})
# urls.py
urlpatterns = [
path('api/missions/<int:pk>/claim/', MissionViewSet.as_view({'post': 'claim_reward'})),
]Spring Boot:
// controller/MissionController.java
@RestController
@RequestMapping("/api/missions")
@RequiredArgsConstructor
public class MissionController {
private final MissionService missionService;
@PostMapping("/{missionId}/claim")
public ResponseEntity<ClaimRewardResponse> claimReward(
@PathVariable Long missionId,
Authentication authentication) {
Long userId = getUserId(authentication);
ClaimRewardResponse response = missionService.claimReward(userId, missionId);
return ResponseEntity.ok(response);
}
}Frontend 對照#
整體架構對比#
| 概念 | Angular | Next.js (App Router) | 本專案位置 |
|---|---|---|---|
| 路由定義 | app-routing.module.ts | 資料夾結構 | app/*/page.tsx |
| 元件 | *.component.ts | *.tsx | components/*.tsx |
| 模板 | *.component.html | JSX (在 tsx 內) | 同上 |
| 樣式 | *.component.scss | Tailwind / CSS Modules | globals.css |
| 狀態管理 | NgRx / Service | Zustand | store/*.ts |
| HTTP 請求 | HttpClient | fetch API | 元件內直接呼叫 |
| 依賴注入 | constructor(private svc: Service) | import + hooks | 直接 import |
具體語法對照#
路由定義#
Angular:
// app-routing.module.ts
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'courses', component: CoursesComponent },
{ path: 'courses/:id', component: CourseDetailComponent },
{ path: 'journeys/:courseId', component: JourneyComponent },
];Next.js App Router:
app/
├── page.tsx # / (HomeComponent)
├── courses/
│ ├── page.tsx # /courses (CoursesComponent)
│ └── [id]/
│ └── page.tsx # /courses/:id (CourseDetailComponent)
└── journeys/
└── [courseId]/
└── page.tsx # /journeys/:courseId (JourneyComponent)元件定義#
Angular:
// course-card.component.ts
@Component({
selector: 'app-course-card',
templateUrl: './course-card.component.html',
styleUrls: ['./course-card.component.scss']
})
export class CourseCardComponent implements OnInit {
@Input() course: Course;
@Output() enroll = new EventEmitter<number>();
ngOnInit() {
console.log('Component initialized');
}
handleEnroll() {
this.enroll.emit(this.course.id);
}
}Next.js (React):
// components/course/CourseCard.tsx
'use client';
import { useEffect } from 'react';
interface CourseCardProps {
course: Course;
onEnroll: (id: number) => void;
}
export function CourseCard({ course, onEnroll }: CourseCardProps) {
useEffect(() => {
console.log('Component initialized');
}, []);
return (
<div className="card">
<h2>{course.name}</h2>
<button onClick={() => onEnroll(course.id)}>
報名
</button>
</div>
);
}狀態管理#
Angular (NgRx):
// store/user.actions.ts
export const setUser = createAction('[User] Set User', props<{ user: User }>());
// store/user.reducer.ts
export const userReducer = createReducer(
initialState,
on(setUser, (state, { user }) => ({ ...state, user }))
);
// 使用
this.store.dispatch(setUser({ user }));
this.user$ = this.store.select(selectUser);Next.js (Zustand):
// store/useUserStore.ts
import { create } from 'zustand';
interface UserState {
user: User | null;
setUser: (user: User) => void;
logout: () => void;
}
export const useUserStore = create<UserState>((set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
}));
// 使用
const { user, setUser } = useUserStore();HTTP 請求#
Angular:
// services/course.service.ts
@Injectable({ providedIn: 'root' })
export class CourseService {
constructor(private http: HttpClient) {}
getCourses(): Observable<Course[]> {
return this.http.get<Course[]>('/api/courses');
}
getCourse(id: number): Observable<Course> {
return this.http.get<Course>(`/api/courses/${id}`);
}
}
// 使用
this.courseService.getCourses().subscribe(courses => {
this.courses = courses;
});Next.js:
// 直接在元件內使用 fetch
'use client';
import { useEffect, useState } from 'react';
export default function CoursesPage() {
const [courses, setCourses] = useState<Course[]>([]);
useEffect(() => {
async function fetchCourses() {
const token = localStorage.getItem('token');
const response = await fetch('/api/courses', {
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
});
const data = await response.json();
setCourses(data);
}
fetchCourses();
}, []);
return (
<div>
{courses.map(course => (
<CourseCard key={course.id} course={course} />
))}
</div>
);
}關鍵差異總結#
Spring Boot 特點#
- Annotation 驅動 -
@Entity,@Service,@RestController取代設定檔 - Lombok 簡化程式碼 -
@Getter,@Setter,@Builder自動產生 - 方法命名查詢 -
findByEmail()自動產生 SQL - 依賴注入 -
@RequiredArgsConstructor+final欄位
Next.js 特點#
- 檔案系統路由 - 資料夾結構即路由
- Server/Client 元件 -
'use client'標記客戶端元件 - Hooks 取代生命週期 -
useEffect,useState取代ngOnInit - 無需 Module - 直接 import 使用
下一步#
理解了對照關係後,建議依序閱讀:
- Entity 層詳解 - 深入了解資料模型
- Controller 層詳解 - 了解 API 設計
- 路由系統 - 了解前端頁面結構