订单取消功能(退款功能、策略模式、定时任务)

目录

订单取消

一、代码实现数据库操作

二、定义定时任务完成退款功能

1)OrdersHandler

2)配置任务调度

三、策略模式

正式改造项目代码:

1)定义策略接口

2)定义策略实现类

3)上下文环境类

4)调用类

四、取消超时订单:

超时取消订单策略类

定时任务类

配置任务调度


订单取消

接下来,我们以待支付派单中 两种状态为例子来讨论取消订单的流程,其基本实现步骤如下:

  1. 更新订单状态:待支付状态--->已取消 派单中---->已关闭

  2. 保存取消订单记录,记录取消订单原因等信息

  3. 如果是派单中状态的订单,需要远程调用支付服务的退款接口申请退款

上述思路可以很好的实现功能,但是退款接口需要调用微信的接口,也就是要连接外部网络,速度可能会变慢

因此可以使用异步的思路来解决它,也就是将上面的第3步变为异步调用,如下图所示

这样提高了系统的效率,但是也出现了一个问题,那就是万一由于网络等原因调用支付服务失败,款就退不了了

为了解决这一问题,我们先将要退款的记录写到一个专门的待退款记录表中,然后使用定时任务完成退款功能

具体流程如下:

  1. 使用数据库事务控制,保存以下数据(更新订单状态、保存取消订单记录、保存待退款记录)

  2. 定时扫描待退款表,对未退款的记录请求支付服务进行退款,退款成功更新订单的退款状态,并删除退款记录

在上面需求中新提到了两张数据表,分别是订单取消表订单待退款表(只记录待退款信息)

一、代码实现数据库操作

根据需求,取消订单需要分两种情况实现:

  • 取消未支付的订单:修改订单的状态为已取消,保存取消订单的记录

  • 取消派单中的订单:修改订单的状态为已关闭,保存取消订单的记录,添加待退款记录

接口路径:PUT /orders-manager/consumer/orders/cancel

ConsumerOrdersController:

java 复制代码
@ApiOperation("取消订单")
@PutMapping("/cancel")
public void cancel(@RequestBody OrderCancelReqDTO orderCancelReqDTO) {
    OrderCancelDTO orderCancelDTO = BeanUtil.toBean(orderCancelReqDTO, OrderCancelDTO.class);
    CurrentUserInfo currentUserInfo = UserContext.currentUser();
    orderCancelDTO.setCurrentUserId(currentUserInfo.getId()); //当前登录用户id
    orderCancelDTO.setCurrentUserName(currentUserInfo.getName());//当前登录用户名称
    orderCancelDTO.setCurrentUserType(currentUserInfo.getUserType());//当前登录用户类型
    ordersManagerService.cancel(orderCancelDTO);
}

IOrdersManagerService:

java 复制代码
/**
 * 取消订单
 *
 * @param orderCancelDTO 取消订单参数
 */
void cancel(OrderCancelDTO orderCancelDTO);

/**
 * 取消待支付订单
 *
 * @param orderCancelDTO 取消订单对象
 */
void cancelByNoPay(OrderCancelDTO orderCancelDTO);

/**
 * 取消派单中订单
 *
 * @param orderCancelDTO 取消订单对象
 */
void cancelByDispatching(OrderCancelDTO orderCancelDTO);

OrdersManagerServiceImpl:

java 复制代码
@Autowired
private IOrdersManagerService owner;

@Autowired
private IOrdersCommonService ordersCommonService;

@Autowired
private OrdersCanceledMapper ordersCanceledMapper;

@Autowired
private OrdersRefundMapper ordersRefundMapper;


@Override
public void cancel(OrderCancelDTO orderCancelDTO) {
    //1. 根据订单id查询订单信息, 如果不存在, 直接报错
    Orders orders = this.getById(orderCancelDTO.getId());
    if (ObjectUtils.isNull(orders)) {
        throw new ForbiddenOperationException("订单不存在");
    }
    //给orderCancelDTO赋值
    BeanUtil.copyProperties(orders, orderCancelDTO);

    //订单状态,0:待支付,100:派单中,200:待服务,300:服务中,400:待评价,500:订单完成,600:已取消,700:已关闭
    //2. 根据订单状态 去分别编写两种情况取消订单的逻辑
    if (ObjectUtil.equal(orders.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())) {
        //取消待支付订单: 1) 更新订单状态为已取消  2) 保存取消订单记录
        owner.cancelByNoPay(orderCancelDTO);
    } else if (ObjectUtil.equal(orders.getOrdersStatus(), OrderStatusEnum.DISPATCHING.getStatus())) {
        //取消派单中订单: 1) 更新订单状态为已关闭  2) 保存取消订单记录  3) 保存待退款的记录
        owner.cancelByDispatching(orderCancelDTO);
    } else {
        throw new ForbiddenOperationException("当前状态订单暂不支持取消");
    }
}


