完整版订单超时自动取消功能

前几天对实习还是继续学习技术产生了抉择,问了一个前辈,他抛给我一个问题,怎么做15分钟订单自动取消,我说然后到时间之后,自动执行这个订单关闭业务,比如把锁了的库存给解开等等操作,然后在数据库里肯定要有体现,比如抖音,查我的订单内一块,在前段应该显示已过期,把支付的按钮变灰或者取消,支付的时候不能直接对库存进行操作,应该是支付模块调用订单模块调用库存模块。

然后把理论予以实现,发现需要考虑的点还是有一些的。

功能架构

该功能使用了三大模块解除耦合,保证高可用性,从高层到底层分别是支付模块->订单模块->商品模块,各司其职,支付模块主要负责支付,退款等相应功能;订单模块主要负责订单查询,订单创建,删除订单相关功能;商品模块主要负责商品上下架,补货,查货相关功能;是环环相扣的,支付模块通过操控订单实现支付,订单模块通过操控商品来实现订单创建等操作。

功能实现流程

由于是全栈开发,本功能实现流程会涵盖前后端都有。由于管理模块,就是添加商品等等操作,在前端并没有实现,就先在数据库里加一些商品。sql如下:

然后写一个接口获取商品列表,然后前端进行渲染,效果如下:

测试阶段,未接入微信支付等,但有字段标识,后期方便扩展,渲染的时候只需要有一点,判断支付类型,如果是钱支付就渲染成¥的形式,如果是积分就渲染成积分的形式。然后点击兑换会弹出一个弹出框如下:

扩展一些信息也会变得非常简单,由于数据库里设置了两个字段一个是销售价(也就是优惠后的价格),一个是原价,直接在前端算出优惠金额即可。然后点击提交订单,就会向后端去发送一个创建的请求,java代码如下所示:

java 复制代码
        // 这里先把订单持续事件设置成15秒
        // 1.1 先校验参数,看看该商品是否有库存
        CommonGoodPo good = goodMapper.queryGoodById(dto.getGoodId());
        if (good == null) return Result.error("已查询不到该商品");
        Boolean isPut = good.getIsPut();
        if (!isPut) return Result.error("无法对已下架的商品下订单");
        // 1.2 看看库存还够不够
        Boolean haveStore = good.getHaveStore();
        Integer store = good.getStore();
        if (haveStore && store <= 0) return Result.error("库存不足,无法下单");

        // 库存足就锁定一个库存
        if (haveStore) goodMapper.lockStore(good.getId());

        // 1.3 todo 如果有一个人只能买一单等等限制,可继续扩展

        // 2.1 然后就要生成订单的信息了,比如订单的id,这里起始应该使用杂乱的数字字母等作为id,图方便就直接使用自增id

        // 2.2 然后生成开始时间和结束时间
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime end = LocalDateTime.now().plusMinutes(15); // 15分钟写死
        PopOrderVo popOrderVo = new PopOrderVo(null, good.getId(), now, end, 15 * 60, dto.getAccount());
        orderMapper.creatOrder(popOrderVo);

        // 3 准备对象,发送消息
        String mes = JSON.toJSONString(popOrderVo);
        sendDelayMessage(mes, 60 * 15, RabbitConstants.DELAY_ORDER_DEL_DIRECT_EXCHANGE, RabbitConstants.DELAY_ORDER_KEY);


        // 然后封装一下给前端,然后前端得到信息后,弹出弹出层,在我的订单里也可以找到该信息
        return Result.ok(popOrderVo);

代码逻辑主要分为三部分,校验参数;然后去执行sql,就是向数据库的order表里去添加一条订单消息,并且要锁库存;最后向rabbitMQ里发一条延迟消息,这个是封装好的方法。到期执行的方法如下所示:

java 复制代码
        log.error("我要开始喽!");
        // 把msg转成对象
        PopOrderVo popOrderVo = null;
        try {
            popOrderVo = JSON.parseObject(msg, PopOrderVo.class);
        } catch (Exception e) {
            throw new RuntimeException("请调用符合规定的api,类型转换错误,无法转换为PopOrderVo");
        }

        // 自动取消订单逻辑
        LocalDateTime now = LocalDateTime.now();
        Integer orderId = popOrderVo.getOrderId();
        Integer goodId = popOrderVo.getGoodId();
        // 修改订单状态
        orderMapper.finishOrder(orderId, now, ORDER_STATUS_OVERTIME);
        // 把锁了的库存释放
        goodMapper.unLockStore(goodId);
        log.error("我结束喽!");

就是去校验一下参数,然后去解锁库存,然后修改订单状态。

订单表如下所示:

然后在点击提交订单后,弹出支付页面,并有前端实现的倒计时。

然后会显示一些订单信息,倒计时到了之后,按钮会变成灰色,并无法点击,点击支付按钮后,进入到后端逻辑。代码如下:

java 复制代码
    @Override
    @Transactional
    public Result payOrder(PayOrderDto dto) {
        Integer orderId = dto.getOrderId();
        OrderPo orderInfo = orderMapper.getOrderInfo(orderId);

        Integer goodId = orderInfo.getGoodId();

        LocalDateTime now = LocalDateTime.now();
        CommonGoodPo good = goodMapper.queryGoodById(goodId);
        // 1 先需要校验参数
        // 1.1 看过没过期,如果现在的时间在截止时间之后,则证明过期了
        if (now.isAfter(orderInfo.getEndTime())) {
           

            return Result.error("该订单已经过期,请重新下单!");
        }
        
        // 1.2 看一下状态是不是待支付状态
        if (orderInfo.getStatus() != 0) return Result.error("该订单已被处理,请刷新重试");
        // 1.3 看余额够不够
        Boolean isEnough = payMapper.checkBalance(orderInfo.getAccount(), good.getSalePrice());
        // 2.1 如果不够,直接报错余额不足
        if (!isEnough) return Result.error("你的余额不足,无法完成支付");
        // 2.2 如果够,扣减余额
        payMapper.decreaseBalance(orderInfo.getAccount(), good.getSalePrice());
        // 2 需要进行三个方面的处理
        // 2.1 商品给增加销量等等
        goodMapper.increaseSales(goodId);
        // 2.2 设置订单状态,截至时间等
        orderMapper.finishOrder(orderId, now, ORDER_STATUS_SUCCESS);
        // 2.3 记录支付日志
        payMapper.logPay(good.getSalePrice(), good.getPayType(), orderInfo.getAccount(), now, goodId);
//        return null;
        return Result.ok("支付成功");

    }

支付逻辑比较简单,就是校验参数,但需要注意需要检验一下订单状态,因为可能因为网络问题等等,导致请求到后端的时候,出现时间延迟问题,出现状态已经不是待支付状态已经被修改的问题(处理幂等问题),余额不足问题,这时就不能再进行支付,要对订单进行一下处理。如果满足条件就进行sql操作。

然后在前端为了订单的丢失,又写了一个我的订单页面,如下所示:

可以根据不同的状态去筛选不同的订单列表,然后并可以在此页面完成支付。

至此本功能结束,使用的是rabbitMQ的延迟队列实现定时操作,也可以使用xxl-job,直接使用策略模式扩展一下即可,在复习专栏里有关于设计模式的文章和例子,后续有时间会讲一下定时操作策略的扩展。

相关推荐
阿伟*rui35 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj3 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck3 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei3 小时前
java的类加载机制的学习
java·学习
Yaml45 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
P.H. Infinity5 小时前
【RabbitMQ】03-交换机
分布式·rabbitmq
小小小妮子~5 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616885 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java6 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet