使用隊列ActiveMQ處理超時訂單

背景:

在商城項目中,有這樣的需求:針對用戶已下單但是一直未去付款的訂單做超時處理,因爲如果一個商品有庫存數量的概念,在用戶每次下單時做庫存減操作,在用戶取消訂單時做庫存返還操作。這時候爲了防止惡意刷庫存,就需要針對已下單但是長時間未支付的訂單做超時處理,返還庫存的操作,此時,就要涉及到了訂單超時的處理。

解決方式:

方式一:定時任務
思路:
使用定時任務每分鐘輪詢數據庫,查詢出超時的訂單,進行update

代碼示例:

// 在Spirng中以註解的方式創建定時任務:
@Scheduled(cron = "0 */1 * * * ?") // 每分鐘執行一次
// 業務代碼
Date date = new Date();
Date dateyq = new Date(date.getTime() - (1000 * 60 * 15));//推遲15分鐘
String dateyqStr = TimeUtil.nowDate2ToString1(dateyq);
Example example = new Example(ConsumptionOrder.class);
example.createCriteria().andEqualTo("orderState", BaseConstant.USER_CONSU_ORDER_AWAIT_PAY)//訂單狀態 1:待付款 
      .andLessThan("createTime", dateyqStr);
List<ConsumptionOrder> list = consumptionOrderMapper.selectByExample(example);

方式二:使用隊列ActiveMQ處理超時訂單

思路:
使用activeMQ的延時投遞功能,在創建訂單成功之後,向隊列中存入訂單id,設置延時發送,延時時長爲15分鐘,此時訂單id將會在15分鐘後存入到activeMq的消息隊列中,然後mq監聽器監聽到消息,拿到隊列中的訂單id進行處理,拿到訂單id以後去查詢該訂單是否完成付款,如果未付款,則更新爲訂單超時交易關閉。

Property name type description
AMQ_SCHEDULED_DELAY long 延遲投遞的時間
AMQ_SCHEDULED_PERIOD long 重複投遞的時間間隔
AMQ_SCHEDULED_REPEAT int 重複投遞次數
AMQ_SCHEDULED_CRON String Cron表達式

1、修改activemq配.xml配置文件,啓用延時投遞。
在這裏插入圖片描述

    <broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">

2、項目中配置 applicationContext-activemq.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 自動掃描MQ包 ,將帶有註解的類 納入spring容器管理 -->
    <context:component-scan base-package="com.pzq.haisong.order.service.impl.activeMq"/>

    <!-- 配置MQ生產者 -->
    <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
        <property name="connectionFactory">
            <!-- 真正可以產生Connection的ConnectionFactory,由對應的JMS服務廠商提供 -->
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="tcp://192.168.1.93:61616"></property>
            </bean>
        </property>
    </bean>

    <!-- 這個是隊列 -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-active-queue"></constructor-arg>
    </bean>

    <!-- 這個是主題 -->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-active-topic"></constructor-arg>
    </bean>

    <!-- Spring提供的JMS工具類,它可以進行消息發送,接收等 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="jmsFactory"></property>
        <property name="defaultDestination" ref="destinationQueue"></property>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"></bean>
        </property>
    </bean>

     <!--配置監聽程序-->
    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="jmsFactory"></property>
        <property name="destination" ref="destinationQueue"></property>
        <!-- 需要實現一個myMessageListener接口 -->
        <property name="messageListener" ref="myMessageListener"></property>
    </bean>

</beans>

使用的這裏需要把mq配置文件加入spring管理
在這裏插入圖片描述
3、創建activeMq生產者和消息監聽器

/**
 * 方法描述:activeMq消息生產者
 * 創建時間:2019-11-30 15:01:27
 */
@Service("delayOrderService")
public class DelayOrderServiceImpl implements DelayOrderService {
    private static final Logger LOGGER = Logger.getLogger(DelayOrderServiceImpl.class);

    private static class CreateMessage implements MessageCreator {

        private JSONObject vipOrder;// vip訂單實體
        private long expireTime;// 超時時間
        public CreateMessage(JSONObject vipOrder, long expireTime) {
            super();
            this.vipOrder = vipOrder;
            this.expireTime = expireTime;
        }

        public Message createMessage(Session session) throws JMSException {
            Message message = session.createObjectMessage(vipOrder);
            // ScheduledMessage.AMQ_SCHEDULED_DELAY 設置消息延遲發送,expireTime:延遲時間
            message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, expireTime);
            return message;
        }
    }

    /**
     * 方法描述:創建訂單時調用此方法,將要處理的訂單id存入消息隊列
     * 創建時間:2019-11-30 15:02:11
     */
    @Override
    public void addDelayOrder(JSONObject vipOrder, long expireTime) {

        LOGGER.info("訂單超時時長:" + expireTime/1000 + "秒,將被髮送給消息隊列,訂單id:" + vipOrder.getLong("orderId"));

        // 第一步:初始化一個spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/applicationContext-activemq.xml");
        // 第二步:從容器中獲得JMSTemplate對象。
        JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class);
        // 第三步:從容器中獲得一個Destination對象
        Queue queue = (Queue) applicationContext.getBean("destinationQueue");

        jmsTemplate.send(queue, new DelayOrderServiceImpl.CreateMessage(vipOrder, expireTime));

        LOGGER.info("存入消息隊列成功");
    }
}
/**
 * 類描述:ActiveMQ 消息監聽器
 * @date:2019/11/30 11:16
 */
@Service
public class MyMessageListener implements MessageListener {
    private static final Logger LOGGER = Logger.getLogger(SpringMQ_Produce.class);

//    @Resource
//    private OrderService orderService;

    @Override
    public void onMessage(Message message) {
        try {
            JSONObject object = (JSONObject)((ObjectMessage) message).getObject();
            Long orderId = object.getLong("orderId");
            Integer orderType = object.getInteger("orderType");
            LOGGER.info("MQ接收到的消息:" + orderId + " + " + TimeUtil.nowTime());
            OrderService orderService = SpringContextHolder.getBean("orderService");
            orderService.updateDelayOrder(orderId, orderType);
        } catch (Exception e) {
            LOGGER.error("MessageListener異常:" + MyPubUtil.getError(e));
        }
    }
}


	/**
	 * 業務代碼:更新延時訂單狀態
	 * @param orderId
	 * @param orderType
	 * @return
	 */
	@Override
	public Map<String, Object> updateDelayOrder(Long orderId, Integer orderType) {
		if (1 == orderType) {
			VipOrder vipOrder = vipOrderMapper.selectByPrimaryKey(orderId);
			if (null != vipOrder && BaseConstant.USER_CONSU_ORDER_AWAIT_PAY.equals(vipOrder.getOrderState())) {
				vipOrder.setOrderState(BaseConstant.USER_CONVER_ORDER_FINISH_CLOSE);// 7-交易關閉
				vipOrder.setUpdateTime(TimeUtil.nowTime());
				vipOrderMapper.updateByPrimaryKey(vipOrder);
				LOGGER.info("處理完成超時訂單[VIP訂單],訂單id:" + orderId);
			}
		} else if (2 == orderType) {
			ConsumptionOrder consumptionOrder = consumptionOrderMapper.selectByPrimaryKey(orderId);
			if (consumptionOrder != null && BaseConstant.USER_CONSU_ORDER_AWAIT_PAY.equals(consumptionOrder.getOrderState())) {
				updateStock(consumptionOrder);// 修改庫存,調用你自己修改庫存的方法,這裏就不貼上來了
				consumptionOrder.setOrderState(BaseConstant.USER_CONSU_ORDER_FINISH_CLOSE);// 7-交易關閉
				consumptionOrder.setUpdateTime(TimeUtil.nowTime());
				consumptionOrderMapper.updateByPrimaryKey(consumptionOrder);
				LOGGER.info("處理完成超時訂單[消費訂單],訂單id:" + orderId);
			}
		}
		return ApiConstant.putok();
	}

這裏就是mq的配置文件和生產者和監聽器的代碼,以下記錄下遇到的問題:
在mq的監聽器中,通過@Resource和@Autowired注入service實例失敗,獲取不到bean。
spring容器中根本沒有完成對註解bean的掃描,因爲dispatcher.xml中配置的註解bean的優先級肯定沒有框架中的contextListener的優先級高,所以這裏獲取的實例是null。

這裏是通過一篇文章找到的解決方案:

解決方案,貼上鍊接

在applicationContext.xml聲明一個bean:
在這裏插入圖片描述

public class SpringContextHolder implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    /**
     * 實現ApplicationContextAware接口的context注入函數, 將其存入靜態變量.
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextHolder.applicationContext = applicationContext; // NOSONAR
    }

    /**
     * 取得存儲在靜態變量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    /**
     * 從靜態變量ApplicationContext中取得Bean, 自動轉型爲所賦值對象的類型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        checkApplicationContext();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 從靜態變量ApplicationContext中取得Bean, 自動轉型爲所賦值對象的類型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        checkApplicationContext();
        return (T) applicationContext.getBeansOfType(clazz);
    }

    /**
     * 清除applicationContext靜態變量.
     */
    public static void cleanApplicationContext() {
        applicationContext = null;
    }

    private static void checkApplicationContext() {
        if (applicationContext == null) {
            throw new IllegalStateException("applicaitonContext未注入,請在applicationContext.xml中定義SpringContextHolder");
        }
    }
}

這裏是用這種方式來獲取bean。

以上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章