//取消待支付订单: 1) 更新订单状态为已取消  2) 保存取消订单记录
@Transactional(rollbackFor = Exception.class)
public void cancelByNoPay(OrderCancelDTO orderCancelDTO) {
    // 1) 更新订单状态为已取消
    // update orders set orders_status = 600 where id = 订单id and orders_status = 0
    OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO.builder()
            .id(orderCancelDTO.getId())//订单id
            .originStatus(OrderStatusEnum.NO_PAY.getStatus())//原始状态
            .targetStatus(OrderStatusEnum.CANCELED.getStatus())//目标状态
            .build();
    Integer i = ordersCommonService.updateStatus(orderUpdateStatusDTO);
    if (i <= 0) {
        throw new ForbiddenOperationException("订单取消失败");
    }

    // 2) 保存取消订单记录
    OrdersCanceled ordersCanceled = new OrdersCanceled();
    ordersCanceled.setId(orderCancelDTO.getId());//订单id
    ordersCanceled.setCancellerId(orderCancelDTO.getCurrentUserId());//取消人
    ordersCanceled.setCancelerName(orderCancelDTO.getCurrentUserName());//取消人名称
    ordersCanceled.setCancellerType(orderCancelDTO.getCurrentUserType());//取消人类型,1:普通用户,4:运营人员
    ordersCanceled.setCancelReason(orderCancelDTO.getCancelReason());//取消原因
    ordersCanceled.setCancelTime(LocalDateTime.now());//取消时间
    ordersCanceledMapper.insert(ordersCanceled);
}


//取消派单中订单: 1) 更新订单状态为已关闭  2) 保存取消订单记录  3) 保存待退款的记录
@Transactional(rollbackFor = Exception.class)
public void cancelByDispatching(OrderCancelDTO orderCancelDTO) {
    // 1) 更新订单状态为已关闭
    // update orders set orders_status = 700 , refund_status = 1 where id = 订单id and orders_status = 100
    OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO.builder()
            .id(orderCancelDTO.getId())//订单id
            .originStatus(OrderStatusEnum.DISPATCHING.getStatus())//原始状态
            .targetStatus(OrderStatusEnum.CLOSED.getStatus())//目标状态
            .refundStatus(OrderRefundStatusEnum.REFUNDING.getStatus()) //退款状态
            .build();
    Integer i = ordersCommonService.updateStatus(orderUpdateStatusDTO);
    if (i <= 0) {
        throw new ForbiddenOperationException("订单取消失败");
    }

    // 2) 保存取消订单记录
    OrdersCanceled ordersCanceled = new OrdersCanceled();
    ordersCanceled.setId(orderCancelDTO.getId());//订单id
    ordersCanceled.setCancellerId(orderCancelDTO.getCurrentUserId());//取消人
    ordersCanceled.setCancelerName(orderCancelDTO.getCurrentUserName());//取消人名称
    ordersCanceled.setCancellerType(orderCancelDTO.getCurrentUserType());//取消人类型,1:普通用户,4:运营人员
    ordersCanceled.setCancelReason(orderCancelDTO.getCancelReason());//取消原因
    ordersCanceled.setCancelTime(LocalDateTime.now());//取消时间
    ordersCanceledMapper.insert(ordersCanceled);

    //3) 保存待退款的记录
    OrdersRefund ordersRefund =  BeanUtil.copyProperties(orderCancelDTO,OrdersRefund.class);
    ordersRefundMapper.insert(ordersRefund);
}

cancel方法中判断订单状态时不能直接判断orders.getOrdersStatus() == 0或100;因为orders.getOrdersStatus()得到的是Integer类型的,其为封装类类型,对象中存储的是对应的数据的地址,而不是数值,直接通过==比较是错误的,因此这里用到equal()方法来判断

这里owner就是注入的代理对象来防止事务失效

随后在cancelByNoPay以及cancelByDispatching方法中调用了updateStatus方法来进行表更改(这里是修改订单表状态):

随后保存取消订单明细并保存退款信息

二、定义定时任务完成退款功能

接下来使用定时任务去读取表中数据,调用支付服务的退款接口完成退款功能

FeiginAPI:RefundRecordApi

请求路径:POST trade/inner/refund-record/refund

请求参数:Long tradingOrderNo 交易系统订单号(由下单接口返回),BigDecimal refundAmount 退款金额

返回结果:ExecutionResultResDTO

1)OrdersHandler

jzo2o-orders-manager 定义一个定时任com.jzo2o.orders.manager.handler.OrdersHandler完成功能:

java 复制代码
package com.jzo2o.orders.manager.handler;

import cn.hutool.core.collection.CollUtil;
import com.jzo2o.api.trade.RefundRecordApi;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.manager.service.IOrdersManagerService;
import com.jzo2o.orders.manager.service.IOrdersRefundService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * 订单处理器类
 */
@Component
@Slf4j
public class OrdersHandler {

    @Autowired
    private IOrdersRefundService ordersRefundService;

    @Autowired
    private RefundRecordApi refundRecordApi;

    @Autowired
    private IOrdersManagerService ordersManagerService;

    @Autowired
    private OrdersHandler owner;

    /**
     * 定时读取退款表中的数据, 然后调用支付服务的退款接口
     */
    @XxlJob(value = "handleRefundOrders")
    public void handleRefundOrders() {
        //1. 读取退款表中的数据
        List<OrdersRefund> ordersRefundList = ordersRefundService.queryRefundOrderListByCount(100);
        if (CollUtil.isEmpty(ordersRefundList)){
            return;
        }

        //2. 遍历查询到的数据
        for (OrdersRefund ordersRefund : ordersRefundList) {
            //3. 然后调用支付服务的退款接口
            ExecutionResultResDTO executionResultResDTO
                    = refundRecordApi.refundTrading(ordersRefund.getTradingOrderNo(), ordersRefund.getRealPayAmount());

            if (executionResultResDTO != null){
                //4. 根据退款接口的返回值做处理
                if (executionResultResDTO.getRefundStatus() == OrderRefundStatusEnum.REFUNDING.getStatus()) {
                    continue;//如果返回值是退款中, 不做后续处理
                }

                //退款后续操作
                owner.afterRefund(ordersRefund,executionResultResDTO);
            }
        }
    }

    @Transactional
    public void afterRefund(OrdersRefund ordersRefund,ExecutionResultResDTO executionResultResDTO) {
        //1) 更新订单表中退款相关字段(refund_status 退款状态 refund_no 支付服务退款单号 refund_id 第三方支付的退款单号)
        Orders orders = new Orders();
        orders.setId(ordersRefund.getId());
        orders.setRefundNo(executionResultResDTO.getRefundNo());
        orders.setRefundId(executionResultResDTO.getRefundId());
        orders.setRefundStatus(executionResultResDTO.getRefundStatus());
        boolean b = ordersManagerService.updateById(orders);

        //2) 删除退款表中的数据
        if (b){
            ordersRefundService.removeById(ordersRefund.getId());
        }
    }
}

这里我们看到并没有用ObjectUtil.equal()方法,这是为什么呢,原因是我们这里的值只有1、2、3(没有超出127),Integer底层会缓存该值的对象,并给所有引用这些数值的对象赋值该引用地址,因此等号两边的封装类对象Integer指向的都是同一地址,因此不需要用equal方法也可以

随后调用第三方接口、修改订单表中退款相关字段并删除退款表中的该条数据

2)配置任务调度

在当前提供的虚拟机中已经配置好了关于退款的定时任务执行器和任务

  • 执行器:jzo2o-orders-manager(订单管理)

  • 任务管理:handleRefundOrders(退款)

目前任务处于停止状态,开始启动后就可以直接使用了

效果展示:

三、策略模式

当前项目中,订单的状态比较多,不同状态下取消订单的逻辑是不同的;而且用户不同,可操作的内容也不同

  • 普通用户:可取消待支付、派单中、待服务的订单

  • 运营人员:可取消派单中、待服务、服务中、完成的订单

因此我们最终实现取消订单的代码逻辑如下所示:

java 复制代码
public void cancel(OrderCancelDTO orderCancelDTO) {
    
    Orders orders = getById(orderCancelDTO.getId());//查询订单信息
    Integer ordersStatus = orders.getOrdersStatus();//订单状态
    CurrentUserInfo currentUserInfo = UserContext.currentUser();//获取当前用户
    Integer userType = currentUserInfo.getUserType();//用户类型
    
    if(UserType.C_USER==userType){//普通用户取消订单
        if(OrderStatusEnum.NO_PAY.getStatus()==ordersStatus){ //订单状态为待支付
            //...
        }else if(OrderStatusEnum.DISPATCHING.getStatus()==ordersStatus){ //订单状态为派单中
            //...
        }else if(OrderStatusEnum.NO_SERVE.getStatus()==ordersStatus){ //订单状态为待服务
            //...
        }else{
            throw new CommonException("当前订单状态不支持取消");
        }
    }else if(UserType.OPERATION==userType){//运营人员取消订单
        if(OrderStatusEnum.DISPATCHING.getStatus()==ordersStatus) { //订单状态为派单中
            //...
        }else if(OrderStatusEnum.NO_SERVE.getStatus()==ordersStatus){//订单状态为待服务
            //...
        }else if(OrderStatusEnum.SERVING.getStatus()==ordersStatus){//订单状态为服务中
            //...
        }else if(OrderStatusEnum.FINISHED.getStatus()==ordersStatus){//订单状态为已完成
            //...
        }else{
            throw new CommonException("当前订单状态不支持取消");
        }
    }
}

这种代码虽然可以实现功能,但是可读性和扩展性都很差,我们可以使用策略模式进行改造

使用场景:针对一个业务,有多个不同的执行分支,我们需要根据不同的条件,切换到不同的分支上去

好处:解决使用条件语句(如if...else)导致的复杂性和扩展性问题

策略模式需要开发下面的接口和类:

  • 策略接口:定义策略方法

  • 策略实现类:需要实现策略接口,并重写接口中声明的策略方法

  • 上下文环境类:需要维护所有策略对象,然后根据不同的条件,调用不同的策略对象

下面以一个**支付方式**选择的场景为例子来讲解一下,需求如下:

  1. 当前项目支持微信和阿里两种支付方式

  2. 用户可以自行选择一种支付方式

  3. 根据用户选择,执行对应的支付代码

1)定义策略接口

创建PayStrategy接口

java 复制代码
package com.jzo2o.orders.manager.service.strategy;

/**
 * 支付策略接口
 */
public interface PayStrategy {

    //策略方法
    void pay();
}

2)定义微信支付策略类

java 复制代码
package com.jzo2o.orders.manager.service.strategy.impl;

import com.jzo2o.orders.manager.service.strategy.PayStrategy;
import org.springframework.stereotype.Component;

/**
 * 微信支付策略实现类
 */
@Component("wxPay")
public class WxPayStrategy implements PayStrategy {
    @Override
    public void pay() {
        System.out.println("使用微信进行支付");
    }
}

3)定义阿里支付策略类

java 复制代码
package com.jzo2o.orders.manager.service.strategy.impl;

import com.jzo2o.orders.manager.service.strategy.PayStrategy;
import org.springframework.stereotype.Component;

/**
 * 阿里支付策略实现类
 */
@Component("aliPay")
public class AliPayStrategy implements PayStrategy {
    @Override
    public void pay() {
        System.out.println("使用阿里进行支付");
    }
}

4)定义上下文环境类

java 复制代码
package com.jzo2o.orders.manager.service.strategy;

import cn.hutool.extra.spring.SpringUtil;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Map;

/**
 * 上下文环境类(策略管理类)
 */
@Component
public class PayStrategyManager {
    Map<String, PayStrategy> payStrategyMap;

    //维护所有策略对象
    @PostConstruct //此注解标注的方法会在当前对象PayStrategyManager创建之后自动执行
    public void init() {
        //key: 当前对象在容器中的id
        //value: 当前对象
        payStrategyMap = SpringUtil.getBeansOfType(PayStrategy.class);
    }

    //根据用户需求, 执行指定的策略对象
    public void pay(String key) {
        PayStrategy payStrategy = payStrategyMap.get(key);
        if (payStrategy == null) {
            throw new RuntimeException("暂不支持当前支付方式");
        }
        payStrategy.pay();
    }
}

5)测试类

java 复制代码
package com.jzo2o.orders.manager.service.strategy;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * 测试支付方式的选择
 */
