【Java实战篇】Day14.在线教育网课平台--消息队列实现异步通知

news2024/10/6 23:26:04

文章目录

  • 一、需求:支付通知
    • 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/463849.html

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

相关文章

HOG+SVM分类器实践

文章目录 HOGSVM分类器实践制作SVM分类器导入所需的库提取HOG特征读取正样本和负样本训练分类器定义主函数小结 测试SVM分类器相关疑问1. 提取HOG特征为什么不能彩色图像呢&#xff1f;2. 出现如下错误3. 测试代码中&#xff0c;当我传入100*100的图片时候&#xff0c;为什么im…

Linux下安装Java8环境

查看主机是否已经安装Java环境 java -version 如下图所示&#xff0c;未找到java命令&#xff0c;则需要安装Java环境 JDK官网下载&#xff1a; https://www.oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html 根据自己系统的位数下载相应版本…

基于Dokcer安装RabbitMQ

基于Dokcer安装RabbitMQ 一、RabbitMQ介绍 1.1 现存问题 服务调用&#xff1a;两个服务调用时&#xff0c;我们可以通过传统的HTTP方式&#xff0c;让服务A直接去调用服务B的接口&#xff0c;但是这种方式是同步的方式&#xff0c;虽然可以采用SpringBoot提供的Async注解实现…

第6章:集合

集合简介 一种无序且唯一的数据结构。不关心顺序&#xff0c;集合里面的元素都是唯一的。 栈&#xff0c;队列&#xff0c;链表他们里面都有可能出现重复的数据&#xff0c;但是集合里面的元素是唯一的。 栈&#xff0c;队列&#xff0c;链表它们都有自己的顺序&#xff0c;但是…

使用媒体查询实现移动端适配,媒体查询meta标签配置(@media screen and,min-width和max-width)

简述&#xff1a;我们在写网站的时候&#xff0c;难免会遇到需要做移动端适配的需求&#xff0c;今天来记录下使用媒体查询实现移动端的适配。媒体查询是一种CSS技术&#xff0c;可以根据设备屏幕的属性&#xff08;如宽度、高度、方向和分辨率&#xff09;选择应用特定样式&am…

火爆全网,JMeter接口自动化测试详细实战(超详细)吐血整理...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 JMeter接口自动化测…

深度学习 - 44.Gate 与 MMOE 实现多目标学习

目录 一.引言 二.摘要 Abstract 三.介绍 Introduction 四.相关工作 RELATED WORK 1.DNN 中的多任务学习 2.SubNet 集成与 Expert 混合 3.多任务学习应用 五.建模方法 MODELING APPROACHES 1.Shared-bottom Multi-task Model 2.Mixture-of-Experts 3.Multi-gate Mixt…

NOPI用法之自定义单元格背景色(3)

NPOI针对office2003使用HSSFWorkbook&#xff0c;对于offce2007及以上使用XSSFWorkbook&#xff1b;今天我以HSSFWorkbook自定义颜色为例说明&#xff0c;Office2007的未研究呢 在NPOI中默认的颜色类是HSSFColor&#xff0c;它内置的颜色有几十种供我们选择&#xff0c;如果不…

模态分解算法 EMD、EEMD、CEEMD

一、模态分解算法EMD算法介绍 &#xff08;一&#xff09;模态分解相关的算法有以下几类 IMF 固有模态函数\EMD经验模态分解\EEMD集合经验模态分解\CEEMD 互补集合经验\&#xff08;EEMD的标准形式&#xff09;CEEMDAN自适应噪声完备集合经验模态分解\VMD 变分模态分解 &…

Crypko.ai:动漫角色生成和设计平台

【产品介绍】 Crypko.ai是一个基于GAN&#xff08;生成对抗网络&#xff09;的高质量动漫角色生成和设计平台&#xff0c;可以让用户通过简单的操作&#xff0c;创造出各种风格和特征的动漫角色&#xff0c;并且可以对角色的头发、脸部、衣服、风格等进行编辑和调整。 Crypko.a…

