课程帮登录入口_课程帮登录入口

课程帮登录入口_课程帮登录入口课程分类查询、课程新增、统一异常处理、统一封装结果类、JSR303校验、修改课程、查询课程计划、新增/修改课程计划_学成在线笔记

导航: 

【Java笔记+踩坑汇总】Java基础+进阶+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud+黑马旅游+谷粒商城+学成在线+MySQL高级篇+设计模式+常见面试题+源码_vincewm的博客-CSDN博客

目录

1【内容模块】课程分类查询

1.1 需求分析

1.2 查询的sql语句,内连接查询

1.2.1【自连接查询】查询两层的课程分类 

1.2.2 回顾内连接查询

1.2.3 回顾自连接查询

1.2.4 回顾MySQL递归查询

1.2.5【最终sql】层序遍历查询多层的课程分类

1.2.6 mysql递归特点,对比Java递归的优势

1.3 dto+mapper+api+Service

1.4 httpClient测试

2【内容模块】课程新增

2.1 业务流程

2.2 数据模型

2.3 请求响应数据

2.4 dto+service+api

2.5 httpclient测试、前后端联调

3【基础模块】统一异常处理

3.1 通用异常信息的枚举类

3.2 自定义异常类

3.3 异常信息模型类

3.4 全局异常处理器,@RestControllerAdvice,@ExceptionHandler

4【基础模块】统一封装结果类

5 JSR303校验

5.1 controller实现JSR303校验

5.2 MethodArgumentNotValidException捕获处理

5.3 分组校验

5.3.1 基础模块创建分组类

5.3.2 实体类分组校验

5.3.3 Controller指定分组,@Validated

5.4【内容模块】修改课程

5.5【内容模块】查询课程计划

5.5.1 预览

5.5.2 数据模型

5.5.3 dto+sql+mapper+service+api

6【内容模块】新增/修改课程计划 

6.1 业务流程

6.2 请求

6.3 dto

6.4 Service

6.5 api


1【内容模块】课程分类查询

1.1 需求分析

新增课程界面需要查询课程分类:

课程帮登录入口_课程帮登录入口

课程等级、课程类型来源于数据字典表,此部分的信息前端已从系统管理服务读取。

course_category课程分类表的结构

课程帮登录入口_课程帮登录入口

这张表是一个树型结构,通过父结点id将各元素组成一个树。

表的数据:

课程帮登录入口_课程帮登录入口

请求:

课程帮登录入口_课程帮登录入口

http://localhost:8601/api/content/course-category/tree-nodes

请求参数为空。

响应数据:

[ { "childrenTreeNodes" : [ { "childrenTreeNodes" : null, "id" : "1-1-1", "isLeaf" : null, "isShow" : null, "label" : "HTML/CSS", "name" : "HTML/CSS", "orderby" : 1, "parentid" : "1-1" }, { "childrenTreeNodes" : null, "id" : "1-1-2", "isLeaf" : null, "isShow" : null, "label" : "JavaScript", "name" : "JavaScript", "orderby" : 2, "parentid" : "1-1" }, ... ], "id" : "1-2", "isLeaf" : null, "isShow" : null, "label" : "移动开发", "name" : "移动开发", "orderby" : 2, "parentid" : "1" } ] 

1.2 查询的sql语句,内连接查询

1.2.1【自连接查询】查询两层的课程分类 

select one.id one_id, one.name one_name, one.parentid one_parentid, one.orderby one_orderby, one.label one_label, two.id two_id, two.name two_name, two.parentid two_parentid, two.orderby two_orderby, two.label two_label from course_category as one inner join course_category as two on one.id = two.parentid #内连接自连接 where one.parentid = 1 #加条件,只查one的一级分类 and one.is_show = 1 #加条件,只查显示状态的分类 and two.is_show = 1 order by one.orderby, #根据排序字段排序 two.orderby 

tip:as起别名时可以省略。

查询结果:

课程帮登录入口_课程帮登录入口 对比原分类表:

课程帮登录入口_课程帮登录入口

1.2.2 回顾内连接查询

