7.1 支付模块 - 用户选课

news2024/10/25 14:35:48

支付模块 - 需求分析、添加选课

文章目录

  • 支付模块 - 需求分析、添加选课
  • 一、需求分析
    • 1.1 选课业务流程
    • 1.2 支付业务流程
    • 1.3 在线学习业务流程
    • 1.4 课程续期业务流程
  • 二、添加选课
    • 2.1 执行流程
    • 2.2 数据模型
      • 2.2.1 选课记录表 choose_course
      • 2.2.2 用户课程表 course_tables
    • 2.3 查询课程基本信息
      • 2.3.1 content服务 - 查询课程发布信息
      • 2.3.2 开启Feign - 内容管理远程接口
      • 2.3.3 课程发布表Dto - 特别处理
    • 2.4 选课
      • 2.4.0 扩展类
      • 2.4.1 MyCourseTablesController 接口
      • 2.4.2 MyCourseTablesServiceImpl 实现类
      • 2.4.3 测试
    • 2.5 查询学习资格

一、需求分析

实现了学生选课、下单支付、学习的整体流程

网站的课程有免费和收费两种,对于免费课程学生选课后可直接学习,对于收费课程学生需要下单且支付成功方可选课、学习

选课:是将课程加入我的课程表的过程

我的课程表:记录我在网站学习的课程,我的课程表中有免费课程和收费课程两种,对于免费课程可直接添加到我的课程表,对于收费课程需要下单、支付成功后自动加入我的课程表

模块整体流程

image-20240130214642073

1.1 选课业务流程

用户通过搜索课程、课程推荐等信息进入课程详情页面,点击“马上学习” 引导进入学习界面去学习

具体流程如下图所示

image-20240130215606307

  • 进入课程详情点击马上学习

image-20240130215633595

  • 课程免费时引导加入我的课程表、或进入学习界面

image-20240130215654234

  • 课程收费时引导去支付、或试学

image-20240130215713808

选课是将课程加入我的课程表的过程

对免费课程选课后可直接加入我的课程表,对收费课程选课后需要下单支付成功系统自动加入我的课程表

image-20240130215820130

1.2 支付业务流程

通过下面的图,我们就能发现在支付前的操作就是选课

image-20240130215846020

1.3 在线学习业务流程

选课成功用户可以在线学习,对于免费课程无需选课即可在线学习

image-20240130215924087

1.4 课程续期业务流程

免费课程加入我的课程表默认为1年有效期,到期用户可申请续期

image-20240130220006931

二、添加选课

新建learning工程完成选课操作

2.1 执行流程

选课是将课程加入我的课程表的过程,根据选课的业务流程进行详细分析,业务流程

image-20240130221055462

选课信息存入选课记录表

选课记录表:记录了什么人在什么时候选择了哪一门课程

如果选择的课程是免费的,那么在选课记录表中,选课状态就是成功,并且此课程已经加入到课表中了

如果选择的课程是收费的,那么在选课记录表中,选课状态就是待支付,等待支付成功后,此课程会加入到课表中

免费课程被选课除了进入选课记录表同时进入我的课程表

收费课程进入选课记录表后需要经过下单、支付成功才可以进入我的课程表

收费课程和免费课程的区别就是,收费课程多了一步付款而已


在学习引导处,可以直接将免费课程加入我的课程表,如下图

image-20240130222553557

对于收费课程先创建选课记录表,支付成功后,收到支付结果由系统自动加入我的课程表

执行流程如下

image-20240130222626920

2.2 数据模型

2.2.1 选课记录表 choose_course

order_type 选课类型:是免费还是收费

status 选课状态:此课程是选课成功还是待支付、选课删除

image-20240130221931552

此表的作用简单的说:什么人在什么时间选择了哪门课,并且选择课程的状态是选课成功还是待支付

对于免费课程:课程价格为0,有效期默认365,开始服务时间为选课时间,结束服务时间为选课时间加1年后的时间,选课状态为选课成功

对于收费课程:按课程的现价、有效期确定开始服务时间、结束服务时间,选课状态为待支付

收费课程的选课记录需要支付成功后选课状态为成功

@Data
@TableName("xc_choose_course")
public class XcChooseCourse implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 课程id
     */
    private Long courseId;

    /**
     * 课程名称
     */
    private String courseName;

    /**
     * 用户id
     */
    private String userId;

    /**
     * 机构id
     */
    private Long companyId;

    /**
     * 选课类型
     */
    private String orderType;

    /**
     * 添加时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createDate;

    /**
     * 课程有效期(天)
     */
    private Integer validDays;

    private Float coursePrice;

    /**
     * 选课状态
     */
    private String status;

    /**
     * 开始服务时间
     */
    private LocalDateTime validtimeStart;

    /**
     * 结束服务时间
     */
    private LocalDateTime validtimeEnd;

    /**
     * 备注
     */
    private String remarks;


}

2.2.2 用户课程表 course_tables

课程表的数据来源于选课记录表

对于免费课程创建选课记录后同时向我的课程表添加记录

对于收费课程创建选课记录后需要下单支付成功后自动向我的课程表添加记录

choose_course_id字段其实就是某个选课记录choose_course表中的主键

image-20240130222041892

@Data
@TableName("xc_course_tables")
public class XcCourseTables implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 选课记录id
     */
    private Long chooseCourseId;

    /**
     * 用户id
     */
    private String userId;

    /**
     * 课程id
     */
    private Long courseId;

    /**
     * 机构id
     */
    private Long companyId;

    /**
     * 课程名称
     */
    private String courseName;
    /**
     * 课程名称
     */
    private String courseType;


    /**
     * 添加时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createDate;

    /**
     * 开始服务时间
     */
    private LocalDateTime validtimeStart;

    /**
     * 到期时间
     */
    private LocalDateTime validtimeEnd;

    /**
     * 更新时间
     */
    private LocalDateTime updateDate;

    /**
     * 备注
     */
    private String remarks;


}

2.3 查询课程基本信息

根据之前的流程,首先我们要查询一下课程信息,主要是想知道某个课程是收费的还是免费的

知道课程基本信息后,我们就能确定收费怎么做、免费怎么做

查询课程基本信息的操作我们要在学习中心learning服务远程调用内容管理content服务

之后content服务会查询数据库中课程发布表,看看此课程是否已经发布以及课程的收费规则

image-20240130225738663

2.3.1 content服务 - 查询课程发布信息

content服务中新增接口 - 查询发布表中某个课程基本信息

内容管理服务提供查询课程信息接口,此接口从课程发布表查询

此接口主要提供其它微服务远程调用,所以此接口不用授权,本项目标记此类接口统一以 /r开头

将来会在白名单中配置

@ApiOperation("查询课程发布信息")
@ResponseBody
@GetMapping("/r/coursepublish/{courseId}")
public CoursePublish getCoursepublish(@PathVariable("courseId") Long courseId) {
    CoursePublish coursePublish = coursePublishService.getCoursePublish(courseId);
    return coursePublish;
}
/**
 * 查询课程发布信息
 *
 * @param courseId 课程id
 * @return
 */
@Override
public CoursePublish getCoursePublish(Long courseId) {
    return coursePublishMapper.selectById(courseId);
}

2.3.2 开启Feign - 内容管理远程接口

在learning-service模块添加Feign

/**
 * @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;
            }
        };
    }
}

可以使用下面的代码远程调用一下content服务中的查询课程发布信息接口

@SpringBootTest
public class FeignClientTest {

    @Autowired
    ContentServiceClient contentServiceClient;


    @Test
    public void testContentServiceClient() {
        CoursePublish coursepublish = contentServiceClient.getCoursepublish(18L);
        Assertions.assertNotNull(coursepublish);
    }
}

2.3.3 课程发布表Dto - 特别处理

在进行feign远程调用时会将字符串转成LocalDateTime,

在CoursePublish 类中LocalDateTime的属性上边添加如下代码:

@JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss")
import com.fasterxml.jackson.annotation.JsonFormat;

@Data
@TableName("course_publish")
public class CoursePublish implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    private Long id;

    /**
     * 机构ID
     */
    private Long companyId;

    /**
     * 公司名称
     */
    private String companyName;

    /**
     * 课程名称
     */
    private String name;

    /**
     * 适用人群
     */
    private String users;

    /**
     * 标签
     */
    private String tags;

    /**
     * 创建人
     */
    private String username;

    /**
     * 大分类
     */
    private String mt;

    /**
     * 大分类名称
     */
    private String mtName;

    /**
     * 小分类
     */
    private String st;

    /**
     * 小分类名称
     */
    private String stName;

    /**
     * 课程等级
     */
    private String grade;

    /**
     * 教育模式
     */
    private String teachmode;

    /**
     * 课程图片
     */
    private String pic;

    /**
     * 课程介绍
     */
    private String description;

    /**
     * 课程营销信息,json格式
     */
    private String market;

    /**
     * 所有课程计划,json格式
     */
    private String teachplan;

    /**
     * 教师信息,json格式
     */
    private String teachers;

    /**
     * 发布时间
     */
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createDate;

    /**
     * 上架时间
     */
    @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime onlineDate;

    /**
     * 下架时间
     */
    @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime offlineDate;

    /**
     * 发布状态
     */
    private String status;

    /**
     * 备注
     */
    private String remark;

    /**
     * 收费规则,对应数据字典--203
     */
    private String charge;

    /**
     * 现价
     */
    private Float price;

    /**
     * 原价
     */
    private Float originalPrice;

    /**
     * 课程有效期天数
     */
    private Integer validDays;


}

之前我们没使用上面的注解是因为使用Http请求的接口,但是我们已经把序列化和反序列化的相关配置都配置好了,如下图所示

使用的都是Jackson的方式

不管是序列化还是反序列化,我们的时间类型都是yyyy-MM-dd HH:mm:ss格式

但是Feign远程调用的时候,下面的配置就不会生效了

import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class LocalDateTimeConfig {

    /*
     * 序列化内容
     *   LocalDateTime -> String
     * 服务端返回给客户端内容
     * */
    @Bean
    public LocalDateTimeSerializer localDateTimeSerializer() {
        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    /*
     * 反序列化内容
     *   String -> LocalDateTime
     * 客户端传入服务端数据
     * */
    @Bean
    public LocalDateTimeDeserializer localDateTimeDeserializer() {
        return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }


    // 配置
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            builder.serializerByType(LocalDateTime.class, localDateTimeSerializer());
            builder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer());
        };
    }

}

2.4 选课

也就是下图中圈出来的这一步

image-20240130233801890

以及下面这一步

image-20240130234216457

2.4.0 扩展类

@Data
@ToString
public class XcChooseCourseDto extends XcChooseCourse {

    //学习资格,[{"code":"702001","desc":"正常学习"},{"code":"702002","desc":"没有选课或选课后没有支付"},{"code":"702003","desc":"已过期需要申请续期或重新支付"}]
    public String learnStatus;

}
@Data
@ToString
public class XcCourseTablesDto extends XcCourseTables {
    //学习资格,[{"code":"702001","desc":"正常学习"},{"code":"702002","desc":"没有选课或选课后没有支付"},{"code":"702003","desc":"已过期需要申请续期或重新支付"}]
    public String learnStatus;
}

2.4.1 MyCourseTablesController 接口

    @ApiOperation("添加选课")
    @PostMapping("/choosecourse/{courseId}")
    public XcChooseCourseDto addChooseCourse(@PathVariable("courseId") Long courseId) {
        // 调用工具类,拿到当前操作的用户
        SecurityUtil.XcUser user = SecurityUtil.getUser();
        if (user == null){
            XueChengPlusException.cast("用户未登录");
        }
        String userId = user.getId();

        // 添加选课
        XcChooseCourseDto xcChooseCourseDto = myCourseTablesService.addChooseCourse(userId, courseId);
        return xcChooseCourseDto;
    }

2.4.2 MyCourseTablesServiceImpl 实现类

/**
 * 选课相关操作
 */
@Slf4j
@Service
public class MyCourseTablesServiceImpl implements MyCourseTablesService {

    @Autowired
    XcChooseCourseMapper xcChooseCourseMapper;

    @Autowired
    XcCourseTablesMapper xcCourseTablesMapper;

    @Autowired
    ContentServiceClient contentServiceClient;

    @Transactional
    @Override
    public XcChooseCourseDto addChooseCourse(String userId, Long courseId) {
        // 1.Feign远程调用内容管理服务查询课程收费规则(从发布表中查询对应的课程是收费还是免费)
        CoursePublish coursepublish = contentServiceClient.getCoursepublish(courseId);
        if (coursepublish == null) {
            XueChengPlusException.cast("课程不存在");
        }
        // 课程收费规则(是否收费)
        String charge = coursepublish.getCharge();
        // 选课记录
        XcChooseCourse chooseCourse = null;
        // 2.免费课程:向选课记录表、我的课程表中写入数据(课程表的数据来源于选课记录表)
        if ("201000".equals(charge)) {
            // 向选课记录表中写入
            chooseCourse = addFreeCourse(userId, coursepublish);
            // 向课程表中写入
            XcCourseTables xcCourseTables = addCourseTables(chooseCourse);
        } else {
            // 3.收费课程:向炫酷记录表写入数据,等待用户支付完成后再向课程表中写入数据
            // 此模块不会向课程表中添加记录了
            chooseCourse = addChargeCourse(userId, coursepublish);
        }

        // 4.判断学生目前对此课程是否具有学习资格,并且要将此学习资格返回
        XcCourseTablesDto courseTablesDto = getLearningStatus(userId, courseId);
        // 构造返回值
        XcChooseCourseDto xcChooseCourseDto = new XcChooseCourseDto();
        BeanUtils.copyProperties(chooseCourse, xcChooseCourseDto);
        xcChooseCourseDto.setLearnStatus(courseTablesDto.getLearnStatus());
        return xcChooseCourseDto;
    }

    //添加免费课程,免费课程加入选课记录表
    public XcChooseCourse addFreeCourse(String userId, CoursePublish coursepublish) {
        // 不一定是添加,因为可能会有人多次点击“添加课程/学习课程”之类的按钮
        // 如果此课程已经被此用户选择了且选课的状态为成功,那就不允许用户再选择,直接返回结果即可
        LambdaQueryWrapper<XcChooseCourse> lqw = new LambdaQueryWrapper<>();
        // 哪一位用户
        lqw.eq(XcChooseCourse::getUserId, userId)
                // 课程id
                .eq(XcChooseCourse::getCourseId, coursepublish.getId())
                // 课程类型为免费课程
                .eq(XcChooseCourse::getOrderType, "700001")
                // 选课成功
                .eq(XcChooseCourse::getStatus, "701001");
        List<XcChooseCourse> xcChooseCourses = xcChooseCourseMapper.selectList(lqw);
        if (xcChooseCourses.size() > 0) {
            return xcChooseCourses.get(0);
        }
        // 运行到这里说明数据库中没有对应的选课记录,添加一份即可
        XcChooseCourse chooseCourse = new XcChooseCourse();
        chooseCourse.setCourseId(coursepublish.getId()); //课程id
        chooseCourse.setCourseName(coursepublish.getName()); //课程名称
        chooseCourse.setCoursePrice(coursepublish.getPrice());//免费课程价格为0
        chooseCourse.setUserId(userId); //用户名
        chooseCourse.setCompanyId(coursepublish.getCompanyId());//机构id
        chooseCourse.setOrderType("700001");//免费课程代码标识
        chooseCourse.setCreateDate(LocalDateTime.now()); //创建时间
        chooseCourse.setStatus("701001");//选课成功,选课状态标识

        chooseCourse.setValidDays(365);//免费课程默认365
        chooseCourse.setValidtimeStart(LocalDateTime.now());// 课程开始时间
        chooseCourse.setValidtimeEnd(LocalDateTime.now().plusDays(365)); //课程结束时间
        int insert = xcChooseCourseMapper.insert(chooseCourse);
        if (insert <= 0) {
            XueChengPlusException.cast("添加课程失败");
        }
        return chooseCourse;
    }

    //添加收费课程
    public XcChooseCourse addChargeCourse(String userId, CoursePublish coursepublish) {
        // 不一定是添加,因为可能会有人多次点击“添加课程/学习课程”之类的按钮
        // 查询选课表中,是否有此收费课程在选课记录表中的选课状态为待支付
        LambdaQueryWrapper<XcChooseCourse> lqw = new LambdaQueryWrapper<>();
        // 哪一位用户
        lqw.eq(XcChooseCourse::getUserId, userId)
                // 课程id
                .eq(XcChooseCourse::getCourseId, coursepublish.getId())
                // 课程类型为收费课程
                .eq(XcChooseCourse::getOrderType, "700002")
                // 状态不是选课成功,而是待支付
                .eq(XcChooseCourse::getStatus, "701002");
        List<XcChooseCourse> xcChooseCourses = xcChooseCourseMapper.selectList(lqw);
        if (xcChooseCourses.size() > 0) {
            return xcChooseCourses.get(0);
        }
        // 运行到这里说明数据库中没有对应的选课记录,添加一份即可
        XcChooseCourse chooseCourse = new XcChooseCourse();
        chooseCourse.setCourseId(coursepublish.getId()); //课程id
        chooseCourse.setCourseName(coursepublish.getName()); //课程名称
        chooseCourse.setCoursePrice(coursepublish.getPrice());//免费课程价格为0
        chooseCourse.setUserId(userId); //用户名
        chooseCourse.setCompanyId(coursepublish.getCompanyId());//机构id
        chooseCourse.setOrderType("700002");//收费课程代码标识
        chooseCourse.setCreateDate(LocalDateTime.now()); //创建时间
        chooseCourse.setStatus("701002");//选课成功,选课状态标识

        chooseCourse.setValidDays(365);//免费课程默认365
        chooseCourse.setValidtimeStart(LocalDateTime.now());// 课程开始时间
        chooseCourse.setValidtimeEnd(LocalDateTime.now().plusDays(365)); //课程结束时间
        int insert = xcChooseCourseMapper.insert(chooseCourse);
        if (insert <= 0) {
            XueChengPlusException.cast("添加课程失败");
        }
        return chooseCourse;
    }

    //添加到我的课程表(同一个人同一门课只会有同一条记录,因为这里我们已经在数据库添加约束了)
    public XcCourseTables addCourseTables(XcChooseCourse xcChooseCourse) {
        //选课记录完成且未过期可以添加课程到课程表
        String status = xcChooseCourse.getStatus();
        if (!"701001".equals(status)) {
            // 701001代表选课完成,其他状态都代表未完成
            XueChengPlusException.cast("选课未成功,无法添加到课程表");
        }
        XcCourseTables xcCourseTables = getXcCourseTables(xcChooseCourse.getUserId(), xcChooseCourse.getCourseId());
        if (xcCourseTables != null) {
            // 说明课程已经在课程表中了
            return xcCourseTables;
        }
        xcCourseTables = new XcCourseTables();
        xcCourseTables.setChooseCourseId(xcChooseCourse.getId()); // 选课表中的主键
        xcCourseTables.setUserId(xcChooseCourse.getUserId());
        xcCourseTables.setCourseId(xcChooseCourse.getCourseId());
        xcCourseTables.setCompanyId(xcChooseCourse.getCompanyId());
        xcCourseTables.setCourseName(xcChooseCourse.getCourseName());
        xcCourseTables.setCreateDate(LocalDateTime.now());
        xcCourseTables.setValidtimeStart(xcChooseCourse.getValidtimeStart());
        xcCourseTables.setValidtimeEnd(xcChooseCourse.getValidtimeEnd());
        xcCourseTables.setCourseType(xcChooseCourse.getOrderType());
        int insert = xcCourseTablesMapper.insert(xcCourseTables);
        if (insert <= 0) {
            XueChengPlusException.cast("课程添加到课程表失败");
        }
        return xcCourseTables;

    }

    /**
     * @param userId
     * @param courseId
     * @return com.xuecheng.learning.model.po.XcCourseTables
     * @description 根据课程和用户查询我的课程表中某一门课程
     */
    public XcCourseTables getXcCourseTables(String userId, Long courseId) {
        LambdaQueryWrapper<XcCourseTables> lqw = new LambdaQueryWrapper<>();
        lqw.eq(XcCourseTables::getUserId, userId)
                .eq(XcCourseTables::getCourseId, courseId);
        return xcCourseTablesMapper.selectOne(lqw);
    }

    /**
     * 查询课程表
     *
     * @param userId
     * @param courseId
     * @return XcCourseTablesDto 学习资格状态 [{"code":"702001","desc":"正常学习"},{"code":"702002","desc":"没有选课或选课后没有支付"},{"code":"702003","desc":"已过期需要申请续期或重新支付"}]
     * @description 判断学习资格
     */
    @Override
    public XcCourseTablesDto getLearningStatus(String userId, Long courseId) {
        // 查询我的课程表
        XcCourseTables xcCourseTables = getXcCourseTables(userId, courseId);
        if (xcCourseTables == null) {
            // 如果查不到,说明没有选课或者选课后未支付
            XcCourseTablesDto xcCourseTablesDto = new XcCourseTablesDto();
            xcCourseTablesDto.setLearnStatus("702002");
            return xcCourseTablesDto;
        }

        //如果有记录,判断是否过期,如果过期了就不能学习,如果没过期可以正常学习
        XcCourseTablesDto xcCourseTablesDto = new XcCourseTablesDto();
        BeanUtils.copyProperties(xcCourseTables, xcCourseTablesDto);
        //是否过期,true过期,false未过期
        boolean isExpires = xcCourseTables.getValidtimeEnd().isBefore(LocalDateTime.now());
        if (!isExpires) {
            //正常学习
            xcCourseTablesDto.setLearnStatus("702001");
            return xcCourseTablesDto;
        } else {
            //已过期
            xcCourseTablesDto.setLearnStatus("702003");
            return xcCourseTablesDto;
        }
    }
}

2.4.3 测试

2.5 查询学习资格

我们只需要写一个Controller接口就行了,具体的实现其实在2.4.2中实现了

@ApiOperation("查询学习资格")
@PostMapping("/choosecourse/learnstatus/{courseId}")
public XcCourseTablesDto getLearnstatus(@PathVariable("courseId") Long courseId) {
    SecurityUtil.XcUser user = SecurityUtil.getUser();
    if (user == null) {
        XueChengPlusException.cast("用户未登录");
    }
    String userId = user.getId();
    
    return myCourseTablesService.getLearningStatus(userId, courseId);

}
        boolean isExpires = xcCourseTables.getValidtimeEnd().isBefore(LocalDateTime.now());
        if (!isExpires) {
            //正常学习
            xcCourseTablesDto.setLearnStatus("702001");
            return xcCourseTablesDto;
        } else {
            //已过期
            xcCourseTablesDto.setLearnStatus("702003");
            return xcCourseTablesDto;
        }
    }
}

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

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

相关文章

在多文件编译时,如果模板类的成员函数的定义和模板类不在一个文件下会怎么样?

编译器将找不到成员函数的定义&#xff0c;哪怕你将存放成员函数定义的test.cpp一块编译&#xff0c;编译器也无法找到该模板类的成员函数的定义。 正确的做法是&#xff1a; 将模板类的声明和成员函数定义都定义在.h文件下

星辰天合参与编制 国内首个可兼顾 AI 大模型训练的高性能计算存储标准正式发布

近日&#xff0c;在中国电子工业标准化技术协会高标委的支持和指导下&#xff0c;XSKY星辰天合作为核心成员参与编制的《高性能计算分布式存储系统技术要求》团体标准&#xff0c;在中国电子工业标准化技术协会网站正式发布。 该团体标准强调了分布式存储系统对包括传统高性能计…

libftdi库编译

目录 1. 下载源码 2. Ubuntu下编译 2.1 配置编译环境 2.2 编译 3. Android NDK下编译 3.1 编译libconfuse 3.2 编译libusb 3.3 编译libudev 3.3 编译libftdi 分2部分&#xff0c;先在Ubuntu中编译&#xff0c;然后在Android NDK中编译。 1. 下载源码 下载地址&#…

开源文生图大模型Playground v2.5发布:超越SD、DALL·E 3和 Midjourney

前言 在AI技术迅速发展的今天&#xff0c;文生图模型成为了艺术创作、设计创新等领域的重要工具。Playground v2.5的发布&#xff0c;不仅在技术上取得了突破&#xff0c;更在开源文化的推广与实践上迈出了重要一步。 Huggingface模型下载&#xff1a;https://huggingface.co/…

一文读懂 Databend 的开放表格式引擎

本文介绍了 Databend 开放表格式引擎的支持情况&#xff0c;包括优势与不足、使用方法、与 Catalog 方案的对比。此外&#xff0c;还包含一个简单的 Workshop &#xff0c;介绍如何利用 Databend Cloud 分析位于对象存储中的 Delta Table 。 Databend 近期发布 Apache Iceberg …

如何排查合并问题——《OceanBase诊断系列》之七

1. 前言 OceanBase数据库的存储引擎以 LSM-Tree 架构为基础&#xff0c;区分静态基线数据&#xff08;存储在只读SSTable&#xff09;和动态增量数据&#xff08;存储在可读写MemTable&#xff09;。其中 SSTable 是只读的&#xff0c;一旦生成就不再被修改&#xff0c;存储于…

每日OJ题_链表①_力扣2. 两数相加

目录 力扣2. 两数相加 解析代码 力扣2. 两数相加 2. 两数相加 难度 中等 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个…

MyBatis-Flex学习总结

写在前面的话 MyBatis-Flex 是一个优雅的 MyBatis 增强框架&#xff0c;它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库&#xff0c;其内置的 QueryWrapper 帮助我们极大的减少了 SQL 编写的工作的同时&#xff0c;减少出错的可能性…

VPN应用场景典型案例-站点到站点组网应用

组网需求 站点到站点IPSEC隧道也是LAN -to -LAN IPSec描述的是两个局域网之间建立IPSec隧道的概念,建立站到站IPSec隧道时,两个专用网络之间跨越一个公用网络,这样就可以实现私有网络A:192.168.0.0/24到私有网络B:192.168.1.0/24之间的安全通信。以下是该典型环境的组网图…

【MOMO_Tips】批量将word转换为PDF格式

批量将word转换为PDF格式 1.打开文件–>选项–>自定义功能区–>开发工具–>确定 2.点开开发工具&#xff0c;选择第一个visual basic 3.进入页面后找到插入–>模块&#xff0c;就可以看到这样的画面之后将下列vba代码复制粘贴到模块中 Sub ConvertWordsToPd…

MySQL 存储过程(超详细)

一、什么是存储过程&#xff1f; 存储过程可称为过程化SQL语言&#xff0c;是在普通SQL语句的基础上增加了编程语言的特点&#xff0c;把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中&#xff0c;通过逻辑判断、循环等操作实现复杂计算的程序语言。换句话说&#xff0c…

私域做不下去的三大因素

私域运营是近年来的一大热门话题&#xff0c;从线下门店到日常外卖、线上购物&#xff0c;几乎所有的企业都在借助微信等社交媒体平台进行推广。然而&#xff0c;据统计&#xff0c;近90%的私域运营最后都不了了之。 原因1&#xff1a;在于企业对私域的认知不足&#xff0c;营…

【重温设计模式】迭代器模式及其Java示例

迭代器模式的介绍 在编程领域&#xff0c;迭代器模式是一种常见的设计模式&#xff0c;它提供了一种方法&#xff0c;使得我们可以顺序访问一个集合对象中的各个元素&#xff0c;而又无需暴露该对象的内部表示。你可以把它想象成一本书&#xff0c;你不需要知道这本书是怎么印…

C语言学习--练习2

目录 1.排序数组 2.多数元素 3.存在重复元素 4.最大间距 5.按奇偶排序数组 6.最小时间差 1.排序数组 /*** Note: The returned array must be malloced, assume caller calls free().*/ int cmp(const void*a,const void*b){return *(int*)a-*(int*)b; } int* sortArray(i…

常见的几种echarts类型

一&#xff1a;折线图 let option {tooltip: {},animation: false,grid: {top: "20%",bottom: "33%", //也可设置left和right设置距离来控制图表的大小left: 5%,right: 5%},xAxis: {boundaryGap:false,data: [1,2,3,4,5],axisLine: {show: true, //隐藏X轴…

MedSAM 项目排坑记录

MedSAM 项目排坑记录 任务排坑过程配置python环境测试构建docker模型训练数据预处理 单GPU训练最后推理 任务 做一个课程大作业&#xff0c;需要进行CVPR2024年医疗影像分割赛题的打榜&#xff08;CVPR 2024: SEGMENT ANYTHING IN MEDICAL IMAGES ON LAPTOP&#xff09;。看到…

实现消息队列(Kafka、ActiveMQ、RabbitMQ和RocketMQ)高可用

概述 单机没有高可用可言&#xff0c;高可用都对集群来说的 要保证消息队列系统&#xff08;如Kafka、ActiveMQ、RabbitMQ和RocketMQ&#xff09;的高可用性&#xff0c;可以采取以下一些通用的措施&#xff1a; 集群部署&#xff1a;将消息队列系统部署为集群&#xff0c;包…

音视频学习笔记——TCP网络原理

✊✊✊&#x1f308;大家好&#xff01;本篇文章主要记录自己在进行音视频学习中&#xff0c;整理的包括可靠传输、流量控制、拥塞控制等部分TCP网络原理相关的内容重点&#x1f607;。 本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习&#xff0c;…

一个平台满足你对测试工具的所有需求

背景 目前&#xff0c;测试人员普遍使用的测试工具有Postman、JMeter等&#xff0c;但这些工具都存在一定的局限性。例如&#xff0c;Postman缺少对API性能测试方面的支持&#xff0c;而JMeter则缺乏一个整合测试报告、测试脚本的统一管理系统以及UI测试功能。 RunnerGo是什么…

常用的几种concrt140.dll丢失的解决方法,关于concrt140.dll修复教程

concrt140.dll是Microsoft Visual Studio 2015&#xff08;或更高版本&#xff09;中包含的一个动态链接库文件&#xff0c;它是C运行时库的一部分&#xff0c;主要用于支持并行计算、并发处理等功能。当你的应用程序需要执行多线程操作或者使用了C的并发库时&#xff0c;就会依…