【网课平台】Day14.集成RabbitMQ:消息队列实现异步通知

news2024/11/26 8:24:43

文章目录

  • 一、需求:支付通知
    • 1、需求分析
    • 2、技术方案
    • 3、集成RabbitMQ
    • 4、生产端发送消息
    • 5、消费方发送消息
  • 二、需求:在线学习
    • 1、需求分析
    • 2、表设计与实体类
    • 3、接口定义--查询课程
    • 4、接口定义获取视频
    • 5、Service层开发
    • 6、FeignClient定义
    • 7、代码完善
  • 三、需求:我的课表
    • 1、需求分析
    • 2、表设计与模型类
    • 3、Controller定义
    • 3、Dao层开发
    • 4、Service层开发
    • 5、完善Controller

一、需求:支付通知

回顾整个流程:

在这里插入图片描述
订单服务要将支付成功的消息传给学习中心服务,已支付的课程,学习中心服务要将信息从选课记录表拿到我的课程表。

1、需求分析

UI设计:

  • 无UI

逻辑设计:

订单服务做为通用服务,要在订单支付成功后将支付结果异步通知给其他相关微服务 。相关微服务有:

  • 学习中心服务:支付完收费课程
  • 学习资源服务:对收费学习资料的购买

2、技术方案

使用消息队列进行异步通知:

订单服务完成支付后将支付结果发给每一个与订单服务对接的微服务,订单服务将消息发给交换机,由交换机广播消息,每个订阅消息的微服务都可以接收到支付结果,微服务收到支付结果根据订单的类型去更新自己的业务数据。
在这里插入图片描述

消息从生产端发送到消费端的过程:

  • 生产者将消息发送到交换机
  • 消息由交换机发送到队列
  • 消费者收到消息进行处理

使用消息队列进行异步通知需要保证消息的可靠性,即生产端要能将消息成功通知到消费端。而RabbitMQ有以下三点来保证消息的可靠性:

  • 生产者确认机制:发送消息前使用数据库事务将消息保存到数据库表中,成功发送到交换机将消息从数据库中删除
  • mq持久化:mq收到消息进行持久化,当mq重启即使消息没有消费完也不会丢失。需要配置交换机持久化、队列持久化、发送消息时设置持久化
  • 消费者确认机制:消费者消费成功自动发送ack,否则重试消费

最后:订单服务通过消息队列将支付结果发给学习中心服务,消息队列采用发布订阅模式。

3、集成RabbitMQ

这里使用docker来启动RabbitMQ,先安装Docker(已安装跳过)

# 1.yum包更新到最新
yum update

# 2.安装需要的软件包(yum-utils提供yum-config-manager的功能,,并且device mapper存储驱动程序需要device-mapper-persistent-data和lvm2)
yum install -y yum-utils device-mapper-persistent-data lvm2

# 3.设置yum源为阿里云
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 4.安装docker
yum install docker-ce -y

# 5.安装后查看docker版本
docker -v

# 6.阿里云镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://73z5h6yb.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

启动RabbitMQ容器:

# 安装启动rabbitmq容器
docker run -d --name myRabbitMQ -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -p 15672:15672 -p 5672:5672 rabbitmq:3.8.14-management

#访问http://host:15672

订单服务集成MQ:(生产端集成)

  • 在订单服务添加消息队列的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

  • 在nacos中加rabbitmq的配置文件rabbitmq-dev.yaml做为通用配置文件
spring:
  rabbitmq:
    host: 66.66.66.66
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    publisher-confirm-type: correlated #correlated 异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback
    publisher-returns: false #开启publish-return功能,同样是基于callback机制,需要定义ReturnCallback
    template:
      mandatory: false #定义消息路由失败时的策略。true,则调用ReturnCallback;false:则直接丢弃消息
    listener:
      simple:
        acknowledge-mode: none #出现异常时返回unack,消息回滚到mq;没有异常,返回ack ,manual:手动控制,none:丢弃消息,不回滚到mq
        retry:
          enabled: true #开启消费者失败重试
          initial-interval: 1000ms #初识的失败等待时长为1秒
          multiplier: 1 #失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3 #最大重试次数
          stateless: true #true无状态;false有状态。如果业务中包含事务,这里改为false

  • 在订单服务中引入这个公共配置