课程帮登录入口_课程帮登录入口

  • 内连接查询 :相当于查询AB交集数据

  • 外连接查询

    • 左外连接查询 :相当于查询A表所有数据和交集部门数据

    • 右外连接查询 : 相当于查询B表所有数据和交集部分数据

内连接查询

相当于查询AB交集数据。

语句:

-- 隐式内连接。没有JOIN关键字,条件使用WHERE指定。书写简单,多表时效率低 SELECT 字段列表 FROM 表1,表2… WHERE 条件; -- 显示内连接。使用INNER JOIN ... ON语句, 可以省略INNER。书写复杂,多表时效率高 SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 条件;
  • 隐式连接好理解好书写,语法简单,担心的点较少。
  • 但是显式连接可以减少字段的扫描,有更快的执行速度。这种速度优势在3张或更多表连接时比较明显 

 示例:

#隐式内连接 SELECT emp. NAME, emp.gender, dept.dname FROM emp, dept WHERE emp.dep_id = dept.did;
#显式内连接 select * from emp inner join dept on emp.dep_id = dept.did;

1.2.3 回顾自连接查询

自连接是一种特殊的内连接,它是指相互连接的表在物理上为同一张表,但可以在逻辑上分为两张表。

注意:自连接查询的列名必须是“表名.*”,而不是直接写“*”

案例:

要求检索出学号为20210的学生的同班同学的信息

SELECT stu.* #一定注意是stu.*,不是* FROM stu JOIN stu AS stu1 ON stu.grade= stu1.grade WHERE stu1.id='20210'

1.2.4 回顾MySQL递归查询

with语法:

 WITH [RECURSIVE] cte_name [(col_name [, col_name] ...)] AS (subquery) [, cte_name [(col_name [, col_name] ...)] AS (subquery)] ... 

recurslve译为递归。

with:在mysql中被称为公共表达式,可以作为一个临时表然后在其他结构中调用.如果是自身调用那么就是后面讲的递归.

cte_name :公共表达式的名称,可以理解为表名,用来表示as后面跟着的子查询

col_name :公共表达式包含的列名,可以写也可以不写

例子:使用MySQL临时表遍历1~5

with RECURSIVE t1 AS #这里t1函数名,也是临时表的表名 ( SELECT 1 as n #n是列的别名,1是初始记录 UNION ALL #把递归结果(2,3,4,5)合并到t1表中 SELECT n + 1 FROM t1 WHERE n < 5 #n+1是参数,t1是函数名,n<5是遍历终止条件 ) SELECT * FROM t1; #正常查询t1这个临时表,相当于调用这个函数。 

 课程帮登录入口_课程帮登录入口

说明:

t1 相当于一个表名

select 1 相当于这个表的初始值,这里使用UNION ALL 不断将每次递归得到的数据加入到表中。

n<5为递归执行的条件,当n>=5时结束递归调用。

1.2.5【最终sql】层序遍历查询多层的课程分类

with recursive t1 as ( #t1是函数名、临时表名 select * from course_category where id= '1' #初始记录,也就是根节点 union all #把递归结果合并到t1表中 select t2.* from course_category as t2 inner join t1 on t1.id = t2.parentid #递归,用分类表t和临时表t1内连接查询 ) select * from t1 order by t1.id, t1.orderby #查t1表,相当于调用这个函数。 

排序顺序为层序遍历树:

第一行是根节点,紧跟着的几行是根节点的直接子节点,以此类推。

课程帮登录入口_课程帮登录入口

1.2.6 mysql递归特点,对比Java递归的优势

mysql递归次数限制:

mysql为了避免无限递归默认递归次数为1000,可以通过设置cte_max_recursion_depth参数增加递归深度,还可以通过max_execution_time限制执行时间,超过此时间也会终止递归操作。

对比Java递归的优势:

mysql递归相当于在存储过程中执行若干次sql语句,java程序仅与数据库建立一次链接执行递归操作。相比之下,Java递归性能就很差,每次递归都会建立一次数据库连接。

1.3 dto+mapper+api+Service

dto

package com.xuecheng.content.model.dto; //继承分类实体类的基础上,多了子节点列表 @Data public class CourseCategoryTreeDto extends CourseCategory implements Serializable { List<CourseCategoryTreeDto> childrenTreeNodes; //多了子节点列表 } 

也可以不加dto,在分类实体类加属性:

 @TableField(exist = false) //表示数据库表中不存在 private List<CategoryEntity> children;

mapper

public interface CourseCategoryMapper extends BaseMapper<CourseCategory> { public List<CourseCategoryTreeDto> selectTreeNodes(String id); //层序遍历查询所有分类 } 

mapper.xml

把sql语句中的数值改成#{}就行,防止sql注入。

<select id="selectTreeNodes" resultType="com.xuecheng.content.model.dto.CourseCategoryTreeDto" parameterType="string"> with recursive t1 as ( select * from course_category p where id= #{id} union all select t.* from course_category t inner join t1 on t1.id = t.parentid ) select * from t1 order by t1.id, t1.orderby </select> 

 api

package com.xuecheng.content.api; @Slf4j @RestController public class CourseCategoryController { @Autowired CourseCategoryService courseCategoryService; @GetMapping("/course-category/tree-nodes") public List<CourseCategoryTreeDto> queryTreeNodes() { return courseCategoryService.queryTreeNodes("1"); } } 

service

下面这方法麻烦,建议多写个方法getChildren(),递归寻找指定节点的子分类。

package com.xuecheng.content.service.impl; @Slf4j @Service public class CourseCategoryServiceImpl implements CourseCategoryService { @Autowired CourseCategoryMapper courseCategoryMapper; @Override public List<CourseCategoryTreeDto> queryTreeNodes(String id) { //1.调用mapper层序遍历,递归查询出分类信息。此时列表的childrenTreeNodes属性为null List<CourseCategoryTreeDto> courseCategoryTreeDtos = courseCategoryMapper.selectTreeNodes(id); //2.找到每个节点的子节点,最终封装成List<CourseCategoryTreeDto> //先将list转成map,key就是结点的id,value就是CourseCategoryTreeDto对象,目的就是为了方便从map获取结点,filter(item->!id.equals(item.getId()))把根结点排除 //Collectors.toMap()第三个参数(key1, key2) -> key2)意思是键重复时,以后添加的为准。 Map<String, CourseCategoryTreeDto> mapTemp = courseCategoryTreeDtos.stream().filter(item -> !id.equals(item.getId())).collect(Collectors.toMap(key -> key.getId(), value -> value, (key1, key2) -> key2)); //定义一个list作为最终返回的list List<CourseCategoryTreeDto> courseCategoryList = new ArrayList<>(); //从头遍历 List<CourseCategoryTreeDto> ,一边遍历一边找子节点放在父节点的childrenTreeNodes courseCategoryTreeDtos.stream().filter(item -> !id.equals(item.getId())).forEach(item -> { if (item.getParentid().equals(id)) { courseCategoryList.add(item); } //找到节点的父节点 CourseCategoryTreeDto courseCategoryParent = mapTemp.get(item.getParentid()); if(courseCategoryParent!=null){ if(courseCategoryParent.getChildrenTreeNodes()==null){ //如果该父节点的ChildrenTreeNodes属性为空要new一个集合,因为要向该集合中放它的子节点 courseCategoryParent.setChildrenTreeNodes(new ArrayList<CourseCategoryTreeDto>()); } //到每个节点的子节点放在父节点的childrenTreeNodes属性中 courseCategoryParent.getChildrenTreeNodes().add(item); } }); //3.返回分类dto列表 return courseCategoryList; } }

1.4 httpClient测试

使用httpclient测试:

定义.http文件

课程帮登录入口_课程帮登录入口

运行测试。

完成前后端连调:

打开前端工程,进入新增课程页面。

课程分类下拉框可以正常显示

课程帮登录入口_课程帮登录入口

2【内容模块】课程新增

2.1 业务流程

课程基本信息:

课程帮登录入口_课程帮登录入口

课程营销信息:

课程帮登录入口_课程帮登录入口

在这个界面中填写课程的基本信息、课程营销信息上。

填写完毕,保存并进行下一步。

在此界面填写课程计划信息

课程帮登录入口_课程帮登录入口

课程计划即课程的大纲目录。

课程计划分为两级,章节和小节。

每个小节需要上传课程视频,用户点击 小节的标题即开始播放视频。

如果是直播课程则会进入直播间。

课程计划填写完毕进入课程师资的管理。

课程帮登录入口_课程帮登录入口

在课程师资界面维护该课程的授课老师。

课程帮登录入口_课程帮登录入口

至此,一门课程新增完成。

2.2 数据模型

课程帮登录入口_课程帮登录入口

2.3 请求响应数据

### 创建课程 POST { 
  {content_host}}/content/course Content-Type: application/json { "mt": "", "st": "", "name": "", "pic": "", "teachmode": "", "users": "初级人员", "tags": "", "grade": "", "description": "", "charge": "", "price": 0, "originalPrice":0, "": "", "wechat": "", "phone": "", "validDays": 365 } ###响应结果如下 #成功响应结果如下 { "id": 109, "companyId": 1, "companyName": null, "name": "测试课程103", "users": "初级人员", "tags": "", "mt": "1-1", "mtName": null, "st": "1-1-1", "stName": null, "grade": "", "teachmode": "", "description": "", "pic": "", "createDate": "2022-09-08 07:35:16", "changeDate": null, "createPeople": null, "changePeople": null, "auditStatus": "", "status": 1, "coursePubId": null, "coursePubDate": null, "charge": "", "price": null, "originalPrice":0, "": "", "wechat": "", "phone": "", "validDays": 365 } 

2.4 dto+service+api

略。

Service注意

  1. Service添加课程方法要添加事务,Service要加@Transactional,启动类加@EnableTransactionManagement
  2. Service要校验参数,毕竟@Valid只能controller用

课程帮登录入口_课程帮登录入口

2.5 httpclient测试、前后端联调

### 新增课程 POST { 
  {content_host}}/content/course Content-Type: application/json { "name" : "新课程", "charge": "", "price": 10, "originalPrice":100, "": "22333", "wechat": "", "phone": "", "validDays": 365, "mt": "1-1", "st": "1-1-1", "pic": "fdsf", "teachmode": "", "users": "初级人员", "tags": "tagstagstags", "grade": "", "description": "java网络编程高级java网络编程高级java网络编程高级" }

前后端联调

打开新增课程页面,除了课程图片其它信息全部输入。

点击保存,观察浏览器请求接口参数及响应结果是否正常。

3【基础模块】统一异常处理

3.1 通用异常信息的枚举类

package com.xuecheng.base.execption; public enum CommonError { UNKOWN_ERROR("执行过程异常,请重试。"), PARAMS_ERROR("非法参数"), OBJECT_NULL("对象为空"), QUERY_NULL("查询结果为空"), REQUEST_NULL("请求参数为空"); private String errMessage; public String getErrMessage() { return errMessage; } private CommonError( String errMessage) { this.errMessage = errMessage; } } 

3.2 自定义异常类

package com.xuecheng.base.execption; public class XueChengPlusException extends RuntimeException { private String errMessage; public XueChengPlusException() { super(); } public XueChengPlusException(String errMessage) { super(errMessage); this.errMessage = errMessage; } public String getErrMessage() { return errMessage; } public static void cast(CommonError commonError){ throw new XueChengPlusException(commonError.getErrMessage()); } public static void cast(String errMessage){ throw new XueChengPlusException(errMessage); } } 

 使用自定义的异常处理:

 if (StringUtils.isBlank(dto.getName())) { // throw new RuntimeException("课程名称为空"); XueChengPlusException.cast("课程名称为空"); }

3.3 异常信息模型类

package com.xuecheng.base.execption; /** * 错误响应参数包装 */ public class RestErrorResponse implements Serializable { private String errMessage; public RestErrorResponse(String errMessage){ this.errMessage= errMessage; } public String getErrMessage() { return errMessage; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } } 

3.4 全局异常处理器,@RestControllerAdvice,@ExceptionHandler

package com.xuecheng.base.execption; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(XueChengPlusException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public RestErrorResponse customException(XueChengPlusException e) { log.error("【系统异常】{}",e.getErrMessage(),e); return new RestErrorResponse(e.getErrMessage()); } @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public RestErrorResponse exception(Exception e) { log.error("【系统异常】{}",e.getMessage(),e); return new RestErrorResponse(CommonError.UNKOWN_ERROR.getErrMessage()); } } 

4【基础模块】统一封装结果类

controller用的结果类

package com.xuecheng.base.model; import lombok.Data; import lombok.ToString; import java.io.Serializable; import java.util.List; /** * @author Mr.M * @version 1.0 * @description 分页查询结果模型类 * @date 2023/2/11 15:40 */ @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; } }

service用的结果类 

package com.xuecheng.base.model; /** * @description 通用结果类型 */ @Data @ToString public class RestResponse<T> { /** * 响应编码,0为正常,-1错误 */ private int code; /** * 响应提示信息 */ private String msg; /** * 响应内容 */ private T result; public RestResponse() { this(0, "success"); } public RestResponse(int code, String msg) { this.code = code; this.msg = msg; } /** * 错误信息的封装 * * @param msg * @param <T> * @return */ public static <T> RestResponse<T> validfail(String msg) { RestResponse<T> response = new RestResponse<T>(); response.setCode(-1); response.setMsg(msg); return response; } public static <T> RestResponse<T> validfail(T result,String msg) { RestResponse<T> response = new RestResponse<T>(); response.setCode(-1); response.setResult(result); response.setMsg(msg); return response; } /** * 添加正常响应数据(包含响应内容) * * @return RestResponse Rest服务封装相应数据 */ public static <T> RestResponse<T> success(T result) { RestResponse<T> response = new RestResponse<T>(); response.setResult(result); return response; } public static <T> RestResponse<T> success(T result,String msg) { RestResponse<T> response = new RestResponse<T>(); response.setResult(result); response.setMsg(msg); return response; } /** * 添加正常响应数据(不包含响应内容) * * @return RestResponse Rest服务封装相应数据 */ public static <T> RestResponse<T> success() { return new RestResponse<T>(); } public Boolean isSuccessful() { return this.code == 0; } }

5 JSR303校验

5.1 controller实现JSR303校验

注意:controller和Service都需要校验。

Contoller使用JSR303校验请求参数的合法性。

Service中要校验的是业务规则相关的内容。

1.导入依赖 

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 

2.实体类注解 课程帮登录入口_课程帮登录入口

示例,实体类注解:

package com.xuecheng.content.model.dto; /** * @description 添加课程dto */ @Data @ApiModel(value="AddCourseDto", description="新增课程基本信息") public class AddCourseDto { @NotEmpty(message = "课程名称不能为空") @ApiModelProperty(value = "课程名称", required = true) private String name; @NotEmpty(message = "适用人群不能为空") @Size(message = "适用人群内容过少",min = 10) @ApiModelProperty(value = "适用人群", required = true) private String users; @ApiModelProperty(value = "课程标签") private String tags; @NotEmpty(message = "课程分类不能为空") @ApiModelProperty(value = "大分类", required = true) private String mt; @NotEmpty(message = "课程分类不能为空") @ApiModelProperty(value = "小分类", required = true) private String st; @NotEmpty(message = "课程等级不能为空") @ApiModelProperty(value = "课程等级", required = true) private String grade; @ApiModelProperty(value = "教学模式(普通,录播,直播等)", required = true) private String teachmode; @ApiModelProperty(value = "课程介绍") private String description; @ApiModelProperty(value = "课程图片", required = true) private String pic; @NotEmpty(message = "收费规则不能为空") @ApiModelProperty(value = "收费规则,对应数据字典", required = true) private String charge; @ApiModelProperty(value = "价格") private BigDecimal price; } 

3.controller方法中添加@Validated注解

@ApiOperation("新增课程基础信息") @PostMapping("/course") public CourseBaseInfoDto createCourseBase(@RequestBody @Validated AddCourseDto addCourseDto){ //机构id,由于认证系统没有上线暂时硬编码 Long companyId = 1L; return courseBaseInfoService.createCourseBase(companyId,addCourseDto); } 

5.2 MethodArgumentNotValidException捕获处理

MethodArgumentNotValidException方法参数不合法异常。

自定义异常类添加方法:

package com.xuecheng.base.execption.XueChengPlusException

@ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public RestErrorResponse methodArgumentNotValidException(MethodArgumentNotValidException e) { BindingResult bindingResult = e.getBindingResult(); List<String> msgList = new ArrayList<>(); //将错误信息放在msgList bindingResult.getFieldErrors().stream().forEach(item->msgList.add(item.getDefaultMessage())); //拼接错误信息 String msg = StringUtils.join(msgList, ","); log.error("【系统异常】{}",msg); return new RestErrorResponse(msg); } 

此时发新增课程请求,name属性为空,运行:

课程帮登录入口_课程帮登录入口

 测试JSR303异常要暂时先把Service里的参数校验注释掉:

课程帮登录入口_课程帮登录入口

5.3 分组校验

5.3.1 基础模块创建分组类

package com.xuecheng.base.execption; public class ValidationGroups { public interface Inster{}; public interface Update{}; public interface Delete{}; } 

5.3.2 实体类分组校验

@NotEmpty(groups = {ValidationGroups.Inster.class},message = "添加课程名称不能为空") @NotEmpty(groups = {ValidationGroups.Update.class},message = "修改课程名称不能为空") // @NotEmpty(message = "课程名称不能为空") @ApiModelProperty(value = "课程名称", required = true) private String name; 

5.3.3 Controller指定分组,@Validated

@ApiOperation("新增课程基础信息") @PostMapping("/course") public CourseBaseInfoDto createCourseBase(@RequestBody @Validated({ValidationGroups.Inster.class}) AddCourseDto addCourseDto){ //机构id,由于认证系统没有上线暂时硬编码 Long companyId = 1L; return courseBaseInfoService.createCourseBase(companyId,addCourseDto); } 

5.4【内容模块】修改课程

略。

  • 请求参数比新增时多了id,EditCourseDto继承AddCourseDto,多了一个id属性。
     
  • controller和Service多了一个回显方法(根据id查询课程信息)。
  • 修改时给数据添加更新时间。

5.5【内容模块】查询课程计划

5.5.1 预览

课程基本信息添加或修改成功将自动进入课程计划编辑器界面,如下图:

课程帮登录入口_课程帮登录入口

课程计划即课程的大纲目录。

课程计划是树形结构,分为两级:第一级为大章节,grade为1、第二级为小章节,grade为2

5.5.2 数据模型

课程计划表teachplan

课程帮登录入口_课程帮登录入口

课程帮登录入口_课程帮登录入口

每个课程计划都有所属课程“课程标识course_id”。

课程计划关联的视频信息在teachplan_media表,结构如下:

课程帮登录入口_课程帮登录入口

课程帮登录入口_课程帮登录入口

teachplan_media表最重要的是“媒体id”和“计划id”两个字段,绑定单个计划和单个媒体的关系。

两张表是一对一关系,每个课程计划只能在teachplan_media表中存在一个视频。

5.5.3 dto+sql+mapper+service+api

GET /teachplan/22/tree-nodes

dto

除了课程计划实体类的数据,多了该计划的“计划与媒体关系”和“子分类列表”数据。

@Data @ToString public class TeachplanDto extends Teachplan { //与媒资管理的信息 private TeachplanMedia teachplanMedia; //小章节list private List<TeachplanDto> teachPlanTreeNodes; }

mapper

public interface TeachplanMapper extends BaseMapper<Teachplan> { public List<TeachplanDto> selectTreeNodes(long courseId); } 

sql

1、一级分类和二级分类通过teachplan表的自链接进行,如果只有一级分类其下边没有二级分类,此时也需要显示一级分类,这里使用左连接,左边是一级分类,右边是二级分类。

2、由于当“还没有关联视频”时teachplan_media对应的记录为空,所以需要teachplan和teachplan_media左连接。

SELECT one.id one_id, one.pname one_pname, one.parentid one_parentid, one.grade one_grade, one.media_type one_mediaType, one.start_time one_stratTime, one.end_time one_endTime, one.orderby one_orderby, one.course_id one_courseId, one.course_pub_id one_coursePubId, two.id two_id, two.pname two_pname, two.parentid two_parentid, two.grade two_grade, two.media_type two_mediaType, two.start_time two_stratTime, two.end_time two_endTime, two.orderby two_orderby, two.course_id two_courseId, two.course_pub_id two_coursePubId, m1.media_fileName mediaFilename, m1.id teachplanMeidaId, m1.media_id mediaId from teachplan one INNER JOIN teachplan two on one.id = two.parentid #自连接,查有子的计划 LEFT JOIN teachplan_media m1 on m1.teachplan_id = two.id #左连接,查计划表,它可以带视频,也可以不带视频 where one.parentid = 0 and one.course_id=#{value} order by one.orderby, two.orderby 

mapper.xml

<!-- 课程分类树型结构查询映射结果 --> <resultMap id="treeNodeResultMap" type="com.xuecheng.content.model.dto.TeachplanDto"> <!-- 一级数据映射 --> <id column="one_id" property="id" /> <result column="one_pname" property="pname" /> <result column="one_parentid" property="parentid" /> <result column="one_grade" property="grade" /> <result column="one_mediaType" property="mediaType" /> <result column="one_stratTime" property="stratTime" /> <result column="one_endTime" property="endTime" /> <result column="one_orderby" property="orderby" /> <result column="one_courseId" property="courseId" /> <result column="one_coursePubId" property="coursePubId" /> <!-- 一级中包含多个二级数据 --> <collection property="teachPlanTreeNodes" ofType="com.xuecheng.content.model.dto.TeachplanDto"> <!-- 二级数据映射 --> <id column="two_id" property="id" /> <result column="two_pname" property="pname" /> <result column="two_parentid" property="parentid" /> <result column="two_grade" property="grade" /> <result column="two_mediaType" property="mediaType" /> <result column="two_stratTime" property="stratTime" /> <result column="two_endTime" property="endTime" /> <result column="two_orderby" property="orderby" /> <result column="two_courseId" property="courseId" /> <result column="two_coursePubId" property="coursePubId" /> <association property="teachplanMedia" javaType="com.xuecheng.content.model.po.TeachplanMedia"> <result column="teachplanMeidaId" property="id" /> <result column="mediaFilename" property="mediaFilename" /> <result column="mediaId" property="mediaId" /> <result column="two_id" property="teachplanId" /> <result column="two_courseId" property="courseId" /> <result column="two_coursePubId" property="coursePubId" /> </association> </collection> </resultMap> <!--课程计划树型结构查询--> <select id="selectTreeNodes" resultMap="treeNodeResultMap" parameterType="long" > select one.id one_id, one.pname one_pname, one.parentid one_parentid, one.grade one_grade, one.media_type one_mediaType, one.start_time one_stratTime, one.end_time one_endTime, one.orderby one_orderby, one.course_id one_courseId, one.course_pub_id one_coursePubId, two.id two_id, two.pname two_pname, two.parentid two_parentid, two.grade two_grade, two.media_type two_mediaType, two.start_time two_stratTime, two.end_time two_endTime, two.orderby two_orderby, two.course_id two_courseId, two.course_pub_id two_coursePubId, m1.media_fileName mediaFilename, m1.id teachplanMeidaId, m1.media_id mediaId from teachplan one INNER JOIN teachplan two on one.id = two.parentid LEFT JOIN teachplan_media m1 on m1.teachplan_id = two.id where one.parentid = 0 and one.course_id=#{value} order by one.orderby, two.orderby </select> 

Service

package com.xuecheng.content.service.impl; @Service public class TeachplanServiceImpl implements TeachplanService { @Autowired TeachplanMapper teachplanMapper; @Override public List<TeachplanDto> findTeachplanTree(long courseId) { return teachplanMapper.selectTreeNodes(courseId); } } 

api

 @Autowired TeachplanService teachplanService; @ApiOperation("查询课程计划树形结构") @ApiImplicitParam(value = "courseId",name = "课程基础Id值",required = true,dataType = "Long",paramType = "path") @GetMapping("teachplan/{courseId}/tree-nodes") public List<TeachplanDto> getTreeNodes(@PathVariable Long courseId){ return teachplanService.findTeachplanTree(courseId); } 

测试

### 查询某个课程的课程计划 GET { 
  {content_host}}/content/teachplan/74/tree-nodes 

6【内容模块】新增/修改课程计划 

6.1 业务流程

添加包括:添加章、添加节 

修改包括:点击章节名称,显示输入框进行修改。

1、进入课程计划界面

课程帮登录入口_课程帮登录入口

2、点击“添加章”新增第一级课程计划。

新增成功自动刷新课程计划列表。

3、点击“添加小节”向某个第一级课程计划下添加小节。

新增成功自动刷新课程计划列表。

新增的课程计划自动排序到最后。

4、点击“章”、“节”的名称,可以修改名称、选择是否免费。

课程帮登录入口_课程帮登录入口

6.2 请求

1、新增第一级课程计划

名称默认为:新章名称 [点击修改]

grade:1

orderby:  所属课程中同级别下排在最后

2、新增第二级课程计划

名称默认为:新小节名称 [点击修改]

grade:2

orderby:  所属课程计划中排在最后

3、修改第一级、第二级课程计划的名称,修改第二级课程计划是否免费

新增章、节 的请求格式是一样的,主要章的等级是1,节的等级是2。

### 新增课程计划--章,当grade为1时parentid为0 POST { 
  {content_host}}/content/teachplan Content-Type: application/json { "courseId" : 74, "parentid": 0, "grade" : 1, "pname" : "新章名称 [点击修改]" } ### 新增课程计划--节 POST { 
  {content_host}}/content/teachplan Content-Type: application/json { "courseId" : 74, "parentid": 247, "grade" : 2, "pname" : "小节名称 [点击修改]" } 

6.3 dto

增改是一个dto,不同点是id是否为空。

保存dto的属性和教学计划实体类基本一样,只是少了几个属性。

package com.xuecheng.content.model.dto; /** * @description 保存课程计划dto,包括新增、修改 */ @Data @ToString public class SaveTeachplanDto { /*** * 教学计划id */ private Long id; /** * 课程计划名称 */ private String pname; /** * 课程计划父级Id */ private Long parentid; /** * 层级,分为1、2、3级 */ private Integer grade; /** * 课程类型:1视频、2文档 */ private String mediaType; /** * 课程标识 */ private Long courseId; /** * 课程发布标识 */ private Long coursePubId; /** * 是否支持试学或预览(试看) */ private String isPreview; } 

6.4 Service

  •  不用写mapper,因为就是基础的语句就能实现。
  • 新增和修改一个方法就行,通过判断id是否为空判断是增还是删
@Transactional @Override public void saveTeachplan(SaveTeachplanDto teachplanDto) { //课程计划id Long id = teachplanDto.getId(); //修改课程计划 if(id!=null){ Teachplan teachplan = teachplanMapper.selectById(id); BeanUtils.copyProperties(teachplanDto,teachplan); teachplanMapper.updateById(teachplan); }else{ //取出同父同级别的课程计划数量 int count = getTeachplanCount(teachplanDto.getCourseId(), teachplanDto.getParentid()); Teachplan teachplanNew = new Teachplan(); //设置排序号 teachplanNew.setOrderby(count+1); BeanUtils.copyProperties(teachplanDto,teachplanNew); teachplanMapper.insert(teachplanNew); } } /** * @description 获取最新的排序号 * @param courseId 课程id * @param parentId 父课程计划id * @return int 最新排序号 */ private int getTeachplanCount(long courseId,long parentId){ LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Teachplan::getCourseId,courseId); queryWrapper.eq(Teachplan::getParentid,parentId); Integer count = teachplanMapper.selectCount(queryWrapper); return count; } 

6.5 api

 @ApiOperation("课程计划创建或修改") @PostMapping("/teachplan") public void saveTeachplan( @RequestBody SaveTeachplanDto teachplan){ teachplanService.saveTeachplan(teachplan); } 

今天的文章
课程帮登录入口_课程帮登录入口分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/80950.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注