@SpringBootTest
public class PayTest {

    @Autowired
    private PayStrategyManager payStrategyManager;

    @Test
    public void test(){
        //测试微信支付
        payStrategyManager.pay("wxPay");
        //测试阿里支付
        payStrategyManager.pay("aliPay");
    }
}

正式改造项目代码:

我们可以将取消订单定义为策略接口,针对不同场景下取消订单的逻辑定义为一个一个的策略类

1)定义策略接口
java 复制代码
package com.jzo2o.orders.manager.strategy;

import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;

/**
 * 订单取消策略接口
 */
public interface OrderCancelStrategy {
    /**
     * 订单取消
     *
     * @param orderCancelDTO 取消订单的参数
     */
    void cancel(OrderCancelDTO orderCancelDTO);
}
2)定义策略实现类

① 普通用户对待支付状态订单取消

java 复制代码
package com.jzo2o.orders.manager.strategy.impl;

import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersCanceledMapper;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.dto.OrderUpdateStatusDTO;
import com.jzo2o.orders.base.service.IOrdersCommonService;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.strategy.OrderCancelStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

//普通用户取消未支付订单
@Component("1:NO_PAY")//用户类型:订单状态
public class CommonUserNoPayOrderCancelStrategy implements OrderCancelStrategy{

    @Autowired
    private OrdersCanceledMapper ordersCanceledMapper;

    @Autowired
    private IOrdersCommonService ordersCommonService;

    @Override
    public void cancel(OrderCancelDTO orderCancelDTO) {
        // 1) 更新订单状态
        //update orders set orders_status = 600 where id = 1 and orders_status = 0
        OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO.builder()
                .id(orderCancelDTO.getId())
                .originStatus(OrderStatusEnum.NO_PAY.getStatus())
                .targetStatus(OrderStatusEnum.CANCELED.getStatus())
                .build();
        Integer i = ordersCommonService.updateStatus(orderUpdateStatusDTO);
        if (i <= 0){
            throw new ForbiddenOperationException("订单取消失败");
        }

        // 2) 保存取消订单记录
        OrdersCanceled ordersCanceled = new OrdersCanceled();
        ordersCanceled.setId(orderCancelDTO.getId());//订单id
        ordersCanceled.setCancellerId(orderCancelDTO.getCurrentUserId());//当前用户id
        ordersCanceled.setCancelerName(orderCancelDTO.getCurrentUserName());//当前用户名称
        ordersCanceled.setCancellerType(orderCancelDTO.getCurrentUserType());//当前用户类型
        ordersCanceled.setCancelReason(orderCancelDTO.getCancelReason());//取消原因
        ordersCanceled.setCancelTime(LocalDateTime.now());//取消时间
        ordersCanceledMapper.insert(ordersCanceled);
    }
}

② 普通用户派单状态取消订单

java 复制代码
package com.jzo2o.orders.manager.strategy.impl;

import cn.hutool.core.bean.BeanUtil;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersCanceledMapper;
import com.jzo2o.orders.base.mapper.OrdersRefundMapper;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.base.model.dto.OrderUpdateStatusDTO;
import com.jzo2o.orders.base.service.IOrdersCommonService;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.strategy.OrderCancelStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

//普通用户派单状态取消订单
@Component("1:DISPATCHING")
public class CommonUserDispatchingOrderCancelStrategy implements OrderCancelStrategy {
    @Autowired
    private OrdersCanceledMapper ordersCanceledMapper;
    @Autowired
    private OrdersRefundMapper ordersRefundMapper;
    @Autowired
    private IOrdersCommonService ordersCommonService;

