Table of contents
1 [Content Module] Course Category Query
1.2 SQL statement for query, inner join query
1.2.1 [Self-join query] Query the two-tier course classification
1.2.3 Review of self-join queries
1.2.4 Review MySQL recursive query
1.2.5 [Final sql] Layer order traversal to query multi-layer course classification
1.2.6 Mysql recursive features, compared with Java recursive advantages
2 [Content Module] new courses
2.5 httpclient test, front-end and back-end joint debugging
3 [Basic Module] Unified Exception Handling
3.1 Enumeration class of general exception information
3.3 Exception Information Model Class
3.4 Global exception handler, @RestControllerAdvice, @ExceptionHandler
4 [Basic module] Unified package result class
5.1 Controller implements JSR303 verification
5.2 MethodArgumentNotValidException capture processing
5.3.1 Basic module to create grouping class
5.3.2 Entity group verification
5.3.3 Controller specified grouping, @Validated
5.4 [Content Module] Modify Course
5.5 [Content Module] Query Course Plan
5.5.3 dto+sql+mapper+service+api
6 [Content Module] Add/Modify Lesson Plan
7 Delete lesson plans, sort lesson plans, teacher management, delete courses
1 [Content Module] Course Category Query
1.1 Demand Analysis
The newly added course interface needs to query the course category:
The course level and course type come from the data dictionary table, and the information front end of this part has been read from the system management service.
The structure of the course_category course classification table
This table is a tree structure, and each element forms a tree through the parent node id.
Table data:
ask:
http://localhost:8601/api/content/course-category/tree-nodes
The request parameter is empty.
Response data:
[ { "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 statement for query, inner join query
1.2.1 [Self-join query] Query the two-tier course classification
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 can be omitted when used as an alias.
search result:
Compared with the original classification table:
1.2.2 Review inner join query
-
Inner join query: equivalent to query AB intersection data
-
Outer join query
-
Left outer join query: equivalent to querying all data in table A and intersection department data
-
Right outer join query: equivalent to querying all data in table B and the intersection data
-
inner join query
It is equivalent to querying AB intersection data.
statement:
-- 隐式内连接。没有JOIN关键字,条件使用WHERE指定。书写简单,多表时效率低
SELECT 字段列表 FROM 表1,表2… WHERE 条件;
-- 显示内连接。使用INNER JOIN ... ON语句, 可以省略INNER。书写复杂,多表时效率高
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 条件;
- Implicit connections are easy to understand and easy to write, the grammar is simple, and there are fewer points to worry about.
- But explicit connection can reduce field scanning and have faster execution speed . This speed advantage is more obvious when 3 or more tables are connected
Example:
#隐式内连接
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 Review of self-join queries
Self-join is a special kind of inner join , which means that the connected tables are physically the same table, but can be logically divided into two tables.
Note: The column name of the self-join query must be "table name.*" instead of directly writing "*"
case:
It is required to retrieve the information of the classmates of the student whose student number is 20210
SELECT stu.* #一定注意是stu.*,不是*
FROM stu JOIN stu AS stu1 ON stu.grade= stu1.grade
WHERE stu1.id='20210'
1.2.4 Review MySQL recursive query
with syntax:
WITH [RECURSIVE]
cte_name [(col_name [, col_name] ...)] AS (subquery)
[, cte_name [(col_name [, col_name] ...)] AS (subquery)] ...
recurslve is translated into recursion.
with: It is called a public expression in mysql , which can be used as a temporary table and then called in other structures. If it is called by itself, it is the recursion mentioned later.
cte_name: the name of the public expression, which can be understood as the table name, used to represent the subquery followed by as
col_name: the column name contained in the public expression, can be written or not
Example: use MySQL temporary table to traverse 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这个临时表,相当于调用这个函数。
illustrate:
t1 is equivalent to a table name
select 1 is equivalent to the initial value of this table, and UNION ALL is used here to continuously add the data obtained by each recursion to the table.
n<5 is the condition of recursive execution, when n>=5, the recursive call ends.
1.2.5 [Final sql] Layer order traversal to query multi-layer course classification
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表,相当于调用这个函数。
The sort order is a level order traversal tree:
The first line is the root node, the next few lines are the direct children of the root node, and so on.
1.2.6 Mysql recursive features, compared with Java recursive advantages
mysql recursion limit:
In order to avoid infinite recursion, mysql defaults to 1000 recursion times. You can increase the recursion depth by setting the cte_max_recursion_depth parameter, and you can also limit the execution time by max_execution_time, which will also terminate the recursive operation after this time.
Advantages over Java recursion:
Mysql recursion is equivalent to executing several SQL statements in the stored procedure, and the java program only establishes a connection with the database to perform recursive operations. In contrast, Java's recursive performance is very poor, and each recursion will establish a database connection.
1.3 dto+mapper+api+Service
dto
package com.xuecheng.content.model.dto;
//继承分类实体类的基础上,多了子节点列表
@Data
public class CourseCategoryTreeDto extends CourseCategory implements Serializable {
List<CourseCategoryTreeDto> childrenTreeNodes; //多了子节点列表
}
You can also add attributes to the classification entity class without adding dto:
@TableField(exist = false) //表示数据库表中不存在 private List<CategoryEntity> children;
mapper
public interface CourseCategoryMapper extends BaseMapper<CourseCategory> {
public List<CourseCategoryTreeDto> selectTreeNodes(String id); //层序遍历查询所有分类
}
mapper.xml
Just change the value in the sql statement to #{} to prevent sql injection.
<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
The following method is troublesome. It is recommended to write an additional method getChildren() to recursively find the subcategories of the specified node.
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 test
Test with httpclient:
define .http file
Run the tests.
Complete the front-end and back-end connection adjustment:
Open the front-end project and enter the new course page.
The course category drop-down box can be displayed normally
2 [Content Module] new courses
2.1 Business process
Basic course information:
Course Marketing Information:
Fill in the basic information of the course and course marketing information in this interface.
After completing, save and proceed to the next step.
Fill in the course plan information on this interface
The course plan is the syllabus of the course.
Lesson plans are divided into two levels, chapters and subsections.
Each section needs to upload the course video, and the user clicks the title of the section to start playing the video.
If it is a live course, it will enter the live room.
After the course plan is completed, enter the management of the course teachers.
Maintain the teaching teacher of this course in the course teacher interface.
So far, a new course has been added.
2.2 Data Model
2.3 Request response data
### 创建课程
POST {
{content_host}}/content/course
Content-Type: application/json
{
"mt": "",
"st": "",
"name": "",
"pic": "",
"teachmode": "200002",
"users": "初级人员",
"tags": "",
"grade": "204001",
"description": "",
"charge": "201000",
"price": 0,
"originalPrice":0,
"qq": "",
"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": "204001",
"teachmode": "200002",
"description": "",
"pic": "",
"createDate": "2022-09-08 07:35:16",
"changeDate": null,
"createPeople": null,
"changePeople": null,
"auditStatus": "202002",
"status": 1,
"coursePubId": null,
"coursePubDate": null,
"charge": "201000",
"price": null,
"originalPrice":0,
"qq": "",
"wechat": "",
"phone": "",
"validDays": 365
}
2.4 dto+service+api
slightly.
Service attention
- Service adds course method to add transaction , Service needs to add @Transactional, startup class adds @EnableTransactionManagement
- Service needs to verify parameters . After all, @Valid can only be used by controllers
2.5 httpclient test, front-end and back-end joint debugging
### 新增课程
POST {
{content_host}}/content/course
Content-Type: application/json
{
"name" : "新课程",
"charge": "201001",
"price": 10,
"originalPrice":100,
"qq": "22333",
"wechat": "223344",
"phone": "13333333",
"validDays": 365,
"mt": "1-1",
"st": "1-1-1",
"pic": "fdsf",
"teachmode": "200002",
"users": "初级人员",
"tags": "tagstagstags",
"grade": "204001",
"description": "java网络编程高级java网络编程高级java网络编程高级"
}
Front-end and back-end joint debugging
Open the new course page, and enter all the information except the course picture.
Click Save to observe whether the browser request interface parameters and response results are normal.
3 [Basic Module] Unified Exception Handling
3.1 Enumeration class of general exception information
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 Custom exception class
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);
}
}
Use custom exception handling:
if (StringUtils.isBlank(dto.getName())) { // throw new RuntimeException("课程名称为空"); XueChengPlusException.cast("课程名称为空"); }
3.3 Exception Information Model Class
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 Global exception handler, @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 [Basic module] Unified package result class
The result class used by the 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;
}
}
The result class for 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 validation
5.1 Controller implements JSR303 verification
Note: Both controller and Service need to be verified.
Contoller uses JSR303 to verify the legitimacy of request parameters.
What needs to be verified in Service is the content related to business rules.
1. Import dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. Entity class annotation
Example, entity class annotation:
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. Add the @Validated annotation to the controller method
@ApiOperation("新增课程基础信息")
@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody @Validated AddCourseDto addCourseDto){
//机构id,由于认证系统没有上线暂时硬编码
Long companyId = 1L;
return courseBaseInfoService.createCourseBase(companyId,addCourseDto);
}
5.2 MethodArgumentNotValidException capture processing
MethodArgumentNotValidException The method parameter is invalid.
Add method of custom exception class:
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);
}
At this time, send a new course request, the name attribute is empty, run:
To test JSR303 exceptions, temporarily comment out the parameter verification in Service:
5.3 Group verification
5.3.1 Basic module to create grouping class
package com.xuecheng.base.execption;
public class ValidationGroups {
public interface Inster{};
public interface Update{};
public interface Delete{};
}
5.3.2 Entity group verification
@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 specified grouping, @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 [Content Module] Modify Course
slightly.
- The request parameter has more ids than when it was added. EditCourseDto inherits AddCourseDto and has an additional id attribute.
- Controller and Service have an additional echo method (query course information based on id).
- Add an update time to the data when it is modified.
5.5 [Content Module] Query Course Plan
5.5.1 Preview
After adding or modifying basic course information, it will automatically enter the course plan editor interface, as shown in the figure below:
The course plan is the syllabus of the course.
The lesson plan is a tree structure , which is divided into two levels: the first level is a large chapter with a grade of 1, and the second level is a small chapter with a grade of 2
5.5.2 Data Model
Lesson plan table teachplan :
Each course plan has a course "course identification course_id" to which it belongs.
The video information associated with the lesson plan is in the teachplan_media table , and the structure is as follows:
The most important fields in the teachplan_media table are the two fields "media id" and "plan id ", which bind the relationship between a single plan and a single media.
The two tables have a one-to-one relationship, and each lesson plan can only have one video in the teachplan_media table.
5.5.3 dto+sql+mapper+service+api
GET /teachplan/22/tree-nodes
dto
In addition to the data of the course plan entity class, there are more "plan and media relations" and "subcategory list" data of the plan.
@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. The first-level classification and the second-level classification are carried out through the self-link of the teachplan table. If there is only the first-level classification and no second-level classification below it, the first-level classification also needs to be displayed at this time. Here, the left connection is used, the left is the first-level classification, and the right is a secondary classification.
2. Since the record corresponding to teachplan_media is empty when "there is no associated video", the left connection between teachplan and teachplan_media is required.
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);
}
test
### 查询某个课程的课程计划
GET {
{content_host}}/content/teachplan/74/tree-nodes
6 [Content Module] Add/Modify Lesson Plan
6.1 Business process
Add includes: add chapter, add section
The modification includes: click on the chapter name to display the input box for modification.
1. Enter the course plan interface
2. Click "Add Chapter" to add a new first-level lesson plan.
Added automatic refresh of the course plan list on success.
3. Click "Add Section" to add a section to a level 1 lesson plan.
Added automatic refresh of the course plan list on success.
Added lesson plans are automatically sorted to the end.
4. Click the name of "chapter" and "section", you can modify the name and choose whether it is free or not.
6.2 Request
1. Add the first-level course plan
The default name is: new chapter name [click to modify]
grade:1
orderby: Ranked last at the same level in the course to which it belongs
2. Add the second level course plan
The default name is: new subsection name [click to modify]
grade:2
orderby: ranked last in the course plan to which it belongs
3. Modify the names of the first-level and second-level course plans, and whether it is free to modify the second-level course plans
The request format for adding new chapters and sections is the same, the level of the main chapter is 1, and the level of the section is 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
The change is a dto, the difference is whether the id is empty.
The attributes of saving dto are basically the same as those of the teaching plan entity class, except for a few attributes.
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
- There is no need to write mapper, because it can be realized with basic statements.
- Just add and modify a method, and judge whether to add or delete by judging whether the id is empty .
@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);
}