mirror of
https://gitee.com/lakernote/easy-admin.git
synced 2026-09-03 13:37:29 +08:00
refactor: DuplicateRequestLimiter and ratelimit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package com.laker.admin.module.ext.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import com.laker.admin.module.ext.service.ProductService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "△△△4.电商△△△")
|
||||
@RequestMapping("/product")
|
||||
@RestController
|
||||
public class ProductController {
|
||||
|
||||
@Autowired
|
||||
private ProductService productService;
|
||||
|
||||
/**
|
||||
* 抢购商品
|
||||
* 运用工具(像 Postman 或者 Apache JMeter)模拟多个线程同时访问 /purchase/{productId} 接口。
|
||||
* 当多个线程同时抢购商品时,就可能出现超卖的情况。
|
||||
*
|
||||
* @param productId 商品ID
|
||||
* @return 抢购结果
|
||||
*/
|
||||
@SaIgnore
|
||||
@GetMapping("/purchase/{productId}")
|
||||
@Operation(summary = "抢购商品")
|
||||
public String purchase(@PathVariable Long productId) {
|
||||
boolean success = productService.purchaseProduct(productId);
|
||||
if (success) {
|
||||
return "抢购成功";
|
||||
} else {
|
||||
return "库存不足,抢购失败";
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/main/java/com/laker/admin/module/ext/entity/Product.java
Normal file
43
src/main/java/com/laker/admin/module/ext/entity/Product.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.laker.admin.module.ext.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
|
||||
public class Product {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String name;
|
||||
private int stock;
|
||||
|
||||
public Product() {
|
||||
}
|
||||
|
||||
public Product(String name, int stock) {
|
||||
this.name = name;
|
||||
this.stock = stock;
|
||||
}
|
||||
|
||||
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 int getStock() {
|
||||
return stock;
|
||||
}
|
||||
|
||||
public void setStock(int stock) {
|
||||
this.stock = stock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.laker.admin.module.ext.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.laker.admin.module.ext.entity.Product;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
public interface ProductMapper extends BaseMapper<Product> {
|
||||
// 更新商品库存,防止超卖
|
||||
@Update("update product set stock = stock - 1 where id = #{productId} and stock > 0")
|
||||
int purchaseProduct(Long productId);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.laker.admin.module.ext.service;
|
||||
|
||||
import com.laker.admin.module.ext.entity.Product;
|
||||
import com.laker.admin.module.ext.mapper.ProductMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ProductService {
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
// @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
|
||||
public boolean purchaseProduct(Long productId) {
|
||||
log.info("开始购买商品,商品ID: {}", productId);
|
||||
// 查询商品库存
|
||||
Product product = productMapper.selectById(productId);
|
||||
// 如果商品不存在或者库存不足,返回失败
|
||||
if (product == null || product.getStock() <= 0) {
|
||||
log.warn("商品不存在或者库存不足,商品ID: {}", productId);
|
||||
return false;
|
||||
}
|
||||
// 更新商品库存,如果库存不足,更新失败
|
||||
final int updateCount = productMapper.purchaseProduct(productId);
|
||||
if (updateCount > 0) {
|
||||
log.info("购买成功,商品ID: {}", productId);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("购买失败,商品ID: {}", productId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 商品表
|
||||
DROP TABLE IF EXISTS `product`;
|
||||
CREATE TABLE `product` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) DEFAULT NULL,
|
||||
`stock` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
INSERT INTO product (id, name, stock) VALUES(1, 'aa', 100);
|
||||
30
src/test/java/com/laker/admin/example/ThreadCounter.java
Normal file
30
src/test/java/com/laker/admin/example/ThreadCounter.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.laker.admin.example;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ThreadCounter {
|
||||
private int count = 0;
|
||||
|
||||
@Test
|
||||
public void testThreadCounter() throws InterruptedException {
|
||||
Runnable task = new Runnable() {
|
||||
public void run() {
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Thread t1 = new Thread(task);
|
||||
t1.start();
|
||||
|
||||
Thread t2 = new Thread(task);
|
||||
t2.start();
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
// Assertions.assertThat(count).isEqualTo(20000);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user