SpringCloud large-scale series of courses are under production, welcome your attention and comments.
Programmers’ daily CV and bricks, but also know why, this series of courses can help beginners learn SpringBooot project development and SpringCloud microservice series project development
1 project preparation
This article is a series of articles. Each article has a corresponding code. The source code of each section is configured on the basis of the previous section. The corresponding video explanation course is being recorded rapidly.
As shown in the figure below, it is the main process of a seckill order process implemented by this project:
2 current limiting
The current limit of this project is that each user visits the interface to obtain the seckill address twice within 5 seconds
@Api(tags="Commodity spike module") @RestController() @RequestMapping("/seckill") @Slf4j public class SecKillController { /** * Get seckill address */ // Interface current limiting @AccessLimit(second = 5, maxCount = 2) @GetMapping("/path/{id}") public R getPath(@PathVariable("id") Long goodsId, @RequestHeader Long userId) { // Create seckill address return secKillService.createPath(userId, goodsId); } }
2.1 Current limit custom annotation AccessLimit
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AccessLimit { int second(); int maxCount(); boolean needLogin() default true; }
@Retention modification annotation is used to indicate the life cycle of the annotation. The length of the life cycle depends on the value specified by the attribute RetentionPolicy of @Retention
-
RetentionPolicy.SOURCE indicates that the annotation is only kept in the source file. When the java file is compiled into a class file, it will disappear. The source file is just to do some checking operations.
-
The RetentionPolicy.CLASS annotation is retained in the class file, but it is discarded when the jvm loads the class file. This is the default life cycle class file (default) requires some preprocessing operations at compile time, such as generating some auxiliary code (such as ButterKnife)
-
The RetentionPolicy.RUNTIME annotation is not only saved in the class file, but also exists after the jvm loads the class file. It also exists at runtime. It is necessary to dynamically obtain annotation information at runtime.
@Target describes the range of objects modified by Annotation
- 1.CONSTRUCTOR: used to describe the constructor
- 2.FIELD: used to describe the field
- 3.LOCAL_VARIABLE: Used to describe local variables
- 4.METHOD: used to describe the method
- 5.PACKAGE: used to describe the package
- 6.PARAMETER: Used to describe parameters
- 7.TYPE: Used to describe classes, interfaces (including annotation types) or enum declarations
2.2 Custom Interceptor Handling Current Limiting
@Component @Slf4j public class AccessLimitInterceptor implements HandlerInterceptor { @Autowired RedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("==============================AccessLimitInterceptor interceptor=============================="); if (handler instanceof HandlerMethod) { HandlerMethod hm = (HandlerMethod) handler; AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class); if (Objects.isNull(accessLimit)) { return true; } int second = accessLimit.second(); int maxCount = accessLimit.maxCount(); boolean needLogin = accessLimit.needLogin(); String uri = request.getRequestURI(); if (needLogin) { //Login is required This project uses Spring Security to implement security authentication //You will come here only after the certification is passed String userId = request.getHeader("userId"); // UserInfo userInfo = getUserInfoFromRequest(request); // if (Objects.isNull(userInfo)) { // toRender(response, "Please log in"); // return false; // } uri = uri + ":" + userId; } return toLimit(response, second, maxCount, uri); } return true; } // Simple counter current limiting private boolean toLimit(HttpServletResponse response, int second, int maxCount, String uri) throws IOException { ValueOperations valueOperations = redisTemplate.opsForValue(); Integer count = (Integer) valueOperations.get(uri); if (Objects.isNull(count)) { valueOperations.set(uri, 1, second, TimeUnit.SECONDS); } else if (count < maxCount) { // Increment the counter by one valueOperations.increment(uri); } else { log.info("Trigger current limiting rules Current limiting{}access in seconds{}times, current visit{} {}Second-rate ",second,maxCount,count,uri); // access limit exceeded toRender(response, "The current order number is queued, please try again later"); return false; } return true; }
3 Initiate seckill
After the user obtains the seckill address, use the seckill address to initiate a seckill
/** * start spike * @param goodsId * @param userId * @return */ @GetMapping("/{path}/toSecKill/{id}") public R toSecKill(@PathVariable("id") Long goodsId, @PathVariable String path, @RequestHeader Long userId) { // Verify that the path is legal boolean isLegal = secKillService.checkPath(path, userId, goodsId); if (!isLegal) { return R.error("invalid path"); } return secKillService.isToSecKill(goodsId, userId); }
First, check the legality of the address, which is consistent with the above-mentioned rules for generating an address, and then the process of pre-ordering and generating an order number:
@Service("secKillService") @Slf4j public class SecKillServiceImpl implements SecKillService, InitializingBean { @Autowired private RedisTemplate redisTemplate; @Autowired private SecKillGoodsService secKillGoodsService; @Autowired private SecKillOrderService secKillOrderService; @Autowired private OrderMQSender mqSender; // A collection of map s with empty inventory private Map<Long, Boolean> emptyStockMap = new HashMap<>(); @Autowired SnowFlakeCompone snowFlakeCompone; @Override public R isToSecKill(Long goodsId, Long userId) { // Repeat buying SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + userId + ":" + goodsId); if (!Objects.isNull(seckillOrder)) { return R.error("Repeat buying"); } // Memory marking to reduce Redis access if (emptyStockMap.get(goodsId)) { // Inventory is empty return R.error("Out of stock"); } //stock key String redisStockKey = "seckillGoods:" + goodsId; Boolean aBoolean = redisTemplate.hasKey(redisStockKey); if(Boolean.FALSE.equals(aBoolean)){ emptyStockMap.put(goodsId, true); return R.error("Out of stock"); } ValueOperations valueOperations = redisTemplate.opsForValue(); // Pre-decrease inventory Long stock = valueOperations.decrement(redisStockKey); // Inventory shortage if (stock < 0) { emptyStockMap.put(goodsId, true); valueOperations.increment(redisStockKey); return R.error("Out of stock"); } //generate order number long sn = snowFlakeCompone.getInstance().nextId(); //Save to redis status doing is being processed redisTemplate.opsForValue().set("sn:"+sn, "doing"); // Spike message SecKillMessage message = new SecKillMessage(userId, goodsId,sn); mqSender.sendSecKillMessage(JsonUtils.toJson(message)); //Return the order number to the front end return R.okData(sn); }
The inventory information stored in the internal memory and the inventory information stored in Redis are synchronized through the scheduled task one hour before the start of the seckill, and the scheduled task will be integrated in subsequent chapters.
The order number is returned to the front end, and the front end starts to query the interface of the order status in turn
/** * Query order status and details * Commodity-order entry call * @param sn * @return */ @GetMapping("/statues/detail/{sn}") public R detailAndStatue(@PathVariable("sn") Long sn) { //Query status in redis Boolean aBoolean = redisTemplate.hasKey("sn:" + sn); if(Boolean.FALSE.equals(aBoolean)){ return R.error("Order failed"); } String snStatues = redisTemplate.opsForValue().get("sn:" +sn).toString(); //Not finished order if(snStatues.equals("doing")){ return R.error(202,"Processing"); } //Failed to place an order if(!snStatues.equals("ok")){ return R.error(203,snStatues); } //The order is placed successfully Return to the order information OrderVo orderVo = orderService.detailFromSn(sn); return R.okData(orderVo); }
After the front-end checks that the order is placed successfully, it loads and displays the order details, and initiates payment.
4 message queue
The definition of message queue and switch is as follows:
@Configuration public class OrderRabbitMQTopicConfig { private static final String QUEUE = "seckillQueue"; private static final String EXCHANGE = "seckillExchange"; @Bean public Queue seckillQueue() { return new Queue(QUEUE); } @Bean public TopicExchange seckillExchange() { return new TopicExchange(EXCHANGE); } @Bean public Binding binding() { return BindingBuilder .bind(seckillQueue()) .to(seckillExchange()).with("seckill.#"); } }
The sender of the pre-order message
@Service @Slf4j public class OrderMQSender { @Autowired private RabbitTemplate rabbitTemplate; /** * The message queue where the seckill order goes * @param msg */ public void sendSecKillMessage(String msg) { log.info("Send a message:{}", msg); //Parameter one switch name //Parameter two route name rabbitTemplate.convertAndSend("seckillExchange", "seckill.message", msg); } }
The message receiver of the seckill order has performed a second check on the inventory of the order
@Service @Slf4j public class OrderMQReceiver { @Autowired private SecKillGoodsService secKillGoodsService; @Autowired private RedisTemplate redisTemplate; @Autowired private OrderService orderService; @RabbitListener(queues = "seckillQueue") public void receiveSecKillMessage(String message) { log.info("Received Lightning Deal order message:{}", message); SecKillMessage secKillMessage = JsonUtils.toObj(message, SecKillMessage.class); Long userId = secKillMessage.getUserId(); Long goodsId = secKillMessage.getGoodsId(); Long sn = secKillMessage.getSn(); //Query flash sale products SeckillGoods seckillGoods = secKillGoodsService.findByGoodsId(goodsId); // Inventory shortage if (seckillGoods.getStockCount() < 1) { //Update redis order status redisTemplate.opsForValue().set("sn:" + sn, "Lightning deal failed Insufficient inventory",1, TimeUnit.DAYS); log.error("Inventory shortage"); return; } // Determine whether to repeat buying // Repeat buying SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + userId + ":" + goodsId); if (!Objects.isNull(seckillOrder)) { //Update redis order status redisTemplate.opsForValue().set("sn:" + sn, "Failed to kill in seckill Repeat snapping up",1, TimeUnit.DAYS); log.error("Repeat buying userId:{} goodsId:{}",userId,goodsId); return; } // place an order orderService.toSecKill(goodsId, userId,sn); } }
The project source code is here: https://gitee.com/android.long/spring-boot-study/tree/master/biglead-api-11-snow_flake
If you are interested, you can pay attention to the public account: biglead
- Create a SpringBoot basic project
- SpringBoot project integrates mybatis
- SpringBoot integrates Druid data source [SpringBoot series 3]
- SpringBoot MyBatis implements page query data [SpringBoot series 4]
- SpringBoot MyBatis-Plus Integration [SpringBoot Series 5]
- SpringBoot mybatis-plus-generator code generator [SpringBoot series 6]
- SpringBoot MyBatis-Plus Paging Query [SpringBoot Series 7]
- SpringBoot integrates Redis cache and realizes basic data cache [SpringBoot series 8]
- SpringBoot integrates Spring Security to achieve security authentication [SpringBoot Series 9]
- SpringBoot Security Authentication Redis Cache User Information [SpringBoot Series 10]
- SpringBoot integrates RabbitMQ message queue [SpringBoot series 11]
- SpringBoot combines RabbitMQ and Redis to realize concurrent ordering of commodities [SpringBoot Series 12]
- SpringBoot snowflake algorithm generates product order number [SpringBoot series 13]
- SpringBoot RabbitMQ Delay Queue Cancel Order [SpringBoot Series 14] This article is developed based on this project