问题描述

用户支付完成会将支付状态及订单状态保存在订单数据库中,由订单服务去维护订单数据库。而学生选课信息在学习中心数据库,由学习服务去维护学习中心数据库的信息。下图是系统结构图:

三消息队列 分布式 消息队列分布式事务_mysql

如何实现两个分布式服务(订单服务、学习服务)共同完成一件事即订单支付成功自动添加学生选课的需求,这里的关键是如何保证两个分布式服务的事务的一致性。

解决方案

尝试解决上边的需求,选择基于消息的分布式事务解决方案,解决方案如下图:

三消息队列 分布式 消息队列分布式事务_队列_02

  1. 支付成功后,订单服务向本地数据库更新订单状态并向消息表写入“添加选课消息”,通过本地数据库保证订单状态和添加选课消息的事务。
  2. 定时任务扫描消息表,取出“添加选课任务“并发向MQ。
  3. 学习服务接收到添加选课的消息,先查询本地数据库的历史消息表是否存在消息,存在则说明已经添加选课, 否则向本地数据库添加选课,并向历史消息表添加选课消息。这里选课表和历史消息表在同一个数据库,通过本地事务保证。
  4. 学习服务接收到添加选课的消息,通过查询消息表判断,如果已经添加选课也向MQ发送“完成添加选课任务的消息”,否则添加选课,完成后向MQ发送“完成添加选课任务的消息”,
  5. 订单服务接收到完成选课的消息后删除订单数据库中消息表的“添加选课消息”,为保证后期对账将消息表的消息先添加到历史消息表再删除消息,表示此消息已经完成。

订单服务定时发送消息

定时任务发送消息流程如下:

  1. 每隔1分钟扫描一次任务表,一次取出多个任务,取出超过1分钟未处理的任务
  2. 考虑订单服务可能集群部署,为避免重复发送任务使用乐观锁的方式每次从任务列表取出要处理的任务
  3. 任务发送完毕更新任务发送时间

关于任务表的添加:
正常的流程是订单支付成功向更新订单支付状态并向任务表写入“添加选课任务”。
目前订单支付功能没有开发,采用手动向任务表添加任务。
该模块涉及到两张表(任务表,历史任务表),用下面的sql语句进行创建

CREATE DATABASE orderdb;
USE `orderdb`;

DROP TABLE IF EXISTS `task`;

CREATE TABLE `task` (
  `id` varchar(32) NOT NULL COMMENT '任务id',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `delete_time` datetime DEFAULT NULL,
  `task_type` varchar(32) DEFAULT NULL COMMENT '任务类型',
  `mq_exchange` varchar(64) DEFAULT NULL COMMENT '交换机名称',
  `mq_routingkey` varchar(64) DEFAULT NULL COMMENT 'routingkey',
  `request_body` varchar(512) DEFAULT NULL COMMENT '任务请求的内容',
  `version` int(10) DEFAULT NULL COMMENT '乐观锁版本号',
  `status` varchar(32) DEFAULT NULL COMMENT '任务状态',
  `errormsg` varchar(512) DEFAULT NULL COMMENT '任务错误信息',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `task_his`;

CREATE TABLE `task_his` (
  `id` varchar(32) NOT NULL COMMENT '任务id',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `delete_time` datetime DEFAULT NULL,
  `task_type` varchar(32) DEFAULT NULL COMMENT '任务类型',
  `mq_exchange` varchar(64) DEFAULT NULL COMMENT '交换机名称',
  `mq_routingkey` varchar(64) DEFAULT NULL COMMENT 'routingkey',
  `request_body` varchar(512) DEFAULT NULL COMMENT '任务请求的内容',
  `version` int(10) DEFAULT '0' COMMENT '乐观锁版本号',
  `status` varchar(32) DEFAULT NULL COMMENT '任务状态',
  `errormsg` varchar(512) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

手动插入一条任务

insert  into `task`(`id`,`create_time`,`update_time`,`delete_time`,`task_type`,`mq_exchange`,`mq_routingkey`,`request_body`,`version`,`status`,`errormsg`) values ('5527858162937cf5016295be04ba0020','2020-10-29 15:19:19','2020-10-29 15:19:19',NULL,'add_choosecourse','ex_learning_addchoosecourse','addchoosecourse','{\"courseId\":\"5028e58161bd22e6f161dd23675a0s05\",\"userId\":\"49\"}',1,'Pending',NULL);

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> 
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>manage-order</artifactId>
    <dependencies>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

server:
  port: 9090
spring:
  application:
    name: manage-order
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/orderdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: qwe123

  #rabbitmq配置
  rabbitmq:
    host: 192.168.130.128
    username: admin
    password: admin

任务实体类

package com.sunyuqi.pojo.task;

import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;


@Data
@ToString
@Entity
@Table(name = "task")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class OrderTask implements Serializable {

    @Id
    @GeneratedValue(generator = "jpa-uuid")
    @Column(length = 32)
    private String id;

    @Column(name = "create_time")
    private Date createTime;
    @Column(name = "update_time")
    private Date updateTime;
    @Column(name = "delete_time")
    private Date deleteTime;
    @Column(name = "task_type")
    private String taskType;
    @Column(name = "mq_exchange")
    private String mqExchange;
    @Column(name = "mq_routingkey")
    private String mqRoutingkey;
    @Column(name = "request_body")
    private String requestBody;
    private Integer version;
    private String status;
    private String errormsg;
}

历史任务实体类

package com.sunyuqi.pojo.task;

import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;


@Data
@ToString
@Entity
@Table(name = "task_his")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class OrderTaskHis implements Serializable {

    @Id
    @GeneratedValue(generator = "jpa-assigned")
    @Column(length = 32)
    private String id;

    @Column(name = "create_time")
    private Date createTime;
    @Column(name = "update_time")
    private Date updateTime;
    @Column(name = "delete_time")
    private Date deleteTime;
    @Column(name = "task_type")
    private String taskType;
    @Column(name = "mq_exchange")
    private String mqExchange;
    @Column(name = "mq_routingkey")
    private String mqRoutingkey;
    @Column(name = "request_body")
    private String requestBody;
    private String version;
    private String status;
}

RabbitMQ配置
向RabbitMQ声明两个队列:添加选课、完成选课,交换机使用路由模式,代码如下:

package com.sunyuqi.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {
    //添加选课任务交换机
    public static final String EX_LEARNING_ADDCHOOSECOURSE = "ex_learning_addchoosecourse";

    //完成添加选课消息队列
    public static final String LEARNING_FINISHADDCHOOSECOURSE = "learning_finishaddchoosecourse";

    //添加选课消息队列
    public static final String LEARNING_ADDCHOOSECOURSE = "learning_addchoosecourse";

    //添加选课路由key
    public static final String LEARNING_ADDCHOOSECOURSE_KEY = "addchoosecourse";

    //完成添加选课路由key
    public static final String LEARNING_FINISHADDCHOOSECOURSE_KEY = "finishaddchoosecourse";

    /**
     * 交换机配置
     * @return the exchange
     */
    @Bean(EX_LEARNING_ADDCHOOSECOURSE)
    public Exchange EX_DECLARE() {
        return ExchangeBuilder.directExchange(EX_LEARNING_ADDCHOOSECOURSE).durable(true).build();
    }
    //声明队列完成添加选课队列
    @Bean(LEARNING_FINISHADDCHOOSECOURSE)
    public Queue QUEUE_DECLARE() {
        Queue queue = new Queue(LEARNING_FINISHADDCHOOSECOURSE,true,false,true);
        return queue;
    }

    //声明队列 添加选课队列
    @Bean(LEARNING_ADDCHOOSECOURSE)
    public Queue QUEUE_DECLARE_2() {
        Queue queue = new Queue(LEARNING_ADDCHOOSECOURSE,true,false,true);
        return queue;
    }
    /**
     * 绑定完成添加选课队列到交换机 .
     * @param queue    the queue
     * @param exchange the exchange
     * @return the binding
     */
    @Bean
    public Binding binding_finishaddchoose_processtask(@Qualifier("learning_finishaddchoosecourse") Queue queue, @Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(LEARNING_FINISHADDCHOOSECOURSE_KEY).noargs();
    }
    /**
     * 绑定添加选课队列到交换机 .
     * @param queue    the queue
     * @param exchange the exchange
     * @return the binding
     */
    @Bean
    public Binding binding_addchoose_processtask(@Qualifier("learning_addchoosecourse") Queue queue, @Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(LEARNING_ADDCHOOSECOURSE_KEY).noargs();
    }

}

任务接口(TaskDao)

package com.sunyuqi.dao;

import com.sunyuqi.pojo.task.OrderTask;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Date;
import java.util.Optional;


public interface TaskRepository extends JpaRepository<OrderTask,String> {


    Optional<OrderTask> findById(String id);
    //查询某个时间之间的前n条任务
    Page<OrderTask> findByUpdateTimeBefore(Pageable pageable, Date updateTime);

    //更新updateTime
    @Modifying
    @Query("update OrderTask t set t.updateTime = :updateTime where t.id = :id")
    public int updateTaskTime(@Param(value = "id") String id, @Param(value = "updateTime") Date updateTime);

    @Modifying
    @Query("update OrderTask t set t.version = :version+1 where t.id = :id and t.version = :version")
    public int updateTaskVersion(@Param(value = "id") String id, @Param(value = "version") int version);
}

历史任务接口(TaskHisDao)

package com.sunyuqi.dao;

import com.sunyuqi.pojo.task.OrderTaskHis;
import org.springframework.data.jpa.repository.JpaRepository;


public interface TaskHisRepository extends JpaRepository<OrderTaskHis,String> {
}

Service
考虑订单服务一般会集群部署,为了避免任务在1分钟内重复执行,这里使用乐观锁,实现思路如下:

  1. 每次取任务时判断当前版本及任务id是否匹配,如果匹配则执行任务,如果不匹配则取消执行。
  2. 如果当前版本和任务Id可以匹配到任务则更新当前版本加1
package com.sunyuqi.service;

import com.sunyuqi.dao.TaskHisRepository;
import com.sunyuqi.dao.TaskRepository;
import com.sunyuqi.pojo.task.OrderTask;
import com.sunyuqi.pojo.task.OrderTaskHis;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
public class TaskService {

    @Autowired
    TaskRepository taskRepository;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    TaskHisRepository taskHisRepository;

    //查询前n条任务
    public List<OrderTask> findTaskList(Date updateTime, int size){
        //设置分页参数
        Pageable pageable = PageRequest.of(0,size);
        //查询前n条任务
        Page<OrderTask> all = taskRepository.findByUpdateTimeBefore(pageable, updateTime);
        List<OrderTask> list = all.getContent();
        return list;
    }

    //发布消息
    public void publish(OrderTask orderTask, String ex, String routingKey){
        Optional<OrderTask> optional = taskRepository.findById(orderTask.getId());
        if(optional.isPresent()){
            rabbitTemplate.convertAndSend(ex,routingKey, orderTask);
            //更新任务时间
            OrderTask one = optional.get();
            one.setUpdateTime(new Date());
            taskRepository.save(one);
        }

    }

    //获取任务
    @Transactional
    public int getTask(String id,int version){
        //通过乐观锁的方式来更新数据表,如果结果大于0说明取到任务
        int count = taskRepository.updateTaskVersion(id, version);
        return count;
    }

    //完成任务
    @Transactional
    public void finishTask(String taskId){
        Optional<OrderTask> optionalTask = taskRepository.findById(taskId);
        if(optionalTask.isPresent()){
            //当前任务
            OrderTask orderTask = optionalTask.get();
            //历史任务
            OrderTaskHis orderTaskHis = new OrderTaskHis();
            BeanUtils.copyProperties(orderTask, orderTaskHis);
            taskHisRepository.save(orderTaskHis);
            taskRepository.delete(orderTask);
        }
    }
}

编写任务类,每分钟执行任务,启动订单工程,观察定时发送消息日志,观察rabbitMQ队列中是否有消息

package com.sunyuqi.mq;

import com.sunyuqi.config.RabbitMQConfig;
import com.sunyuqi.pojo.task.OrderTask;
import com.sunyuqi.service.TaskService;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.List;


@Component
public class ChooseCourseTask {
    private static final Logger LOGGER = LoggerFactory.getLogger(ChooseCourseTask.class);

    @Autowired
    TaskService taskService;

    @RabbitListener(queues = RabbitMQConfig.LEARNING_FINISHADDCHOOSECOURSE)
    public void receiveFinishChoosecourseTask(OrderTask orderTask){
        if(orderTask !=null && StringUtils.isNotEmpty(orderTask.getId())){
            taskService.finishTask(orderTask.getId());
        }
    }


//    @Scheduled(cron="0 0/1 * * * *") //每隔一分钟执行
    @Scheduled(cron="0/3 * * * * *") //每隔3秒执行
    //定时发送加选课任务
    public void sendChoosecourseTask(){
        //得到1分钟之前的时间
        Calendar time = Calendar.getInstance();
        time.add(Calendar.MINUTE, -1);
        Date beforeTime = time.getTime();
        List<OrderTask> orderTaskList = taskService.findTaskList(beforeTime, 100);
        System.out.println(orderTaskList);
        //调用service发布消息,将添加选课的任务发送给mq
        if (!orderTaskList.isEmpty())
        {
            for(OrderTask orderTask : orderTaskList){
                //取任务
                if(taskService.getTask(orderTask.getId(), orderTask.getVersion())>0){
                    String ex = orderTask.getMqExchange();//要发送的交换机
                    String routingKey = orderTask.getMqRoutingkey();//发送消息要带routingKey
                    taskService.publish(orderTask,ex,routingKey);
                }
            }
        }
    }

}

引导类

package com.sunyuqi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling//开启任务调度
@EntityScan(value={"com.sunyuqi.pojo.task"})
@ComponentScan(basePackages={"com.sunyuqi"})
@SpringBootApplication
public class ManageOrderApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(ManageOrderApplication.class, args);
    }

}

运行该类,定时任务检查到消息表中有消息,向消息队列中发送消息,并修改消息的updatetime,同时监听已完成选课的消息队列。

消息发送成功。

三消息队列 分布式 消息队列分布式事务_队列_03

自动添加选课开发

学习服务添加选课
需求分析
学习服务接收MQ发送添加选课消息,执行添加 课操作。 添加选课成功向学生选课表插入记录、向历史任务表插入记录、并向MQ发送“完成选课”消息。
该模块涉及到的表(学生课程表,历史任务表)

CREATE DATABASE `coursedb`
USE `coursedb`;

DROP TABLE IF EXISTS `learning_course`;

CREATE TABLE `learning_course` (
  `id` varchar(32) NOT NULL,
  `course_id` varchar(32) NOT NULL COMMENT '课程id',
  `user_id` varchar(32) NOT NULL COMMENT '用户id',
  `price` float(8,2) DEFAULT NULL COMMENT '课程价格',
  `valid` boolean DEFAULT 0 COMMENT '有效性',
  `start_time` datetime DEFAULT NULL,
  `end_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `learning_list_unique` (`course_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `task_his`;
CREATE TABLE `task_his` (
  `id` varchar(32) NOT NULL COMMENT '任务id',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `delete_time` datetime DEFAULT NULL,
  `task_type` varchar(32) DEFAULT NULL COMMENT '任务类型',
  `mq_exchange` varchar(64) DEFAULT NULL COMMENT '交换机名称',
  `mq_routingkey` varchar(64) DEFAULT NULL COMMENT 'routingkey',
  `request_body` varchar(512) DEFAULT NULL COMMENT '任务请求的内容',
  `version` int(10) DEFAULT '0' COMMENT '乐观锁版本号',
  `status` varchar(32) DEFAULT NULL COMMENT '任务状态',
  `errormsg` varchar(512) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> 
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>managecourse</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.1.37</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

server:
  port: 9091
spring:
  application:
    name: manage-order
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/coursedb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: qwe123

  #rabbitmq配置
  rabbitmq:
    host: 192.168.130.128
    username: admin
    password: admin

课程实体类

package com.sunyuqi.pojo.course;

import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;


@Data
@ToString
@Entity
@Table(name="learning_course")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class LearningCourse implements Serializable {
    private static final long serialVersionUID = -916357210051789799L;
    @Id
    @GeneratedValue(generator = "jpa-uuid")
    @Column(length = 32)
    private String id;
    @Column(name = "course_id")
    private String courseId;
    @Column(name = "user_id")
    private String userId;
    private Boolean valid;
    @Column(name = "start_time")
    private Date startTime;
    @Column(name = "end_time")
    private Date endTime;
}

任务实体类和历史任务实体类代码和订单服务中相同

RabbitMQ配置
学习服务监听MQ的添加选课队列,并且声明完成选课队列,配置代码同订单服务中RabbitMQ配置

课程Dao

package com.sunyuqi.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import com.sunyuqi.pojo.course.LearningCourse;

public interface LearningCourseRepository extends JpaRepository<LearningCourse,String> {

    //根据用户id和课程id查询
    LearningCourse findByUserIdAndCourseId(String userId, String courseId);
}

历史任务Dao

package com.sunyuqi.dao;

import com.sunyuqi.pojo.task.OrderTaskHis;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;


public interface TaskHisRepository extends JpaRepository<OrderTaskHis,String> {

    Optional<OrderTaskHis> findById(String id);
}

Service
添加选课方法向learning_course添加记录,为保证不重复添加选课,先查询历史任务表,如果从历史任务表查询不到任务说明此任务还没有处理,此时则添加选课并添加历史任务。

package com.sunyuqi.service;

import com.sunyuqi.dao.LearningCourseRepository;
import com.sunyuqi.dao.TaskHisRepository;
import com.sunyuqi.pojo.course.LearningCourse;
import com.sunyuqi.pojo.task.OrderTask;
import com.sunyuqi.pojo.task.OrderTaskHis;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Optional;


@Service
public class LearningService {

    @Autowired
    LearningCourseRepository LearningCourseRepository;

    @Autowired
    TaskHisRepository TaskHisRepository;


    //添加选课
    @Transactional
    public Integer addcourse(String userId, String courseId, Boolean valid, Date startTime, Date endTime, OrderTask orderTask){

        //向历史任务表播入记录
        Optional<OrderTaskHis> optional = TaskHisRepository.findById(orderTask.getId());
        if(optional.isPresent()){
           return 1;
        }
        //添加历史任务
        OrderTaskHis orderTaskHis = new OrderTaskHis();
        BeanUtils.copyProperties(orderTask,orderTaskHis);
        TaskHisRepository.save(orderTaskHis);
        LearningCourse course = LearningCourseRepository.findByUserIdAndCourseId(userId, courseId);
        if(course!=null){
            //更新选课记录
            //课程的开始时间
            course.setStartTime(startTime);
            course.setEndTime(endTime);
            LearningCourseRepository.save(course);
        }else{
            //添加新的选课记录
            course = new LearningCourse();
            course.setUserId(userId);
            course.setCourseId(courseId);
            course.setValid(valid);
            course.setStartTime(startTime);
            course.setEndTime(endTime);
            LearningCourseRepository.save(course);
        }
        return 1;
    }
}

接收添加选课消息
接收到添加选课的消息调用添加选课方法完成添加选课,并发送完成选课消息。

package com.sunyuqi.mq;

import com.alibaba.fastjson.JSON;
import com.sunyuqi.config.RabbitMQConfig;
import com.sunyuqi.pojo.task.OrderTask;
import com.sunyuqi.service.LearningService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.Map;

@Component
public class ChooseCourseTask {

    @Autowired
    LearningService learningService;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @RabbitListener(queues = RabbitMQConfig.LEARNING_ADDCHOOSECOURSE)
    public void receiveChoosecourseTask(OrderTask orderTask){

        //取出消息的内容
        String requestBody = orderTask.getRequestBody();
        Map map = JSON.parseObject(requestBody, Map.class);
        String userId = (String) map.get("userId");
        String courseId = (String) map.get("courseId");

        Calendar time = Calendar.getInstance();
        time.add(Calendar.YEAR, +1);
        Date endtime = time.getTime();

        //添加选课
        Integer result = learningService.addcourse(userId, courseId, true, new Date(), endtime, orderTask);
        if(result==1){
            //添加选课成功,要向mq发送完成添加选课的消息
            rabbitTemplate.convertAndSend(RabbitMQConfig.EX_LEARNING_ADDCHOOSECOURSE,RabbitMQConfig.LEARNING_FINISHADDCHOOSECOURSE_KEY,orderTask);
        }
    }
}

引导类,运行引导类,监听消息。

package com.sunyuqi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@SpringBootApplication
@EnableTransactionManagement
@EntityScan(value={"com.sunyuqi.pojo"})
@ComponentScan(basePackages={"com.sunyuqi"})//扫描本项目下的所有类
public class CourseApplication {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(CourseApplication.class, args);
    }
}

消息处理完成,发送完成选课消息。

三消息队列 分布式 消息队列分布式事务_分布式_04


订单服务模块监听到完成选课消息后将任务表中的任务移动到历史任务表中,意味着任务完成。(定时任务不会发送该任务消息了)

三消息队列 分布式 消息队列分布式事务_队列_05