shared-configs:
  - data-id: rabbitmq-${spring.profiles.active}.yaml
    group: DEFAULT_GROUP
    refresh: true

  • 在订单服务service工程编写MQ配置类,配置交换机
package com.xuecheng.orders.config;

import com.alibaba.fastjson.JSON;
import com.xuecheng.messagesdk.model.po.MqMessage;
import com.xuecheng.messagesdk.service.MqMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class PayNotifyConfig implements ApplicationContextAware {

    //交换机名称
    public static final String PAYNOTIFY_EXCHANGE_FANOUT = "paynotify_exchange_fanout";
    //支付结果通知消息类型
    public static final String MESSAGE_TYPE = "payresult_notify";
    //支付通知队列的队列名称
    public static final String PAYNOTIFY_QUEUE = "paynotify_queue";

    //声明交换机,且持久化
    //这个Bean初始化成功后,就会在RabbitMQ创建一个交换机
    @Bean(PAYNOTIFY_EXCHANGE_FANOUT)
    public FanoutExchange paynotify_exchange_fanout() {
        // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
        return new FanoutExchange(PAYNOTIFY_EXCHANGE_FANOUT, true, false);
    }
    //支付通知队列,且持久化
    @Bean(PAYNOTIFY_QUEUE)
    public Queue course_publish_queue() {
        return QueueBuilder.durable(PAYNOTIFY_QUEUE).build();
    }

    //交换机和支付通知队列绑定
    @Bean
    public Binding binding_course_publish_queue(@Qualifier(PAYNOTIFY_QUEUE) Queue queue, @Qualifier(PAYNOTIFY_EXCHANGE_FANOUT) FanoutExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange);
    }
    
	//消息发到交换机,交换机再发到队列
	//在交换机发送到队列的时候,如果发送失败,则回调ReturnCallback
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取RabbitTemplate
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        //消息处理service
        MqMessageService mqMessageService = applicationContext.getBean(MqMessageService.class);
        // 设置ReturnCallback
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            // 投递失败,记录日志
            log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                    replyCode, replyText, exchange, routingKey, message.toString());
            //把这个发送失败的消息解析出来
            MqMessage mqMessage = JSON.parseObject(message.toString(), MqMessage.class);
            //将消息再添加到消息表mq_message
            mqMessageService.addMessage(mqMessage.getMessageType(),mqMessage.getBusinessKey1(),mqMessage.getBusinessKey2(),mqMessage.getBusinessKey3());

        });
    }
}

mq_message表结构:

在这里插入图片描述

学习中心服务集成MQ:(消费端集成)

和生产端一样,不同的是最后的配置类:

  • 添加消息队列的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

  • 在学习中心服务引入RabbitMQ的公共配置文件
shared-configs:
  - data-id: rabbitmq-${spring.profiles.active}.yaml
    group: DEFAULT_GROUP
    refresh: true
  • 添加配置类
package com.xuecheng.learning.config;

import com.alibaba.fastjson.JSON;
import com.xuecheng.messagesdk.model.po.MqMessage;
import com.xuecheng.messagesdk.service.MqMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Mr.M
 * @version 1.0
 * @description TODO
 * @date 2023/2/23 16:59
 */
@Slf4j
@Configuration
public class PayNotifyConfig {

    //交换机
    public static final String PAYNOTIFY_EXCHANGE_FANOUT = "paynotify_exchange_fanout";
    //支付结果通知消息类型
    public static final String MESSAGE_TYPE = "payresult_notify";
    //支付通知队列
    public static final String PAYNOTIFY_QUEUE = "paynotify_queue";

    //声明交换机,且持久化
    @Bean(PAYNOTIFY_EXCHANGE_FANOUT)
    public FanoutExchange paynotify_exchange_fanout() {
        // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
        return new FanoutExchange(PAYNOTIFY_EXCHANGE_FANOUT, true, false);
    }
    //支付通知队列,且持久化
    @Bean(PAYNOTIFY_QUEUE)
    public Queue course_publish_queue() {
        return QueueBuilder.durable(PAYNOTIFY_QUEUE).build();
    }

