Xuecheng Online Notes 3 - [Content Module] Course classification query, course addition, modification and deletion, course plan addition, deletion and modification query, unified exception handling + JSR303 verification

Table of contents

1 [Content Module] Course Category Query

1.1 Demand Analysis

1.2 SQL statement for query, inner join query

1.2.1 [Self-join query] Query the two-tier course classification 

1.2.2 Review inner join query

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

1.3 dto+mapper+api+Service

1.4 httpClient test

2 [Content Module] new courses

2.1 Business process

2.2 Data Model

2.3 Request response data

2.4 dto+service+api

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.2 Custom exception class

3.3 Exception Information Model Class

3.4 Global exception handler, @RestControllerAdvice, @ExceptionHandler

4 [Basic module] Unified package result class

5 JSR303 validation

5.1 Controller implements JSR303 verification

5.2 MethodArgumentNotValidException capture processing

5.3 Group verification

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.1 Preview

5.5.2 Data Model

5.5.3 dto+sql+mapper+service+api

6 [Content Module] Add/Modify Lesson Plan 

6.1 Business process

6.2 Request

6.3 dto

6.4 Service

6.5 api

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

  1. Service adds course method to add transaction , Service needs to add @Transactional, startup class adds @EnableTransactionManagement
  2. 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);
    }

7 Delete lesson plans, sort lesson plans, teacher management, delete courses

Guess you like

Origin blog.csdn.net/qq_40991313/article/details/129686045