<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
//配置
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1");
//创建RedissonClient对象
return Redisson.create(config);
}
}
RLock lock = redissonClient.getLock("lock:order" + userId);
//获取锁,无参就失败不等待,直接返回
boolean isLock = lock.tryLock();
//判断是否获取锁成功
if (!isLock) {
//获取锁失败,返回错误或重试
return Result.fail("一个人只允许下一单");
}
try {
//获取代理对象(事务)
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
lock.unlock();
}
优惠券秒杀、分布式锁逻辑层代码(乐观锁、悲观锁)实现了一人一单
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private SeckillVoucherMapper seckillVoucherMapper;
@Resource
private VoucherOrderMapper voucherOrderMapper;
@Resource
private RedisIdWorker redisIdWorker;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedissonClient redissonClient;
@Override
public Result seckillVoucher(Long voucherId) {
//1.查询秒杀优惠卷
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
//2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
return Result.fail("秒杀尚未开始");
}
//3.判断秒杀是否结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("秒杀已经结束");
}
//判断库存是否充足
if (voucher.getStock() <= 0) {
return Result.fail("库存不足");
}
Long userId = UserHolder.getUser().getId();
//确保当用户id值一样时,就用一把锁,不同的用户用不同的锁
//确保获取锁,提交事务,释放锁,线程就安全了
//创建锁对象
// SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
RLock lock = redissonClient.getLock("lock:order" + userId);
//获取锁,无参就失败不等待,直接返回
boolean isLock = lock.tryLock();
//判断是否获取锁成功
if (!isLock) {
//获取锁失败,返回错误或重试
return Result.fail("一个人只允许下一单");
}
try {
//获取代理对象(事务)
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
lock.unlock();
}
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
//查询数据库有没有数据时,不能用乐观锁,需用悲观锁
//6.一人一单 判断订单表中是否存在用户对应某优惠券的单条记录
//6.1 查询订单
Long userId = UserHolder.getUser().getId();
QueryWrapper<VoucherOrder> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",userId).eq("voucher_id", voucherId);
Integer count = voucherOrderMapper.selectCount(queryWrapper);
//6.2 判断是否存在
if(count > 0){
//已经购买过一次
return Result.fail("用户已经购买过一次");
}
//扣减库存
// boolean success = seckillVoucherService.update()
// .setSql("stock = stock - 1")
// .eq("voucher_id", voucherId).update();
UpdateWrapper<SeckillVoucher> updateWrapper = new UpdateWrapper<>();
/**
* 使用乐观锁,乐观锁是在更新数据时使用
* 为了防止超卖,应该设置查询时的票数等于自己准备修改时的票数,此时会出现少卖问题,
* 所以直接改为当修改时票数大于0,即可修改
*/
updateWrapper.eq("voucher_id", voucherId).gt("stock", 0);
updateWrapper.setSql("stock = stock - 1");
int success = seckillVoucherMapper.update(null, updateWrapper);
if (success == 0) {
return Result.fail("库存不足");
}
//没有买过就创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//6.1订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//6.2用户id
voucherOrder.setUserId(userId);
//6.3代金券id
voucherOrder.setVoucherId(voucherId);
voucherOrderMapper.insert(voucherOrder);
//返回订单id
return Result.ok(orderId);
}
}
里面使用到了代理,需引入pom.xml 和 启动类上加注解
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
@EnableAspectJAutoProxy(exposeProxy = true)
public interface IVoucherOrderService extends IService<VoucherOrder> {
Result seckillVoucher(Long voucherId);
Result createVoucherOrder(Long voucherId);
}
RedisIdWorker工具类(唯一ID生成器)
@Component
public class RedisIdWorker {
//开始时间戳
private static final long BEGIN_TIMESTAMP = 1704067200L;
//序列号的位数
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 获取当前日期,精确到天,每天一个key,方便统计订单量
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
//2.2自增长
Long count = stringRedisTemplate.opsForValue().increment("icr" + keyPrefix + ":" + date);
//3.拼接并返回 时间戳放左边 序列号放右边 将时间戳右移
// |count 时间戳右移32位后,右边32位全为0,或之后,原来是什么就是什么
return timestamp << COUNT_BITS | count;
}
//用于生成开始时间的时间戳
// public static void main(String[] args) {
// LocalDateTime time = LocalDateTime.of(2024, 1, 1, 0, 0, 0);
// long second = time.toEpochSecond(ZoneOffset.UTC); //将当前时间转换为秒数
// System.out.println(second);
// }
}