点评plus---异步消费之后可靠的生成订单

异步消费之后可靠的生成订单

来自阿星不是程序员开源项目点评plus

之前已经对秒杀系统进行了异步化升级。

用户的购券请求会先通过 Lua 脚本完成原子性校验(库存校验、一人一单校验)以及 Redis 预扣库存。

校验成功后,系统会将订单信息封装成 Kafka 消息,由生产者发送到 Kafka Broker,

再由 Kafka 消费者异步创建订单,实现流量削峰与业务解耦。

代码:

java 复制代码
    /**
     * 创建秒杀订单 V2版本
     *
     * 功能:处理秒杀消息,创建订单,扣减库存,记录订单路由信息,缓存订单数据,记录对账日志
     *
     * @param message Kafka消息对象,里面包装了秒杀消息体 SeckillVoucherMessage
     * @return boolean 是否创建成功
     *
     * 注解说明:
     * - @Override:重写父类方法
     * - @RepeatExecuteLimit:自定义注解,防止重复执行(基于消息的uuid,避免消息重复消费)
     * - @Transactional:开启事务,任何异常都会回滚
     */
    @Override
	/*
	*@RepeatExecuteLimit 是一个防重复执行注解,
	主要用于Kafka消息消费幂等控制。
	*/
    @RepeatExecuteLimit(name = SECKILL_VOUCHER_ORDER, keys = {"#message.uuid"})
    @Transactional(rollbackFor = Exception.class)
    public boolean createVoucherOrderV2(MessageExtend<SeckillVoucherMessage> message) {


        // 从消息扩展对象中取出真正的业务消息体
        SeckillVoucherMessage messageBody = message.getMessageBody();
        // 获取用户ID(谁在抢购)
        Long userId = messageBody.getUserId();


        // 查询数据库:该用户是否已经成功购买过这个优惠券(且订单状态是正常状态)
        VoucherOrder normalVoucherOrder = lambdaQuery()
                .eq(VoucherOrder::getVoucherId, messageBody.getVoucherId())  // 优惠券ID相同
                .eq(VoucherOrder::getUserId, userId)                         // 用户ID相同
                .eq(VoucherOrder::getStatus, OrderStatus.NORMAL.getCode())   // 订单状态正常(未取消/未退款)
                .one();  // 查询一条记录

        // 如果查询到了,说明用户已经买过了,不允许重复购买
        if (Objects.nonNull(normalVoucherOrder)) {
            log.warn("已存在此订单,voucherId:{},userId:{}", normalVoucherOrder.getVoucherId(), userId);
            throw new HmdpFrameException(BaseCode.VOUCHER_ORDER_EXIST);  // 抛出订单已存在异常
        }

        // 使用乐观锁扣减库存:
        // - setSql("stock = stock - 1"):库存减1
        // - eq("voucher_id", ...):定位到指定的优惠券
        // - gt("stock", 0):保证库存大于0才扣减(防止超卖)
        // update() 返回 true 表示更新成功(扣减成功),false 表示失败(库存不足或优惠券不存在)
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", messageBody.getVoucherId())
                .gt("stock", 0)
                .update();

        // 扣减失败,说明库存不足,抛异常触发事务回滚
        if (!success) {
            throw new HmdpFrameException("优惠券库存不足!优惠券id:" + messageBody.getVoucherId());
        }

        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(messageBody.getOrderId());        // 订单ID(雪花算法生成)
        voucherOrder.setUserId(messageBody.getUserId());      // 用户ID
        voucherOrder.setVoucherId(messageBody.getVoucherId()); // 优惠券ID
        voucherOrder.setCreateTime(LocalDateTimeUtil.now());   // 创建时间

        // 保存订单到 voucher_order 表
        save(voucherOrder);

        // 订单路由表作用:可能是用于后续的订单查询、分库分表、或者消息追踪
        VoucherOrderRouter voucherOrderRouter = new VoucherOrderRouter();
        voucherOrderRouter.setId(snowflakeIdGenerator.nextId());  // 路由表自己的ID
        voucherOrderRouter.setOrderId(voucherOrder.getId());       // 关联订单ID
        voucherOrderRouter.setUserId(userId);                      // 用户ID
        voucherOrderRouter.setVoucherId(voucherOrder.getVoucherId()); // 优惠券ID
        voucherOrderRouter.setCreateTime(LocalDateTimeUtil.now());  // 创建时间
        voucherOrderRouter.setUpdateTime(LocalDateTimeUtil.now());  // 更新时间

        // 保存路由记录
        voucherOrderRouterService.save(voucherOrderRouter);


        // 将订单信息存入Redis,过期时间60秒
        // 用途:可能用于快速查询用户刚刚创建的订单,或者用于后续流程的快速访问
        redisCache.set(
                RedisKeyBuild.createRedisKey(
                        RedisKeyManage.DB_SECKILL_ORDER_KEY,  // 缓存key前缀
                        messageBody.getOrderId()               // 订单ID作为key后缀
                ),
                voucherOrder,      // 缓存的值:订单对象
                60,                // 过期时间60秒
                TimeUnit.SECONDS   // 时间单位:秒
        );

        // 记录库存扣减日志,用于后续对账(保证数据一致性)
        // LogType.DEDUCT:扣减类型
        // BusinessType.SUCCESS:成功状态
        voucherReconcileLogService.saveReconcileLog(
                LogType.DEDUCT.getCode(),           // 日志类型:扣减
                BusinessType.SUCCESS.getCode(),     // 业务结果:成功
                "order created",                     // 描述信息
                message                              // 原始消息(方便追踪)
        );
        return true;
    }
相关推荐
极创信息21 分钟前
国产化信创适配认证高频术语:信创适配、软件自主可控、国产化率、代码溯源率、代码自主率、代码开源率是什么?
java·python·struts·eclipse·开源·php·hibernate
Data_Journal22 分钟前
什么是 CAPTCHA,它是如何工作的?
java·大数据·服务器·前端·数据库
mqiqe34 分钟前
响应式流中的错误处理:Project Reactor 异常治理全体系
java·架构
我命由我1234541 分钟前
Android 开发问题:TopAppBar 和 topAppBarColors API is experimental...
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
AC赳赳老秦1 小时前
语义采集进阶实战:利用 OpenClaw AI 语义识别自动提取网页核心信息,无需手动编写选择器
java·运维·服务器·python·信息可视化·deepseek·openclaw
AI人工智能+电脑小能手1 小时前
大白话说Java设计模式-23-桥接模式(源码剖析篇)
java·设计模式·jdbc·桥接模式·源码分析·awt·java logging
李高钢1 小时前
C# WPF Prism 进阶(二):区域(Region)与模块化(Module)
java·前端·数据库
long3161 小时前
枚举(Enums)
java·开发语言·数据库
墨雨晨曦881 小时前
2026/08/15 spring AI学习总结
java·tomcat
wno7042 小时前
Spring Boot JdbcTemplate配置Druid多数据源
java·spring boot·后端