Redisson入门(优惠券秒杀、分布式锁)

复制代码
        <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);
//    }
}
相关推荐
爱勇宝19 小时前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
IT_陈寒1 天前
SpringBoot这个自动配置坑我跳了三次
前端·人工智能·后端
kyriewen1 天前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
牧艺1 天前
从零到协同:构建类飞书在线文档系统的五个技术重难点
前端·人工智能
用户3521802454751 天前
当 Prompt 学会"热更新":Spring Boot × Nacos3 AI 实战
java·spring boot·ai编程
红尘散仙1 天前
想写一个像样的终端 App?试试把 React 的开发体验搬进 Rust TUI
前端·rust
袋鼠云数栈UED团队1 天前
一套 Spec-First 的 AI 编程工作流
前端·人工智能
袋鼠云数栈前端1 天前
一套 Spec-First 的 AI 编程工作流
前端·ai+
angerdream1 天前
Android手把手编写儿童手机远程监控App之vue3 路由守卫
前端
不服老的小黑哥1 天前
AI规范驱动编程-harness工程项目实战
前端