首页IT科技springcloude(SpringCloud(十一)- 秒杀 抢购)

springcloude(SpringCloud(十一)- 秒杀 抢购)

时间2025-08-02 08:31:37分类IT科技浏览4900
导读:1、流程图 1.1 数据预热...

1             、流程图

1.1 数据预热

1.2 抢购

1.3 生成订单 (发送订单消息)

1.4 订单入库 (监听 消费订单消息)

1.5 查看订单状态

1.6 支付 (获取支付链接 )

1.7 支付成功 微信回调 (发送 支付成功消息)

1.8 支付成功 返回给前端成功 (监听 支付成功消息)

2                   、incr 和 setnx

2.1 incr

Redis Incr 命令将 key 中储存的数字值增一             。 如果 key 不存在             ,那么 key 的值会先被初始化为 0                    ,然后再执行 INCR 操作                    。且将key的有效时间设置为长期有效       。

2.1.1 常见使用场景 2.1.1.1 计数

我们可能常会统计网站页面每天访问量       ,通过incr命令在redis中设置key       ,每次增加1                   ,设置24小时过期             。

2.1.1.2 限流

日常的开放平台API一般常有限流             ,利用redis的incr命令可以实现一般的限流操作                    。如限制某接口每分钟请求次数上限1000次

2.1.1.3 幂等

MQ防止重复消费也可以利用INCR命令实现       ,如订单防重                    ,订单5分钟之内只能被消费一次             ,订单号作为redis的key

2.2 sexnx

Redis使用setnx命令实现分布式锁;

2.1.1 加锁 版本一#但是这种方式的缺点是容易产生死锁,因为客户端有可能忘记解锁                    ,或者解锁失败      。 setnx key value 版本二#给锁增加了过期时间                    ,避免出现死锁       。但这两个命令不是原子的,第二步可能会失败             ,依然无法避免死锁问题                    。 setnx key value expire key seconds 版本三(推荐)#通过“set...nx...             ”命令                    ,将加锁       、过期命令编排到一起       ,它们是原子操作了             ,可以避免死锁             。 set key value nx ex seconds 2.1.2 解锁

解锁就是删除代表锁的那份数据       。

del key

参考博客:https://blog.csdn.net/weixin_45525272/article/details/126562119

3       、实现代码

主要是抢购的业务;

3.1模块分析

3.2 web模块