    //交换机和支付通知队列绑定
    @Bean
    public Binding binding_course_publish_queue(@Qualifier(PAYNOTIFY_QUEUE) Queue queue, @Qualifier(PAYNOTIFY_EXCHANGE_FANOUT) FanoutExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange);
    }

}

需要注意的是,在消费方写配置类是因为:消费端也要监听队列,如果消费端不加这个配置类,而消费端服务又先启动起来了,则报错。而消费端不用进行生产端的交换机发送到队列的操作,所以不用实现ApplicationContextAware和重写最后的方法。

最后:重启订单服务,登录rabbitmq,查看交换机自动创建成功
在这里插入图片描述

查看队列自动成功

在这里插入图片描述

4、生产端发送消息

现在订单服务中定义发送消息的接口:

/**
 * 发送通知结果
 * @param message是要发送的消息
 */
public void notifyPayResult(MqMessage message);

写实现方法:

@Override
public void notifyPayResult(MqMessage message) {

    //1、消息体,转json
    String msg = JSON.toJSONString(message);
    //设置消息持久化
    Message msgObj = MessageBuilder.withBody(msg.getBytes(StandardCharsets.UTF_8))
            .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
            .build();
    // 2.全局唯一的消息ID,需要封装到CorrelationData中
    CorrelationData correlationData = new CorrelationData(message.getId().toString());
    // 3.添加callback
    correlationData.getFuture().addCallback(
            result -> {
                if(result.isAck()){
                    // 3.1.ack,消息成功
                    log.debug("通知支付结果消息发送成功, ID:{}", correlationData.getId());
                    //删除消息表中的记录
                    mqMessageService.completed(message.getId());
                }else{
                    // 3.2.nack,消息失败
                    log.error("通知支付结果消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason());
                }
            },
            ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage())
    );
    // 发送消息,""即广播模式
    rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT, "", msgObj,correlationData);

}

订单服务收到第三方平台的支付结果时,在saveAliPayStatus方法中添加代码,向数据库消息表添加消息并进行发送消息

//完善之前的保存支付结果的方法
@Transactional
@Override
public void saveAliPayStatus(PayStatusDto payStatusDto) {
        .......
        //保存消息记录,参数1:支付结果通知类型,2: 业务id,3:业务类型(好让收到广播的消费方知道这是谁的东西)
        MqMessage mqMessage = mqMessageService.addMessage("payresult_notify", orders.getOutBusinessId(), orders.getOrderType(), null);
        //调用发送消息的方法,通知消息
        notifyPayResult(mqMessage);
    }
}

5、消费方发送消息

消费方监听MQ,接收支付结果,在课程中心服务中定义ReceivePayNotifyService类如下:

package com.xuecheng.learning.service.impl;

import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.learning.config.PayNotifyConfig;
import com.xuecheng.learning.service.MyCourseTablesService;
import com.xuecheng.messagesdk.model.po.MqMessage;
import com.xuecheng.messagesdk.service.MqMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
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.Service;

import java.io.IOException;

/**
 * @description 接收支付结果
 */
@Slf4j
@Service
public class ReceivePayNotifyService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    MqMessageService mqMessageService;

    @Autowired
    MyCourseTablesService myCourseTablesService;


    //监听消息队列接收支付结果通知,queues属性指定队列名
    @RabbitListener(queues = PayNotifyConfig.PAYNOTIFY_QUEUE)
    public void receive(Message message, Channel channel) {
    	//如果消费消息失败,重回消息队列后再处理,则先歇5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //获取消息
        MqMessage mqMessage = JSON.parseObject(message.getBody(), MqMessage.class);
        log.debug("学习中心服务接收支付结果:{}", mqMessage);

        //消息类型
        String messageType = mqMessage.getMessageType();
        //订单类型,60201表示购买课程
        String businessKey2 = mqMessage.getBusinessKey2();
        //这里只处理支付结果通知
        if (PayNotifyConfig.MESSAGE_TYPE.equals(messageType) && "60201".equals(businessKey2)) {
            //选课记录id
            String choosecourseId = mqMessage.getBusinessKey1();
            //添加选课
            boolean b = myCourseTablesService.saveChooseCourseStauts(choosecourseId);
            if(!b){
                //添加选课失败,抛出异常,消息重回队列
                //想要重回队列,就拿异常说话
                MyException.cast("收到支付结果,添加选课失败");
            }
        }


    }


}

添加选课方法,按之前的逻辑就是:更新选课记录表的支付状态字段、将数据拿到我的课程表中:

public boolean saveChooseCourseStauts(String choosecourseId){
	XcChooseCourse xcChooseCourse = choseCourseMapper.selectById(choosecourseId);
	if(null == xcChooseCourse){
		log.debug("已从消息队列接收到购买课程的消息,但课程id查不到选课记录,可能为脏数据,id:{}",choosecourseId);
		return false;
	}
	//选课状态
	String status = xcChooseCourse.getStatus();
	//只有状态为未支付时,才更新为已支付
	if("701002".equals(status)){
		xcChooseCourse.setStatus("701000");
		int result = chooseCourseMapper.updateById(xcChooseCourse);
		if(i <= 0){
			log.debug("添加选课记录失败:{}",xcChooseCourse);
			MyException.cast("添加选课记录失败");
		}
		//最后将选课表的数据拿到我的课程表
		//向我的课程表中插入记录,调用之前的方法
		XcCourseTables xcCourseTables = addCourseTables(xcChooseCourse);
		return true;
	}
	return false;
	
	
	
}

到此,支付的所有流程结束。

二、需求:在线学习

1、需求分析

用户通过课程详情界面点击[马上学习]进入视频插放界面进行视频点播。获取视频资源时进行学习资格校验:

在这里插入图片描述

如何判断是否有学习资格:

  • 免费视频,有资格
  • 非试学课程,判断是否登录
  • 登录则判断是否选课,如果已经选课且没有过期可以正常学习

在这里插入图片描述

2、表设计与实体类

无新增表,定义Vo类:

@Data
public class CoursePreviewVo{

	//课程基本信息,营销信息
	private CourseBaseInfoDto courseBase;
	//课程计划信息
	private List<TechplanDto> techplans;
	
}

3、接口定义–查询课程

在内容管理服务定义查询课程信息的接口:

@ApiOperation("获取课程发布信息")
@ResponseBody
@GetMapping("/course/whole/{courseId}")
public CoursePreviewVo getCoursePublish(@PathVariable("courseId") Long courseId) {
    //查询课程发布信息
    CoursePublish coursePublish = coursePublishService.getCoursePublish(courseId);
    if (coursePublish == null) {
       return new CoursePreviewVo();
     }

    //课程基本信息
    CourseBaseInfoDto courseBase = new CourseBaseInfoDto();
    BeanUtils.copyProperties(coursePublish, courseBase);
    //课程计划,JSON转List
    List<TeachplanDto> teachplans = JSON.parseArray(coursePublish.getTeachplan(), TeachplanDto.class);
    CoursePreviewVo coursePreviewInfo = new CoursePreviewVo();
    coursePreviewInfo.setCourseBase(courseBase);
    coursePreviewInfo.setTeachplans(teachplans);
    return coursePreviewInfo;
}

4、接口定义获取视频

在这里插入图片描述
问:用户信息传给学习中心服务,有学习资格则调用媒资管理服务。为什么不选择直接传给媒资管理服务,然后调用学习中心服务来判断资格,有则返回视频链接?

答:直接请求媒资管理服务,传入用户信息,也就是接口的代码写在媒资管理服务,不合理,因为这里负责视频的相关信息,没有用户体系校验的相关接口。简单的说就是这个模块不管用户,每个模块的定位不一样!

//在学习中心模块定义接口
package com.xuecheng.learning.api;

/**
 * @description 学习过程管理需求的接口
 */
@Api(value = "学习过程管理需求相关接口", tags = "学习过程管理接口")
@Slf4j
@RestController
public class MyLearningController {

    @Autowired
    LearningService learningService;