    //取消派单中订单
    @Transactional(rollbackFor = Exception.class)
    public void cancel(OrderCancelDTO orderCancelDTO) {
        // 1) 更新订单状态
        //update orders set orders_status = 700 ,  refund_status =1 where id = 1 and orders_status = 100
        OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO.builder()
                .id(orderCancelDTO.getId())
                .originStatus(OrderStatusEnum.DISPATCHING.getStatus())
                .targetStatus(OrderStatusEnum.CLOSED.getStatus())
                .refundStatus(OrderRefundStatusEnum.REFUNDING.getStatus())
                .build();
        Integer i = ordersCommonService.updateStatus(orderUpdateStatusDTO);
        if (i <= 0){
            throw new ForbiddenOperationException("订单取消失败");
        }

        // 2) 保存取消订单记录
        OrdersCanceled ordersCanceled = new OrdersCanceled();
        ordersCanceled.setId(orderCancelDTO.getId());//订单id
        ordersCanceled.setCancellerId(orderCancelDTO.getCurrentUserId());//当前用户id
        ordersCanceled.setCancelerName(orderCancelDTO.getCurrentUserName());//当前用户名称
        ordersCanceled.setCancellerType(orderCancelDTO.getCurrentUserType());//当前用户类型
        ordersCanceled.setCancelReason(orderCancelDTO.getCancelReason());//取消原因
        ordersCanceled.setCancelTime(LocalDateTime.now());//取消时间
        ordersCanceledMapper.insert(ordersCanceled);

        // 3) 保存待退款记录
        OrdersRefund ordersRefund = BeanUtil.copyProperties(orderCancelDTO, OrdersRefund.class);
        ordersRefundMapper.insert(ordersRefund);
    }
}
3)上下文环境类
java 复制代码
package com.jzo2o.orders.manager.strategy;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class OrderCancelStrategyManager {
    
    @Autowired
    private OrdersMapper ordersMapper;

    //key格式:userType+":"+orderStatusEnum,例:1:NO_PAY
    private Map<String, OrderCancelStrategy> strategyMap = new HashMap<>();

    @PostConstruct //此注解标注的方法会在当前对象创建后自动调用
    public void init() {
        strategyMap = SpringUtil.getBeansOfType(OrderCancelStrategy.class);
        log.debug("订单取消策略类初始化到map完成!");
    }

    public void cancel(OrderCancelDTO orderCancelDTO) {
        //1. 根据订单id查询订单信息,如果订单不存在, 直接返回错误
        Orders orders = ordersMapper.selectById(orderCancelDTO.getId());
        if (ObjectUtil.isNull(orders)) {
            throw new ForbiddenOperationException("订单不存在");
        }
        BeanUtil.copyProperties(orders, orderCancelDTO);
        
        //2. 根据用户类型和订单状态获取获取策略对象
        String key = orderCancelDTO.getCurrentUserType() + ":" + OrderStatusEnum.codeOf(orders.getOrdersStatus()).toString();
        OrderCancelStrategy strategy =  strategyMap.get(key);
        if (ObjectUtil.isEmpty(strategy)) {
            throw new ForbiddenOperationException("不被许可的操作");
        }
        
        //3. 执行策略对象的方法
        strategy.cancel(orderCancelDTO);
    }
}

这里的核心就是通过SpringUtil.getBeansOfType(OrderCancelStrategy.class)这一方法获取OrderCancelStrategy接口的实现类Bean对象并封装到Map当中,且以我们命名好的Bean名称作为String类型的key(@Component("1:DISPATCHING")),把对象当作value

4)调用类

修改原有OrdersManagerServiceImpl的取消订单逻辑,然后删除掉接口和实现类中的cancelByNoPay和cancelByDispatching方法

java 复制代码
@Autowired
private OrderCancelStrategyManager orderCancelStrategyManager;

@Override
public void cancel(OrderCancelDTO orderCancelDTO) {
    //调用取消订单策略管理器处理取消订单
    orderCancelStrategyManager.cancel(orderCancelDTO);
}

效果展示:

四、取消超时订单:

我们选择用定时任务、策略模式实现该功能;具体实现步骤如下:

  1. 定义取消超时未支付订单的策略类

  2. 在OrderHandler中添加定时任务方法调用策略管理器处理超时未支付订单

  3. 在xxl-job的任务调度中心配置任务的执行时机

超时取消订单策略类

新建策略类SystemNoPayOrderCancelStrategy:

java 复制代码
package com.jzo2o.orders.manager.strategy.impl;

import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersCanceledMapper;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.dto.OrderUpdateStatusDTO;
import com.jzo2o.orders.base.service.IOrdersCommonService;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.strategy.OrderCancelStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
 * 系统定时任务取消待支付状态订单的策略类
 */
@Component("0:NO_PAY")//用户类型(UserType):订单状态(OrderStatusEnum)
public class SystemNoPayOrderCancelStrategy implements OrderCancelStrategy {
    @Autowired
    private IOrdersCommonService ordersCommonService;