3.2.1 application.yml 点击查看代码 # 端口 server: port: 8106 # 服务名 spring: application: name: edocmall-seckill-web # redis 配置 redis: host: 127.0.0.1 port: 6379 # eureka 注册中心的配置 eureka: client: service-url: defaultZone: http://127.0.0.1:8096/eureka # 注册中心的地址 # 秒杀抢购自定义配置 kh96: seckill: buy-user-count-prefix: seckill-buy-user-count # 请求用户数量的限制头标识 buy-prod-stock-count: 100 # 初始化腔骨商品的库存数量                   ,存入redis       ,实际开发中       ,没有此配置(初始化商品库存                   ,在洗头膏添加抢购商品是) buy-prod-stock-prefix: seckill-buy-prod-stock # 抢购商品数量 头标识 buy-user-lock-prefix: seckill-buy-user-lock # 锁定抢购用户的头标识 buy-prod-lock-prefix: seckill-buy-prod-lock # 锁定商品库存锁头标识 3.2.2 SeckillConfig 配置类 点击查看代码 /** * Created On : 9/11/2022. * <p> * Author : huayu * <p> * Description: SeckillConfig */ @Data @Component @ConfigurationProperties(prefix = "kh96.seckill") public class SeckillConfig { /* 请求用户数量的限制头标识 */ private String buyUserCountPrefix; /* 初始化抢购商品的库存数量 */ private Integer buyProdStockCount; /* 抢购商品数量 头标识 */ private String buyProdStockPrefix; /* 锁定抢购用户的头标识 */ private String buyUserLockPrefix; /* 锁定商品库存锁头标识 */ private String buyProdLockPrefix; } 3.2.3 抢购接口和实现类 3.2.3.1 接口 点击查看代码 /** * Created On : 9/11/2022. * <p> * Author : huayu * <p> * Description: 秒杀抢购的业务接口 */ public interface SeckillService { /** * @author : huayu * @date : 9/11/2022 * @param : [prodId, stockCount] * @return : void * @description : 初始化商品库存到缓存 */ boolean initProdStock2Redis(String prodId,Integer stockCount); /** * @author : huayu * @date : 9/11/2022 * @param : [productId] * @return : boolean * @description : 校验抢购商品的请求数量是否达到上限 */ boolean checkBuyUserCount(String productId); /** * @author : huayu * @date : 9/11/2022 * @param : [prodId, buyCount] * @return : boolean * @description : 校验商品库存是否充足 */ boolean checkProdStockEnough(String prodId,Integer buyCount); /** * @author : huayu * @date : 9/11/2022 * @param : [userId, prodId] * @return : boolean * @description : 校验用户是否已经抢购过当前商品 */ boolean checkBuyUserBought(String userId,String prodId); /** * @author : huayu * @date : 9/11/2022 * @param : [userId] * @return : boolean * @description : 校验商品库存是否锁定 */ boolean checkProdStockLocked(String prodId); /** * @author : huayu * @date : 9/11/2022 * @param : [prodId] * @return : boolean * @description : 释放库存锁 */ void unLockProdStock(String prodId); /** * @author : huayu * @date : 9/11/2022 * @param : [prodId, butCount] * @return : void * @description : 扣减库存 */ void subProdStockCount(String prodId, Integer butCount); } 3.2.3.2 实现类 点击查看代码 /** * Created On : 9/11/2022. * <p> * Author : huayu * <p> * Description: 抢购业务接口实现类 */ @Service public class SeckillServiceImpl implements SeckillService { @Autowired private RedisUtils redisUtils; @Autowired private SeckillConfig seckillConfig; @Override public boolean initProdStock2Redis(String prodId, Integer stockCount) { // 判断redis中             ,是否存在已经初始化的商品库存数据       ,如果已经存在                    ,不需要再次初始化 if (ObjectUtils.isEmpty(redisUtils.get(seckillConfig.getBuyProdStockPrefix() + ":" + prodId))){ // 将商品库存添加到redis中 return redisUtils.set(seckillConfig.getBuyProdStockPrefix() + ":" + prodId, stockCount); } // 已经存在商品库存             ,不需要设置 return false; } @Override public boolean checkBuyUserCount(String prodId) { // 使用redis的incr操作,校验当前商品的抢购用户数是否达到上限 return redisUtils.incr(seckillConfig.getBuyUserCountPrefix() + ":" + prodId, 1) > Integer.parseInt(redisUtils.get(seckillConfig.getBuyProdStockPrefix() + ":" + prodId).toString()) * 2; } @Override public boolean checkProdStockEnough(String prodId, Integer buyCount) { //校验商品库存                    ,是否大于等于用户抢购数量                    ,如果小于,代表库存不足 //TODO 如果redis 没有查到库存(库存没初始化             ,或者后台误删                    ,必须要进行数据库查询操作       ,获取库存             , // 要加锁                   ,只有一个线程取操作)       ,再同步到redis return Integer.parseInt(redisUtils.get(seckillConfig.getBuyProdStockPrefix() + ":" + prodId).toString()) >= buyCount; } @Override public boolean checkBuyUserBought(String userId, String prodId) { //使用redis的分布式锁       ,sexnx, // 如果上锁成功                   ,代表没有买过             ,可以继续购买,如果上锁失败       ,表示购买过不能继续购买 //锁时长为正抢购活动时长 return ! redisUtils.lock( seckillConfig.getBuyUserLockPrefix()+":"+userId, null, 15*60); //模拟 抢购时长 } @Override public boolean checkProdStockLocked(String prodId) { //使用redis的分布式锁                    ,sexnx,如果上锁成功             ,代表库存没有被锁,如果上锁失败代表库存被其他用户锁定不能购买 //锁库存必须要增加时长限制                    ,防止库存锁释放失败                    ,导致用户无法抢购,过期仍然会释放 return ! redisUtils.lock(seckillConfig.getBuyProdLockPrefix()+":"+prodId, null, 2*60); //模拟锁2分钟 } @Override public void unLockProdStock(String prodId) { //删除redis中的 库存锁 redisUtils.del(seckillConfig.getBuyProdLockPrefix()+":"+prodId); } @Override public void subProdStockCount(String prodId, Integer butCount) { //扣减 redis中缓存的商品库存数量 //TODO redis缓存中操成功后             ,要发异步消息 到队列 更新数据库中商品库存呢 redisUtils.decr(seckillConfig.getBuyProdStockPrefix() + ":" + prodId, butCount); } } 3.2.4 远程调用订单模块进行下单 点击查看代码 /** * Created On : 10/11/2022. * <p> * Author : huayu * <p> * Description: SeckillOrderFeignService */ @FeignClient(value = "edocmall-seckill-order") public interface SeckillOrderFeignService { /** * @author : huayu * @date : 10/11/2022 * @param : [userId, prodId, buyCount] * @return : java.lang.String * @description : 远程调用订单中心                    ,生成秒杀抢购订单的接口 */ @GetMapping("/createSeckillOrder") String feignInvokeCreateSeckillOrder(@RequestParam(value = "userId") String userId, @RequestParam(value = "prodId") String prodId, @RequestParam(value = "buyCount") Integer buyCount); } 3.2.5 抢购 控制层 点击查看代码 /** * Created On : 9/11/2022. * <p> * Author : huayu * <p> * Description: 秒杀操作入口 */ @Slf4j @RestController @Api(tags = "秒杀操作") public class SeckillController { @Autowired private SeckillService seckillService; @Autowired private SeckillOrderFeignService seckillOrderFeignService; /** * @author : zhukang * @date : 2022/11/9 * @param : [userId, prodId, buyCount] * @return : com.kgc.scd.util.RequestResult<?> * @description : 模拟初始化商品库存       ,实际开发中             ,没有此操作                   ,是在后台添加抢购商品预热是       ,直接同步到redis */ @GetMapping("/initProdStock") @ApiOperation(value = "1 初始库存", notes = "抢购商品预热       ,初始化商品库存数量到redis中") @ApiImplicitParams({ @ApiImplicitParam(value = "初始化商品id",name = "prodId"), @ApiImplicitParam(value = "初始化商品数量",name = "stockCount") }) public RequestResult<?> initProdStock(@RequestParam String prodId, @RequestParam Integer stockCount){ log.info("------ 初始化商品:{}                   ,库存数量:{}             ,到redis缓存中 ------", prodId, stockCount); // 增加业务逻辑处理:防止瞬时请求过多       ,使用redis的incr原子操作                    ,进行请求用户数的统计计数             ,如果请求已经达到了上限(比如:设定请求用户数最多是抢购商品库存的2倍),其它后续所有的请求都默认无效                    ,返回失败:当前请求用户过多                    ,请稍后重试 if(seckillService.initProdStock2Redis(prodId, stockCount)){ return ResultBuildUtil.success("初始化商品库存成功!"); } // 初始化商品库存失败 return ResultBuildUtil.fail("601", "初始化商品库存失败,请确认redis库存是否已初始化!"); } @GetMapping("/seckillBuy") @ApiOperation(value = "2 开始秒杀", notes = "基于redis             ,RabbitMQ消息队列                    ,实现有序秒杀抢购功能") @ApiImplicitParams({ @ApiImplicitParam(value = "用户id",name = "userId"), @ApiImplicitParam(value = "商品id",name = "prodId"), @ApiImplicitParam(value = "抢购数量",name = "buyCount") }) public RequestResult<?> seckillBuy(@RequestParam String userId, @RequestParam String prodId, @RequestParam Integer buyCount){ log.info("------ 用户:{}       ,购买商品:{}             ,购买数量:{}                   ,开始抢购 ------", userId, prodId, buyCount); // TODO 增加请求许可校验(自定义注解+拦截器)       ,校验请求此接口的所有用户是否是已登录用户       ,如果没有登录                   ,提示请登录 // TODO 增加接口参数的校验             ,判断用户是否是系统正常用户       ,不能是状态异常用户                    ,判断商品的数据是否正确(切记:涉及系统内数据不要信任请求参数             ,要信任系统的缓存的数据库) // TODO 为了提高抢购入口的并发处理能力,要减少数据库交互                    ,可以设计为根据商品编号                    ,从redis缓存中查询商品,如果商品信息存在             ,则参与抢购                    ,如果不存在       ,还是需要到数据库查询商品             ,如果数据库中存在                   ,将商品信息存入redis缓存       ,如果数据库不存在       ,则直接提示抢购失败                    。 // TODO 此种场景                   ,正常情况             ,没有问题       ,可能存在的问题                    ,某个商品             ,是首次参与抢购,缓存中没有数据                    ,但是数据库有                    ,虽然上面的处理方式,可以解决             ,但是在高并发场景下                    ,同一时刻会有大批量的请求来秒杀此商品       ,此时同时去缓存中获取商品数据             ,没有获取到                   ,又同时去数据库查询       ,就会导致数据库扛不住压力       ,可能直接数据库挂掉             。 // TODO 解决方式:缓存商品数据一般都是在后台添加抢购商品时                   ,直接对商品进行预热处理             ,即:事先把参与抢购的商品直接同步到redis缓存中       ,这样当抢购开始                    ,直接从redis缓存就可以获取到商品             ,而不会出现缓存击穿问题。 // TODO 虽然商品数据预热方式,可以解决此类问题                    ,但是可能还会存在例外(比如:缓存中的商品由于后台失误操作                    ,导致设置的过期时间不对,缓存时间提前过期             ,或者缓存数据误删除)                    ,此种情况还是需要当缓存不存在商品数据       ,从数据库查询             ,放入缓存的逻辑                    。 // TODO 解决方式:可以进行加锁                   ,限制在高并发的情况下访问数据库       ,如果同一时刻       ,缓存中没有获取到商品数据库                   ,就进入查询数据库操作             ,但是在进入查询前       ,增加分布式锁                    ,只有获取到锁的请求             ,才可以查询数据库并加入到缓存中(加完就释放锁),获取不到锁的请求                    ,直接返回失败(损失当前请求                    ,后续请求进行弥补,只要一个操作成功             ,缓存中的数据就存在                    ,后续的请求自然可以获取到数据) // TODO 极端情况:redis无法使用       ,必须要增加redis的高可用             ,确保redis永远是有效的                   ,考虑的模式就是集群模式下的哨兵机制                    。或者把大批量的请求直接放到消息队列       ,进行缓冲处理。 log.info("------------------------------ 进行请求用户数的统计计数       ,防止请求过多 -----------------------------------------"); // 增加业务逻辑处理:防止瞬时请求过多                   ,使用redis的incr原子操作             ,进行请求用户数的统计计数       ,如果请求已经达到了上限(比如:设定请求用户数最多是抢购商品库存的2倍)                    ,其它后续所有的请求都默认无效             ,返回失败:当前请求用户过多,请稍后重试 if(seckillService.checkBuyUserCount(prodId)){ return ResultBuildUtil.fail("602", "抢购失败                    ,当前抢购用户过多                    ,请稍后重试!"); } log.info("------------------------------ 查看库存是否充足 -----------------------------------------"); //校验商品库存数量是否充足,可以进行后续抢购             ,日过不足,直接抢购失败 log.info("------ 用户:{}                    ,购买商品:{}       ,购买数量:{}             ,库存校验 ------", userId, prodId, buyCount); if(!seckillService.checkProdStockEnough(prodId,buyCount)){ //库存不足                   ,返回抢购失败 log.info("------ 用户:{}       ,购买商品:{}       ,购买数量:{}                   ,库存不足 ------", userId, prodId, buyCount); return ResultBuildUtil.fail("603", "抢购失败             ,库存不足       ,缺货!"); } log.info("------------------------------ 判断用户是否抢购过 -----------------------------------------"); //增加幂等操作:当前抢购用户只能抢购一次                    ,如果已经抢购过商品             ,不允许再次抢购(限制一个用户同一个抢购商品,整个抢购期间只能抢购一次) log.info("------ 用户:{}                    ,购买商品:{}                    ,购买数量:{},锁定抢购用户,如果 已经抢购过商品             ,不允许再次抢购 ------", userId, prodId, buyCount); if(seckillService.checkBuyUserBought(userId,prodId)){ //用户抢购过                    ,返回抢购失败 log.info("------ 用户:{}       ,购买商品:{}             ,购买数量:{}                   ,重复抢购 ------", userId, prodId, buyCount); return ResultBuildUtil.fail("604", "抢购失败       ,重复抢购!"); } log.info("------------------------------ 用户获取库存锁 -----------------------------------------"); //执行抢购业务       ,先给商品库存上锁(锁库存)                   ,如果上锁成功             ,代表当前用户继续抢购商品       ,如果上锁失败                    ,说明有人抢购             ,进行等待 log.info("------ 用户:{},购买商品:{}                    ,购买数量:{}                    ,(尝试获取库存锁) 执行抢购业务,先给商品库存上锁(锁库存),拿到 库存锁 才可以开始下单 ------", userId, prodId, buyCount); while (true){ //死循环             ,尝试锁库存                    ,如果锁库存成功       ,代表库存所已经被释放 if(!seckillService.checkProdStockLocked(prodId)){ log.info("------ 用户:{}             ,购买商品:{}                   ,购买数量:{}       ,锁库存成功       ,拿到 库存锁                    ,开始下单------", userId, prodId, buyCount); //结束循环             ,执行抢购下单 break; } log.debug("------ 用户:{}       ,购买商品:{}                    ,购买数量:{}             ,锁库存成功失败,等待             。                    。      。------", userId, prodId, buyCount); } log.info("------------------------------ 已经获得库存锁,再次判断库存是否充足 -----------------------------------------"); //考虑高并发的场景: //多人同时校验库存成功                    ,但是进入到抢购下单业务时                    ,库存呢只够部分人购买,需要再确定库存是否足够 //校验商品库存数量是否充足             ,可以进行后续抢购                    ,日过不足,直接抢购失败 log.info("------ 用户:{}       ,购买商品:{}             ,购买数量:{}                   ,锁库存后(拿到库存锁后) 再次 库存校验 ------",userId, prodId, buyCount); if(!seckillService.checkProdStockEnough(prodId,buyCount)){ //释放库存锁       ,后续用户继续尝试购买 //库存剩余2两       ,还有三个人买                   ,库存不足             ,后续两个人各买一件 //释放库存锁 seckillService.unLockProdStock(prodId); log.info("------ 用户:{}       ,购买商品:{}                    ,购买数量:{}             ,再次 库存校验,库存不足 ------", userId, prodId, buyCount); //库存不足                    ,返回抢购失败 log.info("------ 用户:{}                    ,购买商品:{},购买数量:{}             ,抢购下单中                    ,库存不足 ------", userId, prodId, buyCount); return ResultBuildUtil.fail("605", "抢购失败       ,库存不足             ,缺货!"); } log.info("------------------------------ 开始扣减库存 -----------------------------------------"); //执行扣减商品库存 log.info("------ 用户:{}                   ,购买商品:{}       ,购买数量:{}       ,扣减库存 ------", userId, prodId, buyCount); seckillService.subProdStockCount(prodId,buyCount); log.info("------------------------------ 开始下单 -----------------------------------------"); //开始调用订单中心的生成抢购订单接口                   ,下单并返回给前端             ,抢购结果 //先模拟下单成功       ,返回一个抢购订单号 log.info("------ 用户:{}                    ,购买商品:{}             ,购买数量:{},开始下单 ------", userId, prodId, buyCount); // String seckillOrderNo = "SK"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) // + UUID.randomUUID().toString().substring(0,5); String seckillOrder = seckillOrderFeignService.feignInvokeCreateSeckillOrder(userId, prodId, buyCount); log.info("------------------------------ 下单成功 主动释放库存锁 -----------------------------------------"); //生成抢购订单成功                    ,立刻释放库存锁                    ,给其他抢购用户购买 log.info("------ 用户:{},购买商品:{}             ,购买数量:{}                    ,下单成功       ,释放库存锁 ------", userId, prodId, buyCount); seckillService.unLockProdStock(prodId); log.info("------ 用户:{}             ,购买商品:{}                   ,购买数量:{}       ,抢购成功 ------", userId, prodId, buyCount); //返回抢购成功       ,实际开发不能返回此种格式的数据 //必须使用key和value的返回                   ,方便前端获取订单号 return ResultBuildUtil.success("抢购成功             ,抢购订单"+seckillOrder); } }

