S SmartDocs
Série: Springboot java 184 linhas · Atualizado 2026-08-14

BookController.java

Springboot/bookstore-api/src/main/java/com/example/bookstore/controller/BookController.java

package com.example.bookstore.controller;

import com.example.bookstore.dto.BookPatchRequest;
import com.example.bookstore.dto.BookRequest;
import com.example.bookstore.dto.BookResponse;
import com.example.bookstore.dto.PageResponse;
import com.example.bookstore.service.BookService;
import jakarta.validation.Valid;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

/**
 * 書籍 Controller:完整示範 REST API 的五種 HTTP 方法。
 *
 * ┌────────┬──────────────────────┬────────────────┬──────────┐
 * │ 方法    │ 路徑                  │ 用途            │ 權限      │
 * ├────────┼──────────────────────┼────────────────┼──────────┤
 * │ GET    │ /api/books           │ 分頁查詢全部     │ 公開      │
 * │ GET    │ /api/books/{id}      │ 查詢單筆        │ 公開      │
 * │ GET    │ /api/books/search    │ 關鍵字搜尋      │ 公開      │
 * │ POST   │ /api/books           │ 新增            │ USER 以上 │
 * │ PUT    │ /api/books/{id}      │ 完整更新        │ USER 以上 │
 * │ PATCH  │ /api/books/{id}      │ 部分更新        │ USER 以上 │
 * │ DELETE │ /api/books/{id}      │ 刪除            │ ADMIN    │
 * └────────┴──────────────────────┴────────────────┴──────────┘
 *
 * 【Controller 層的職責】只做三件事:
 *   1. 接收並解析 HTTP 請求(路徑、參數、本文)
 *   2. 呼叫 Service 執行商業邏輯
 *   3. 包裝 HTTP 回應(狀態碼、Header、JSON 本文)
 * 商業邏輯一律放在 Service,Controller 保持輕薄。
 */
