优惠券秒杀项目

一、添加优惠券的同时,将优惠券信息,以及用户列表放到redis中

@Override
@Transactional
public void addSeckillVoucher(Voucher voucher) {// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher = new SeckillVoucher();seckillVoucher.setVoucherId(voucher.getId());seckillVoucher.setStock(voucher.getStock());seckillVoucher.setBeginTime(voucher.getBeginTime());seckillVoucher.setEndTime(voucher.getEndTime());seckillVoucherService.save(seckillVoucher);// 保存秒杀内容放到 Redis 中,包括库存、开始时间和结束时间,使用 hashMap<String, String> voucherData = new HashMap<>();voucherData.put("stock", String.valueOf(voucher.getStock()));voucherData.put("beginTime", String.valueOf(voucher.getBeginTime().toInstant(ZoneOffset.UTC).toEpochMilli()));voucherData.put("endTime", String.valueOf(voucher.getEndTime().toInstant(ZoneOffset.UTC).toEpochMilli()));stringRedisTemplate.opsForHash().putAll("seckill:voucher:" + voucher.getId(), voucherData);// 加一个 set 集合,用来放抢到优惠券的用户 ID,防止重复抢stringRedisTemplate.opsForSet().add("seckill:voucher:users:" + voucher.getId(), "0");
}

image-20240822163631066

使用Redis生成订单ID:

@Component
public class RedisIdWorker {/*** 开始时间戳*/private static final long BEGIN_TIMESTAMP = 1640995200L;/*** 序列号的位数*/private static final int COUNT_BITS = 32;private StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public long nextId(String keyPrefix) {// 1.生成时间戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timestamp = nowSecond - BEGIN_TIMESTAMP;// 2.生成序列号// 2.1.获取当前日期,精确到天String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 2.2.自增长//如果不存在,会自动创建然后开始自增long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接并返回return timestamp << COUNT_BITS | count;}
}

二、使用lua查看是否满足购买条件

2.1 编写lua脚本

-- KEYS 和 ARGV 是 Redis 提供的两种参数类型-- KEYS[1] 是秒杀活动的哈希表键,包含库存、开始时间和结束时间
-- KEYS[2] 是记录用户ID的集合,防止重复购买-- ARGV[1] 是用户ID,用于检查该用户是否已经购买过
-- ARGV[2] 是当前时间的时间戳(秒),用于与秒杀活动的开始和结束时间进行比较-- 获取秒杀活动的信息
local stock = redis.call('hget', KEYS[1], 'stock')
local beginTime = redis.call('hget', KEYS[1], 'beginTime')
local endTime = redis.call('hget', KEYS[1], 'endTime')-- 当前时间
local currentTime = tonumber(ARGV[2])-- 检查活动是否在有效时间内
beginTime = tonumber(beginTime)
endTime = tonumber(endTime)if currentTime < beginTime thenreturn -4  -- 活动尚未开始
endif currentTime > endTime thenreturn -5  -- 活动已经结束
end-- 检查库存是否足够
stock = tonumber(stock)if stock <= 0 thenreturn -2  -- 库存不足
end-- 获取用户ID
local userId = ARGV[1]-- 检查用户是否已经购买过
local userExists = redis.call('sismember', KEYS[2], userId)
if userExists == 1 thenreturn -3  -- 用户已经购买过
end-- 扣减库存
redis.call('hincrby', KEYS[1], 'stock', -1)-- 记录用户购买信息
redis.call('sadd', KEYS[2], userId)-- 成功,返回 1
return 1
package com.hmdp.service.impl;import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.List;/*** <p>*  服务实现类* </p>** @author 虎哥* @since 2021-12-22*/
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@AutowiredISeckillVoucherService seckillVoucherService;@AutowiredRedisIdWorker redisIdWorker;@AutowiredStringRedisTemplate stringRedisTemplate;@Autowiredprivate RabbitTemplate rabbitTemplate;// 秒杀 Lua 脚本, 当类加载的时候就执行private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}@Overridepublic Result seckillVoucher(Long voucherId) {// 1. 使用 Redis 的 Lua 脚本,查看是否满足购买条件,包括时间、库存、用户是否已经购买String voucherKey = "seckill:voucher:" + voucherId;String userKey = "seckill:voucher:users:" + voucherId;long currentTimeMillis = LocalDateTime.now().toInstant(ZoneOffset.UTC).toEpochMilli();long userId = 1L; // 从用户会话中获取// 2. 调用 Lua 脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT, // Lua 脚本Arrays.asList(voucherKey, userKey), // Key 列表String.valueOf(userId), // 参数列表:用户 ID 转为字符串String.valueOf(currentTimeMillis) // 参数列表:当前时间转为字符串);// 3. 处理 Lua 脚本返回的结果if (result != null) {int r = result.intValue();switch (r) {case -2:return Result.fail("库存不足!");case -3:return Result.fail("您已购买过该优惠券!");case -4:return Result.fail("秒杀活动尚未开始!");case -5:return Result.fail("秒杀活动已经结束!");case 1:// 成功抢购,将订单信息发送到 RabbitMQ 队列中VoucherOrder voucherOrder = createOrder(voucherId, userId);rabbitTemplate.convertAndSend("seckill.direct", "order.routing.key", voucherOrder);return Result.ok("抢购成功,订单正在处理中!");default:return Result.fail("未知错误!");}}return Result.fail("操作失败!");}// 创建订单private VoucherOrder createOrder(Long voucherId, Long userId) {VoucherOrder order = new VoucherOrder();order.setVoucherId(voucherId);order.setUserId(userId);order.setId(redisIdWorker.nextId("order")); // 生成订单 IDreturn order;}
}
package com.hmdp.consumer;import com.hmdp.entity.VoucherOrder;
import com.hmdp.service.IVoucherOrderService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.QueueBinding;import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Component
@Slf4j
public class OrderConsumer {@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate IVoucherOrderService voucherOrderService;@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "seckill.order.queue"),exchange = @Exchange(name = "seckill.direct", type = ExchangeTypes.DIRECT),key = "order.routing.key"))public void processOrder(VoucherOrder order) {String lockKey = "order:lock:" + order.getId();RLock lock = redissonClient.getLock(lockKey);try {boolean isLockAcquired = lock.tryLock(10, 5, TimeUnit.SECONDS);if (isLockAcquired) {try {voucherOrderService.save(order);} finally {lock.unlock();}} else {// 无法获取锁,可能日志记录}} catch (InterruptedException e) {Thread.currentThread().interrupt();// 错误处理} catch (Exception e) {// 错误处理}}}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.hmdp</groupId><artifactId>hm-dianping</artifactId><version>0.0.1-SNAPSHOT</version><name>hm-dianping</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope><version>5.1.47</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency><!--rabbitmq--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!--redission--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version></dependency><!--fastjson--><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.9.10</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
server:port: 8081
spring:application:name: hmdpdatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&serverTimezone=UTCusername: rootpassword: rootredis:host: 192.168.0.103port: 6379password: 123lettuce:pool:max-active: 10max-idle: 10min-idle: 1time-between-eviction-runs: 10srabbitmq:host: 192.168.0.103 # 你的虚拟机IPport: 5672 # 端口virtual-host: /seckill # 虚拟主机username: seckill # 用户名password: 123123 # 密码jackson:default-property-inclusion: non_null # JSON处理时忽略非空字段
mybatis-plus:type-aliases-package: com.hmdp.entity # 别名扫描包configuration:map-underscore-to-camel-case: true
logging:level:com.hmdp: debugorg.springframework.amqp: DEBUG

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/408019.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

easyexcel--多sheet页导入导出

多sheet页导出 核心代码就是下图里面的&#xff0c;使用EasyExcel.writeSheet创建一个sheet,然后用excelWriter写入就行了&#xff0c;很简单 GetMapping("downloadMultiSheet")public void downloadMultiSheet(HttpServletResponse response) throws IOException {…

【Qt】输入类控件QDateTimeEdit

目录 输入类控件QDateTimeEdit 例子&#xff1a;实现日期计算器 输入类控件QDateTimeEdit QDate Edit作为日期的微调框 QTime Edit作为时间的微调框 QDateTimeEdit作为时间日期的微调框 下面主要讲解QDateTimeEdit&#xff1a; 核心属性 属性说明 dateTime 时间⽇期的值.…

【Hot100】LeetCode—101. 对称二叉树

目录 1- 思路借助队列 2- 实现⭐101. 对称二叉树——题解思路 3- ACM 实现 原题连接&#xff1a;101. 对称二叉树 1- 思路 借助队列 1- 创建队列&#xff1a;Queue<TreeNode> queue&#xff0c;初始化加入 root.left 和 root.right2- 判断逻辑&#xff1a;while(!queu…

软件开发者的首选:最佳Bug测试工具Top 10

本篇文章介绍了以下软件bug测试管理工具&#xff1a;PingCode、Worktile、Test360、禅道、码云Gitee、优云测试、Jira、GitHub、Axosoft、Bugzilla。 在开发过程中&#xff0c;Bug的管理往往是最让人头疼的问题之一。小问题积累起来不仅会拖延项目进度&#xff0c;还可能影响到…

如何优雅处理异步组件加载:Vue 3 的 Suspense 特性

在日常开发中&#xff0c;我们可能会遇到网络不佳或内容加载时间较长的情况。如果当前页面没有任何内容提示&#xff0c;用户的体验非常糟糕&#xff0c;可能会反复刷新以便加载成功。因此&#xff0c;我们需要给用户提供一个加载中的效果&#xff0c;告知用户“我在努力加载中…

基于单片机的人体健康监测系统的设计

本设计以STM32F103C8T6单片机作为主控&#xff0c;通过MAX30102采集心率、血氧值&#xff0c;通过MSP20血压采集模块检测血压值&#xff0c;通过MLX90614红外体温采集模块检测体温值。OLED屏可以显示以上检测的信息&#xff0c;并可以通过蓝牙模块将信息发送给手机APP。当检测值…

利用VirtualBox安装CentOS系统

博主这次用VirtualBox虚拟机安装CentOS系统。无论是大小型项目都是要发布到云主机上面&#xff0c;必然要用到Linux系统&#xff0c;有的人的本地电脑硬件配置不高&#xff0c;没有办法运行数据库集群&#xff0c;所以只能借助云主机。毕竟云主机也是Linux系统&#xff0c;大家…

大数据-92 Spark 集群 SparkRDD 原理 Standalone详解 ShuffleV1V2详解 RDD编程优化

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

OZON什么产品好卖丨OZON婴儿用具产品

Top1 摇铃 Деревянная стойка тренажер Монтессори для мобилей и игрушек для новорожденных / развивающая дуга 商品id&#xff1a;1557614414 月销量&#xff1a;707 OZON婴儿用具…

Leetcode-day31-01背包问题

46. 携带研究材料 1.dp数组代表的是什么&#xff1f; 这里的dp数组是一个二维数组&#xff0c;dp[i][j]是从前i个物品中任选放入容量j内的最大价值。 2.递推公式。 不放物品i&#xff1a;由dp[i - 1][j]推出&#xff0c;即背包容量为j&#xff0c;里面不放物品i的最大价值&am…

uniapp检测手机是否打开定位权限Vue3-直接复制粘贴

安卓示例&#xff1a; 苹果示例&#xff1a; 代码实现&#xff08;vue3写法&#xff09;&#xff1a; const checkGPS ()>{console.log(开始监听GPS状态);let system uni.getSystemInfoSync(); // 获取系统信息if (system.platform android) { // 判断平台var context …

全国上市公司网络安全风险指数(2001-2023年)

数据来源&#xff1a;本数据参考耿勇老师等&#xff08;2024&#xff09;做法采集了2001-2023年的上市公司年报&#xff0c;所有年报均来自于深交所和上交所官方网站&#xff0c;通过对上市公司的年报进行精读&#xff0c;提取出包括网络安全、网络攻击等在内的39个关键词构成企…

牛客笔试小题

目录 牛客.小红取数 牛客.哈夫曼编码​编辑 牛客.字符编码(上一道题的资料) 牛客.最小的完全平方数 牛客.小红取数 01背包问题:在满足总和必须为k的倍数的情况下&#xff0c;选择最大的总和 1.状态表示: dp[i][j]:表示从前面i个数字中挑选&#xff0c;总和%k等于j时候,最大的…

微服务——远程调用

为什么需要远程调用&#xff1f; 在微服务架构中&#xff0c;每个服务都是独立部署和运行的&#xff0c;它们之间需要相互协作以完成复杂的业务逻辑。因此&#xff0c;远程调用成为微服务之间通信的主要方式。通过远程调用&#xff0c;一个服务可以请求另一个服务执行某些操作或…

数学建模国赛获奖技巧

一、团队分工合作的技巧&#xff08;三角形配合&#xff09; &#xff08;1&#xff09;队长要组织多沟通多交流&#xff1b; &#xff08;2&#xff09;建议定期开组会&#xff0c;互相讲授自己学习的东西&#xff0c;一人学习&#xff0c;三人收获。 二、AI辅助思路解析&am…

Eureka 原理与实践全攻略

一、Eureka 概述 Eureka 在微服务架构中具有举足轻重的地位。它作为服务注册与发现的核心组件&#xff0c;为分布式系统中的服务管理提供了关键支持。 Eureka 的主要功能包括服务注册、服务发现、服务健康监测和自我保护机制。服务注册功能使得服务提供者能够在启动时将自身的…

【Unity】移动端草海解决方案

草海是开放大世界渲染的必不可少的因素&#xff0c;Unity 原生的 Terrain 草海效率较低&#xff0c;而且无法与 RVT 结合起来&#xff0c;无法在移动端上实现。因此我们自己搓出来一套草海系统&#xff0c;使用 C# 多线程辅助运算&#xff0c;并能支持割草、烧草等进阶玩法。草…

系统编程 网络 http协议

http协议------应用层的协议 万维网&#xff1a;http解决万维网之间互联互通 计算机web端网络只能看到文字 1.如何在万维网中表示一个资源&#xff1f; url <协议>&#xff1a;//<主机>&#xff1a;<端口>/<路径> ------------------------------…

2024软件测试面试,别玩这些题目,轻松拿捏百分之95的测试!

1、你会封装自动化测试框架吗&#xff1f; 自动化框架主要的核心框架就是分层PO模式&#xff1a;分别为&#xff1a;基础封装层BasePage&#xff0c;PO页面对象层&#xff0c;TestCase测试用例层。然后再加上日志处理模块&#xff0c;ini配置文件读取模块&#xff0c;unittest…

计算机毕业设计--基于深度学习(PSPNet、空洞卷积Atrous Convolutions)的多类型图像通用分割模型

基于深度学习(PSPNet、空洞卷积Atrous Convolutions)的多类型图像通用分割模型 更多基于深度学习的毕业设计请关注专栏 --- 计算机毕业设计 ✨ 动物图分割&#xff08;使用训练集DIS5K-TR&#xff0c;DIS-TEs&#xff0c;DUTS-TR_TE &#xff09; ✨自然与人类图像分割&#xf…