3.3 订单模块

3.3.1 application.yml 点击查看代码 # 端口 server: port: 8107 # 服务名 spring: application: name: edocmall-seckill-order # redis redis: host: 127.0.0.1 port: 6379 # RabbitMQ rabbitmq: host: 1.117.75.57 port: 5672 username: admin password: admin # eureka 注册中心的配置 eureka: client: service-url: defaultZone: http://127.0.0.1:8096/eureka # 注册中心的地址 3.3.2 OrderMQDirectConfig 点击查看代码 /** * Created On : 1/11/2022. * <p> * Author : huayu * <p> * Description: Direct直连模式       ,自动配置类                    ,自动创建队列             ,交换机,并将队列绑定到交换机                    ,指定唯一路由 */ @Configuration public class OrderMQDirectConfig { /** * @author : huayu * @date : 1/11/2022 * @param : [] * @return : org.springframework.amqp.core.Queue * @description : directQueue */ @Bean public Queue directQueue(){ return new Queue(OrderMQConstant.SECKILL_SAVE_ORDER_QUEUE_KH96,true); } /** * @author : huayu * @date : 1/11/2022 * @param : [] * @return : org.springframework.amqp.core.DirectExchange * @description : 创建直连交换机 */ @Bean public DirectExchange directExchange(){ // 创建支持持久化的直连交换机                    ,指定交换机的名称 return new DirectExchange(OrderMQConstant.SECKILL_SAVE_ORDER_EXCHANGE_KH96U); } /** * @author : huayu * @date : 1/11/2022 * @param : [] * @return : org.springframework.amqp.core.Binding * @description : 将直连队列和直连交换机进行绑定,并指定绑定的唯一路由键 */ @Bean public Binding directBinding(){ // 将直连队列和直连交换机进行绑定             ,并指定绑定的唯一路由键 return BindingBuilder.bind(directQueue()) .to(directExchange()) .with(OrderMQConstant.SECKILL_SAVE_ORDER_ROUTING_KH96); } } 3.3.3 OrderMQConstant 点击查看代码 /** * Created On : 1/11/2022. * <p> * Author : huayu * <p> * Description: RabbitMQ 常量类                    ,系统的所有队列名       ,交换机名             ,路由键名等                   ,统一进行配置管理 */ public class OrderMQConstant { //========================== 直连模式 /** * Direct直连模式 队列名 */ public static final String SECKILL_SAVE_ORDER_QUEUE_KH96 ="seckill_save_order_queue_kh96"; /** * Direct直连模式 交换机名 */ public static final String SECKILL_SAVE_ORDER_EXCHANGE_KH96U ="seckill_save_order_exchange_kh96"; /** * Direct直连模式 路由键 */ public static final String SECKILL_SAVE_ORDER_ROUTING_KH96 ="seckill_save_order_routing_kh96"; //========================== 扇形模式 /** * Fanout 扇形模式 队列名 */ public static final String ACCOUNT_FANOUT_QUEUE_KH96 ="account_pay_result_queue_kh96"; } 3.3.4 下订单业务层 3.3.4.1 接口 点击查看代码 /** * Created On : 10/11/2022. * <p> * Author : huayu * <p> * Description: */ public interface SeckillOrderService { /** * @author : huayu * @date : 11/11/2022 * @param : [seckillOrder] * @return : void * @description : 生成秒杀订单 */ void saveSeckillOrder(Map<String,Object> seckillOrder); } 3.3.4.2实现类 点击查看代码 /** * Created On : 10/11/2022. * <p> * Author : huayu * <p> * Description: SeckillOrderServiceImpl */ @Service @Slf4j public class SeckillOrderServiceImpl implements SeckillOrderService { @Autowired private RabbitTemplate rabbitTemplate; @Autowired private RedisUtils redisUtils; @Override public void saveSeckillOrder(Map<String, Object> seckillOrder) { //发送生成抢购订单的消息到消息队列       ,并在redis中添加此订单的记录       ,模拟交互 //0 正在生成 if(redisUtils.set(seckillOrder.get("seckillOrderNo").toString(),0)){ //发送生成订单的消息 rabbitTemplate.convertAndSend(OrderMQConstant.SECKILL_SAVE_ORDER_EXCHANGE_KH96U, OrderMQConstant.SECKILL_SAVE_ORDER_ROUTING_KH96, seckillOrder); } } } 3.3.5 SeckillOrderSaveListener 点击查看代码 /** * Created On : 1/11/2022. * <p> * Author : huayu * <p> * Description: Direct 直连模式消费者 One */ @Slf4j @Component //指定接听的 消息队列 名字 @RabbitListener(queues = OrderMQConstant.SECKILL_SAVE_ORDER_QUEUE_KH96) public class SeckillOrderSaveListener { @Autowired private RedisUtils redisUtils; /** * @author : huayu * @date : 1/11/2022 * @param : [directMsgJson] * @return : void * @description : Direct 直连模式消费者One,消费信息 */ //指定消息队列中的消息                   ,交给对应的方法处理 @RabbitHandler public void saveSeckillOrder(Map<String,Object> seckillOrder){ log.info("***** 秒杀抢购订单:{}             ,开始入库 ******",seckillOrder.get("seckillOrderNo")); //TODO 将消息中的订单实体对象       ,调入业务接口                    ,插入到数据库 redisUtils.set(seckillOrder.get("seckillOrderNo").toString(),1); log.info("***** 秒杀抢购订单:{}             ,入库成功 ******",seckillOrder.get("seckillOrderNo")); } } 3.3.6 下单 控制层 点击查看代码 /** * Created On : 10/11/2022. * <p> * Author : huayu * <p> * Description: 秒杀抢购订单入口 */ @Slf4j @RestController public class SeckillOrderController { @Autowired private SeckillOrderService seckillOrderService; /** * @author : huayu * @date : 10/11/2022 * @param : [userId, prodId, buyCount] * @return : java.lang.String * @description : 生成秒杀抢购订单 */ @GetMapping("/createSeckillOrder") public String createSeckillOrder(@RequestParam String userId, @RequestParam String prodId, @RequestParam Integer buyCount){ log.info("****** 用户:{},购买商品:{}                    ,购买数量:{}                    ,生成抢购订单 ******", userId, prodId, buyCount); //TODO 必须要有参数校验,必须要有用户             ,商品信息的校验                    ,确定用户是否正常       ,商品是否还在抢购中 //TODO 再次强调所有的中心模块             ,数据来源                   ,不能信任外部接口来源的参数       ,都必须从数据库或者缓存中获取       ,尤其是跟金钱相关 //TODO 所有的接口必需要校验结束                   ,通过获取的数据             ,封装订单实体对象       ,用户不关系订单的生成业务                    ,可以使用异步消息队列             ,实现晓峰,并快速响应 //模拟生成一个抢购订单号                    ,并封装成订单实体对象                    ,通过map集合模拟 String seckillOrderNo = "SK"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + UUID.randomUUID().toString().substring(0,5); //创建订单实体对象 Map<String, Object> seckillOrder = new HashMap<>(); seckillOrder.put("userId",userId); seckillOrder.put("prodId",prodId); seckillOrder.put("buyCount",buyCount); //TODO 其他的订单属性封装,比如收货人             ,总价格                    ,优惠       ,时间等 seckillOrder.put("seckillOrderNo",seckillOrderNo); //发送到生成订单的消息队列中             ,使用异步处理 seckillOrderService.saveSeckillOrder(seckillOrder); //返回抢购订单 return seckillOrder.toString(); } }

3.4 查询订单状态和订单支付部分不再赘述

4                   、测试

4.1 初始化库存

4.2 抢购

4.2.1 抢购情况

4.2.2 redis中数据变化情况

4.3 查看订单详情

4.4 订单支付

支付的时候数注意穿透的路径;

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
美国vps云服务器 网站群服(美国服务器搭建站群引流有哪些好处) isahelp应用程序已停止工作怎么处理(isstart.exe是什么进程 isstart进程查询)