    @ApiOperation("获取视频")
    //定义为open接口,即不用登录也可以调用
    @GetMapping("/open/learn/getvideo/{courseId}/{teachplanId}/{mediaId}")
    public RestResponse<String> getvideo(@PathVariable("courseId") Long courseId,@PathVariable("teachplanId") Long teachplanId, @PathVariable("mediaId") String mediaId) {
        //工具类获取当前登录用户
        XcUser user = SecurityUtil.getUser();
        String userId = null;
        if(user != null){
            userId = user.getId();
        }
        //判断学习资格
        //获取视频(远程调用媒资服务)
       //穿课程计划id,是因为有的收费课程的部分章节是允许试学的
       return null;

    }

}

5、Service层开发

定义接口:

package com.xuecheng.learning.service;

/**
 * @description 学习过程管理service接口
 */
public interface LearningService {

/**
 * @description 获取教学视频
 * @Param userId 用户id,用于资格判断
 * @param courseId 课程id
 * @param teachplanId 课程计划id
 * @param mediaId 视频文件id
*/
public RestResponse<String> getVideo(String userId,Long courseId,Long teachplanId,String mediaId);

}

问:userId能不能在Service中调用工具类来获取,而不再依靠controller传进来?


不能!userId是从Spring-Security框架的上下文中拿的,是从jwt中解析出来的,而接token的是接口,不是Service层方法!所以只能在controller中来获取!

写接口的实现类:

public class LearningServiceImpl implements LearningService{

	@Override
	public RestResponse<String> getVideo(String userId,Long courseId,Long teachplanId, String mediaId) {
		//校验学习资格
		//获取视频信息
	}

}

以上两步需要调用其他微服务的,先定义Feignclient

6、FeignClient定义

在学习中心服务中定义媒资管理的Feignclient

//媒资管理服务远程接口
package com.xuecheng.learning.feignclient;

import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(value = "media-api",fallbackFactory = MediaServiceClientFallbackFactory.class)
@RequestMapping("/media")
public interface MediaServiceClient {

 	@GetMapping("/open/preview/{mediaId}")
 	public RestResponse<String> getPlayUrlByMediaId(@PathVariable("mediaId") String mediaId);

 }

定义FeignClient接口降级处理类:

@Slf4j
@Component
public class MediaServiceClientFallbackFactory implements FallbackFactory<MediaServiceClient> {
    @Override
    public MediaServiceClient create(Throwable throwable) {
        return new MediaServiceClient() {
            @Override
            public RestResponse<String> getPlayUrlByMediaId(String mediaId) {
                log.error("远程调用媒资管理服务熔断异常:{}",throwable.getMessage());
                return null;
            }
        };
    }
}

在调用方学习中心服务中定义内容管理服务的FeignClient

/**
 * @description 内容管理远程接口
 */
@FeignClient(value = "content-api",fallbackFactory = ContentServiceClientFallbackFactory.class)
public interface ContentServiceClient {

    @ResponseBody
    @GetMapping("/content/r/coursepublish/{courseId}")
    public CoursePublish getCoursepublish(@PathVariable("courseId") Long courseId);

}

定义降级处理类:

@Slf4j
@Component
public class ContentServiceClientFallbackFactory implements FallbackFactory<ContentServiceClient> {
    @Override
    public ContentServiceClient create(Throwable throwable) {
        return new ContentServiceClient() {

            @Override
            public CoursePublish getCoursepublish(Long courseId) {
                log.error("调用内容管理服务发生熔断:{}", throwable.toString(),throwable);
                return null;
            }
        };
    }
}

7、代码完善

完善Service层:

@Resource
ContentServiceClient contentServiceClient;

@Resource
MediaServiceClient mediaServiceClient

