秒杀接口

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 @RestController
@RequestMapping("/poi/excel") @RequestMapping("/poi/excel")
public class PoiExcelApi { public class PoiExcelApi {
private final PlaceService placeService; private final PlaceService placeService;
public PoiExcelApi(PlaceService placeService){ public PoiExcelApi(PlaceService placeService){

View File

@ -19,6 +19,10 @@
<spring-boot.version>2.6.13</spring-boot.version> <spring-boot.version>2.6.13</spring-boot.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
@ -34,7 +38,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId> <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 cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource; 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.common.result.Result;
import com.etl.spike.config.SnowFlakeComponeConfig; import com.etl.spike.config.SnowFlakeComponeConfig;
import com.etl.spike.entity.Order; import com.etl.spike.entity.Order;
import com.etl.spike.entity.request.OrderRequest; import com.etl.spike.entity.request.OrderRequest;
import com.etl.spike.entity.request.Stu;
import com.etl.spike.enums.EnumMsg; import com.etl.spike.enums.EnumMsg;
import com.etl.spike.service.IOrderService; import com.etl.spike.service.IOrderService;
import org.redisson.api.RLock; import org.redisson.api.RLock;
import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -37,11 +40,13 @@ public class OrderController {
private final IOrderService orderService; private final IOrderService orderService;
private final RedissonClient redissonClient; private final RedissonClient redissonClient;
private final SnowFlakeComponeConfig snowFlakeComponeConfig; 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.redisTemplate = redisTemplate;
this.orderService = orderService; this.orderService = orderService;
this.redissonClient = redissonClient; this.redissonClient = redissonClient;
this.snowFlakeComponeConfig = snowFlakeComponeConfig; this.snowFlakeComponeConfig = snowFlakeComponeConfig;
this.rabbitTemplate = rabbitTemplate;
} }
/** /**
@ -75,9 +80,9 @@ public class OrderController {
String sign = orderRequest.getId ().toString () + orderRequest.getId ().toString () + "&" + seconds; String sign = orderRequest.getId ().toString () + orderRequest.getId ().toString () + "&" + seconds;
//生成MD5值 //生成MD5值
String md5Hex = DigestUtil.md5Hex (sign); String md5Hex = DigestUtil.md5Hex (sign);
if ( !md5Hex.equals (orderRequest.getSign ()) ) { // if ( !md5Hex.equals (orderRequest.getSign ()) ) {
throw new RuntimeException ("验签失败"); // throw new RuntimeException ("验签失败");
} // }
//定义锁键 (不同的商品获取不同的锁) //定义锁键 (不同的商品获取不同的锁)
String lockKey = "redissonLock_"+orderRequest.getGoodsId (); String lockKey = "redissonLock_"+orderRequest.getGoodsId ();
//获取锁 //获取锁
@ -88,20 +93,42 @@ public class OrderController {
if(!tryLock){ if(!tryLock){
return Result.error ("获取锁失败"); 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 ("该商品库存异常"); throw new RuntimeException ("该商品库存异常");
} }
String num = Objects.requireNonNull (redisTemplate.opsForHash ().get (EnumMsg.REDIS_GOODS_NUM.getMessage () , String num = Objects.requireNonNull (redisTemplate.opsForHash ().get (EnumMsg.REDIS_GOODS_NUM.getMessage () ,
orderRequest.getGoodsId ())).toString (); numKey)).toString ();
long goodsNum = Long.parseLong (num); long goodsNum = Long.parseLong (num);
if(goodsNum<=0){ if(goodsNum<=0){
throw new RuntimeException ("该商品库存不足"); throw new RuntimeException ("该商品库存不足");
} }
//生成订单(订单参数赋值) //订单编号雪花算法(获取订单编号)
Order order = new Order ();
//订单编号雪花算法
long id = snowFlakeComponeConfig.getInstance ().nextId (); 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 (); return Result.success ();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread ().interrupt (); //保持中断状态 Thread.currentThread ().interrupt (); //保持中断状态
@ -113,19 +140,6 @@ public class OrderController {
} }
} }
} }
public static void main(String[] args){ 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; this.exportPdfTest = exportPdfTest;
} }
/** /**
* pdf * pdf
* @return * @return

View File

@ -16,6 +16,4 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/times-goods") @RequestMapping("/times-goods")
public class TimesGoodsController { 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.IdType;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@ -21,42 +22,36 @@ import java.time.LocalDateTime;
@Data @Data
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
@Accessors(chain = true) @Accessors(chain = true)
@TableName("order") @TableName("orders")
@Builder
public class Order implements Serializable { public class Order implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
* ID * ID
*/ */
@TableId(value = "id", type = IdType.AUTO) @TableId(value = "id", type = IdType.AUTO)
private Integer id; private Long id;
/** /**
* ID * ID
*/ */
private Integer goodId; private Long goodId;
/** /**
* ID * ID
*/ */
private Integer userId; private Long userId;
/** /**
* *
*/ */
private Integer flag; private Integer flag;
/** /**
* *
*/ */
private LocalDateTime time; private LocalDateTime time;
/** /**
* *
*/ */
private String payType; private String payType;
/** /**
* *
*/ */

View File

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

View File

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

View File

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