@RestController
@RequestMapping("/api/books")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    /**
     * 【GET】分頁查詢所有書籍。
     *
     *   GET /api/books?page=0&size=10&sortBy=price&direction=desc
     *
     * @RequestParam:從「查詢字串(Query String)」取值,
     * defaultValue 讓所有參數都可省略:GET /api/books 也能直接用。
     *
     * GET 的特性:安全(不改變資料)且冪等(重複呼叫結果相同)。
     */
    @GetMapping
    public ResponseEntity<PageResponse<BookResponse>> getAllBooks(
            @RequestParam(defaultValue = "0") int page,          // 頁碼(從 0 開始)
            @RequestParam(defaultValue = "10") int size,         // 每頁筆數
            @RequestParam(defaultValue = "id") String sortBy,    // 排序欄位
            @RequestParam(defaultValue = "asc") String direction // asc 升冪 / desc 降冪
    ) {
        // 組合排序與分頁條件,Spring Data 會自動轉成 ORDER BY ... LIMIT ...
        Sort sort = "desc".equalsIgnoreCase(direction)
                ? Sort.by(sortBy).descending()
                : Sort.by(sortBy).ascending();
        Pageable pageable = PageRequest.of(page, size, sort);

        return ResponseEntity.ok(bookService.findAll(pageable));   // 200 OK
    }

    /**
     * 【GET】依 id 查詢單一書籍。
     *
     *   GET /api/books/1
     *
     * @PathVariable:從「路徑」中取值(/api/books/1 → id = 1)。
     * 查無資料時 Service 拋出 ResourceNotFoundException → 404。
     */
    @GetMapping("/{id}")
    public ResponseEntity<BookResponse> getBookById(@PathVariable Long id) {
        return ResponseEntity.ok(bookService.findById(id));        // 200 OK
    }

    /**
     * 【GET】關鍵字搜尋(書名或作者含關鍵字)。
     *
     *   GET /api/books/search?keyword=Spring
     */
    @GetMapping("/search")
    public ResponseEntity<List<BookResponse>> searchBooks(@RequestParam String keyword) {
        return ResponseEntity.ok(bookService.search(keyword));     // 200 OK
    }

    /**
     * 【POST】新增書籍。
     *
     *   POST /api/books
     *   Header: Authorization: Bearer <token>
     *   Body:   { "title": "...", "author": "...", "isbn": "...", ... }
     *
     * POST 的特性:非冪等——重複呼叫會(嘗試)建立多筆資料。
     *
     * REST 慣例:建立成功回 201 Created,
     * 並在 Location Header 告知「新資源的網址」。
     */
    @PostMapping
    public ResponseEntity<BookResponse> createBook(@Valid @RequestBody BookRequest request) {
        BookResponse created = bookService.create(request);

        // 組出新資源的 URI:目前請求路徑 + /{id} → /api/books/42
        URI location = ServletUriComponentsBuilder
                .fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(created.id())
                .toUri();

        return ResponseEntity.created(location).body(created);     // 201 Created
    }

    /**
     * 【PUT】完整更新書籍——「整個資源替換」。
     *
     *   PUT /api/books/1
     *   Body: 必須提供「所有」欄位(少一個就會 400 驗證失敗)
     *
     * PUT 的特性:冪等——用同樣的內容呼叫一次和呼叫十次,結果相同。
     */
    @PutMapping("/{id}")
    public ResponseEntity<BookResponse> updateBook(
            @PathVariable Long id,
            @Valid @RequestBody BookRequest request) {
        return ResponseEntity.ok(bookService.update(id, request)); // 200 OK
    }

    /**
     * 【PATCH】部分更新書籍——「只改有提供的欄位」。
     *
     *   PATCH /api/books/1
     *   Body: { "price": 450 }   ← 只想調價,其他欄位完全不用帶
     *
     * 與 PUT 的差別:PUT 要求完整資料、PATCH 允許只給部分欄位。
     */
    @PatchMapping("/{id}")
    public ResponseEntity<BookResponse> patchBook(
            @PathVariable Long id,
            @Valid @RequestBody BookPatchRequest request) {
        return ResponseEntity.ok(bookService.patch(id, request));  // 200 OK
    }

    /**
     * 【DELETE】刪除書籍——僅限管理員。
     *
     *   DELETE /api/books/1
     *   Header: Authorization: Bearer <admin 的 token>
     *
     * @PreAuthorize:方法層級授權(Method Security)。
     * SecurityConfig 的 URL 規則已限制 DELETE 需要 ADMIN,
     * 這裡再標一次是「雙重防線」,也順便示範第二種授權寫法。
     *
     * DELETE 的特性:冪等——刪除同一筆資料多次,資源狀態都是「不存在」。
     * REST 慣例:刪除成功回 204 No Content(沒有回應本文)。
     */
    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> deleteBook(@PathVariable Long id) {
        bookService.delete(id);
        return ResponseEntity.noContent().build();                 // 204 No Content
    }
}

Artigos relacionados

Springboot java EN Atualizado 2026-08-14

BookstoreApiApplication.java

BookstoreApiApplication.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/BookstoreApiApplication.java).

Ler artigo →
Springboot java EN Atualizado 2026-08-14

DataInitializer.java

DataInitializer.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/config/DataInitializer.java).

Ler artigo →
Springboot java EN Atualizado 2026-08-14

SecurityConfig.java

SecurityConfig.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/config/SecurityConfig.java).

Ler artigo →
Springboot java EN Atualizado 2026-08-14

AuthController.java

AuthController.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/controller/AuthController.java).

Ler artigo →
Springboot java EN Atualizado 2026-08-14

ApiErrorResponse.java

ApiErrorResponse.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/dto/ApiErrorResponse.java).

Ler artigo →
Springboot java EN Atualizado 2026-08-14

AuthResponse.java

AuthResponse.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/dto/AuthResponse.java).

Ler artigo →