@Override
public RestResponse<String> getVideo(String userId,Long courseId,Long teachplanId, String mediaId) {
    //查询课程信息
    CoursePublish coursepublish = contentServiceClient.getCoursepublish(courseId);
    if(coursepublish==null){
        MyException.cast("课程信息不存在");
    }
    //判断是否支持试学(很多课程的前几节支持试学)
    if(1 == coursepublish.getTechPlan().getIsPreview()){
		return mediaServiceClient.getPlayUrlByMediaId(mediaId);
	}

    //不允许试学,开始判断登录状态:如果登录
    if(StringUtils.isNotEmpty(userId)){

        //判断是否选课,根据选课情况判断学习资格
        XcCourseTablesDto xcCourseTablesDto = myCourseTablesService.getLeanringStatus(userId, courseId);
        //学习资格状态 [{"code":"702001","desc":"正常学习"},{"code":"702002","desc":"没有选课或选课后没有支付"},{"code":"702003","desc":"已过期需要申请续期或重新支付"}]
        String learnStatus = xcCourseTablesDto.getLearnStatus();
        if(learnStatus.equals("702001")){
            return mediaServiceClient.getPlayUrlByMediaId(mediaId);
        }else if(learnStatus.equals("702003")){
            return RestResponse.validfail("您的选课已过期需要申请续期或重新支付");
        }
    }

    //未登录或未选课判断是否收费
    String charge = coursepublish.getCharge();
    if(charge.equals("201000")){//免费可以正常学习
        return mediaServiceClient.getPlayUrlByMediaId(mediaId);
    }

    return RestResponse.validfail("请购买课程后继续学习");


}

完善Controller:

@Slf4j
@RestController
public class MyLearningController {

    @Autowired
    LearningService learningService;


    @ApiOperation("获取视频")
    //定义为open接口,即不用登录也可以调用
    @GetMapping("/open/learn/getvideo/{courseId}/{teachplanId}/{mediaId}")
    public RestResponse<String> getvideo(@PathVariable("courseId") Long courseId,@PathVariable("teachplanId") Long teachplanId, @PathVariable("mediaId") String mediaId) {
        //工具类获取当前登录用户
        XcUser user = SecurityUtil.getUser();
        String userId = null;
        if(user != null){
            userId = user.getId();
        }
       
       return learningService.getVideo;

    }

}

三、需求:我的课表

1、需求分析

  • 登录后点击[我的学习],进入个人中心
    在这里插入图片描述
  • 个人中心首页显示我的课程表
    在这里插入图片描述
  • 我的课表中显示了选课成功的免费课程、收费课程。最近学习课程显示了当前用户最近学习的课程信息。
  • 点击继续学习进入当前学习章节的视频继续学习
  • 点击课程评价进入课程评价界面

2、表设计与模型类

无新增表,查询我的课程表即可。根据前端的筛选条件,定义Dto类:

//Dto
@Data
public class MyCourseTableParams{

	private String userId;  //当前登录用户,不写在dto,直接在controller中拿到传给Service也行
	private String courseType;  //课程类型,免费700001、收费700002
	private String sortType;  //排序类型,1:按学习时间,2:按加入时间
	private String expiresType;  //1、即将过期 2、已失效
	private int page = 1;  //默认首页
	private int startIndex;
	private int size = 4; //每页默认4条
		
}

定义个分页的通用类,PageResult

package com.llg.base.modle;

import lombok.Data;
import lombok.ToSring;
import java.io.Serializable;
import java.util.List;

@Data
@ToString
public class PageResult<T> implements Serializable{

	//数据列表
	private List<T> items;
	//总记录数
	private long counts;
	//当前页码
	private long page;
	//每页记录数
	private long pageSize;
	public PageResult(List<T> items, long counts, long page, long pageSize){
		this.items = items;
		this.counts = counts;
		this.page = page;
		this.pageSize = pageSize;
	}
}

3、Controller定义

@ApiOperation("我的课程表")
@GetMapping("/mycoursetable")
public PageResult<XcCourseTables> mycoursetable(MyCourseTableParams params) {
   

}

3、Dao层开发

直接集成BaseMapper<PO>就够用

4、Service层开发

定义接口中的方法:

/**
 * @description 我的课程表
 * @param params
*/
public PageResult<XcCourseTables> mycourestabls(MyCourseTableParams params);

实现类中重写该方法:

@Override
public PageResult<XcCourseTables> mycourestabls( MyCourseTableParams params){
    //页码
    long pageNo = params.getPage();
    //每页记录数,固定为4
    long pageSize = 4;
    //分页条件
    Page<XcCourseTables> page = new Page<>(pageNo, pageSize);
    //根据用户id查询
    String userId = params.getUserId();
    LambdaQueryWrapper<XcCourseTables> lambdaQueryWrapper = new LambdaQueryWrapper<XcCourseTables>().eq(XcCourseTables::getUserId, userId);
	//分页查询
    Page<XcCourseTables> pageResult = courseTablesMapper.selectPage(page, lambdaQueryWrapper);
    //数据列表
    List<XcCourseTables> records = pageResult.getRecords();
	long total = pageResult.getTotal();
    PageResult<XcCourseTables> courseTablesResult = new PageResult<>(records, total, pageNo, pageSize);
    return courseTablesResult;

}

