技術對照表#

本章節幫助你將熟悉的 Django/Angular 概念對應到 Spring Boot/Next.js。

Backend 對照#

整體架構對比#

層級DjangoSpring Boot本專案位置
資料模型models.py@Entity 類別entity/*.java
資料存取Django ORM / ManagerJpaRepositoryrepository/*.java
業務邏輯services.py 或 views 內@Service 類別service/*.java
API 端點views.py + urls.py@RestControllercontroller/*.java
序列化serializers.pyDTO 類別dto/*.java
設定檔settings.pyapplication.propertiesresources/
資料遷移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 對照#

整體架構對比#

概念AngularNext.js (App Router)本專案位置
路由定義app-routing.module.ts資料夾結構app/*/page.tsx
元件*.component.ts*.tsxcomponents/*.tsx
模板*.component.htmlJSX (在 tsx 內)同上
樣式*.component.scssTailwind / CSS Modulesglobals.css
狀態管理NgRx / ServiceZustandstore/*.ts
HTTP 請求HttpClientfetch 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 特點#

  1. Annotation 驅動 - @Entity, @Service, @RestController 取代設定檔
  2. Lombok 簡化程式碼 - @Getter, @Setter, @Builder 自動產生
  3. 方法命名查詢 - findByEmail() 自動產生 SQL
  4. 依賴注入 - @RequiredArgsConstructor + final 欄位

Next.js 特點#

  1. 檔案系統路由 - 資料夾結構即路由
  2. Server/Client 元件 - 'use client' 標記客戶端元件
  3. Hooks 取代生命週期 - useEffect, useState 取代 ngOnInit
  4. 無需 Module - 直接 import 使用

下一步#

理解了對照關係後,建議依序閱讀:

  1. Entity 層詳解 - 深入了解資料模型
  2. Controller 層詳解 - 了解 API 設計
  3. 路由系統 - 了解前端頁面結構