<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version></dependency>
@Configuration
public class RedissonConfig {@Beanpublic 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 {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate SeckillVoucherMapper seckillVoucherMapper;@Resourceprivate VoucherOrderMapper voucherOrderMapper;@Resourceprivate RedisIdWorker redisIdWorker;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate RedissonClient redissonClient;@Overridepublic 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();}}@Transactionalpublic 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订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//6.2用户idvoucherOrder.setUserId(userId);//6.3代金券idvoucherOrder.setVoucherId(voucherId);voucherOrderMapper.insert(voucherOrder);//返回订单idreturn 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);
// }
}