秒杀接口
parent
0b54d0e840
commit
9df23e4aa5
File diff suppressed because one or more lines are too long
|
@ -17,7 +17,6 @@ import java.io.File;
|
|||
@RestController
|
||||
@RequestMapping("/poi/excel")
|
||||
public class PoiExcelApi {
|
||||
|
||||
private final PlaceService placeService;
|
||||
|
||||
public PoiExcelApi(PlaceService placeService){
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ public class PdfController {
|
|||
this.exportPdfTest = exportPdfTest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 导出为pdf
|
||||
* @return
|
||||
|
|
|
@ -16,6 +16,4 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
@RequestMapping("/times-goods")
|
||||
public class TimesGoodsController {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 收货地址
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -34,12 +34,10 @@ public class GoodsRedisJob {
|
|||
this.timesService = timesService;
|
||||
this.timesGoodsService = timesGoodsService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开启一个定时任务将mysql中的最近三天的场次,商品信息和库存存入redis
|
||||
*/
|
||||
@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 ());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue