秒杀接口

ays
An Yong Shuai 2024-07-13 13:52:42 +08:00
parent 0b54d0e840
commit 9df23e4aa5
12 changed files with 196 additions and 99 deletions

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,6 @@ import java.io.File;
@RestController
@RequestMapping("/poi/excel")
public class PoiExcelApi {
private final PlaceService placeService;
public PoiExcelApi(PlaceService placeService){

View File

@ -19,6 +19,10 @@
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -34,7 +38,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>

View File

@ -0,0 +1,56 @@
package com.etl.spike.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* mq
*/
@Configuration
public class RabbitMqConfig {
//创建一个普通队列
@Bean
public Queue createPublicQueue(){
return new Queue ("public_queue");
}
//创建一个延迟队列
@Bean
public Queue createDelayQueue(){
HashMap<String, Object> map = new HashMap<> ();
map.put ("x-message-ttl",60000);
map.put ("x-dead-letter-exchange","order_change");
map.put ("x-dead-letter-routing-key","public_queue");
return new Queue ("delay_queue",true,false,false,map);
}
//创建一个交换机
@Bean
public Exchange createExchange(){
return new DirectExchange ("order_change");
}
//绑定
@Bean
public Binding createBinding(){
return new Binding ("public_queue",Binding.DestinationType.QUEUE,"order_change", "public_queue",null);
}
//绑定
@Bean
public Binding createDelayBinding(){
return new Binding ("delay_queue",Binding.DestinationType.QUEUE,"order_change", "delay_queue",null);
}
}

View File

@ -3,15 +3,17 @@ package com.etl.spike.controller;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.etl.common.result.Result;
import com.etl.spike.config.SnowFlakeComponeConfig;
import com.etl.spike.entity.Order;
import com.etl.spike.entity.request.OrderRequest;
import com.etl.spike.entity.request.Stu;
import com.etl.spike.enums.EnumMsg;
import com.etl.spike.service.IOrderService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
@ -19,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@ -37,11 +40,13 @@ public class OrderController {
private final IOrderService orderService;
private final RedissonClient redissonClient;
private final SnowFlakeComponeConfig snowFlakeComponeConfig;
public OrderController(StringRedisTemplate redisTemplate , IOrderService orderService , RedissonClient redissonClient , SnowFlakeComponeConfig snowFlakeComponeConfig){
private final RabbitTemplate rabbitTemplate;
public OrderController(StringRedisTemplate redisTemplate , IOrderService orderService , RedissonClient redissonClient , SnowFlakeComponeConfig snowFlakeComponeConfig , RabbitTemplate rabbitTemplate){
this.redisTemplate = redisTemplate;
this.orderService = orderService;
this.redissonClient = redissonClient;
this.snowFlakeComponeConfig = snowFlakeComponeConfig;
this.rabbitTemplate = rabbitTemplate;
}
/**
@ -75,9 +80,9 @@ public class OrderController {
String sign = orderRequest.getId ().toString () + orderRequest.getId ().toString () + "&" + seconds;
//生成MD5值
String md5Hex = DigestUtil.md5Hex (sign);
if ( !md5Hex.equals (orderRequest.getSign ()) ) {
throw new RuntimeException ("验签失败");
}
// if ( !md5Hex.equals (orderRequest.getSign ()) ) {
// throw new RuntimeException ("验签失败");
// }
//定义锁键 (不同的商品获取不同的锁)
String lockKey = "redissonLock_"+orderRequest.getGoodsId ();
//获取锁
@ -88,20 +93,42 @@ public class OrderController {
if(!tryLock){
return Result.error ("获取锁失败");
}
//判断用户是否重复购买
Order one = orderService.getOne (new LambdaQueryWrapper<Order> ().eq (Order :: getUserId ,
orderRequest.getUserId ()).eq (Order :: getGoodId , orderRequest.getGoodsId ()));
if(one!=null){
throw new RuntimeException ("不能重复购买");
}
String numKey = EnumMsg.REDIS_GOODS_HASH.getMessage () + orderRequest.getGoodsId ();
//检验库存
if( !redisTemplate.opsForHash ().hasKey (EnumMsg.REDIS_KEY.getMessage () , orderRequest.getGoodsId ())) {
if( !redisTemplate.opsForHash ().hasKey (EnumMsg.REDIS_GOODS_NUM.getMessage () , numKey)) {
throw new RuntimeException ("该商品库存异常");
}
String num = Objects.requireNonNull (redisTemplate.opsForHash ().get (EnumMsg.REDIS_GOODS_NUM.getMessage () ,
orderRequest.getGoodsId ())).toString ();
numKey)).toString ();
long goodsNum = Long.parseLong (num);
if(goodsNum<=0){
throw new RuntimeException ("该商品库存不足");
}
//生成订单(订单参数赋值)
Order order = new Order ();
//订单编号雪花算法
//订单编号雪花算法(获取订单编号)
long id = snowFlakeComponeConfig.getInstance ().nextId ();
//生成订单(订单参数赋值)
Order order = Order.builder ().userId (orderRequest.getUserId ()).place ("河南省濮阳市").id (id).flag (1).goodId (orderRequest.getGoodsId ()).payType (
"支付宝支付").time (LocalDateTime.now ()).build ();
boolean save = orderService.save (order);
if(!save){
throw new RuntimeException ("订单保存失败");
}
//锁定redis的库存
Object o = redisTemplate.opsForHash ().get (EnumMsg.REDIS_GOODS_NUM.getMessage () ,
numKey);
String string = o.toString ();
int value = Integer.parseInt (string);
Integer nums = value - 1;
redisTemplate.opsForHash ().put (EnumMsg.REDIS_GOODS_NUM.getMessage () , numKey , nums.toString ());
//推送到mq
rabbitTemplate.convertAndSend ("order_change","delay_queue", JSON.toJSONString (order));
return Result.success ();
} catch (InterruptedException e) {
Thread.currentThread ().interrupt (); //保持中断状态
@ -113,19 +140,6 @@ public class OrderController {
}
}
}
public static void main(String[] args){
// int a=1;
// int b=1;
// System.out.println (a==b); //true
// String c="1";
// String d="1";
// System.out.println (c.equals (d)); //true
Stu stu = new Stu ("aa",1);
Stu stu1 = new Stu ("aa" , 1);
System.out.println (stu1==stu);
System.out.println (stu1.equals (stu));
}
}

View File

@ -19,6 +19,7 @@ public class PdfController {
this.exportPdfTest = exportPdfTest;
}
/**
* pdf
* @return

View File

@ -16,6 +16,4 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/times-goods")
public class TimesGoodsController {
}

View File

@ -3,6 +3,7 @@ package com.etl.spike.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@ -21,42 +22,36 @@ import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("order")
@TableName("orders")
@Builder
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Long id;
/**
* ID
*/
private Integer goodId;
private Long goodId;
/**
* ID
*/
private Integer userId;
private Long userId;
/**
*
*/
private Integer flag;
/**
*
*/
private LocalDateTime time;
/**
*
*/
private String payType;
/**
*
*/

View File

@ -5,7 +5,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
@ -29,11 +28,14 @@ public class OrderRequest {
/**
*
*/
@NotBlank(message = "加密签名不能为空")
// @NotBlank(message = "加密签名不能为空")
private String sign;
/**
*
*/
@NotNull(message = "购买数量不能为空")
private Long num;
@NotNull(message = "用户ID不能为空")
public Long userId;
}

View File

@ -11,7 +11,8 @@ public enum EnumMsg {
// 定义一个带有错误码和描述性字符串的枚举常量
REDIS_KEY(401, "times:"),
REDIS_SMALL_KEY(402, "hashKey:"),
REDIS_GOODS_NUM(403,"goodNum:");
REDIS_GOODS_NUM(403,"goodNum:"),
REDIS_GOODS_HASH(404,"numKey:");
private final int code;
private final String message;

View File

@ -34,12 +34,10 @@ public class GoodsRedisJob {
this.timesService = timesService;
this.timesGoodsService = timesGoodsService;
}
/**
* mysqlredis
*/
@Scheduled(cron = "0 0 0 * * ?")
@Scheduled(cron = "*/30 * * * * ?")
public void testJob(){
log.info ("定时任务执行(-------------- redis场次信息同步 --------------");
//计算三天前的日期
@ -71,7 +69,8 @@ public class GoodsRedisJob {
//存入redis的hash结构,键用event前缀拼接时间戳+场次id
redisTemplate.opsForHash ().put (EnumMsg.REDIS_KEY.getMessage (), redisKey , JSON.toJSONString (item));
//库存存入redis
redisTemplate.opsForHash ().put (EnumMsg.REDIS_GOODS_NUM.getMessage () , item.getGoodsId (), item.getGoodsNum ());
String numKey = EnumMsg.REDIS_GOODS_HASH.getMessage () + item.getGoodsId ();
redisTemplate.opsForHash ().put (EnumMsg.REDIS_GOODS_NUM.getMessage () , numKey, item.getGoodsNum ().toString ());
});
}
}

View File

@ -23,3 +23,14 @@ redisson.password=123456
# ????
server.workId=2
server.datacenterId=2
spring.rabbitmq.host=47.101.130.221
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.listener.simple.prefetch=1
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual