day02-实现我的课表接口功能

本文详细介绍了在完成《我的课程表》功能开发时的流程,包括原型分析、接口设计、数据库设计等步骤。重点讨论了如何通过MQ通知将购买的课程加入课表,以及分页查询、添加和删除课程的相关接口。同时,提到了使用ThreadLocal存储用户信息,确保并发请求的安全性,并提供了代码示例来实现相关功能。
摘要由CSDN通过智能技术生成

在演示项目业务流程时,我们发现搜索课程、报名课程等流程都已经完成开发了。并且在《个人中心-我的订单》页面可以看到我们下单报名的课程:
在这里插入图片描述
然而,在我的课程页面中,却看不到这些课程:
在这里插入图片描述
看不到课程自然就无法学习。所以今天我们要完成的任务就是开发学习中心的《我的课程表》相关接口,让学员看到课程,然后才可以学习课程。
开发流程:
在这里插入图片描述
需要强调的一点是,开发中最重要的环节其实是前两步:

  • 原型分析、接口设计
  • 数据库设计

只要前两步分析完成,功能开发就比较简单了。因此,我们将遵循企业开发的流程,先分析原型、设计接口,再设计数据库结构,最后再开发接口功能。

接口设计

在用户的《个人中心》,有一个《我的课程》列表页,如图:
在这里插入图片描述
那么这些课程是从何而来的,原型的页面说明中有告诉我们:
在这里插入图片描述
从这里可以看出,凡是购买过的课程,都应该加入到课程列表中。
需要注意的是,刚刚加入课表的课程处于未学习状态,这个时候学员可以创建一个学习计划,规划后期的学习节奏:
在这里插入图片描述
页面开发规则

  1. 已购课程状态变化
    1) 未学习,已购买课程还未开始学习,可以开始学习
    2) 已学习,已购买课程已开始学习,展示学习进度,可以继续学习
    3) 已学完,已购买课程已经学完,可以重新学习
    4) 已失效,已购买课程已过期,不可继续学习,只能删除课程操作

推测出《课程表》的业务流转过程是这样的:
在这里插入图片描述

接口统计

加入课表

首先,用户支付完成后,需要将购买的课程加入课表:
而支付成功后,交易服务会基于MQ通知的方式,通知学习服务来执行加入课表的动作。因此,我们要实现的第一个接口就是:
在这里插入图片描述

分页查询课表

在加入课表以后,用户就可以在个人中心查看到这些课程:
在这里插入图片描述
因此,这里就需要第二个接口:
在这里插入图片描述
当课程学完后,可以选择删除课程:
所以,还要有删除课程的接口:第三个接口
在这里插入图片描述

除此以外,如果用户退款,也应该删除课表中的课程,这里同样是通过MQ通知来实现:第4个接口
在这里插入图片描述

查询学习进度

在个人中心,我的课表页面,还能看到用户最近的学习进度:
在这里插入图片描述
这里就包含两个接口:
在这里插入图片描述

查询指定课程学习状态

在课程详情页面,如果当前课程已经购买,也要展示出课程的学习进度:
在这里插入图片描述
接口:
在这里插入图片描述

内部访问接口

除了页面原型中看到的接口以外,其它微服务也对tj-learning服务有数据需求,并且也定义了一些需要我们实现的Feign接口。
在项目中,所有Feign接口都定义在了tj-api模块下,learning服务的接口定义在com.tianji.api.client.learning模块下:
在这里插入图片描述
这里包含两个接口:
在这里插入图片描述

总结 我的课表有关的接口

在这里插入图片描述

接口设计的核心要素

  • 请求方式
  • 请求路径
  • 请求参数格式
  • 返回值格式

分页查询我的课表

  • 请求方式:按照Restful风格,查询请求应该使用GET方式。

  • 请求路径:一般是资源名称,比如这里资源是课表,所以资源名可以使用lessons,同时这里是分页查询,可以在路径后跟一个/page,代表分页查询

  • 请求参数:因为是分页查询,首先肯定要包含分页参数,一般有两个:

    • pageNo:页码
    • pageSize:每页大小
  • 返回值:这里的返回值复杂一些,需要结合页面需要展示的信息来看:
    在这里插入图片描述
    最终的接口信息如下:
    在这里插入图片描述

与这个接口对应的,我们需要定义一下几个实体:

  • 统一的分页请求Query实体
  • 统一的分页结果DTO实体
  • 课表分页VO实体
    由于分页请求、分页结果比较常见,我们提前在tj-common模块定义好了。

其中,统一分页请求实体,称为PageQuery:
在这里插入图片描述
统一分页结果实体,称为PageDTO:
[图片]
最后,返回结果中的课表VO实体

@Data
@ApiModel(description = "课程表信息")
public class LearningLessonVO {

    @ApiModelProperty("主键lessonId")
    private Long id;

    @ApiModelProperty("课程id")
    private Long courseId;

    @ApiModelProperty("课程名称")
    private String courseName;

    @ApiModelProperty("课程封面")
    private String courseCoverUrl;

    @ApiModelProperty("课程章节数量")
    private Integer sections;

    @ApiModelProperty("课程状态,0-未学习,1-学习中,2-已学完,3-已失效")
    private LessonStatus status;

    @ApiModelProperty("总已学习章节数")
    private Integer learnedSections;

    @ApiModelProperty("总已报名课程数")
    private Integer courseAmount;

    @ApiModelProperty("课程购买时间")
    private LocalDateTime createTime;

    @ApiModelProperty("课程过期时间,如果为null代表课程永久有效")
    private LocalDateTime expireTime;

    @ApiModelProperty("习计划状态,0-没有计划,1-计划进行中")
    private PlanStatus planStatus;

    @ApiModelProperty("计划的学习频率")
    private Integer weekFreq;

    @ApiModelProperty("最近学习的小节名")
    private String latestSectionName;

    @ApiModelProperty("最近学习的小节编号")
    private Integer latestSectionIndex;
}

添加课程到课表(MQ异步)

当用户支付完成或者报名免费课程后,应该立刻将课程加入到课表中。交易服务会通过MQ通知学习服务,我们需要查看交易服务的源码,查看MQ通知的消息格式,来确定监听消息的格式。

获取用户id使用的threadlocal:基本用法:

  • 在并发请求情况下,因为每次请求都有不同的用户信息,我们必须保证每次请求保存的用户信息互不干扰,线程独立。
  • 注意:这里不是解决多线程资源共享问题,而是要保证每个线程都有自己的用户资源,互不干扰 ;
  • ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的
  • ThreadLocal提供线程局部变量,一个变量用在多个线程中分别有独立的值(副本)
  • 不同线程,内部有自己的ThreadLocalMap,因此Map中的资源互相不会干扰
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
    private Long id;
    private String nickName;
    private String icon;
}

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}
然后我们需要在登录的拦截器中,也就是在preHandle()方法中就将用户信息存到ThreadLocal中,
也就是在执行所有controller之前,这个方法一般来说是用来通过校验token判断用户是否登录的。
  String token= request.getHeader("Authorization");

我们以免费报名课程为例来看:
在trade-service的OrderController中,有一个报名免费课程的接口:

@ApiOperation("免费课立刻报名接口")
@PostMapping("/freeCourse/{courseId}")
public PlaceOrderResultVO enrolledFreeCourse(
    @ApiParam("免费课程id") @PathVariable("courseId") Long courseId) {
    return orderService.enrolledFreeCourse(courseId);
}

可以看到这里调用了OrderService的enrolledFreeCourse()方法:

@Override
@Transactional
public PlaceOrderResultVO enrolledFreeCourse(Long courseId) {

    Long userId = UserContext.getUser();//从Threadlocal获取用户id  

    // 1.查询课程信息
    List<Long> cIds = CollUtils.singletonList(courseId);
    List<CourseSimpleInfoDTO> courseInfos = getOnShelfCourse(cIds);
    if (CollUtils.isEmpty(courseInfos)) {
        // 课程不存在
        throw new BizIllegalException(TradeErrorInfo.COURSE_NOT_EXISTS);
    }
    CourseSimpleInfoDTO courseInfo = courseInfos.get(0);
    if(!courseInfo.getFree()){
        // 非免费课程,直接报错
        throw new BizIllegalException(TradeErrorInfo.COURSE_NOT_FREE);
    }
    // 2.创建订单
    Order order = new Order();
    // 2.1.基本信息
    order.setUserId(userId);
    order.setTotalAmount(0);
    order.setDiscountAmount(0);
    order.setRealAmount(0);
    order.setStatus(OrderStatus.ENROLLED.getValue());
    order.setFinishTime(LocalDateTime.now());
    order.setMessage(OrderStatus.ENROLLED.getProgressName());
    // 2.2.订单id
    Long orderId = IdWorker.getId(order);
    order.setId(orderId);

    // 3.订单详情
    OrderDetail detail = packageOrderDetail(courseInfo, order);

    // 4.写入数据库
    saveOrderAndDetails(order, CollUtils.singletonList(detail));

    // 5.发送MQ消息,通知报名成功
    rabbitMqHelper.send(
            MqConstants.Exchange.ORDER_EXCHANGE,
            MqConstants.Key.ORDER_PAY_KEY,
            OrderBasicDTO.builder().orderId(orderId).userId(userId).courseIds(cIds).build());
    // 6.返回vo
    return PlaceOrderResultVO.builder()
            .orderId(orderId)
            .payAmount(0)
            .status(order.getStatus())
            .build();
}

其中,通知报名成功的逻辑是这部分:
在这里插入图片描述
由此,我们可以得知发送消息的Exchange、RoutingKey,以及消息体。消息体的格式是OrderBasicDTO,包含四个字段:

  • orderId:订单id
  • userId:下单的用户id
  • courseIds:购买的课程id集合
  • finishTime:支付完成时间

因此,在学习服务,我们需要编写的消息监听接口规范如下:
在这里插入图片描述
其中的请求参数实体,由于是与交易服务公用的数据传递实体,也就是DTO,因此已经提前定义到了tj-api模块下的DTO包了。
在这里插入图片描述

查询正在学习的课程

页面原型中,有两个地方需要查看正在学习的课程。
第一个,在个人中心-我的课程:
另一个,在已登录情况下,首页的悬浮窗中:
最终的接口规则如下:
在这里插入图片描述

根据id查询某课程学习状态

在课程详情页,课程展示有两种不同形式:

  • 对于未购买的课程:展示为立刻购买或加入购物车
  • 在这里插入图片描述
  • 对于已经购买的课程:展示为马上学习,并且显示学习的进度、有效期
    -
    最终的接口设计如下:
    在这里插入图片描述
    这里的返回值VO结构在之前定义的LearningLessonVO中都包含了,因此可以直接复用该VO,不再重复定义。

ER图

我们可以结合原型图中包含的信息来画一个ER图,分析我的课表包含的信息:
在这里插入图片描述

创建分支

一般开发新功能都需要创建一个feature类型分支,不能在DEV分支直接开发,因此这里我们新建一个功能分支。我们在项目目录中打开terminal控制台,输入命令:

git checkout -b feature-lessons

Feature:功能分支,从Develop分支创建得来。开发测试完成后会合并到Develop分支。

发现整个项目都切换到了新的功能分支:
在这里插入图片描述

MP代码生成

我们使用的是Mybatis作为持久层框架,并且引入了MybatisPlus来简化开发。因此,在创建据库以后,就需要创建对应的实体类、mapper、service等。
这些代码格式固定,编写起来又比较费时。好在IDEA中提供了一个MP插件,可以生成这些重复代码:
在这里插入图片描述
安装完成以后,我们先配置一下数据库地址:
[图片]
然后配置代码自动生成的设置:
在这里插入图片描述
严格按照下图的模式去设置(图片放大后更清晰),不要填错项目名称和包名称:在这里插入图片描述
最后,点击code generatro按钮,即可生成代码:
在这里插入图片描述
按照Restful风格,对请求路径做修改:
在这里插入图片描述

3.实现接口功能

  • 添加课程到课表
  • 分页查询我的课表

添加课程到课表

回顾一下接口信息:
在这里插入图片描述其中的Exchange、RoutingKey都已经在tj-common中的MqConstants内定义好了:
在这里插入图片描述
我们只需要定义消息监听器就可以了。

定义消息监听器

在tj-learning服务中定义一个MQ的监听器:
在这里插入图片描述

@Slf4j
@Component
@RequiredArgsConstructor //用于构造注入
public class LessonChangeListener {
	
    private final ILearningLessonService lessonService;

    /**
     * 监听订单支付或课程报名的消息
     * @param order 订单信息
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "learning.lesson.pay.queue", durable = "true"),
            exchange = @Exchange(name = MqConstants.Exchange.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC),
            key = MqConstants.Key.ORDER_PAY_KEY
    ))
    public void listenLessonPay(OrderBasicDTO order){
        // 1.健壮性处理
        if(order == null || order.getUserId() == null || CollUtils.isEmpty(order.getCourseIds())){
            // 数据有误,无需处理
            log.error("接收到MQ消息有误,订单数据为空");
            return;
        }
        // 2.添加课程
        log.debug("监听到用户{}的订单{},需要添加课程{}到课表中", order.getUserId(), order.getOrderId(), order.getCourseIds());
        lessonService.addUserLessons(order.getUserId(), order.getCourseIds());
    }
}

订单中与课表有关的字段就是userId、courseId,因此这里要传递的就是这两个参数。

注意,这里添加课程的核心逻辑是在ILearningLessonService中实现的,首先是接口声明:


/**
 * <p>
 * 学生课程表 服务类
 * </p>
 */
public interface ILearningLessonService extends IService<LearningLesson> {
    void addUserLessons(Long userId, List<Long> courseIds);
}
@Service
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson>
 implements ILearningLessonService {

    @Override
    public void addUserLessons(Long userId, List<Long> courseIds) {
        // TODO 添加课程信息到用户课程表
    }
}

订单中一个用户可能购买多个课程。因此请求参数中的courseId集合就需要逐个处理,将来会有多条课表数据。我们要做的事情就是根据courseId集合查询课程信息,然后分别计算每个课程的有效期,组织多个LearingLesson的数据,形成集合。最终批量新增到数据库即可。
流程如图:
在这里插入图片描述
我们该如何根据课程id查询课程信息呢?

获取课程信息

课程(course)的信息是由课程服务(course-service)来维护的,目前已经开发完成并部署到了虚拟机的开发环境中。
我们现在需要查询课程信息,自然需要调用课程服务暴露的Feign接口。

3.1实现添加课程到课表

我们正式实现LearningLessonServiceImpl中的addUserLessons方法:

package com.tianji.learning.service.impl;

// 略

@SuppressWarnings("ALL")
@Service
@RequiredArgsConstructor //用于构造注入 
@Slf4j
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson>
 implements ILearningLessonService {
	//构造注入 远程服务对象
    private final CourseClient courseClient;

    @Override
    @Transactional
    public void addUserLessons(Long userId, List<Long> courseIds) {
        // 1.根据课程id 从course服务中得到课程信息 
        List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(courseIds);
        if (CollUtils.isEmpty(cInfoList)) {
            // 课程不存在,无法添加
            log.error("课程信息不存在,无法添加到课表");
            return;
        }
        // 2.循环遍历,处理LearningLesson数据
        List<LearningLesson> list = new ArrayList<>(cInfoList.size());
        for (CourseSimpleInfoDTO cInfo : cInfoList) {
            LearningLesson lesson = new LearningLesson();
            // 2.1.获取过期时间
            Integer validDuration = cInfo.getValidDuration();
            if (validDuration != null && validDuration > 0) {
                LocalDateTime now = LocalDateTime.now();
                lesson.setCreateTime(now);
                lesson.setExpireTime(now.plusMonths(validDuration));
            }
            // 2.2.填充userId和courseId
            lesson.setUserId(userId);
            lesson.setCourseId(cInfo.getId());
            list.add(lesson);
        }
        // 3.批量新增
        saveBatch(list);
    }
}

3.2.分页查询我的课表

回顾一下接口信息:
在这里插入图片描述

3.2.1.实体

在实现接口的时候,往往需要先把接口的请求参数、返回值对应的实体类声明出来。

3.2.1.1.Query实体

在这个接口中,请求参数是一个通用的分页参数,我们在tj-common已经声明了:
在这里插入图片描述
主要的四个字段如下:

@Data
@ApiModel(description = "分页请求参数")
@Accessors(chain = true)
public class PageQuery {
    public static final Integer DEFAULT_PAGE_SIZE = 20;
    public static final Integer DEFAULT_PAGE_NUM = 1;

    @ApiModelProperty(value = "页码", example = "1")
    @Min(value = 1, message = "页码不能小于1")
    private Integer pageNo = DEFAULT_PAGE_NUM;

    @ApiModelProperty(value = "每页大小", example = "5")
    @Min(value = 1, message = "每页查询数量不能小于1")
    private Integer pageSize = DEFAULT_PAGE_SIZE;

    @ApiModelProperty(value = "是否升序", example = "true")
    private Boolean isAsc = true;

    @ApiModelProperty(value = "排序字段", example = "id")
    private String sortBy;
3.2.1.2.DTO实体

返回值是一个分页结果,因为分页太常用了,所以我们在tj-common定义了一个通用的分页结果类:
在这里插入图片描述

3.2.1.3.VO实体

返回值的分页结果中有一个实体集合,也就是VO实体

3.2.2.接口声明

首先是controller,tj-learning服务的LearningLessonController:

@Api(tags = "我的课表相关接口")
@RestController
@RequestMapping("/lessons")
@RequiredArgsConstructor
public class LearningLessonController {
    
    private final ILearningLessonService lessonService;

    @ApiOperation("查询我的课表,排序字段 latest_learn_time:学习时间排序,create_time:购买时间排序")
    @GetMapping("/page")
    public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {
    									//参数 : 3.2.1.1.Query实体
        return lessonService.queryMyLessons(query);
    }
}

然后是service的接口,tj-learning服务的ILearningLessonService:

PageDTO<LearningLessonVO> queryMyLessons(PageQuery query);

最后是实现类,tj-learning服务的LearningLessonServiceImpl:

@Override
public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {
    // TODO 分页查询我的课表
    return null;
}

3.2.3.获取登录用户

既然是分页查询我的课表,除了分页信息以外,我还必须知道当前登录的用户是谁。那么,该从哪里获取用户信息呢?

3.2.3.1.实现思路

基于JWT实现登录,登录信息就保存在请求头的token中。因此要获取当前登录用户,只要获取请求头,解析其中的token即可。

但是,每个微服务都可能需要登录用户信息,在每个微服务都做token解析就属于重复编码了。因此我们的把token解析的行为放到了网关中,然后由网关把用户信息放入请求头,传递给下游微服务。
在这里插入图片描述
每个微服务要从请求头拿出用户信息,在业务中使用,也比较麻烦,所以我们定义了一个HandlerInterceptor,拦截进入微服务的请求,并获取用户信息,存入UserContext(底层基于ThreadLocal)。这样后续的业务处理时就能直接从UserContext中获取用户了

修改之前的tj-learning中的LearningLessonServiceImpl的queryMyLessons方法:

@Override
public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {
    // 1.获取当前登录用户
    Long userId = UserContext.getUser();
    // 2.分页查询
    // select * from learning_lesson where user_id = #{userId} order by latest_learn_time limit 0, 5
    
    //MP实现分页查询代码 
    Page<LearningLesson> page = lambdaQuery()
            .eq(LearningLesson::getUserId, userId) // where user_id = #{userId}
            .page(query.toMpPage("latest_learn_time", false));//以谁(latest_learn_time)为准排序
            
    List<LearningLesson> records = page.getRecords();
    if (CollUtils.isEmpty(records)) {
        return PageDTO.empty(page);
    }
    // 3.查询课程信息
    Map<Long, CourseSimpleInfoDTO> cMap = queryCourseSimpleInfoList(records);

    // 4.封装VO返回
    List<LearningLessonVO> list = new ArrayList<>(records.size());
    // 4.1.循环遍历,把LearningLesson转为VO
    for (LearningLesson r : records) {
        // 4.2.拷贝基础属性到vo
        LearningLessonVO vo = BeanUtils.copyBean(r, LearningLessonVO.class);
        // 4.3.获取课程信息,填充到vo
        CourseSimpleInfoDTO cInfo = cMap.get(r.getCourseId());
        vo.setCourseName(cInfo.getName());
        vo.setCourseCoverUrl(cInfo.getCoverUrl());
        vo.setSections(cInfo.getSectionNum());
        list.add(vo);
    }
    return PageDTO.of(page, list);
}

private Map<Long, CourseSimpleInfoDTO> queryCourseSimpleInfoList(List<LearningLesson> records) {
    // 3.1.获取课程id
    Set<Long> cIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
    // 3.2.查询课程信息
    List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(cIds);
    if (CollUtils.isEmpty(cInfoList)) {
        // 课程不存在,无法添加
        throw new BadRequestException("课程信息不存在!");
    }
    // 3.3.把课程集合处理成Map,key是courseId,值是course本身
    Map<Long, CourseSimpleInfoDTO> cMap = cInfoList.stream()
            .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));
    return cMap;
}

3.3.查询正在学习的课程

回顾一下接口信息:
在这里插入图片描述
可以看到返回值结果与分页查询的课表VO基本类似,因此这里可以复用LearningLessonVO实体,但是需要添加几个字段:

  • courseAmount
  • latestSectionName
  • latestSectionIndex

小节名称、序号信息都在课程微服务(course-service)中,因此可以通过课程微服务提供的接口来查询:
在这里插入图片描述
接口:
在这里插入图片描述
其中CataSimpleInfoDTO中就包含了章节信息:

@Data
public class CataSimpleInfoDTO {
    @ApiModelProperty("目录id")
    private Long id;
    @ApiModelProperty("目录名称")
    private String name;
    @ApiModelProperty("数字序号,不包含章序号")
    private Integer cIndex;
}

省略controller和service 直接写实现类:如下

private final CatalogueClient catalogueClient;

@Override
public LearningLessonVO queryMyCurrentLesson() {
    // 1.获取当前登录的用户
    Long userId = UserContext.getUser();
    // 2.查询正在学习的课程 select * from xx where user_id = #{userId} AND status = 1 order by latest_learn_time limit 1
    // 取最近的 一个  即最后学习的第一条记录  limit 1
    LearningLesson lesson = lambdaQuery()
            .eq(LearningLesson::getUserId, userId)
            .eq(LearningLesson::getStatus, LessonStatus.LEARNING.getValue())//status = 1(LEARNING)表示正在学习的课程
            .orderByDesc(LearningLesson::getLatestLearnTime)
            .last("limit 1")
            .one();
    if (lesson == null) {
        return null;
    }

    // 3.拷贝PO基础属性到VO
    LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
    // 4.查询课程信息
    CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
    if (cInfo == null) {
        throw new BadRequestException("课程不存在");
    }
    vo.setCourseName(cInfo.getName());
    vo.setCourseCoverUrl(cInfo.getCoverUrl());
    vo.setSections(cInfo.getSectionNum());
    // 5.统计课表中的课程数量 select count(1) from xxx where user_id = #{userId}
    Integer courseAmount = lambdaQuery()
            .eq(LearningLesson::getUserId, userId)
            .count();
    vo.setCourseAmount(courseAmount);
    // 6.查询小节信息
    List<CataSimpleInfoDTO> cataInfos =
            catalogueClient.batchQueryCatalogue(CollUtils.singletonList(lesson.getLatestSectionId()));
    if (!CollUtils.isEmpty(cataInfos)) {
        CataSimpleInfoDTO cataInfo = cataInfos.get(0);
        vo.setLatestSectionName(cataInfo.getName());
        vo.setLatestSectionIndex(cataInfo.getCIndex());
    }
    return vo;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值