5、完善Controller

@ApiOperation("我的课程表")
@GetMapping("/mycoursetable")
public PageResult<XcCourseTables> mycoursetable(MyCourseTableParams params) {
	 //登录用户
	 SecurityUtil.XcUser user = SecurityUtil.getUser();
	 if(user == null){
	  XueChengPlusException.cast("请登录后继续选课");
	 }
	 String userId = user.getId();
	//设置当前的登录用户
	 params.setUserId(userId);

  	return myCourseTablesService.mycourestabls(params);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/475567.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字设计小思 - D触发器与死缠烂打的亚稳态

前言 本系列整理数字系统设计的相关知识体系架构&#xff0c;为了方便后续自己查阅与求职准备。在FPGA和ASIC设计中&#xff0c;D触发器是最常用的器件&#xff0c;也可以说是时序逻辑的核心&#xff0c;本文根据个人的思考历程结合相关书籍内容和网上文章&#xff0c;聊一聊D…

Hudi数据湖技术之数据中心案例实战

目录 1 案例架构2 业务数据2.1 客户信息表2.2 客户意向表2.3 客户线索表2.4 线索申诉表2.5 客户访问咨询记录表 3 Flink CDC 实时数据采集3.1 开启MySQL binlog3.2 环境准备3.3 实时采集数据3.3.1 客户信息表3.3.2 客户意向表3.3.3 客户线索表3.3.4 客户申诉表3.3.5 客户访问咨…

微信小程序 WebSocket 通信 —— 在线聊天

在Node栏目就讲到了Socket通信的内容&#xff0c;使用Node实现Socke通信&#xff0c;还使用两个流行的WebSocket 库&#xff0c;ws 和 socket.io&#xff0c;在小程序中的WebSocket接口和HTML5的WebSocket基本相同&#xff0c;可以实现浏览器与服务器之间的全双工通信。那么本篇…

SSH 服务器、NFS 服务器、TFTP 服务器详解及测试

文章目录 前言一、SSH 服务器1、SSH 能做什么&#xff1f;2、安装 SSH 服务器3、测试 SSH 服务4、用 SecureCRT 测试 二、NFS 服务器1、NFS 能做什么&#xff1f;2、安装 NFS 软件包3、添加 NFS 共享目录4、启动 NFS 服务5、测试 NFS 服务器 三、TFTP 服务器1、TFTP 能做什么&a…

轻松掌握mysql事务的四大特性ACID及实现原理

1、介绍 要实现这四大特性&#xff0c;我们先了解下mysql中的缓冲池和数据页 2、保证原子性和一致性 1、通过undo log保证数据的原子性和一致性 undo log保证了事务的原子性和一致性。 3、保证隔离性 1、并发事务产生时容易产生的隔离性问题 脏读 不可重复读 幻读…

【数据库复习】第四章数据库保护 1

数据库安全性&#xff1a; 数据库的一大特点是数据可以共享 数据共享必然带来数据库的安全性问题 数据库系统中的数据共享不能是无条件的共享 用户标识与鉴别 用户名和口令易被窃取&#xff0c;每个用户预先约定好一个计算过程或者函数 存取控制 常用存取控制方法 自主存…

电子电气架构——车辆E/E架构常识

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 人只有在举棋不定,无从把握的时候才感到疲惫。只有去行动就能获得解放,哪怕做的不好也比无所作为强! 本文主要介绍车辆E/E架构常识,主要涉及E/E架构面临…

LNMP、Tomca

构建Nginx服务器 使用源码包安装nginx软件包 [rootproxy ~]# yum -y install gcc pcre-devel openssl-devel #安装依赖包 [rootproxy ~]# useradd -s /sbin/nologin nginx [rootproxy ~]# tar -xf nginx-1.17.6.tar.gz [rootproxy ~]# cd nginx-1.17.6 [rootproxy …

【Shell编程之循环语句与函数】

目录 一、for循环语句示例:示例1示例2 示例3 二、跳出循环举例 转义符三、while 语句的结构示例: 四、until语句的结构1、基本格式 五、seq命令 一、for循环语句 读取不同的变量值&#xff0c;用来逐个执行同一组命令 #!/bin/bash for(( i0;i<5;i ))i0 定义for循环i变量初…

【逻辑位移和算数位移】

<< 运算符 && >> 运算符 正数位移 当 x>>n 中 x 为正数时&#xff0c;会将x的所有位右移x位&#xff0c;同时左边高位补0 显而易见&#xff0c;运算结束后&#xff0c;值为1 。 可知右移n位&#xff0c;结果就是 x / 2^n&#xff1a;7 / 2 ^2 1;…

运行vue项目报DONE Build complete. The dist directory is ready to be deployed.解决办法

一、问题描述 今天在运行一个vue项目时发现运行途中报这样一个错误&#xff0c;经过查阅相关资料可知&#xff0c;这是dist文件夹下 二、解决办法 根据官方文档&#xff0c;目录需要启动一个 HTTP 服务器来访问 (除非你已经将 publicPath 配置为了一个相对的值)&#xff0c;所…

flex垂直方向布局与overflow结合使用

主要是需要留意 flex布局和overflow 之间的关系&#xff0c; 最外面的container 和 里面的main-box 之间分别使用了flex布局 和 overflow:hiddenmain-box 和 里面的main-body 之间分别使用了 flex布局 和 overflow:auto 有点类似于聊天的窗口布局 <!DOCTYPE html> <h…

zabbix监控远程主机

zabbix监控远程主机 在Zabbix服务器上安装Zabbix代理程序 在远程主机上安装Zabbix代理程序。安装方式取决于操作系统&#xff0c;可以从Zabbix官网上下载相应的安装包进行安装。 监控agent1 在agent1上安装agent yum install zabbix-agent另外在zabbix server上要关闭防火…

【前端知识】内存泄漏与垃圾回收机制 (上)

【前端知识相关分享】内存泄漏与垃圾回收机制 &#xff08;上&#xff09; 1. 内存的生命周期1.1 内存生命周期的一般流程1.2 C&#xff0c;JS和python内存分配和释放的区别 2. JS中的内存管理2.1 两种数据类型与两种内存一个思考2.2 两种内存空间的区别对比 3. 内存泄漏的定义…

HMM理论学习笔记-隐马尔可夫模型的三个元素、假设和问题

文章目录 概率论基础条件概率全概公式边缘概率联合概率联合概率与边缘概率的关系贝叶斯公式&#xff08;条件联合概率&#xff09;马尔科夫链的概念 HMM简述HMM的三个元素符号定义1、状态转移概率矩阵A2、观测概率矩阵B3、初始状态概率向量π HMM的三个假设1、齐次马尔可夫假设…

netstat命令解析

一、linux系统中netstat命令的帮助信息 └──╼ $netstat -h usage: netstat [-vWeenNcCF] [<Af>] -r netstat {-V|--version|-h|--help}netstat [-vWnNcaeol] [<Socket> ...]netstat { [-vWeenNac] -i | [-cnNe] -M | -s [-6tuw] }-r, --route …

VMware12安装图解

目录 一、介绍VMware虚拟机 二、安装VMware12虚拟机 三、VMware虚拟机内部新建虚拟机 清理磁盘 一、介绍VMware虚拟机 VMware是一个虚拟机。 什么是虚拟机&#xff1f;字面意思‘虚拟’&#xff0c;那就不算是真的&#xff1b;‘机’可以理解为一台电脑或者一个电脑系统。…

SimpleDateFormat以及Date的使用

Date Date currentTime new Date(); 获取当前的时间 输出&#xff1a; System.out.println(currentTime); 就会以这样的格式输出 那我们不想要这样的格式&#xff0c;而是输出格式怎么办呢&#xff1f; SimpleDateFormat闪亮登场 食用方法&#xff1a; SimpleDateForma…