学成在线--内容管理模块
1.模块需求分析
1.1什么是模块需求分析
- 在百度百科中对需求分析的定义如下:
- 需求分析也称为软件需求分析、系统需求分析或需求分析工程等,是开发人员经过深入细致的调研和分析,准确理解用户和项目的功能、性能、可靠性等具体要求,将用户非形式的需求表述转化为完整的需求定义,从而确定系统必须做什么的过程。
- 简单理解就是要搞清楚问题域,问题域就是用户的需求,软件要为用户解决什么问题,实现哪些业务功能,满足什么样的性能要求。
- 如何作需求分析?
- 首先确认用户需求
- 用户需求即用户的原始需求。
- 通过用户访谈、问卷调查、开会讨论、查阅资料等调研手段梳理用户的原始需求。
- 产品人员根据用户需求会绘制界面原型,通过界面原型再和用户确认需求。
- 确认关键问题
- 用户的原始需求可能 是含糊不清的,需求分析要从繁杂的问题中梳理出关键问题。
- 比如:教学机构的老师想要将课程发布到网上,这是原始需求,根据这个用户需求我们需要进行扩展分析,扩展出几下几点:
- 课程发布需要发布哪些信息
- 如果发布了不良信息怎么办?
- 课程发布后用户怎么查看课程?
- 根据以上几点继续延伸性分析:
- 课程发布需要发布哪些信息
- 课程名称、课程介绍、课程价格、课程图片、师资等信息
- 继续延伸分析:
- 这么多课程信息进行归类,方便用户编辑,分为课程基本信息、课程营销信息、课程师资等信息。
- 按照这样的思路对用户需求逐项分析,梳理出若干问题,再从中找到关键问题。比如:上边对课程信息分类后,哪些是关键信息,课程名称、课程图片、课程介绍等基本信息为关键信息,所以发布课程的第一步要编写课程基本信息。
- 找到了关键问题,下一步就可以进行数据建模,创建课程基本信息表,并设计其中的字段。
- 课程发布需要发布哪些信息
- 梳理业务流程
- 业务流程是由一个或多个用户参与完成为了完成一个目标所进行的一系列的业务操作,不论是整个系统还是一个模块通常首先分析核心的业务流程,比如:内容管理模块的核心业务流程是课程发布,本项目的核心业务流程是学生选课学习流程。
- 数据建模
- 数据建模要根据分析的关键问题将其相关的信息全部建模。比如:根据发布课程的用户需求,可创建课程基本信息表、课程营销信息表、课程师资表、课程发布记录表、课程审核记录表等。
- 编写需求规格说明书
- 需求分析阶段的成果物是需求分析规格说明书,针对每一个问题编写需求用例,需求用例包括:功能名称、功能描述、参与者、基本事件流程、可选事件流、数据描述、前置条件、后置条件等内容。
1.2模块介绍
- 内容管理这个词存在于很多软件系统,什么是内容管理?
- 通过百度百科查询其意思:
- 内容管理系统(content management system,CMS),是一种位于WEB前端(Web 服务器)和后端办公系统或流程(内容创作、编辑)之间的软件系统。内容的创作人员、编辑人员、发布人员使用内容管理系统来提交、修改、审批、发布内容。这里指的“内容”可能包括文件、表格、图片、数据库中的数据甚至视频等一切你想要发布到Internet、Intranet以及Extranet网站的信息。
- 本项目作为一个大型的在线教育平台,其内容管理模块主要对课程及相关内容进行管理,包括:课程的基本信息、课程图片、课程师资信息、课程的授课计划、课程视频、课程文档等内容的管理。
1.3业务流程
- 内容管理的业务由教学机构人员和平台的运营人员共同完成。
- 教学机构人员的业务流程如下:
- 登录教学机构。
- 维护课程信息,添加一门课程需要编辑课程的基本信息、上传课程图片、课程营销信息、课程计划、上传课程视频、课程师资信息等内容。
- 课程信息编辑完成,通过课程预览确认无误后提交课程审核。
- 待运营人员对课程审核通过后方可进行课程发布。
- 运营人员的业务流程如下:
- 查询待审核的课程信息。
- 审核课程信息。
- 提交审核结果。
- 下图是课程编辑与发布的整体流程。
1.4界面原型
- 产品工程师根据用户需求制作产品界面原型,开发工程师除了根据用户需求进行需求分析以外,还会根据界面原型上的元素信息进行需求分析。
内容管理模块的界面原型如下
- 课程列表 :
- 点击添加课程:
- 选择录播课程 ,填写课程信息:
- 填写课程计划信息:
- 填写课程师资信息:
- 课程填写完毕进行课程发布:
- 当审核状态为通过时发布按钮点亮,点击发布按钮 即可对该课程进行发布。
1.5数据原型
- 课程列表 :
- 内容管理模块的基础表涉及9张,如下:
2.创建工程模块
2.1工程模块结构
- 结合项目父工程、项目基础工程后,如下图:
- xuecheng-plus-content-api:接口工程,为前端提供接口。
- xuecheng-plus-content-service: 业务工程,为接口工程提供业务支撑。
- xuecheng-plus-content-model: 数据模型工程,存储数据模型类、数据传输类型等。
- xuecheng-plus-content:内容管理模块工程,负责聚合xuecheng-plus-content-api、xuecheng-plus-content-service、xuecheng-plus-content-model。
2.2创建工程
创建xuecheng-plus-content
- 作用聚合三个子工程
- pom文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>xuecheng-plus-parent</artifactId>
<groupId>com.xuecheng</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../xuecheng-plus-parent</relativePath>
</parent>
<artifactId>xuecheng-plus-content</artifactId>
<name>xuecheng-plus-content</name>
<description>xuecheng-plus-content</description>
<packaging>pom</packaging>
<modules>
<module>xuecheng-plus-content-api</module>
<module>xuecheng-plus-content-model</module>
<module>xuecheng-plus-content-service</module>
</modules>
</project>
创建xuecheng-plus-content-model
- pom文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>xuecheng-plus-content</artifactId>
<groupId>com.xuecheng</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>xuecheng-plus-content-model</artifactId>
<dependencies>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xuecheng-plus-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
创建xuecheng-plus-content-service
- pom文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>xuecheng-plus-content</artifactId>
<groupId>com.xuecheng</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>xuecheng-plus-content-service</artifactId>
<dependencies>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xuecheng-plus-content-model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
创建xuecheng-plus-content-api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>xuecheng-plus-content</artifactId>
<groupId>com.xuecheng</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>xuecheng-plus-content-api</artifactId>
<dependencies>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xuecheng-plus-content-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project> - 目前结构
2.3前后端联调
- 这里进行前后联调的目的是体会前后端联调的流程,测试的功能为课程查询功能。
- 启动前端工程,再启内容管理服务端。
- 修改服务端地址
- 前端默认连接的是项目的网关地址,由于现在网关工程还没有创建,这里需要更改前端工程的参数配置文件 ,修改网关地址为内容管理服务的地址。
- npm install 下载依赖
- npm run serve 启动前端工程
- http://localhost:8601/#/organization/course-list 访问
3.课程查询
3.0生成po类
- 导入xuecheng-plus-generator类
- 添加为maven
- 找到ContentCodeGenerator类,修改数据库及要生成的表,执行方法
- 把生成的generator里面类拷贝到modle的po里面
出现mp注解爆红
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!--存在mybatisplus注解添加相关注解保证不报错-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>3.1需求分析
下边从查询条件、查询列表两个方面分析数据模型
- 查询条件:
- 包括:课程名称、课程审核状态、课程发布状态
- 课程名称:可以模糊搜索
- 课程审核状态:未提交、已提交、审核通过、审核未通过
- 课程发布状态:未发布、已发布、已下线
- 因为是分页查询所以查询条件中还要包括当前页码、每页显示记录数。
- 查询结果:
- 查询结果中包括:课程id、课程名称、任务数、创建时间、是否付费、审核状态、类型,操作
- 任务数:该课程所包含的课程计划数,即课程章节数。
- 是否付费:课程包括免费、收费两种。
- 类型:录播、直播。
- 因为是分页查询所以查询结果中还要包括总记录数、当前页、每页显示记录数。
course_base表
3.2接口定义
- 接口设计
接口设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44POST /content/course/list?pageNo=2&pageSize=1
Content-Type: application/json
{
"auditStatus": "202002",
"courseName": "",
"publishStatus":""
}
###成功响应结果
{
"items": [
{
"id": 26,
"companyId": 1232141425,
"companyName": null,
"name": "spring cloud实战",
"users": "所有人",
"tags": null,
"mt": "1-3",
"mtName": null,
"st": "1-3-2",
"stName": null,
"grade": "200003",
"teachmode": "201001",
"description": "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战Spring Boot 4.注册中心eureka。",
"pic": "https://cdn.educba.com/academy/wp-content/uploads/2018/08/Spring-BOOT-Interview-questions.jpg",
"createDate": "2019-09-04 09:56:19",
"changeDate": "2021-12-26 22:10:38",
"createPeople": null,
"changePeople": null,
"auditStatus": "202002",
"auditMind": null,
"auditNums": 0,
"auditDate": null,
"auditPeople": null,
"status": 1,
"coursePubId": null,
"coursePubDate": null
}
],
"counts": 23,
"page": 2,
"pageSize": 1
} - 定义模型类
接收分页查询模型类 --由于多个地方用到所有定义在base里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.xuecheng.base.model;
//分页查询参数
import lombok.Data;
import lombok.ToString;
public class PageParams {
//当前页码
private Long pageNo = 1L;
//每页记录数默认值
private Long pageSize =10L;
public PageParams(){
}
public PageParams(long pageNo,long pageSize){
this.pageNo = pageNo;
this.pageSize = pageSize;
}
}接收课程查询特有的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.xuecheng.content.model.dto;
import lombok.Data;
import lombok.ToString;
//课程查询条件模型类
public class QueryCourseParamsDto {
//审核状态
private String auditStatus;
//课程名称
private String courseName;
//发布状态
private String publishStatus;
}创建接收响应体参数 --针对分页查询结果经过分析也存在固定的数据和格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package com.xuecheng.base.model;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.util.List;
//分页查询结果模型类
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;
}
}
- 导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<!--cloud的基础环境包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<!-- Spring Boot 的 Spring Web MVC 集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 排除 Spring Boot 依赖的日志包冲突 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Boot 集成 log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Spring Boot 集成 swagger -->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.0.RELEASE</version>
</dependency> - 创建CourseBaseInfoController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.xuecheng.content.api;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.content.model.dto.QueryCourseParamsDto;
import com.xuecheng.content.model.po.CourseBase;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
public class CourseBaseInfoController {
public PageResult<CourseBase> list(PageParams pageParams,{ QueryCourseParamsDto queryCourseParamsDto)
return null;
}
} - 创建启动类
1
2
3
4
5
6
7
8
9
10package com.xuecheng;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
public class ContentApplication {
public static void main(String[] args) {
SpringApplication.run(ContentApplication.class,args);
}
} - 创建配置类
- 把日志文件复制到resource
- 创建bootstrap.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16server:
servlet:
context-path: /content
port: 63040
#微服务配置
spring:
application:
name: content-api
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.101.65:3306/xcplus_content?serverTimezone=UTC&userUnicode=true&useSSL=false&
username: root
password: mysql
# 日志文件配置路径
logging:
config: classpath:log4j2-dev.xml3.3开发持久层
- 导入上回生成的mapper文件
- 导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis plus的依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<!-- Spring Boot 集成 Junit -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 排除 Spring Boot 依赖的日志包冲突 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Spring Boot 集成 log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency> - 把api模块的启动类和recourse拷贝到service模块中
- 修改yaml文件 —去掉端口
- 配置分页插件的配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.xuecheng.content.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class MybatisPlusConfig {
/**
* 定义分页拦截器
*/
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
} 分页插件的原理
- 首先分页参数放到ThreadLocal中,拦截执行的sql,根据数据库类型添加对应的分页语句重写sql,例如:(select from table where a) 转换为 (select count(※) from table where a)和(select from table where a limit ,)
计算出了total总条数、pageNum当前第几页、pageSize每页大小和当前页的数据,是否为首页,是否为尾页,总页数等。
- 首先分页参数放到ThreadLocal中,拦截执行的sql,根据数据库类型添加对应的分页语句重写sql,例如:(select from table where a) 转换为 (select count(※) from table where a)和(select from table where a limit ,)
创建测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63package com.xuecheng.content;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.content.mapper.CourseBaseMapper;
import com.xuecheng.content.model.dto.QueryCourseParamsDto;
import com.xuecheng.content.model.po.CourseBase;
import org.apache.commons.lang.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
public class CourseBaseMapperTests {
CourseBaseMapper courseBaseMapper;
public void testCourseBaseMapper(){
CourseBase courseBase = courseBaseMapper.selectById(18);
Assertions.assertNotNull(courseBase);
//详细进行分页查询的单元测试
//查询条件
QueryCourseParamsDto courseParamsDto = new QueryCourseParamsDto();
courseParamsDto.setCourseName("java");//课程名称查询条件
//拼装查询条件
LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
//根据名称模糊查询,在sql中拼接 course_base.name like '%值%'
queryWrapper.like(StringUtils.isNotEmpty(courseParamsDto.getCourseName())
,CourseBase::getName
,courseParamsDto.getCourseName());
//根据课程审核状态查询,在sql中拼接 course_base.audit_status = ? queryWrapper.eq(StringUtils.isNotEmpty(courseParamsDto.getAuditStatus())
,CourseBase::getAuditStatus
,courseParamsDto.getAuditStatus());
//分页参数对象
PageParams pageParams = new PageParams();
pageParams.setPageNo(1L);
pageParams.setPageSize(2L);
//创建Page分页参数对象 ,参数:页码和每页记录数
Page<CourseBase> Page = new Page<>(pageParams.getPageNo(),pageParams.getPageSize());
//开始进行分页查询
Page<CourseBase> pageResult = courseBaseMapper.selectPage(Page, queryWrapper);
//数据列表
List<CourseBase> items = pageResult.getRecords();
//总记录数
long total = pageResult.getTotal();
//List<T> items , Long counts , Long page , Long ,pageSize
PageResult<CourseBase> courseBasePageResult =
new PageResult<CourseBase>(items,total,pageParams.getPageNo()
,pageParams.getPageSize());
System.out.println(courseBasePageResult);
}
}3.4开发业务层
数字词典
- 把数字定义为专门的汉字,方便以后修改
创建CourseBaseInfoService
1
2
3
4public interface CourseBaseInfoService {
//课程分页查询
public PageResult<CourseBase> queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto);
}CourseBaseInfoServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56package com.xuecheng.content.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.content.mapper.CourseBaseMapper;
import com.xuecheng.content.model.dto.QueryCourseParamsDto;
import com.xuecheng.content.model.po.CourseBase;
import com.xuecheng.content.service.CourseBaseInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
public class CourseBaseInfoServiceImpl implements CourseBaseInfoService {
CourseBaseMapper courseBaseMapper;
//课程分页查询和条件查询
public PageResult<CourseBase> queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto courseParamsDto) {
//拼装查询条件
LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
//根据名称模糊查询,在sql中拼接 course_base.name like '%值%'
queryWrapper.like(StringUtils.isNotEmpty(courseParamsDto.getCourseName())
,CourseBase::getName
,courseParamsDto.getCourseName());
//根据课程审核状态查询,在sql中拼接 course_base.audit_status = ? queryWrapper.eq(StringUtils.isNotEmpty(courseParamsDto.getAuditStatus())
,CourseBase::getAuditStatus
,courseParamsDto.getAuditStatus());
//根据课程状态查询
queryWrapper.eq(StringUtils.isNotEmpty(courseParamsDto.getPublishStatus())
,CourseBase::getStatus
,courseParamsDto.getPublishStatus());
//创建Page分页参数对象 ,参数:页码和每页记录数
Page<CourseBase> Page = new Page<>(pageParams.getPageNo(),pageParams.getPageSize());
//开始进行分页查询
Page<CourseBase> pageResult = courseBaseMapper.selectPage(Page, queryWrapper);
//数据列表
List<CourseBase> items = pageResult.getRecords();
//总记录数
long total = pageResult.getTotal();
//List<T> items , Long counts , Long page , Long ,pageSize
PageResult<CourseBase> courseBasePageResult =
new PageResult<CourseBase>(items,total,pageParams.getPageNo()
,pageParams.getPageSize());
System.out.println(courseBasePageResult);
return courseBasePageResult;
}
}创建CourseBaseInfoServiceTests测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CourseBaseInfoServiceTests {
CourseBaseInfoService courseBaseInfoService;
public void testCourseBaseInfoService() {
//查询条件
QueryCourseParamsDto courseParamsDto = new QueryCourseParamsDto();
courseParamsDto.setCourseName("java");//课程名称查询条件
//分页参数对象
PageParams pageParams = new PageParams();
pageParams.setPageNo(1L);
pageParams.setPageSize(2L);
PageResult<CourseBase> courseBasePageResult = courseBaseInfoService.queryCourseBaseList(pageParams, courseParamsDto);
System.out.println(courseBasePageResult);
}
}完善接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CourseBaseInfoController {
CourseBaseInfoService courseBaseInfoService;
public PageResult<CourseBase> list(PageParams pageParams,
{ QueryCourseParamsDto queryCourseParamsDto)
PageResult<CourseBase> courseBasePageResult =
courseBaseInfoService.queryCourseBaseList(pageParams,queryCourseParamsDto);
return courseBasePageResult;
}
}Httpclient测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23{
"dev": {
"access_token": "",
"gateway_host": "localhost:63010",
"content_host": "localhost:63040",
"system_host": "localhost:63110",
"media_host": "localhost:63050",
"search_host": "localhost:63080",
"auth_host": "localhost:63070",
"checkcode_host": "localhost:63075",
"learning_host": "localhost:63020"
}
}
### 查询课程信息
POST {{gateway_host}}/content/course/list?pageNo=1&pageSize=2
Content-Type: application/json
{
"auditStatus": "202004",
"courseName": "java",
"publishStatus": "203001"
}- 把数字定义为专门的汉字,方便以后修改
- 前后端联调
- 通过前端查看的请求记录
1
2
3
4请求网址: http://localhost:8601/api/content/course-category/tree-nodes
请求方法: GET
状态代码: 404 Not Found
远程地址: 127.0.0.1:8601 - 接口返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204[
{
"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"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-3",
"isLeaf" : null,
"isShow" : null,
"label" : "jQuery",
"name" : "jQuery",
"orderby" : 3,
"parentid" : "1-1"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-4",
"isLeaf" : null,
"isShow" : null,
"label" : "ExtJS",
"name" : "ExtJS",
"orderby" : 4,
"parentid" : "1-1"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-5",
"isLeaf" : null,
"isShow" : null,
"label" : "AngularJS",
"name" : "AngularJS",
"orderby" : 5,
"parentid" : "1-1"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-6",
"isLeaf" : null,
"isShow" : null,
"label" : "ReactJS",
"name" : "ReactJS",
"orderby" : 6,
"parentid" : "1-1"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-7",
"isLeaf" : null,
"isShow" : null,
"label" : "Bootstrap",
"name" : "Bootstrap",
"orderby" : 7,
"parentid" : "1-1"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-8",
"isLeaf" : null,
"isShow" : null,
"label" : "Node.js",
"name" : "Node.js",
"orderby" : 8,
"parentid" : "1-1"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-9",
"isLeaf" : null,
"isShow" : null,
"label" : "Vue",
"name" : "Vue",
"orderby" : 9,
"parentid" : "1-1"
},
{
"childrenTreeNodes" : null,
"id" : "1-1-10",
"isLeaf" : null,
"isShow" : null,
"label" : "其它",
"name" : "其它",
"orderby" : 10,
"parentid" : "1-1"
}
],
"id" : "1-1",
"isLeaf" : null,
"isShow" : null,
"label" : "前端开发",
"name" : "前端开发",
"orderby" : 1,
"parentid" : "1"
},
{
"childrenTreeNodes" : [
{
"childrenTreeNodes" : null,
"id" : "1-2-1",
"isLeaf" : null,
"isShow" : null,
"label" : "微信开发",
"name" : "微信开发",
"orderby" : 1,
"parentid" : "1-2"
},
{
"childrenTreeNodes" : null,
"id" : "1-2-2",
"isLeaf" : null,
"isShow" : null,
"label" : "iOS",
"name" : "iOS",
"orderby" : 2,
"parentid" : "1-2"
},
{
"childrenTreeNodes" : null,
"id" : "1-2-3",
"isLeaf" : null,
"isShow" : null,
"label" : "手游开发",
"name" : "手游开发",
"orderby" : 3,
"parentid" : "1-2"
},
{
"childrenTreeNodes" : null,
"id" : "1-2-4",
"isLeaf" : null,
"isShow" : null,
"label" : "Swift",
"name" : "Swift",
"orderby" : 4,
"parentid" : "1-2"
},
{
"childrenTreeNodes" : null,
"id" : "1-2-5",
"isLeaf" : null,
"isShow" : null,
"label" : "Android",
"name" : "Android",
"orderby" : 5,
"parentid" : "1-2"
},
{
"childrenTreeNodes" : null,
"id" : "1-2-6",
"isLeaf" : null,
"isShow" : null,
"label" : "ReactNative",
"name" : "ReactNative",
"orderby" : 6,
"parentid" : "1-2"
},
{
"childrenTreeNodes" : null,
"id" : "1-2-7",
"isLeaf" : null,
"isShow" : null,
"label" : "Cordova",
"name" : "Cordova",
"orderby" : 7,
"parentid" : "1-2"
},
{
"childrenTreeNodes" : null,
"id" : "1-2-8",
"isLeaf" : null,
"isShow" : null,
"label" : "其它",
"name" : "其它",
"orderby" : 8,
"parentid" : "1-2"
}
],
"id" : "1-2",
"isLeaf" : null,
"isShow" : null,
"label" : "移动开发",
"name" : "移动开发",
"orderby" : 2,
"parentid" : "1"
}
] - 根据以上接收数据构建出模型类
1
2
3
4
5
6
7
8
9package com.xuecheng.content.model.dto;
import com.xuecheng.content.model.po.CourseCategory;
import lombok.Data;
import java.util.List;
public class CourseCategoryTreeDto extends CourseCategory implements java.io.Serializable{
List<CourseCategoryTreeDto> childrenTreeNodes;
}4.2接口定义
- 创建CourseCategoryController类
1
2
3
4
5
6
7
8
9
10
11
12
13package com.xuecheng.content.api;
import com.xuecheng.content.model.dto.CourseCategoryTreeDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
public class CourseCategoryController {
public List<CourseCategoryTreeDto> queryTreeNodes() {
return null;
}
}4.3接口实现
- 树型表的查询方法
- 如果树的层级固定可以使用表的自链接去查询—-(2层)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18select
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 one
inner join course_category two on one.id = two.parentid
where one.parentid = 1
and one.is_show = 1
and two.is_show = 1
order by one.orderby,
two.orderby
- 如果树的层级不确定,此时可以使用MySQL递归实现
1
2
3
4
5
6with recursive t1 as (
select * from course_category p where id= '1' //初始数据
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
- 如果树的层级固定可以使用表的自链接去查询—-(2层)
- Mapper层
- CourseCategoryMapper接口
1
2
3
4
5
6
7
8
9
10
11package com.xuecheng.content.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xuecheng.content.model.dto.CourseCategoryTreeDto;
import com.xuecheng.content.model.po.CourseCategory;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
public interface CourseCategoryMapper extends BaseMapper<CourseCategory> {
//使用递归查询分类
public List<CourseCategoryTreeDto> selectTreeNodes(String id);
} - CourseCategoryMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<mapper namespace="com.xuecheng.content.mapper.CourseCategoryMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.xuecheng.content.model.po.CourseCategory">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="label" property="label" />
<result column="parentid" property="parentid" />
<result column="is_show" property="isShow" />
<result column="orderby" property="orderby" />
<result column="is_leaf" property="isLeaf" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, label, parentid, is_show, orderby, is_leaf </sql>
<select id="selectTreeNodes" parameterType="string" resultType="com.xuecheng.content.model.dto.CourseCategoryTreeDto">
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>
</mapper> - CourseCategoryMapperTests测试类
1
2
3
4
5
6
7
8
9
10
11
12
public class CourseCategoryMapperTests {
CourseCategoryMapper courseCategoryMapper;
public void testCourseCategoryMapper(){
List<CourseCategoryTreeDto> courseCategoryTreeDtos = courseCategoryMapper.selectTreeNodes("1");
System.out.println(courseCategoryTreeDtos);
}
}
- CourseCategoryMapper接口
- Server层
- CourseCategoryService接口
1
2
3
4public interface CourseCategoryService {
//课程分类树型结构查询
public List<CourseCategoryTreeDto> queryTreeNodes(String id);
} - CourseCategoryServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class CourseCategoryServiceImpl implements CourseCategoryService {
CourseCategoryMapper courseCategoryMapper;
public List<CourseCategoryTreeDto> queryTreeNodes(String id) {
//调用mapper递归查询出分类信息
List<CourseCategoryTreeDto> courseCategoryTreeDtos = courseCategoryMapper.selectTreeNodes(id);
//找到每个节点的子节点,最终封装成List<CourseCategoryTreeDto>
//先将list转成map,key就是结点的id,value就是CourseCategoryTreeDto对象,目的就是为了方便从map获取结点,filter(item->!id.equals(item.getId()))把根结点排除
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);
}
});
return courseCategoryList;
}
} - 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CourseCategoryServiceTests {
CourseCategoryService categoryService;
public void CourseCategoryServiceTests() {
List<CourseCategoryTreeDto> courseCategoryTreeDtos = categoryService.queryTreeNodes("1");
System.out.println(courseCategoryTreeDtos);
}
}
api-text
### 查询课程分类
GET {{content_host}}/content/course-category/tree-nodes
- CourseCategoryService接口
- Controller层
1
2
3
4
5
6
7
8
9
public class CourseCategoryController {
CourseCategoryService categoryService;
public List<CourseCategoryTreeDto> queryTreeNodes() {
return categoryService.queryTreeNodes("1");
}
}
5.新增课程
5.1需求分析
课程信息表和营销表
5.2接口定义
1 | ### 创建课程 |
- 创建DTO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68package com.xuecheng.content.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
//@description 添加课程dto
public class AddCourseDto {
private String name;
private String users;
private String tags;
private String mt;
private String st;
private String grade;
private String teachmode;
private String description;
private String pic;
private String charge;
private Float price;
private Float originalPrice;
private String qq;
private String wechat;
private String phone;
private Integer validDays;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package com.xuecheng.content.model.dto;
import com.xuecheng.content.model.po.CourseBase;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
//@description 课程基本信息dto
public class CourseBaseInfoDto extends CourseBase {
//收费规则,对应数据字典
private String charge;
//价格
private Float price;
//原价
private Float originalPrice;
//咨询qq
private String qq;
//微信
private String wechat;
//电话
private String phone;
//有效期天数
private Integer validDays;
//大分类名称
private String mtName;
//小分类名称
private String stName;
} - 定义接口如下:
1
2
3
4
5
public CourseBaseInfoDto createCourseBase({ AddCourseDto addCourseDto)
return null;
}
5.3接口实现
- 定义service接口,这里额外需要一个机构id,因为我们的业务是教学机构登录账号,然后添加该教学机构的下属课程
1
2
3
4
5
6
7/**
* 新增课程基本信息
* @param companyId 教学机构id
* @param addCourseDto 课程基本信息
* @return
*/
CourseBaseInfoDto createCourseBase(Long companyId, AddCourseDto addCourseDto); - 编写service接口实现类
- 首先我们需要对请求参数做合法性校验,判断一下用户是否输入了必填项,还有一些项的默认值
- 然后对请求参数进行封装,调用mapper进行数据持久化
- 组装返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
CourseBaseMapper courseBaseMapper;
CourseMarketMapper courseMarketMapper;
CourseCategoryMapper courseCategoryMapper;
public CourseBaseInfoDto createCourseBase(Long companyId, AddCourseDto addCourseDto) {
// 1. 合法性校验
if (StringUtils.isBlank(addCourseDto.getName())) {
throw new RuntimeException("课程名称为空");
}
if (StringUtils.isBlank(addCourseDto.getMt())) {
throw new RuntimeException("课程分类为空");
}
if (StringUtils.isBlank(addCourseDto.getSt())) {
throw new RuntimeException("课程分类为空");
}
if (StringUtils.isBlank(addCourseDto.getGrade())) {
throw new RuntimeException("课程等级为空");
}
if (StringUtils.isBlank(addCourseDto.getTeachmode())) {
throw new RuntimeException("教育模式为空");
}
if (StringUtils.isBlank(addCourseDto.getUsers())) {
throw new RuntimeException("适应人群为空");
}
if (StringUtils.isBlank(addCourseDto.getCharge())) {
throw new RuntimeException("收费规则为空");
}
// 2. 封装请求参数
// 封装课程基本信息
CourseBase courseBase = new CourseBase();
BeanUtils.copyProperties(addCourseDto, courseBase);
// 2.1 设置默认审核状态(去数据字典表中查询状态码)
courseBase.setAuditStatus("202002");
// 2.2 设置默认发布状态
courseBase.setStatus("203001");
// 2.3 设置机构id
courseBase.setCompanyId(companyId);
// 2.4 设置添加时间
courseBase.setCreateDate(LocalDateTime.now());
// 2.5 插入课程基本信息表
int baseInsert = courseBaseMapper.insert(courseBase);
Long courseId = courseBase.getId();
// 封装课程营销信息
CourseMarket courseMarket = new CourseMarket();
BeanUtils.copyProperties(addCourseDto, courseMarket);
courseMarket.setId(courseId);
// 2.6 判断收费规则,若课程收费,则价格必须大于0
String charge = courseMarket.getCharge();
if ("201001".equals(charge)) {
Float price = addCourseDto.getPrice();
if (price == null || price.floatValue() <= 0) {
throw new RuntimeException("课程设置了收费,价格不能为空,且必须大于0");
}
}
// 2.7 插入课程营销信息表
int marketInsert = courseMarketMapper.insert(courseMarket);
if (baseInsert <= 0 || marketInsert <= 0) {
throw new RuntimeException("新增课程基本信息失败");
}
// 3. 返回添加的课程信息
return getCourseBaseInfo(courseId);
}
private CourseBaseInfoDto getCourseBaseInfo(Long courseId) {
CourseBaseInfoDto courseBaseInfoDto = new CourseBaseInfoDto();
// 1. 根据课程id查询课程基本信息
CourseBase courseBase = courseBaseMapper.selectById(courseId);
if (courseBase == null)
return null;
// 1.1 拷贝属性
BeanUtils.copyProperties(courseBase, courseBaseInfoDto);
// 2. 根据课程id查询课程营销信息
CourseMarket courseMarket = courseMarketMapper.selectById(courseId);
// 2.1 拷贝属性
if (courseMarket != null)
BeanUtils.copyProperties(courseMarket, courseBaseInfoDto);
// 3. 查询课程分类名称,并设置属性
// 3.1 根据小分类id查询课程分类对象
CourseCategory courseCategoryBySt = courseCategoryMapper.selectById(courseBase.getSt());
// 3.2 设置课程的小分类名称
courseBaseInfoDto.setStName(courseCategoryBySt.getName());
// 3.3 根据大分类id查询课程分类对象
CourseCategory courseCategoryByMt = courseCategoryMapper.selectById(courseBase.getMt());
// 3.4 设置课程大分类名称
courseBaseInfoDto.setMtName(courseCategoryByMt.getName());
return courseBaseInfoDto;
}
- 完善controller
1
2
3
4
5
6
7
public CourseBaseInfoDto createCourseBase( { AddCourseDto addCourseDto)
// 机构id,暂时硬编码模拟假数据
Long companyId = 1232141425L;;
return courseBaseInfoService.createCourseBase(companyId, addCourseDto);
} - 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22### 新增课程
POST {{content_host}}/content/course
Content-Type: application/json
{
"charge": "201001",
"price": 10,
"originalPrice":100,
"qq": "22333",
"wechat": "223344",
"phone": "13333333",
"validDays": 365,
"mt": "1-1",
"st": "1-1-1",
"name": "java网络编程高级",
"pic": "asd",
"teachmode": "200002",
"users": "初级人员",
"tags": "aaa",
"grade": "204001",
"description": "java网络编程高级"
}
6.自定义异常处理
6.1设置通用信息
1 | package com.xuecheng.base.exception; |
6.2自定义异常类型
1 | package com.xuecheng.base.exception; |
6.3响应用户的统一类型
1 | package com.xuecheng.base.exception; |
6.4全局异常处理
1 | package com.xuecheng.base.exception; |
7.JSR303校验
- Contoller中校验请求参数的合法性,包括:必填项校验,数据格式校验,比如:是否是符合一定的日期格式,等。
- Service中要校验的是业务规则相关的内容,比如:课程已经审核通过所以提交失败。
- Service中根据业务规则去校验不方便写成通用代码,Controller中则可以将校验的代码写成通用代码。
- 依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>在实体类添加注解
- 在参数前面添加@Validated注解
- 在全局异常处理器中捕获异常,解析出异常信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public RestErrorResponse exception(Exception e){
//记录异常
log.error("系统异常{}",e.getMessage(),e);
//解析出异常信息
RestErrorResponse restErrorResponse = new RestErrorResponse(CommonError.UNKOWN_ERROR.getErrMessage());
return restErrorResponse;
}
public RestErrorResponse MethodArgumentNotValidException(MethodArgumentNotValidException e){
//存储错误信息
List<String> errors = new ArrayList<>();
BindingResult bindingResult = e.getBindingResult();
bindingResult.getFieldErrors().stream().forEach(item->{
errors.add(item.getDefaultMessage());
});
//将list中的错误信息拼接起来
String errMessage = StringUtils.join(errors,",");
//记录异常
log.error("系统异常{}",e.getMessage(),errMessage);
//解析出异常信息
RestErrorResponse restErrorResponse = new RestErrorResponse(errMessage);
return restErrorResponse;
} - 当有多个接口同时使用一个模型类会出现冲突 —分组校验
- 定义不同分组
1
2
3
4
5public class ValidationGroups {
public interface Inster{};
public interface Update{};
public interface Delete{};
} - 模型类
1
2
3
4
5
// @NotEmpty(message = "课程名称不能为空")
private String name; - 使用
1
2
3
4
5
6
7
public CourseBaseInfoDto createCourseBase({ AddCourseDto addCourseDto)
//机构id,由于认证系统没有上线暂时硬编码
Long companyId = 1L;
return courseBaseInfoService.createCourseBase(companyId,addCourseDto);
}8.修改课程
8.1需求分析
- 点击课程列表查询
- 点击编辑,此时应该看到对应数据的回显数据
- 点击保存,完成修改
- 修改课程还是涉及到之前的课程基本信息表和课程营销信息表
- 但是修改课程提交的数据比新增课程多了一项课程id,因为修改课程需要对某个具体的课程进行修改,新增课程的课程id是系统生成的
- 修改完成保存数据时,还需要更新课程基本信息表中的修改时间
课程信息表和营销表
8.2接口定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30###查询课程信息
GET /content/course/40
Content-Type: application/json
#响应结果
#{
# "id": 40,
# "companyId": 1232141425,
# "companyName": null,
# "name": "SpringBoot核心",
# "users": "Spring Boot初学者",
# "tags": "Spring项目的快速构建",
# "mt": "1-3",
# "mtName": null,
# "st": "1-3-2",
# "stName": null,
# "grade": "200003",
# "teachmode": "201001",
# "description": "课程系统性地深度探讨 Spring Boot 核心特性,引导小伙伴对 Java 规范的重视,启发对技术原理性的思考,掌握排查问题的技能,以及学习阅读源码的方法和技巧,全面提升研发能力,进军架构师队伍。",
# "pic": "https://cdn.educba.com/academy/wp-content/uploads/2018/08/Spring-BOOT-Interview-questions.jpg",
# "createDate": "2019-09-10 16:05:39",
# "changeDate": "2022-09-09 07:27:48",
# "createPeople": null,
# "changePeople": null,
# "auditStatus": "202004",
# "status": "203001",
# "coursePubId": 21,
# "coursePubDate": null,
# "charge": "201001",
# "price": 0.01
#}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47### 修改课程
PUT /content/course
Content-Type: application/json
{
"id": 40,
"companyName": null,
"name": "SpringBoot核心",
"users": "Spring Boot初学者",
"tags": "Spring项目的快速构建",
"mt": "1-3",
"st": "1-3-2",
"grade": "200003",
"teachmode": "201001",
"description": "课程系统性地深度探讨 Spring Boot 核心特性,引导小伙伴对 Java 规范的重视,启发对技术原理性的思考,掌握排查问题的技能,以及学习阅读源码的方法和技巧,全面提升研发能力,进军架构师队伍。",
"pic": "https://cdn.educba.com/academy/wp-content/uploads/2018/08/Spring-BOOT-Interview-questions.jpg",
"charge": "201001",
"price": 0.01
}
###修改成功响应结果如下
#{
# "id": 40,
# "companyId": 1232141425,
# "companyName": null,
# "name": "SpringBoot核心",
# "users": "Spring Boot初学者",
# "tags": "Spring项目的快速构建",
# "mt": "1-3",
# "mtName": null,
# "st": "1-3-2",
# "stName": null,
# "grade": "200003",
# "teachmode": "201001",
# "description": "课程系统性地深度探讨 Spring Boot 核心特性,引导小伙伴对 Java 规范的重视,启发对技术原理性的思考,掌握排查问题的技能,以及学习阅读源码的方法和技巧,全面提升研发能力,进军架构师队伍。",
# "pic": "https://cdn.educba.com/academy/wp-content/uploads/2018/08/Spring-BOOT-Interview-questions.jpg",
# "createDate": "2019-09-10 16:05:39",
# "changeDate": "2022-09-09 07:27:48",
# "createPeople": null,
# "changePeople": null,
# "auditStatus": "202004",
# "status": "203001",
# "coursePubId": 21,
# "coursePubDate": null,
# "charge": "201001",
# "price": 0.01
#}8.3接口实现
查询接口
- 由于在前面的新增课程中已经实现了查询方法,所有只需要在service中暴露接口即可
- CourseBaseInfoController
1
2
3
4
5
6
public CourseBaseInfoDto getCourseBaseInfo({ Long courseId)
CourseBaseInfoDto courseBaseInfo = courseBaseInfoService.getCourseBaseInfo(courseId);
return courseBaseInfo;
} - 暴露接口
1
2//根据id查询课程信息
public CourseBaseInfoDto getCourseBaseInfo(Long couseId); - 前面实现的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//查询课程信息
public CourseBaseInfoDto getCourseBaseInfo(Long couseId){
//从课程基本信息表查询
CourseBase courseBase = courseBaseMapper.selectById(couseId);
if (courseBase == null){
return null;
}
//从课程营销表查询
CourseMarket courseMarket = courseMarketMapper.selectById(couseId);
//组装在一起
CourseBaseInfoDto courseBaseInfoDto = new CourseBaseInfoDto();
BeanUtils.copyProperties(courseBase,courseBaseInfoDto);
if (courseMarket!=null){
BeanUtils.copyProperties(courseMarket,courseBaseInfoDto);
}
//通过courseCategoryMapper查询分类信息,将分类名称放在对象中
//todo:课程分类的名称设置到courseBaseInfoDto
return courseBaseInfoDto;
}
修改接口
- 创建一个Dto
1
2
3
4
5
6
7
8package com.xuecheng.content.model.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
public class EditCourseDto extends AddCourseDto{
private Long id;
} - CourseBaseInfoController
1
2
3
4
5
6
7
8
public CourseBaseInfoDto modifyCourseBase({ EditCourseDto editCourseDto)
//获取到用户所属机构的id
Long companyId = 1232141425L;
CourseBaseInfoDto courseBaseInfoDto = courseBaseInfoService.updateCourseBase(companyId,editCourseDto);
return courseBaseInfoDto;
} - CourseBaseInfoService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//修改课程
public CourseBaseInfoDto updateCourseBase(Long companyId, EditCourseDto editCourseDto);
//修改课程
public CourseBaseInfoDto updateCourseBase(Long companyId, EditCourseDto editCourseDto) {
//拿到课程id
Long courseId = editCourseDto.getId();
//查询课程信息
CourseBase courseBase = courseBaseMapper.selectById(courseId);
if (courseId == null) {
XueChengPlusException.cast("课程不存在");
}
//数据合法性校验
//根据具体的业务逻辑去校验(本机构只能修改本机构的课程)
if (!companyId.equals(courseBase.getCompanyId())){
XueChengPlusException.cast("本机构只能修改本机构的课程");
}
//封装数据
BeanUtils.copyProperties(editCourseDto,courseBase);
//修改时间
courseBase.setChangeDate(LocalDateTime.now());
//更新数据库
int i = courseBaseMapper.updateById(courseBase);
if (i <= 0){
XueChengPlusException.cast("修改课程失败");
}
//更新营销信息
//todo:...
//查询课程信息
CourseBaseInfoDto courseBaseInfo = getCourseBaseInfo(courseId);
return courseBaseInfo;
}
9.查询课程计划
9.1需求分析
课程计划表teachplan
课程计划与媒资表关联表teachplan_media
媒资表teachplan_work
9.2接口定义
1 | GET /teachplan/22/tree-nodes |
- 定义数据模型
1
2
3
4
5
6
7
8
9
10
11
12package com.xuecheng.content.model.dto;
import com.xuecheng.content.model.po.Teachplan;
import com.xuecheng.content.model.po.TeachplanMedia;
import lombok.Data;
import java.util.List;
public class TeachplanDto extends Teachplan {
//与媒资管理的信息
private TeachplanMedia teachplanMedia;
//小章节list
private List<TeachplanDto> teachplanDtos;
} - TeachplanController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.xuecheng.content.api;
import com.xuecheng.content.model.dto.TeachplanDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
public class TeachplanController {
public List<TeachplanDto> getTreeNodes({ Long courseId)
return null;
}
}9.3接口实现
- 树型结构的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31select
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接口
1
2//课程计划查询
public List<TeachplanDto> selectTreeNodes(Long courseId); - 实现mapper接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71<!-- 课程分类树型结构查询映射结果 -->
<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="startTime" />
<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="teachplanDtos" 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="startTime" />
<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" parameterType="long" resultMap="treeNodeResultMap">
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> - 单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13//课程计划mapper测试
public class TeachplanMapperTests {
TeachplanMapper teachplanMapper;
public void testSelectTreeNodes(){
List<TeachplanDto> teachplanDtos = teachplanMapper.selectTreeNodes(117L);
System.out.println(teachplanDtos);
}
} - service层实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15T//根据课程id查询课程计划
public List<TeachplanDto> findTeachplanTree(Long courseId);
public class findTeachplanTreeImpl implements TeachplanService {
TeachplanMapper teachplanMapper;
public List<TeachplanDto> findTeachplanTree(Long courseId) {
List<TeachplanDto> teachplanDtos = teachplanMapper.selectTreeNodes(courseId);
return teachplanDtos;
}
}10.新增修改课程计划
10.1需求分析
- 添加大章节和小章节和修改章节信息
课程计划表
10.2接口定义
- 接口分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20### 新增课程计划--章,当grade为1时parentid为0
POST /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" : "小节名称 [点击修改]"
} - 创建响应实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.xuecheng.content.model.dto;
import lombok.Data;
//新增大章节,小章节,修改章节信息
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;
} - 创建TeachplanController
1
2
3
4
5
public void saveTeachplan( { SaveTeachplanDto teachplan)
teachplanService.saveTeachplan(teachplan);
}10.3接口实现
- TeachplanService接口
1
2//新增,修改,保存课程计划
public void saveTeachplan(SaveTeachplanDto saveTeachplanDto); - findTeachplanTreeImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//新增,修改,保存课程计划
public void saveTeachplan(SaveTeachplanDto saveTeachplanDto) {
//通过课程计划id判断是新增还是修改
Long teachplanId = saveTeachplanDto.getId();
if (teachplanId == null){
//新增
Teachplan teachplan = new Teachplan();
BeanUtils.copyProperties(saveTeachplanDto,teachplan);
//确定排序字段,找到同级节点个数,n+1 (select count(1) from teachplan where course-id=117 and parentid=268)
Long parentid = saveTeachplanDto.getParentid();
Long courseId = saveTeachplanDto.getCourseId();
LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper = queryWrapper.eq(Teachplan::getCourseId, courseId).eq(Teachplan::getParentid, parentid);
Integer count = teachplanMapper.selectCount(queryWrapper);
teachplan.setOrderby(count+1);
teachplanMapper.insert(teachplan);
}else {
//修改
Teachplan teachplan = teachplanMapper.selectById(teachplanId);
//将参数复制到teachplan
BeanUtils.copyProperties(saveTeachplanDto,teachplan);
teachplanMapper.updateById(teachplan);
}
} - 出现问题
- 出现点击添加大章节时,页面没有显示
- 原因:新增大章节时,里面没有小章节,mapper查询不出来
- 解决:把查询sql语句的one.id=two.parentid改为左连接
11.删除课程计划
11.1需求分析
- 删除第一级别的大章节时要求大章节下边没有小章节时方可删除
- 删除第二级别的小章节的同时需要将teachplan_media表关联的信息也删除
11.2接口定义
1
2
3
4
5
6
7Request URL: /content/teachplan/246
Request Method: DELETE
如果失败返回:
{"errCode":"120409","errMessage":"课程计划信息还有子级信息,无法操作"}
如果成功:状态码200,不返回信息 - TeachplanController
1
2
3
4
5
public void deleteTeachplan({ Long teachplanId)
teachplanService.deleteTeachplan(teachplanId);
}11.3接口实现
- TeachplanService接口
1
2//删除课程计划
public void deleteTeachplan(Long teachplanId); - TeachplanServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//删除课程计划
public void deleteTeachplan(Long teachplanId) {
//判断课程id是否为空
if (teachplanId == null){
XueChengPlusException.cast("课程id为空");
}
//获取章节的子级信息
LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Teachplan::getParentid, teachplanId);
Integer count = teachplanMapper.selectCount(queryWrapper);
if (count > 0){
XueChengPlusException.cast("课程计划信息还有子级信息,无法操作");
}else {//无小节,直接删除
//删除课程计划信息
teachplanMapper.deleteById(teachplanId);
//删除课程的媒资信息
LambdaQueryWrapper<TeachplanMedia> mediaQueryWrapper = new LambdaQueryWrapper<>();
mediaQueryWrapper.eq(TeachplanMedia::getTeachplanId,teachplanId);
teachplanMediaMapper.delete(mediaQueryWrapper);
}
}12.课程计划排序
12.1需求分析
12.2接口定义
- 每次移动传递两个参数:
- 移动类型: movedown和moveup
- 课程计划id
- 向下移动
1
2Request URL: http://localhost:8601/api/content/teachplan/movedown/43
Request Method: POST - 向上移动
1
2Request URL: http://localhost:8601/api/content/teachplan/moveup/43
Request Method: POST - TeachplanController
1
2
3
4
5
public void moveupTeachplan({ String moveType, Long teachplanId)
teachplanService.moveTeachplan(moveType,teachplanId);
}12.3接口实现
- TeachplanService接口
1
2//课程计划移动
public void moveTeachplan(String moveType,Long teachplanId); - TeachplanServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public void moveTeachplan(String moveType, Long teachplanId) {
Teachplan teachplan = teachplanMapper.selectById(teachplanId);
//获取层级和orderby
Integer grade = teachplan.getGrade();
Integer orderby = teachplan.getOrderby();
//章节移动是比较同一课程id下的orderby
Long courseId = teachplan.getCourseId();
//小节移动是比较同一章节下的orderby
Long parentid = teachplan.getParentid();
//判断移动方向
if ("moveup".equals(moveType)){
//章节上移,找到上一章节的orderby并交换
if (grade == 1){
LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Teachplan::getGrade,1)
.eq(Teachplan::getCourseId,courseId)
.lt(Teachplan::getOrderby,orderby)
.orderByDesc(Teachplan::getOrderby)
.last("limit 1");
Teachplan tmp = teachplanMapper.selectOne(queryWrapper);
exchangeOrderby(teachplan,tmp);
}
//小节上移
if (grade == 2){
LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Teachplan::getParentid,parentid)
.lt(Teachplan::getOrderby,orderby)
.orderByDesc(Teachplan::getOrderby)
.last("limit 1");
Teachplan tmp = teachplanMapper.selectOne(queryWrapper);
exchangeOrderby(teachplan,tmp);
}
}else if("movedown".equals(moveType)){
//章节下移
if (grade == 1){
LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Teachplan::getGrade,1)
.eq(Teachplan::getCourseId,courseId)
.gt(Teachplan::getOrderby,orderby)
.orderByAsc(Teachplan::getOrderby)
.last("limit 1");
Teachplan tmp = teachplanMapper.selectOne(queryWrapper);
exchangeOrderby(teachplan,tmp);
}
//小节下移
if (grade == 2){
LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Teachplan::getParentid,parentid)
.gt(Teachplan::getOrderby,orderby)
.orderByAsc(Teachplan::getOrderby)
.last("limit 1");
Teachplan tmp = teachplanMapper.selectOne(queryWrapper);
exchangeOrderby(teachplan,tmp);
}
}
}
private void exchangeOrderby(Teachplan teachplan,Teachplan tmp){
if (tmp == null){
XueChengPlusException.cast("已经到头了,不能在移动");
}
//交换orderby并刷新
Integer orderby = teachplan.getOrderby();
Integer tmpOrderby = tmp.getOrderby();
teachplan.setOrderby(tmpOrderby);
tmp.setOrderby(orderby);
teachplanMapper.updateById(tmp);
teachplanMapper.updateById(teachplan);
}13.师资管理
13.1需求分析
只允许向机构自己的课程中添加老师、删除老师。(前面已经完成—不允许修改机构外的课程)
机构id统一使用:1232141425L13.2接口定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14get /courseTeacher/list/75
75为课程id,请求参数为课程id
响应结果
[
{
"id":23,
"courseId":75,
"teacherName":"张老师",
"position":"讲师",
"introduction":"张老师教师简介张老师教师简介张老师教师简介张老师教师简介",
"photograph":null,"createDate":null
}
]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18post /courseTeacher
请求参数:
{
"courseId": 75,
"teacherName": "王老师",
"position": "教师职位",
"introduction": "教师简介"
}
响应结果:
{
"id":24,
"courseId":75,
"teacherName":"王老师",
"position":"教师职位",
"introduction":"教师简介",
"photograph":null,"createDate":null
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20put /courseTeacher
请求参数:
{
"id": 24,
"courseId": 75,
"teacherName": "王老师",
"position": "教师职位",
"introduction": "教师简介",
"photograph": null,
"createDate": null
}
响应:
{
"id":24,
"courseId":75,
"teacherName":"王老师",
"position":"教师职位",
"introduction":"教师简介",
"photograph":null,"createDate":null
}1
2
3
4
5
6
7delete /courseTeacher/course/75/26
75:课程id
26:教师id,即course_teacher表的主键
请求参数:课程id、教师id
响应:状态码200,不返回信息 - CourseTeacherController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package com.xuecheng.content.api;
import com.xuecheng.content.model.po.CourseTeacher;
import com.xuecheng.content.service.CourseTeacherService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
public class CourseTeacherController {
CourseTeacherService courseTeacherService;
public List<CourseTeacher> selectcourseTeacher({ Long courseId)
return courseTeacherService.selectCourseTeacher(courseId);
}
public CourseTeacher savacourseTeacher({ CourseTeacher courseTeacher)
return courseTeacherService.savacourseTeacher(courseTeacher);
}
public void deleteCourseTeacher({ Long courseId, Long teacherId)
courseTeacherService.deleteCourseTeacher(courseId,teacherId);
}
}13.3接口实现
- CourseTeacherService
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.xuecheng.content.service;
import com.xuecheng.content.model.po.CourseTeacher;
import java.util.List;
public interface CourseTeacherService {
//查询教师接口
public List<CourseTeacher> selectCourseTeacher(Long courseId);
//添加/修改教师接口
public CourseTeacher savacourseTeacher(CourseTeacher courseTeacher);
//删除教师接口
public void deleteCourseTeacher(Long courseId,Long teacherId);
} - CourseTeacherServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64package com.xuecheng.content.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.content.mapper.CourseTeacherMapper;
import com.xuecheng.content.model.po.CourseTeacher;
import com.xuecheng.content.service.CourseTeacherService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
public class CourseTeacherServiceImpl implements CourseTeacherService {
CourseTeacherMapper courseTeacherMapper;
//查询教师
public List<CourseTeacher> selectCourseTeacher(Long courseId) {
LambdaQueryWrapper<CourseTeacher> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CourseTeacher::getCourseId,courseId);
List<CourseTeacher> CourseTeacher = courseTeacherMapper.selectList(queryWrapper);
return CourseTeacher;
}
//添加/修改教师
public CourseTeacher savacourseTeacher(CourseTeacher courseTeacher) {
//获取id
Long id = courseTeacher.getId();
//判断是否有id
if (id == null){
//添加
CourseTeacher teacher = new CourseTeacher();
BeanUtils.copyProperties(courseTeacher,teacher);
teacher.setCreateDate(LocalDateTime.now());
int insert = courseTeacherMapper.insert(teacher);
if (insert <= 0){
XueChengPlusException.cast("添加失败");
}
return courseTeacherMapper.selectById(teacher.getId());
}else {
//修改
CourseTeacher teacher = courseTeacherMapper.selectById(id);
BeanUtils.copyProperties(courseTeacher,teacher);
int update = courseTeacherMapper.updateById(teacher);
if (update <= 0){
XueChengPlusException.cast("修改失败");
}
return courseTeacherMapper.selectById(teacher.getId());
}
}
//删除教师
public void deleteCourseTeacher(Long courseId, Long teacherId) {
LambdaQueryWrapper<CourseTeacher> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CourseTeacher::getCourseId,courseId)
.eq(CourseTeacher::getId,teacherId);
int delete = courseTeacherMapper.delete(queryWrapper);
if (delete <= 0){
XueChengPlusException.cast("删除失败");
}
}
}14.删除课程
14.1需求分析
- 课程的审核状态为未提交时方可删除。只能删除本机构的
- 删除课程需要删除课程相关的基本信息、营销信息、课程计划、课程教师信息
14.2接口定义
1
2
3
4delete /course/87
87为课程id
请求参数:课程id
响应:状态码200,不返回信息 - CourseBaseInfoController
1
2
3
4
5
6
public void deleteCourse({ Long courseId)
Long companyId = 1232141425L;
courseBaseInfoService.deleteCourse(companyId,courseId);
}14.3接口实现
- CourseBaseInfoService
1
2//删除课程
public void deleteCourse(Long companyId,Long courseId); - CourseBaseInfoServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//课程删除
public void deleteCourse(Long companyId,Long courseId) {
CourseBase courseBase = courseBaseMapper.selectById(courseId);
if (!companyId.equals(courseBase.getCompanyId())){
XueChengPlusException.cast("只能删除本机构的课程");
}
//删除课程基本信息
courseBaseMapper.deleteById(courseId);
//删除课程营销信息
courseMarketMapper.deleteById(courseId);
//删除课程计划
LambdaQueryWrapper<Teachplan> teachplanLambdaQueryWrapper = new LambdaQueryWrapper<>();
teachplanLambdaQueryWrapper.eq(Teachplan::getCourseId, courseId);
teachplanMapper.delete(teachplanLambdaQueryWrapper);
//删除课程教师信息
LambdaQueryWrapper<CourseTeacher> teacherLambdaQueryWrapper = new LambdaQueryWrapper<>();
teacherLambdaQueryWrapper.eq(CourseTeacher::getCourseId, courseId);
courseTeacherMapper.delete(teacherLambdaQueryWrapper);
}