【GNN】谱域图卷积

谱域图卷积 1. 谱域卷积的背景知识 1.1 谱域图卷积实现思路 f 1 ( t ) ⋆ f 2 ( t ) F − 1 [ F 1 ( w ) F 2 ( w ) ] f_1(t) \star f_2(t) F^{-1}[F_1(w)F_2(w) ] f1​(t)⋆f2​(t)F−1[F1​(w)F2​(w)] 1.2 如何定义图上的傅里叶变换 经典傅里叶变换&#xff1a; x ( …

人工智能+自助餐:一种有效减少食物浪费的创新方案

一、案例背景&#xff1a; 自助餐是一种受欢迎的餐饮形式&#xff0c;可以满足不同顾客的口味和需求。但是&#xff0c;自助餐也存在着浪费食物的问题&#xff0c;有的顾客拿得多吃得少&#xff0c;有的顾客只吃部分食物&#xff0c;剩下的扔掉。据统计&#xff0c;2022年中国…

【算法竞赛】实现约瑟夫问题的四种方法(附手绘图详解)

&#x1f48c; 博客内容&#xff1a;实现约瑟夫问题的四种方法 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准前端&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&…

视频剪辑配乐技巧 视频剪辑配音推荐

视频是视觉加听觉的艺术&#xff0c;视频的背景音乐不同&#xff0c;所呈现的效果也不同。接下来为大家带来大家视频剪辑配乐技巧&#xff0c;视频剪辑配音推荐的相关内容。 一、视频剪辑配乐技巧 视频剪辑时选好了配乐&#xff0c;视频就成功了一半。那如何找到合适的配乐呢…

yapi一键安装 文档开源系统

访问 GitHub - Ryan-Miao/docker-yapi: Docker build and run yapi as serviceDocker build and run yapi as service. Contribute to Ryan-Miao/docker-yapi development by creating an account on GitHub.https://github.com/Ryan-Miao/docker-yapi git clone https://githu…

Class类

package com.hspedu.reflection.class_;import com.hspedu.Cat;import java.util.ArrayList;/*** author 韩顺平* version 1.0* 对Class类特点的梳理*/ public class Class01 {public static void main(String[] args) throws ClassNotFoundException {//看看Class类图//1. Cla…

python版电报API接入从零到一(有彩蛋)

文章链接 编号分类文章及链接介绍作者来源分类撰写日期收录日期F1框架python版telegram接入开源botpython-telegram-botGithub2023-04-24 申明&#xff1a;本文仅作试验研究用&#xff0c;不对参考本文操作产生的各种结果承担任何责任。 Q&A 使用Telegram的API需要交费吗…

三顾茅庐,七面阿里,成功上岸25k16薪,我行你也行~

写在片头&#xff1a;声明&#xff0c;勿杠 首先简单说一下&#xff0c;这三次面试阿里并不是一次性去面的&#xff0c;实际上第一次面试时候还在大四&#xff0c;找的实习岗&#xff0c;不太清楚是什么部门&#xff0c;别问我为什么还记得面试题&#xff0c;有记录和复盘的习惯…

什么是OADM光分插复用器

文章导读&#xff1a; 什么是OADM光分插复用器 光分插复用器的功能 光分插复用器的类型&#xff08;FOADM, TOADM&#xff09; OADM的应用 1、什么是OADM光分插复用器 由不同的光通道进出单模光纤。 它的主要功能是在不影响其他波长信道传输的情况下&#xff0c;选择性地下载或…

Vue(监测数据改变、收集表单数据、过滤器)

一、监测数据改变原理 1. 监测对象数据改变原理 当数据发生改变之后&#xff1a;直接会显示数据改变&#xff08;一种强硬写法&#xff09; let data { name: "北京大学", address: "北京" };// 以下通过temp进行监视&#xff1a;还得还原temp值&#xf…