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
}
}
Artículos relacionados
BookstoreApiApplication.java
BookstoreApiApplication.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/BookstoreApiApplication.java).
Leer artículo →DataInitializer.java
DataInitializer.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/config/DataInitializer.java).
Leer artículo →SecurityConfig.java
SecurityConfig.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/config/SecurityConfig.java).
Leer artículo →AuthController.java
AuthController.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/controller/AuthController.java).
Leer artículo →ApiErrorResponse.java
ApiErrorResponse.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/dto/ApiErrorResponse.java).
Leer artículo →AuthResponse.java
AuthResponse.java — java source code from the Springboot learning materials (Springboot/bookstore-api/src/main/java/com/example/bookstore/dto/AuthResponse.java).
Leer artículo →