iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0
Software Development

我的SpringBoot絕學:7+2個專案,從新手變專家系列 第 20

Day20 第六個Spring Boot專案:小型電商購物車系統(6)商品與篩選功能

  • 分享至 

  • xImage
  •  

建立Product

購物車系統一定會有商品,我們建立Product.java,來創造Product的Entity。

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private Integer price;
    private String image;
    private String category;

    public Product() {

    }

    public Product(Long id, String name, String description, Integer price, String image, String category) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.price = price;
        this.image = image;
        this.category = category;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }
}

下一個是ProductRepository.java

public interface ProductRepository extends JpaRepository<Product, Long> {
}

接著是ProductService.java

@Service
public class ProductService {
    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public Product addProduct(Product product) {
        return productRepository.save(product);
    }

    public String deleteProduct(Long id){
        productRepository.deleteById(id);
        return "Product deleted successfully";
    }

    public Product getProductById(Long id) throws Exception{
        Optional<Product> opt = productRepository.findById(id);
        if(opt.isPresent()){
            return opt.get();
        }
        throw new Exception("Product not found");
    }
}

最後是ProductController.java,完成新增、刪除、查詢商品的功能。

@RestController
@RequestMapping("/api/product")
public class ProductController {
    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @PostMapping("/")
    public ResponseEntity<Product> addProduct(@RequestBody Product product) {
        Product createdProduct = productService.addProduct(product);
        return new ResponseEntity<>(createdProduct, HttpStatus.CREATED);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteProduct(@PathVariable("id") Long id) {
        return new ResponseEntity<>(productService.deleteProduct(id), HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable("id") Long id) throws Exception {
        return new ResponseEntity<>(productService.getProductById(id), HttpStatus.OK);
    }
}

以上的部分大家應該都很熟悉了,不多做解釋。

測試

測試一下能否運作。

  • 新增商品

首先測試新增商品。

先登入取得有效的token,設定到Authorization,再來POST http://localhost:8080/api/product/,request body的內容如下

{
  "name": "Anime Inspired Novelty Bag Paimon is my Emergency Food Traveller Gift Anime Lover Gift",
  "description": "The words on bag Paimon is my Emergency food. If you are a fan of paimon, you will love this bag. It would be a great addition to your fan collection. Make a funny gift.",
  "price": 118,
  "image": "https://cdn.pixabay.com/photo/2020/10/17/03/55/woman-5661078_1280.png",
  "category": "Bags"
}

這是個範例,左邊的name、description、price、image、category是固定的,冒號的右邊可以自行修改成想要的內容。

token沒過期的話,就能得到201 Created的回應。

  • 查詢商品

再來測試查詢商品,GET http://localhost:8080/api/product/1,和新增商品的結果一樣,狀態碼變成200 OK。

  • 刪除商品

最後是刪除商品,回傳Product deleted successfully的訊息。

過濾商品的功能

我們參考以下知名的電商網站,發現它們不會一次把全部的商品都呈現在網頁上,而是分成許多頁,一頁只顯示十幾個。另外,這些網站都有篩選過濾產品的功能。

Controller

將這些功能加入到專案中,我們從Controller開始修改,讓它接收前端提供的過濾用參數(商品類別、最低價、最高價、排序方式、頁數、一頁有幾個產品),經過處理後把分頁完成的結果交給前端使用。

//UserController.java

//根據條件篩選並分頁商品
    @GetMapping("/")
    //value用來定位URL中的參數,required = false代表可以不填,required = true是強制要填資料
    public ResponseEntity<Page<Product>> findProductByFilter(@RequestParam(value = "category", required = false) String category,
                                                             @RequestParam(value = "minPrice", required = false) Integer minPrice,
                                                             @RequestParam(value = "maxPrice", required = false) Integer maxPrice,
                                                             @RequestParam(value = "sort", required = false) String sort,
                                                             @RequestParam(value = "pageNumber", required = true) Integer pageNumber,
                                                             @RequestParam(value = "pageSize", required = true) Integer pageSize){
        Page<Product> filteredProductsPage = productService.getProductsByFilter(category, minPrice, maxPrice, sort, pageNumber, pageSize);
        return new ResponseEntity<>(filteredProductsPage, HttpStatus.OK);
    }

Service

來到Service完成Controller設定的功能,取得分頁和過濾後的產品。

//UserService.java

public Page<Product> getProductsByFilter(String category, Integer minPrice, Integer maxPrice,
                                             String sort, Integer pageNumber, Integer pageSize) {

取得第pageNumber(頁數是從0開始),每頁有pageSize個產品

Pageable pageable = PageRequest.of(pageNumber, pageSize);

從資料庫取得符合條件的產品

List<Product> products = productRepository.findProductsByFilter(category, minPrice, maxPrice, sort);

設定從哪裡開始取資料。

取得的指定頁數前面有多少資料,數值等於pageNumber*pageSize。

int startIndex = (int) pageable.getOffset();

設定從哪裡結束

如果剩餘的資料>=pageSize,就只取其中的pageSize筆。

如果剩餘的資料<pageSize,將剩下的資料全部取得。

int endIndex = Math.min((startIndex + pageable.getPageSize()), products.size());

從過濾後的產品列表,截取對應頁數和數量的產品

List<Product> pageContent = products.subList(startIndex, endIndex);

回傳內容、分頁資訊(頁碼、一頁有幾筆資料)、符合過濾條件的產品數量

        return new PageImpl<>(pageContent, pageable, products.size());
    }

Repository

在Repository實現過濾的功能,這次Spring Data JPA無法幫我們了,因為條件太多了,只能自行編寫SQL語句篩選符合的資料,每一行的SQL語句添加了解釋,幫助大家理解它們的作用。


//ProductRepository.java

@Query("SELECT p FROM Product p " +

OR前的部分:如果指定了類別,就只找這個類別的商品

OR後:沒有指定類別,那就把所有商品都找出來。

"WHERE (p.category = :category OR :category LIKE '') " +

如果指定了最低價格,就只找價格比這個最低價格高的商品

"AND (:minPrice IS NULL OR p.price >= :minPrice) " +

如果指定了最高價格,就只找價格比這個最高價格低的商品

如果使用者兩個都沒指定,那就把所有商品都找出來。

"AND (:maxPrice IS NULL OR p.price <= :maxPrice) " +

依據指定的排序方式,對商品價格進行排序。

"ORDER BY " +

如果sort是price_low,按照價格由低到高(ASC)排序

"CASE :sort WHEN 'price_low' THEN p.price END ASC, " +

sort是price_high,按照價格由高到低(DESC)排序

price_low和price_high是我們自己設定的,不是SQL規定的語句

            "CASE :sort WHEN 'price_high' THEN p.price END DESC")
    public List<Product> findProductsByFilter(@Param("category") String category,
                                              @Param("minPrice") Integer minPrice,
                                              @Param("maxPrice") Integer maxPrice,
                                              @Param("sort") String sort);

一次新增多筆產品

目前我們資料庫中的產品太少了,看不出分頁的作用,需要更多的產品,一筆一筆加入的效率不高,必須想辦法提升,改良成一次可以接受多個建立產品的版本。

修改UserController.java

@PostMapping("/")
    public ResponseEntity<List<Product>> addProducts(@RequestBody Product[] products) {
        List<Product> createdProducts = new ArrayList<>();
        for(Product product : products) {
            Product p = productService.addProduct(product);
            createdProducts.add(p);
        }
        return new ResponseEntity<>(createdProducts, HttpStatus.CREATED);
    }

我們就能快速擴充資料庫中的產品,在這邊提供request body給大家使用。

[
{
  "name": "Anime Inspired Novelty Bag Paimon is my Emergency Food Traveller Gift Anime Lover Gift",
  "description": "The words on bag Paimon is my Emergency food. If you are a fan of paimon, you will love this bag. It would be a great addition to your fan collection. Make a funny gift.",
  "price": 118,
  "image": "https://cdn.pixabay.com/photo/2020/10/17/03/55/woman-5661078_1280.png",
  "category": "Bags"
},{
  "name": "Stereo Gaming Headset, Noise Cancelling Over Ear Headphones with Mic, LED Light, Bass Surround, Soft Memory Earmuffs",
  "description": "Surrounding Stereo Subwoofer Clear sound operating strong brass",
  "price": 219,
  "image": "https://cdn.pixabay.com/photo/2015/09/09/20/30/headphones-933157_1280.jpg",
  "category": "Headset"
},{
  "name": "RGB Gaming Mouse, 8000 DPI Wired Optical Gamer Mouse with 11 Programmable Buttons & 5 Backlit Modes, Software Supports DIY Keybinds Rapid Fire Button",
  "description": "Pentakill, 5 DPI Levels - Geared with 5 redefinable DPI levels (default as: 500/1000/2000/3000/4000)",
  "price": 199,
  "image": "https://cdn.pixabay.com/photo/2022/08/14/16/39/mouse-7386247_1280.jpg",
  "category": "Mice"
},
  {
  "name": "Gaming Chair, Computer Chair with Footrest and Lumbar Support, Height Adjustable Game Chair with 360°-Swivel Seat and Headrest and for Office or Gaming",
  "description": "Most Comfortable And Relaxing: Equipped with headrest and lumbar pillow.",
  "price": 1899,
  "image": "https://images.pexels.com/photos/7862491/pexels-photo-7862491.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
  "category": "Chair"
},
  {
  "name": "18-Piece Service for 6 Dinnerware Set, Triple Layer Glass and Chip Resistant, Lightweight Round Plates and Bowls Set, Country Cottage",
  "description": "18-PIECE SET: Includes (6) 10-1/4-inch dinner plates, (6) 6-3/4-inch appetizer plates and (6) 18-oz soup/cereal bowls. This set has everything you need for a full table service of 6, and it comes with the classic Corelle style.",
  "price": 699,
  "image": "https://cdn.pixabay.com/photo/2014/11/11/10/22/plate-526603_1280.jpg",
  "category": "Dinnerware"
},
  {
  "name": "Night Light, Night Lights Plug into Wall 4-Pack, Nightlight Plug in Night Light, Dusk to Dawn Night Lamp Led Night Light for Kids Bedroom, Bathroom, Hallway Warm White",
  "description": "Night Lights Plug into Wall : 0.5 watts, 50 lumen, 3000k warm white Night Light. 4pcs long-life LED, if lights up to 7hrs each day, only 0.7 Kwh per year.",
  "price": 99,
  "image": "https://cdn.pixabay.com/photo/2022/11/08/08/52/lamp-7578025_1280.jpg",
  "category": "Light"
},
  {
  "name": "Men's Non Slip Running Shoes Ultra Light Breathable Casual Walking Shoes Fashion Sneakers Mesh Workout Sports Shoes",
  "description": "Memory Foam Insole: The memory foam insole is comfortable to touch,absorbs the impact force in motion, reduces the burden on the body. It feels like you are walking on the clouds.",
  "price": 199,
  "image": "https://cdn.pixabay.com/photo/2014/06/18/18/42/running-shoe-371625_1280.jpg",
  "category": "Shoe"
},
  {
  "name": "Mens Riggs Workwear Advanced Comfort Five Pocket Jeans",
  "description": "For long days on the job, you want a work jean that keeps you comfortable. Constructed with a gusseted crotch and improved fit in the seat, thigh, and knee, this jean gives greater range of motion.",
  "price": 217,
  "image": "https://images.pexels.com/photos/603022/pexels-photo-603022.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
  "category": "Jean"
},{
  "name": "Wireless Bluetooth Headphones Over Ear,5.0 Foldable Gaming Wireless Headset Noise Cancelling Earphones Built-in Mic Macaron Bass Call Headset Hi-Fi Stereo Headsets Earbuds for Cell Phone PC Laptop",
  "description": "Large capacity Long battery life, built-in large-capacity lithium battery, long standby time, 6 hours of talk on a single charge.",
  "price": 99,
  "image": "https://images.pexels.com/photos/205926/pexels-photo-205926.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
  "category": "Headset"
},
  {
  "name": "75% Keyboard with Color Multimedia Display Mechanical Gaming Keyboard, Hot Swappable Keyboard, Gasket Mount RGB Custom Keyboard, Pre-lubed Stabilizer for Mac/Win, Black Kanagawa Theme",
  "description": "Vibrant Color Multimedia Screen: The DIY multimedia display screen design in the upper right corner of the keyboard",
  "price": 799,
  "image": "https://images.pexels.com/photos/1772123/pexels-photo-1772123.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
  "category": "Keyboard"
},
  {
  "name": " Joker Hot Swap RGB Tri-Mode Mechanical Keyboard,Wireless Bluetooth 5.0/2.4G/Wired Type-C PBT Sublimation Keycaps 65% Gaming Keyboard",
  "description": "65% Compact Hot-swappable keyboard Gateron Mechanical 3 pins switches can be hotswapped at wish and replaced by other switches.",
  "price": 699,
  "image": "https://cdn.pixabay.com/photo/2023/04/10/10/30/keyboard-7913431_1280.jpg",
  "category": "Keyboard"
}
]

測試

  1. 新增多筆商品

  1. 我們傳送GET請求到http://localhost:8080/api/product/?minPrice=&maxPrice=&category=&sort=&pageNumber=0&pageSize=5。

目前第一筆資料的id是2

  1. 將pageNumber=0修改成pageNumber=1,就能看到頁數更改造成的變化了。

可以看到第一筆資料的id變成7

下面就不放結果的圖了,因為內容看起來都很相似。

  1. GET http://localhost:8080/api/product/?minPrice=99&maxPrice=199&category=&sort=&pageNumber=0&pageSize=5 ,限制商品的最低和最高價。
  2. http://localhost:8080/api/product/?minPrice=&maxPrice=&category=Bags&sort=&pageNumber=0&pageSize=5 可以選出類別是Bags的商品
  3. http://localhost:8080/api/product/?minPrice=&maxPrice=&category=&sort=price_low&pageNumber=0&pageSize=5 將商品價格從低到高排序
  4. http://localhost:8080/api/product/?minPrice=&maxPrice=&category=&sort=price_high&pageNumber=0&pageSize=5 則是反過來,從高到低

上一篇
Day19 第六個Spring Boot專案:小型電商購物車系統(5)導入token驗證
下一篇
Day21 第六個Spring Boot專案:小型電商購物車系統(7)購物車實作
系列文
我的SpringBoot絕學:7+2個專案,從新手變專家31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言