文章目录
- 一、需求:支付通知
- 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);
}