    @Autowired
    private OrdersCanceledMapper ordersCanceledMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(OrderCancelDTO orderCancelDTO) {
        // 1) 更新订单状态为已取消
        // update orders set orders_status = 600 where id = 订单id and orders_status = 0
        OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO.builder()
                .id(orderCancelDTO.getId())//订单id
                .originStatus(OrderStatusEnum.NO_PAY.getStatus())//原始状态
                .targetStatus(OrderStatusEnum.CANCELED.getStatus())//目标状态
                .build();
        Integer i = ordersCommonService.updateStatus(orderUpdateStatusDTO);
        if (i <= 0) {
            throw new ForbiddenOperationException("订单取消失败");
        }

        // 2) 保存取消订单记录
        OrdersCanceled ordersCanceled = new OrdersCanceled();
        ordersCanceled.setId(orderCancelDTO.getId());//订单id
        ordersCanceled.setCancellerId(orderCancelDTO.getCurrentUserId());//取消人
        ordersCanceled.setCancelerName(orderCancelDTO.getCurrentUserName());//取消人名称
        ordersCanceled.setCancellerType(orderCancelDTO.getCurrentUserType());//取消人类型,1:普通用户,4:运营人员
        ordersCanceled.setCancelReason(orderCancelDTO.getCancelReason());//取消原因
        ordersCanceled.setCancelTime(LocalDateTime.now());//取消时间
        ordersCanceledMapper.insert(ordersCanceled);
    }
}

定时任务类

在OrderHandler中添加定时任务方法处理超时未支付订单

java 复制代码
@Autowired
private OrderCancelStrategyManager orderCancelStrategyManager;

/**
 * 取消超时订单
 */
@XxlJob("cancelOverTimePayOrder")
public void cancelOverTimePayOrder() {
    //1. 查询超时未支付的订单
    //select * from orders where orders_status = 0 and pay_status = 2 and create_time < 当前时间 - 15分钟
    List<Orders> list = ordersManagerService.lambdaQuery()
            .eq(Orders::getOrdersStatus, OrderStatusEnum.NO_PAY.getStatus())//orders_status = 0
            .eq(Orders::getPayStatus, OrderPayStatusEnum.NO_PAY.getStatus())//pay_status = 2
            .lt(Orders::getCreateTime, LocalDateTime.now().minusMinutes(15))//create_time < 当前时间 - 15分钟
            .last("limit 100")//限制每次最多查100条
            .list();
    if (CollUtil.isEmpty(list)){
        return;
    }

    //2. 遍历集合, 获取到每一笔订单
    for (Orders orders : list) {
        //然后去取消
        OrderCancelDTO orderCancelDTO = new OrderCancelDTO();
        orderCancelDTO.setId(orders.getId());//订单id
        orderCancelDTO.setCurrentUserId(0L);//当前用户id
        orderCancelDTO.setCurrentUserName("系统定时任务");//当前用户名称
        orderCancelDTO.setCurrentUserType(UserType.SYSTEM);//当前用户类型
        orderCancelDTO.setCancelReason("超时未支付");//取消原因
        orderCancelStrategyManager.cancel(orderCancelDTO);
    }
}

配置任务调度

在当前提供的xxl-job的任务调度中心已经配置好了关于取消超时未支付订单的执行器和任务

  • 执行器:jzo2o-orders-manager(订单管理)

  • 任务管理:cancelOverTimePayOrder(取消支付超时订单)

目前任务处于停止状态,开始启动后就可以直接使用了

相关推荐
章鱼哥7301 小时前
Java 策略模式 + 聚合对象:实现多模块的统计与聚合,快速扩展的实战
java·开发语言·策略模式
是店小二呀1 小时前
openGauss进阶:使用DBeaver可视化管理与实战
开发语言·人工智能·yolo
万粉变现经纪人1 小时前
如何解决 pip install 编译报错 ‘cl.exe’ not found(缺少 VS C++ 工具集)问题
开发语言·c++·人工智能·python·pycharm·bug·pip
U***e631 小时前
JavaScript数据分析
开发语言·javascript·数据分析
Cx330❀2 小时前
C++ map 全面解析:从基础用法到实战技巧
开发语言·c++·算法
1***Q7842 小时前
Python增强现实案例
开发语言·python·ar
枫叶丹42 小时前
openGauss:面向数字时代的下一代企业级开源关系型数据库
开发语言·数据库·开源·自动化
Demon--hx3 小时前
[C++]迭代器
开发语言·c++
BanyeBirth3 小时前
C++窗口问题
开发语言·c++·算法