1 模块需求分析
1.1 什么是需求分析
在百度百科中对需求分析的定义如下:
需求分析也称为软件需求分析、系统需求分析或需求分析工程等,是开发人员经过深入细致的调研和分析,准确理解用户和项目的功能、性能、可靠性等具体要求,将用户非形式的需求表述转化为完整的需求定义,从而确定系统必须做什么的过程。
简单理解就是要搞清楚问题域,问题域就是用户的需求,软件要为用户解决什么问题,实现哪些业务功能,满足什么样的性能要求。
如何作需求分析?
第一:首先确认用户需求
用户需求即用户的原始需求。
通过用户访谈、问卷调查、开会讨论、查阅资料等调研手段梳理用户的原始需求。
产品人员根据用户需求会绘制界面原型,通过界面原型再和用户确认需求。
第二:确认关键问题
用户的原始需求可能 是含糊不清的,需求分析要从繁杂的问题中梳理出关键问题。
比如:教学机构的老师想要将课程发布到网上,这是原始需求,根据这个用户需求我们需要进行扩展分析,扩展出几下几点:
1)课程发布需要发布哪些信息
2)如果发布了不良信息怎么办?
3)课程发布后用户怎么查看课程?
根据以上几点继续延伸性分析:
1)课程发布需要发布哪些信息
课程名称、课程介绍、课程价格、课程图片、师资等信息
继续延伸分析:
这么多课程信息进行归类,方便用户编辑,分为课程基本信息、课程营销信息、课程师资等信息。
按照这样的思路对用户需求逐项分析,梳理出若干问题,再从中找到关键问题。比如:上边对课程信息分类后,哪些是关键信息,课程名称、课程图片、课程介绍等基本信息为关键信息,所以发布课程的第一步要编写课程基本信息。
找到了关键问题,下一步就可以进行数据建模,创建课程基本信息表,并设计其中的字段。
第三:梳理业务流程
业务流程是由一个或多个用户参与完成为了完成一个目标所进行的一系列的业务操作,不论是整个系统还是一个模块通常首先分析核心的业务流程,比如:内容管理模块的核心业务流程是课程发布,本项目的核心业务流程是学生选课学习流程。
第四:数据建模
数据建模要根据分析的关键问题将其相关的信息全部建模。比如:根据发布课程的用户需求,可创建课程基本信息表、课程营销信息表、课程师资表、课程发布记录表、课程审核记录表等。
第五:编写需求规格说明书
需求分析阶段的成果物是需求分析规格说明书,针对每一个问题编写需求用例,需求用例包括:功能名称、功能描述、参与者、基本事件流程、可选事件流、数据描述、前置条件、后置条件等内容。
比如:添加课程的需求用例如下:
项目 | 添加课程 |
功能名称 | 添加课程 |
功能描述 | 添加课程基本信息 |
参与者 | 教学机构管理员 |
前置条件 | 教学机构管理只允许向自己机构添加课程 拥有添加课程的权限 |
基本事件流程 | 1、登录教学机构平台 2、进入课程列表页面 3、点击添加课程按钮进入添加课程界面 4、填写课程基本信息 5、点击提交。 |
可选事件流程 | 成功:提示添加成功,跳转到课程营销信息添加界面 失败:提示具体的失败信息,用户根据失败信息进行修改。 |
数据描述 | 课程基本信息:课程id、课程名称、课程介绍、课程大分类、课程小分类、课程等级、课程图片、所属机构、课程创建时间、课程修改时间、课程状态 |
后置条件 | 向课程基本信息插入一条记录 |
补充说明 | |
1.2 模块介绍
内容管理这个词存在于很多软件系统,什么是内容管理 ?
通过百度百科查询其意思:内容管理系统_百度百科
内容管理系统(content management system,CMS),是一种位于WEB前端(Web 服务器)和后端办公系统或流程(内容创作、编辑)之间的软件系统。内容的创作人员、编辑人员、发布人员使用内容管理系统来提交、修改、审批、发布内容。这里指的“内容”可能包括文件、表格、图片、数据库中的数据甚至视频等一切你想要发布到Internet、Intranet以及Extranet网站的信息。
本项目作为一个大型的在线教育平台,其内容管理模块主要对课程及相关内容进行管理,包括:课程的基本信息、课程图片、课程师资信息、课程的授课计划、课程视频、课程文档等内容的管理。
1.3 业务流程
内容管理的业务由教学机构人员和平台的运营人员共同完成。
教学机构人员的业务流程如下:
1、登录教学机构。
2、维护课程信息,添加一门课程需要编辑课程的基本信息、上传课程图片、课程营销信息、课程计划、上传课程视频、课程师资信息等内容。
3、课程信息编辑完成,通过课程预览确认无误后提交课程审核。
4、待运营人员对课程审核通过后方可进行课程发布。
运营人员的业务流程如下:
1、查询待审核的课程信息。
2、审核课程信息。
3、提交审核结果。
下图是课程编辑与发布的整体流程。![](https://i-blog.csdnimg.cn/blog_migrate/fc3a4a4274d279801c9695c1b020d0a7.png)
1.4 界面原型
产品工程师根据用户需求制作产品界面原型,开发工程师除了根据用户需求进行需求分析以外,还会根据界面原型上的元素信息进行需求分析。
内容管理模块的界面原型如下:
课程列表 :![](https://i-blog.csdnimg.cn/blog_migrate/985dabdfeff2616eb8ff57419a357f17.png)
点击添加课程:![](https://i-blog.csdnimg.cn/blog_migrate/0ee78167350b8269b154c529d8c89a07.png)
选择录播课程 ,填写课程信息:![](https://i-blog.csdnimg.cn/blog_migrate/4c16ad5fca5e76f18b2487fcf524999e.png)
填写课程计划信息:![](https://i-blog.csdnimg.cn/blog_migrate/bd4fcab1a33a4515997acacc84f0cad3.png)
填写课程师资信息:![](https://i-blog.csdnimg.cn/blog_migrate/8d34a763bbbca86867a7945ac21dddae.png)
课程填写完毕进行课程 发布:
当审核状态为通过时发布按钮点亮,点击发布按钮 即可对该课程进行发布。![](https://i-blog.csdnimg.cn/blog_migrate/5f58b42bba46ff08c0c06ce3fc7c6ea1.png)
1.5 数据模型
内容管理模块的基础表涉及9张,如下:
使用PowerDesigner打开课程资料下的 "数据库\模型\学成在线项目.sws"
![](https://i-blog.csdnimg.cn/blog_migrate/758488dbe889b31a98420ad33de7a86e.png)
2. 创建模块工程
2.1 模块工程结构
在第一章节创建了项目父工程、项目基础工程,如下图:![](https://i-blog.csdnimg.cn/blog_migrate/09bde49e72729e9cd2664fbba92ad105.png)
接下来要创建内容管理模块的工程结构。
本项目是一个前后端分离项目,前端与后端开发人员之间主要依据接口进行开发。
下图是前后端交互的流程图:
1、前端请求后端服务提供的接口。(通常为http协议 )
2、后端服务的控制层Controller接收前端的请求。
3、Contorller层调用Service层进行业务处理。
4、Service层调用Dao持久层对数据持久化。![](https://i-blog.csdnimg.cn/blog_migrate/48e5eca809c26b11477ebf300eaf5d2c.png)
整个流程分为前端、接口层、业务层三部分。
所以模块工程的结构如下图所示:![](https://i-blog.csdnimg.cn/blog_migrate/cd62c53d85e4aad1a8bc35c4a3d7dc37.png)
xuecheng-plus-content-api:接口工程,为前端提供接口。
xuecheng-plus-content-service: 业务工程,为接口工程提供业务支撑。
xuecheng-plus-content-model: 数据模型工程,存储数据模型类、数据传输类型等。
结合项目父工程、项目基础工程后,如下图:![](https://i-blog.csdnimg.cn/blog_migrate/768a38696e4e1e913e2e99e3b3c4bf6c.png)
xuecheng-plus-content:内容管理模块工程,负责聚合xuecheng-plus-content-api、xuecheng-plus-content-service、xuecheng-plus-content-model。
2.2 创建模块工程
1、首先在项目根目录创建内容管理模块的父工程xuecheng-plus-content
![](https://i-blog.csdnimg.cn/blog_migrate/e8261e2a86efa16c9e9cd20810615c67.png)
创建完成,只保留pom.xml文件,删除多余的文件。
内容管理父工程的主要职责是聚合内容管理接口和内容管理接口实现两个工程,它的父工程是
xuecheng-plus-parent。
pom.xml如下
XML <?xml version="1.0" encoding="UTF-8"?> <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-api和xuecheng-plus-content-service两个工程还没有创建所以modules报错。
2、在xuecheng-plus-content下创建xuecheng-plus-content-model数据模型工程。![](https://i-blog.csdnimg.cn/blog_migrate/4ca7d8880b47cd936a105bad825f60ee.png)
创建完成,只保留包和pom.xml文件 ,删除多余的文件。
修改pom.xml文件
XML <?xml version="1.0" encoding="UTF-8"?> <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> |
3、在xuecheng-plus-content下创建xuecheng-plus-content-service接口实现工程。
创建完成,只保留包和pom.xml文件 ,删除多余的文件
,pom.xml如下:
XML <?xml version="1.0" encoding="UTF-8"?> <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> |
4、在xuecheng-plus-content下创建xuecheng-plus-content-api接口工程。
xuecheng-plus-content-api接口工程的父工程是xuecheng-plus-content,它依赖了xuecheng-plus-base基础工程。
XML <?xml version="1.0" encoding="UTF-8"?> <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> |
到此内容管理模块的四个工程创建完毕,工程结构图如下:
3.课程查询
3.1 需求分析
3.1.1 业务流程
课程查询的业务流程如下:
1、教学机构人员点击课程管理首先进入课程查询界面,如下:![](https://i-blog.csdnimg.cn/blog_migrate/de1bc3e9fce1252db03a21c7481269bc.png)
2.在课程进行列表查询页面输入查询条件查询课程信息
当不输入查询条件时输入全部课程信息。
输入查询条件查询符合条件的课程信息。
约束:本教学机构查询本机构的课程信息。![](https://i-blog.csdnimg.cn/blog_migrate/d9b80058d135865aeb48543e74f5910b.png)
3.1.2 数据模型
下边从查询条件、查询列表两个方面分析数据模型
1、查询条件:
包括:课程名称、课程审核状态、课程发布状态
课程名称:可以模糊搜索
课程审核状态:未提交、已提交、审核通过、审核未通过
课程发布状态:未发布、已发布、已下线
因为是分页查询所以查询条件中还要包括当前页码、每页显示记录数。
2、查询结果:
查询结果中包括:课程id、课程名称、任务数、创建时间、是否付费、审核状态、类型,操作
任务数:该课程所包含的课程计划数,即课程章节数。
是否付费:课程包括免费、收费两种。
类型:录播、直播。
因为是分页查询所以查询结果中还要包括总记录数、当前页、每页显示记录数。
3.2 创建数据库表及PO类型
3.2.1 创建数据库表
1、创建内容管理数据库
2、向创建的内容管理数据库导入数据
选择课程资料中的xc_content.sql脚本,这里使用DataGrid 客户端工具连接mysql并执行脚本。
执行成功,查询course_base数据表,如下:![](https://i-blog.csdnimg.cn/blog_migrate/6029e07fa48604c6eae6b0d086e59fd9.png)
3.2.2 生成PO类
PO即持久对象(Persistent Object),它们是由一组属性和属性的get和set方法组成,PO对应于数据库的表。
在开发持久层代码时需要根据数据表编写PO类,在实际开发中通常使用代码生成器(工具)生成PO类的代码。
由于在需求分析阶段对数据模型进行分析,PO类对应于数据模型,所以在需求分析阶段即可使用工具生成PO类,为下面的接口定义准备好模型类。
在企业开发中通常使用代码生成工具去自动生成这些文件,
本项目使用mybatis-plus的generator工程生成PO类、Mapper接口、Mapper的xml文件,地址在:GitHub - baomidou/generator: Any Code generator
将课程资料目录下的xuecheng-plus-generator.zip解压后拷贝至项目工程根目录
打开IDEA将其导入项目工程 ,打开xuecheng-plus-generator工程的pom.xml,右键 点击“Add as Maven Project” 自动识别maven工程。
如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/0ec5493fc7fa44a331683e0e23b68167.png)
修改ContentCodeGenerator类中的信息,包括:数据库地址、数据库账号、数据库密码、生成的表、生成路径,如下:
Java //数据库账号 private static final String DATA_SOURCE_USER_NAME = "root"; //数据库密码 private static final String DATA_SOURCE_PASSWORD = "mysql"; //生成的表 private static final String[] TABLE_NAMES = new String[]{ "course_base", "course_market", "course_teacher", "course_category", "teachplan", "teachplan_media", "course_publish", "course_publish_pre" }; // TODO 默认生成entity,需要生成DTO修改此变量 // 一般情况下要先生成 DTO类 然后修改此参数再生成 PO 类。 private static final Boolean IS_DTO = false;
public static void main(String[] args) { .... //生成路径 gc.setOutputDir(System.getProperty("user.dir") + "/xuecheng-plus-generator/src/main/java"); .... // 数据库配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setDbType(DbType.MYSQL); dsc.setUrl("jdbc:mysql://192.168.101.65:3306/xcplus_" + SERVICE_NAME+"166" + "?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8"); ... |
修改完成,执行该类的main方法,自动生成content包
在该包下自动生成了内容管理模块的controller、mapper、po及service相关代码,这里我们只需要po类。
将po类拷贝到model工程
![](https://i-blog.csdnimg.cn/blog_migrate/3f93a7a232c1157a2261262e06b5aa79.png)
Java <dependencies> <dependency> <groupId>com.xuecheng</groupId> <artifactId>xuecheng-plus-base</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!--存在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> </dependencies> |
3.3 设计接口
3.3.1 接口设计分析
设计一个接口需要包括以下几个方面:
1)协议
通常协议采用HTTP,查询类接口通常为get或post,查询条件较少的使用get,较多的使用post。
本接口使用 http post。
还要确定content-type,参数以什么数据格式提交,结果以什么数据格式响应。
一般情况没有特殊情况结果以json 格式响应。
2)分析请求参数
根据前边对数据模型的分析,请求参数为:课程名称、课程审核状态、当前页码、每页显示记录数。
根据分析的请求参数定义模型类。
3)分析响应结果
根据前边对数据模型的分析,响应结果为数据列表加一些分页信息(总记录数、当前页、每页显示记录数)。
数据列表中数据的属性包括:课程id、课程名称、任务数、创建时间、审核状态、类型。
注意:查询结果中的审核状态为数据字典中的代码字段,前端会根据审核状态代码 找到对应的名称显示。
根据分析的响应结果定义模型类。
4)分析完成,使用SpringBoot注解开发一个Http接口。
5)使用接口文档工具查看接口的内容。
6)接口中调用Service方法完成业务处理。
4)接口请求示例
Java POST /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 } |
3.3.2 定义模型类
根据接口分析需要定义模型类接收请求的参数,并定义模型类用于响应结果。
1、分页查询模型类
由于分页查询这一类的接口在项目较多,这里针对分页查询的参数(当前页码、每页显示记录数)单独在xuecheng-plus-base基础工程中定义。![](https://i-blog.csdnimg.cn/blog_migrate/3038443266d6da351dbbaa60d7676706.png)
内容如下:
Java package com.xuecheng.base.model;
import lombok.Data; import lombok.ToString; import lombok.extern.java.Log;
/** * @description 分页查询通用参数 * @author Mr.M * @date 2022/9/6 14:02 * @version 1.0 */ @Data @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; }
} |
由于上边类中用到了lombok注解所以在base工程添加依赖包如下:
Java <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> |
2、查询条件模型类
除了分页查询参数,剩下的就是课程查询的特有参数,此时需要在内容管理的model工程中定义课程查询参数模型类。![](https://i-blog.csdnimg.cn/blog_migrate/249f12493d8ac864f91e066d2b370942.png)
内容如下:
Java package com.xuecheng.content.model.dto;
import lombok.Data; import lombok.ToString;
/** * @description 课程查询参数Dto * @author Mr.M * @date 2022/9/6 14:36 * @version 1.0 */ @Data @ToString public class QueryCourseParamsDto {
//审核状态 private String auditStatus; //课程名称 private String courseName; //发布状态 private String publishStatus;
} |
3、响应模型类
根据接口分析,下边定义响应结果模型类。
针对分页查询结果经过分析也存在固定的数据和格式,所以在base工程定义一个基础的模型类。![](https://i-blog.csdnimg.cn/blog_migrate/e301315bb2f71b37b517b5675013d4e6.png)
内容如下:
Java package com.xuecheng.base.model;
import lombok.Data; import lombok.ToString;
import java.io.Serializable; import java.util.List;
/** * @description 分页查询结果模型类 * @author Mr.M * @date 2022/9/6 14:15 * @version 1.0 */ @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; }
} |
我们发现此模型类中定义了List属性,此属性存放数据列表,且支持泛型,课程查询接口的返回类型可以是此模型类型。
List中的数据类型用什么呢?根据需求分析使用生成的PO类即可,所以课程查询接口返回结果类型如下:
Java 泛型中填写CourseBase类型。 PageResult<CourseBase> |
3.3.3 定义接口
根据分析,此接口提供 HTTP post协议,查询条件以json格式提交,响应结果为json 格式。
可使用SpringBoot注解在Controller类中实现。
1、首先在api工程添加依赖
Java <dependencies> <dependency> <groupId>com.xuecheng</groupId> <artifactId>xuecheng-plus-content-service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!--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>
</dependencies> |
2、定义controller方法
Java package com.xuecheng.content.api;
import com.xuecheng.base.model.PageParams; import com.xuecheng.base.model.PageResult; import com.xuecheng.content.model.dto.QueryCourseParamsDto; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController;
/** * @description 课程信息编辑接口 * @author Mr.M * @date 2022/9/6 11:29 * @version 1.0 */ @RestController public class CourseBaseInfoController {
@RequestMapping("/course/list") public PageResult<CourseBase> list(PageParams pageParams, @RequestBody QueryCourseParamsDto queryCourseParams){
return null;
}
} |
说明:pageParams分页参数通过url的key/value传入,queryCourseParams通过json数据传入,使用@RequestBody注解将json转成QueryCourseParamsDto对象。
3、定义启动类
Java package com.xuecheng;
import com.spring4all.swagger.EnableSwagger2Doc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication public class ContentApplication { public static void main(String[] args) { SpringApplication.run(ContentApplication.class, args); } } |
3、添加配置文件
创建 log4j2-dev.xml、bootstrap.yml文件。
log4j2-dev.xml:从课程资料/项目工程 获取.
bootstrap.yml内容如下:
Java server: 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_content148?serverTimezone=UTC&userUnicode=true&useSSL=false& username: root password: mysql # 日志文件配置路径 logging: config: classpath:log4j2-dev.xml |
4、下边启动服务,测试接口是否可以正常请求
在controller方法中打断点,debug启动微服务,在浏览器访问http://localhost:63040/content/course/list
浏览器报400错误,400错误通常由于你访问的页面域名不存在或者请求错误。一般是因为我们输入的语法格式有错误,服务器无法理解用户的请求,不知道要表达的是什么。这个时候我们需要认真检查下语义、请求参数是否有误,不然再怎么刷新都没有用。
接口接收两部分参数,一部分是分页参数,它是通过http url传递key/value串,另一部分是业务查询条件,通过http body传入json内容。
服务端使用RequestBody接收json内容,我们在测试并没有传递json内容这里导致错误。
下边在@RequestBody后添加(required=false)表示此参数不是必填项,如下:
Bash
@PostMapping("/course/list") public PageResult<CourseBase> list(PageParams pageParams, @RequestBody(required=false) QueryCourseParamsDto queryCourseParams){
return null;
} |
再次测试,运行到 断点处暂停。
![](https://i-blog.csdnimg.cn/blog_migrate/4fa17ad73ba8356f373e05578087f426.png)
3.3.4 模型类的作用
现在项目中有两类模型类:DTO数据传输对象、PO持久化对象,DTO用于接口层向业务层之间传输数据,PO用于业务层与持久层之间传输数据,有些项目还会设置VO对象,VO对象用在前端与接口层之间传输数据,如下图:![](https://i-blog.csdnimg.cn/blog_migrate/81eaaf37d8f7f2c572cb62785571e266.png)
当前端有多个平台且接口存在差异时就需要设置VO对象用于前端和接口层传输数据。
比如:
课程列表查询接口,根据需求用户在手机端也要查询课程信息,此时课程查询接口是否需要编写手机端和PC端两个接口呢?如果用户要求通过手机和PC的查询条件或查询结果不一样,此时就需要定义两个Controller课程查询接口,每个接口定义VO对象与前端传输数据。
手机查询:根据课程状态查询,查询结果只有课程名称和课程状态。
PC查询:可以根据课程名称、课程状态、课程审核状态等条件查询,查询结果也比手机查询结果内容多。
此时,Service业务层尽量提供一个业务接口,即使两个前端接口需要的数据不一样,Service可以提供一个最全查询结果,由Controller进行数据整合。
如下图:![](https://i-blog.csdnimg.cn/blog_migrate/5aa36f44d68d7928c69fe395af4c6d05.png)
如果前端的接口没有多样性且比较固定,此时可以取消VO,只用DTO即可。
如下图:![](https://i-blog.csdnimg.cn/blog_migrate/d51fa2c9210a21740a594fc83e022c99.png)
3.4 生成接口文档
在前后端分离开发中通常由后端程序员设计接口,完成后需要编写接口文档,最后将文档交给前端工程师,前端工程师参考文档进行开发。
可以通过一些工具快速生成接口文档 ,本项目通过Swagger生成接口在线文档 。
什么是Swagger?
OpenAPI规范(OpenAPI Specification 简称OAS)是Linux基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful服务开发过程,目前版本是V3.0,并且已经发布并开源在github上。
(GitHub - OAI/OpenAPI-Specification: The OpenAPI Specification Repository)
Swagger是全球最大的OpenAPI规范(OAS)API开发工具框架,Swagger是一个在线接口文档的生成工具,前后端开发人员依据接口文档进行开发。 (API Documentation & Design Tools for Teams | Swagger)
Spring Boot 可以集成Swagger,Swaager根据Controller类中的注解生成接口文档 ,只要添加Swagger的依赖和配置信息即可使用它。
1、在API工程添加swagger-spring-boot-starter依赖
Java <!-- Spring Boot 集成 swagger --> <dependency> <groupId>com.spring4all</groupId> <artifactId>swagger-spring-boot-starter</artifactId> </dependency> |
2、在 bootstrap.yml中配置swagger的扫描包路径及其它信息,base-package为扫描的包路径,扫描Controller类。
Java swagger: title: "学成在线内容管理系统" description: "内容系统管理系统对课程相关信息进行管理" base-package: com.xuecheng.content enabled: true version: 1.0.0 |
3、在启动类中添加@EnableSwagger2Doc注解
再次启动服务,工程启动起来,访问http://localhost:63040/content/swagger-ui.html查看接口信息
下图为swagger接口文档的界面:![](https://i-blog.csdnimg.cn/blog_migrate/3434594b3bc5bc8b1e08b9ae48b51d25.png)
这个文档存在两个问题:
1、接口名称显示course-base-info-controller名称不直观
2、课程查询是post方式只显示post /course/list即可。
下边进行修改,添加一些接口说明的注解,并且将RequestMapping改为PostMapping,如下:
Bash @Api(value = "课程信息编辑接口",tags = "课程信息编辑接口") @RestController public class CourseBaseInfoController {
@ApiOperation("课程查询接口") @PostMapping("/course/list") public PageResult<CourseBase> list(PageParams pageParams, @RequestBody(required=false) QueryCourseParamsDto queryCourseParams){
//....
}
} |
5、再次启动服务,工程启动起来,访问http://localhost:63040/content/swagger-ui.html查看接口信息
下图为swagger接口文档的界面:
![](https://i-blog.csdnimg.cn/blog_migrate/16fa63280f90d84e60bae24a93e365e7.png)
接口文档中会有关于接口参数的说明,在模型类上也可以添加注解对模型类中的属性进行说明,方便对接口文档的阅读。
比如:下边标红的属性名称,可以通过swaager注解标注一个中文名称,方便阅读接口文档。
标注的方法非常简单:
找到模型类,在属性上添加注解:
Java public class PageParams { ... @ApiModelProperty("当前页码") private Long pageNo = 1L;
@ApiModelProperty("每页记录数默认值") private Long pageSize = 30L; ... public class QueryCourseParamsDto {
//审核状态 @ApiModelProperty("审核状态") private String auditStatus; //课程名称 @ApiModelProperty("课程名称") private String courseName;
} |
重启服务,再次进入接口文档,如下图:![](https://i-blog.csdnimg.cn/blog_migrate/b20a8caf27389132d0f37c7587a647fc.png)
Swaager的常用注解如下:
在Java类中添加Swagger的注解即可生成Swagger接口,常用Swagger注解如下:
Java @Api:修饰整个类,描述Controller的作用 @ApiOperation:描述一个类的一个方法,或者说一个接口 @ApiParam:单个参数描述 @ApiModel:用对象来接收参数 @ApiModelProperty:用对象接收参数时,描述对象的一个字段 @ApiResponse:HTTP响应其中1个描述 @ApiResponses:HTTP响应整体描述 @ApiIgnore:使用该注解忽略这个API @ApiError :发生错误返回的信息 @ApiImplicitParam:一个请求参数 @ApiImplicitParams:多个请求参数 |
@ApiImplicitParam属性如下:
属性 | 取值 | 作用 |
paramType | | 查询参数类型 |
| path | 以地址的形式提交数据 |
| query | 直接跟参数完成自动映射赋值 |
| body | 以流的形式提交 仅支持POST |
| header | 参数在request headers 里边提交 |
| form | 以form表单的形式提交 仅支持POST |
dataType | | 参数的数据类型 只作为标志说明,并没有实际验证 |
| Long | |
| String | |
name | | 接收参数名 |
value | | 接收参数的意义描述 |
required | | 参数是否必填 |
| true | 必填 |
| false | 非必填 |
defaultValue | | 默认值 |
使用Swagger可以进行接口的测试。
修改接口内容,添加一些测试代码
Java @ApiOperation("课程查询接口") @PostMapping("/course/list") public PageResult<CourseBase> list(PageParams pageParams, @RequestBody(required=false) QueryCourseParamsDto queryCourseParams){
CourseBase courseBase = new CourseBase(); courseBase.setName("测试名称"); courseBase.setCreateDate(LocalDateTime.now()); List<CourseBase> courseBases = new ArrayList(); courseBases.add(courseBase); PageResult pageResult = new PageResult<CourseBase>(courseBases,10,1,10); return pageResult;
} |
debug方式启动,在 return 处打断点,再用swagger请求接口。
通过下图可以看到请求参数已经正常请求至controller方法
![](https://i-blog.csdnimg.cn/blog_migrate/cc67a4d21055f91ec1b16dbb63207ccf.png)
放行继续运行,观察swagger界面,结果可以正常返回![](https://i-blog.csdnimg.cn/blog_migrate/a2839b9d1f4b4c4ba4ca62c9243799b9.png)
不过存在一个问题就是LocalDateTime类型的数据转json后数据格式并不是我们要的年月日时分秒
在base工程com.xuecheng.base.config包下加配置LocalDateTimeConfig 类实现转json时字符串与LocalDateTime类型的转换,LocalDateTimeConfig 类可从课程资料下的项目工程目录中直接拷贝。
3.5 开发持久层
3.5.1 生成mapper
本项目使用MyBatis-Plus开发持久层,需要创建PO类、Mapper接口、Mapper的xml文件,每个PO类对应数据库的每张表,每张表需要创建一个Mapper接口和Mapper的xml映射文件 。
下边将使用generator工程生成的mapper接口和mapper映射文件 拷贝到service工程
service工程即业务层为api接口工程提供业务处理支撑,本项目业务层包括了持久层的代码,一些大型公司的团队职责划分更细,会将持久层和业务层分为两个工程,不过这需要增加成本。
本项目使用持久层框架MyBatis-Plus进行开发,下边将mapper接口和xml文件 拷贝到 service工程
到此mapper接口与mapper映射文件生成完毕。
3.5.2 测试mapper
下边对mapper进行单元测试,测试course_base表的查询接口。
1、下边在service工程的pom.xml中添加依赖
XML <dependencies> <dependency> <groupId>com.xuecheng</groupId> <artifactId>xuecheng-plus-content-model</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
<!-- 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>
</dependencies> |
2、配置扫描mapper及分页插件
从课程资料/工程目录下拷贝MybatisPlusConfig 到 service工程的com.xuecheng.content.config包下:
Java package 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;
/** * <P> * Mybatis-Plus 配置 * </p> */ @Configuration @MapperScan("com.xuecheng.content.mapper") public class MybatisPlusConfig { /** * 定义分页拦截器 */ @Bean 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每页大小和当前页的数据,是否为首页,是否为尾页,总页数等。
4、单元测试所需要的配置文件
在test/resources下创建 log4j2-dev.xml、bootstrap.yml:
log4j2-dev.xml:从课程资料/项目工程 获取.
bootstrap.yml:
YAML spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/xcplus_content?serverTimezone=UTC&userUnicode=true&useSSL=false& username: root password: mysql # 日志文件配置路径 logging: config: classpath:log4j2-dev.xml |
5、编写启动类:
单元测试工作在test目录,在test下添加启动类
代码如下:
Java package com.xuecheng;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication public class ContentApplication { public static void main(String[] args) { SpringApplication.run(ContentApplication.class, args); } } |
6、编写测试类
代码如下:
Java package 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;
@SpringBootTest class CourseBaseMapperTests {
@Autowired CourseBaseMapper courseBaseMapper;
@Test void testCourseBaseMapper() { CourseBase courseBase = courseBaseMapper.selectById(74L); Assertions.assertNotNull(courseBase);
//测试查询接口 LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>(); //查询条件 QueryCourseParamsDto queryCourseParamsDto = new QueryCourseParamsDto(); queryCourseParamsDto.setCourseName("java"); queryCourseParamsDto.setAuditStatus("202004"); queryCourseParamsDto.setPublishStatus("203001");
//拼接查询条件 //根据课程名称模糊查询 name like '%名称%' queryWrapper.like(StringUtils.isNotEmpty(queryCourseParamsDto.getCourseName()),CourseBase::getName,queryCourseParamsDto.getCourseName()); //根据课程审核状态 queryWrapper.eq(StringUtils.isNotEmpty(queryCourseParamsDto.getAuditStatus()),CourseBase::getAuditStatus,queryCourseParamsDto.getAuditStatus());
//分页参数 PageParams pageParams = new PageParams(); pageParams.setPageNo(1L);//页码 pageParams.setPageSize(3L);//每页记录数 Page<CourseBase> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
//分页查询E page 分页参数, @Param("ew") Wrapper<T> queryWrapper 查询条件 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<>(items, total, pageParams.getPageNo(), pageParams.getPageSize()); System.out.println(courseBasePageResult); }
} |
运行测试类的测试方法进行测试,测试成功
3.6 开发业务层
3.6.1 创建数据字典表
课程基本信息查询的主要数据来源是课程基本信息表,这里有一个点需要注意,就是课程的审核状态、发布状态。
审核状态在查询条件和查询结果中都存在,审核状态包括:未审核、审核通过、审核未通过三种,下边思考一个问题:一个课程的审核状态如果是“审核未通过”那么在课程基本信息表记录“审核未通过”三个字合适吗?
如果将“审核未通过”五个字记录在课程基本信息表中,显示出来的审核状态就是“审核未通过”这五个字,看起来没有什么问题,如果有一天客户想要将审核未通过的记录在显示时改为“未通过”三个字,怎么办?
这时你可以需要批量处理数据库中记录了,写一个 update 语句,审核状态等于“审核未通过”的全部更新 为“未通过”。看起来解决了问题,如果有一天客户又让改了呢?
和审核状态同类的有好多这样的信息,比如:课程状态、课程类型、用户类型等等,这一类数据有一个共同点就是它有一些分类项,且这些分类项较为固定。针对这些数据,为了提高系统的可扩展性,专门定义数据字典表去维护。
下边是课程审核状态的定义:
Java [ {"code":"202001","desc":"审核未通过"}, {"code":"202002","desc":"未审核"}, {"code":"202003","desc":"审核通过"} ] |
每一项都由代码和名称组成。
此时我们好像要干 什么了 ,该课程 的审核状态为审核未通过,那么我们在课程基本信息表存储202001,也就是审核未通过对应的代码,这样查询出的数据在前端展示时根据代码取出它对应的内容显示给用户。如果用户要修改“审核未通过”的显示内容只需要在数据字典表修改,无法修改课程基本信息表。
数据字典表在系统管理数据库中存储,首先导入系统管理数据库,创建系统管理服务的数据库
![](https://i-blog.csdnimg.cn/blog_migrate/2f73288d26d4234fed715bc2e5b0c9f5.png)
创建成功如下图:![](https://i-blog.csdnimg.cn/blog_migrate/0645ce376bbd5e06116474481b054096.png)
创建系统管理的数据库,导入课程资料中的xcplus_system.sql脚本。
![](https://i-blog.csdnimg.cn/blog_migrate/bd185cedf18d79dc17d92e5a3a50bf7d.png)
3.6.2 编写Service
接下来开发Service方法,首先创建Service接口:
Java package com.xuecheng.content.service;
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;
/** * @description 课程基本信息管理业务接口 * @author Mr.M * @date 2022/9/6 21:42 * @version 1.0 / public interface CourseBaseInfoService {
/* * @description 课程查询接口 * @param pageParams 分页参数 * @param queryCourseParamsDto 条件条件 * @return com.xuecheng.base.model.PageResult<com.xuecheng.content.model.po.CourseBase> * @author Mr.M * @date 2022/9/6 21:44 */ PageResult<CourseBase> queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto);
} |
再创建接口实现类
Java package 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 org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
import java.util.List;
/** * @description 课程信息管理业务接口实现类 * @author Mr.M * @date 2022/9/6 21:45 * @version 1.0 */ @Service public class CourseBaseInfoServiceImpl implements CourseBaseInfoService {
@Autowired CourseBaseMapper courseBaseMapper;
@Override public PageResult<CourseBase> queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto) {
//构建查询条件对象 LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>(); //构建查询条件,根据课程名称查询 queryWrapper.like(StringUtils.isNotEmpty(queryCourseParamsDto.getCourseName()),CourseBase::getName,queryCourseParamsDto.getCourseName()); //构建查询条件,根据课程审核状态查询 queryWrapper.eq(StringUtils.isNotEmpty(queryCourseParamsDto.getAuditStatus()),CourseBase::getAuditStatus,queryCourseParamsDto.getAuditStatus()); //构建查询条件,根据课程发布状态查询 //todo:根据课程发布状态查询
//分页对象 Page<CourseBase> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize()); // 查询数据内容获得结果 Page<CourseBase> pageResult = courseBaseMapper.selectPage(page, queryWrapper); // 获取数据列表 List<CourseBase> list = pageResult.getRecords(); // 获取数据总数 long total = pageResult.getTotal(); // 构建结果集 PageResult<CourseBase> courseBasePageResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize()); return courseBasePageResult;
}
} |
3.6.3 测试service
下边对service进行单元测试,编写单元测试类:
Java package com.xuecheng.content;
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 com.xuecheng.content.service.CourseBaseInfoService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest class CourseBaseInfoServiceTests {
@Autowired CourseBaseInfoService courseBaseInfoService;
@Test void testCourseBaseInfoService() { //查询条件 QueryCourseParamsDto queryCourseParamsDto = new QueryCourseParamsDto(); queryCourseParamsDto.setCourseName("java"); queryCourseParamsDto.setAuditStatus("202004"); queryCourseParamsDto.setPublishStatus("203001");
//分页参数 PageParams pageParams = new PageParams(); pageParams.setPageNo(1L);//页码 pageParams.setPageSize(3L);//每页记录数
PageResult<CourseBase> courseBasePageResult = courseBaseInfoService.queryCourseBaseList(pageParams, queryCourseParamsDto); System.out.println(courseBasePageResult); }
} |
3.6.4 完善todo
课下完成service方法中的todo:根据课程发布状态查询课程信息。
3.7 接口测试
3.7.1 接口完善
控制层、业务层以及持久层三层通常可以面向接口并行开发,比如:业务层开发的同事可以先只编写一个Service接口,接口层的同事即可面向Service接口去开发,待接口层和业务层完成后进行连调。
下边课程查询接口的实现。
Java @ApiOperation("课程查询接口") @PostMapping("/course/list") public PageResult<CourseBase> list(PageParams pageParams, @RequestBody QueryCourseParamsDto queryCourseParams){ PageResult<CourseBase> pageResult = courseBaseInfoService.queryCourseBaseList(pageParams, queryCourseParams); return pageResult; } |
代码编辑完毕,再次打开Swagger进行测试。
输入查询条件:![](https://i-blog.csdnimg.cn/blog_migrate/4ca7179a96a79ab1f40f7edf38a2d0bf.png)
测试,观察结果是否正确。
![](https://i-blog.csdnimg.cn/blog_migrate/ae5f112962a5fc8a67a3dfa844fb381c.png)
3.7.2 Httpclient测试
Swagger是一个在线接口文档,虽然使用它也能测试但需要浏览器进入Swagger,最关键的是它并不能保存测试数据。
在IDEA中有一个非常方便的http接口测试工具httpclient,下边介绍它的使用方法,后边我们会用它进行接口测试。
如果IDEA版本较低没有自带httpclient,需要安装httpclient插件![](https://i-blog.csdnimg.cn/blog_migrate/cd3beeb0daf5f8e8f851d8bd549bcf92.png)
点击Generate request in HTTP Client即可生成的一个测试用例。
我们可以添加请求参数进行测试
参数添加完毕可以运行它
观察控制台,测试通过。
Java http://localhost:63040/course/list?pageNo=2&pageSize=10
HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Wed, 07 Sep 2022 00:54:50 GMT Keep-Alive: timeout=60 Connection: keep-alive
{ "items": [ { "id": 88, "companyId": 1232141425, "companyName": null, "name": "1", "users": "1", "tags": "1", "mt": "1-1", "mtName": null, "st": "1-1-1", "stName": null, "grade": "204001", "teachmode": "200002", "description": "1", "pic": "http://r3zc5rung.hd-bkt.clouddn.com/cb1b6038-ef68-4362-8c29-a966886d1dc5sakUiFHLb5sRFdIK", "createDate": "2021-12-27 20:14:53", "changeDate": "2021-12-27 20:28:58", "createPeople": null, "changePeople": null, "auditStatus": "202002", "auditMind": null, "auditNums": 0, "auditDate": null, "auditPeople": null, "status": 1, "coursePubId": null, "coursePubDate": null }, .... ], "counts": 14, "page": 2, "pageSize": 10 } Response file saved. > 2022-09-07T085450.200.json
Response code: 200; Time: 392ms (392 ms); Content length: 1916 bytes (1.92 kB) |
.http文件即测试用例文档 ,它可以随着项目工程一起保存,这样测试的数据就可以保存下来,方便进行测试。
为了方便保存.http文件 ,我们单独在项目工程的根目录创建一个目录单独存放它们。![](https://i-blog.csdnimg.cn/blog_migrate/f7623b93ee124798b961b16b82a74fde.png)
我们以模块为单位创建.http文件。![](https://i-blog.csdnimg.cn/blog_migrate/e40c4b0c196e4bb59623949a2786cf0b.png)
打开内容管理模块的 http文件 ,把刚才测试数据拷贝上去![](https://i-blog.csdnimg.cn/blog_migrate/5bee91672197a46a9d0bdaa6d31c367f.png)
为了方便将来和网关集成测试,这里我们把测试主机地址在配置文件http-client.env.json 中配置![](https://i-blog.csdnimg.cn/blog_migrate/9a1a302ae34cbacdef1bfe8d6f11daa5.png)
注意:文件名称http-client.env.json保持一致,否则无法读取dev环境变量的内容。
内容如下:
Java { "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" } } |
再回到xc-content-api.http文件,将http://localhost:63040 用变量代替![](https://i-blog.csdnimg.cn/blog_migrate/00b83136585c2f322bd697879df72673.png)
到此就完成了httpclient的配置与使用测试。
3.8 前后端联调
3.8.1准备环境
什么是前后端联调?
通常由后端工程师将接口设计好并编写接口文档,将接口文档交给前端工程师,前后端的工程师就开始并行开发,前端开发人员会使用mock数据(假数据)进行开发,当前后端代码完成后开始进行接口联调,前端工程师将mock数据改为请求后端接口获取,前端代码请求后端服务测试接口是否正常,这个过程是前后端联调。
当前后端联调出现问题需要根据测试环境下接口的请求及响应数据内容去判断是否符合接口文档的要求。查出是前端或后端的问题由具体的工程师负责修改缺陷,修改后再次回归测试。
在教学中进行前后端联调,首先配置前端环境,下边我们安装前端工程运行的环境。
首先从软件工具目录找到node-v16.17.0-x64.msi安装nodejs
安装完成,查看版本号![](https://i-blog.csdnimg.cn/blog_migrate/09e53cd7772ceb0186a38ddb6d408e22.png)
在idea中配置node.js的路径![](https://i-blog.csdnimg.cn/blog_migrate/fdfd5f1e1d65b1d11da561af56387bb1.png)
下边启动前端工程,从前端工程拷贝project-xczx2-portal-vue-ts.zip到代码目录并解压,并使用IDEA或VS Code打开project-xczx2-portal-vue-ts目录,下边以IDEA为例进行说明:
右键点击project-xczx2-portal-vue-ts目录下的package.json文件,![](https://i-blog.csdnimg.cn/blog_migrate/7a3dec4da40e5d232157d4ef76a0e024.png)
点击Show npm Scripts打开npm窗口![](https://i-blog.csdnimg.cn/blog_migrate/0d98ca9225ef9412b6e8c4ee25025ff7.png)
点击“Edit 'serve'” setting,下边对启动项目的一些参数进行配置,选择nodejs、npm。![](https://i-blog.csdnimg.cn/blog_migrate/37e3a41527483947e87092516b0b5ed7.png)
右键点击Serve,点击“Run serve”启动工程。![](https://i-blog.csdnimg.cn/blog_migrate/d91ec10fae6aadfab1df9551275f4765.png)
出现如下访问链接说明启动成功
![](https://i-blog.csdnimg.cn/blog_migrate/4d249897c9057337e879255be9de1bb5.png)
如果存在问题通过以下命令启动:
1、cmd进入工程根目录
2、运行以下命令
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm i
npm run serve
3.8.2 安装系统管理服务
启动前端工程成功,在浏览器通过http://localhost:8601/地址访问前端工程。
前端工程报错如下:![](https://i-blog.csdnimg.cn/blog_migrate/cb7e016e63df5dc260e2cc118ec3f59f.png)
http://localhost:8601/system/dictionary/all 指向的是系统管理服务。在前端讲解内容管理模块的需求时我们提到一个数据字典表,此链接正是在前端请求后端获取数据字典数据的接口地址。
数据字典表中配置了项目用的字典信息,此接口是查询字典中的全部数据 ,在此我们不再开发,按照下边的步骤安装系统管理服务即可。
从课程资料/项目工程目录获取xuecheng-plus-system.zip,并解压
将xuecheng-plus-system目录拷贝到项目工程根目录,刷新maven,或进入pom.xml右键转为pom工程。![](https://i-blog.csdnimg.cn/blog_migrate/f24127b416f9861ef1a3e28e1445d828.png)
进入xuecheng-plus-system-service工程,找到resources下的application.yml修改数据库连接参数。
启动系统管理服务,启动成功,在浏览器请求:http://localhost:63110/system/dictionary/all
系统服务的端口是63110
如果可以正常读取数据字典信息则说明系统管理服务安装成功。
3.8.3 解决跨域问题
在浏览器通过http://localhost:8601/地址访问前端工程。
chrome浏览器报错如下:
Java Access to XMLHttpRequest at 'http://localhost:63110/system/dictionary/all' from origin 'http://localhost:8601' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. |
firefox浏览器报错如下:
提示:从http://localhost:8601访问http://localhost:63110/system/dictionary/all被CORS policy阻止,因为没有Access-Control-Allow-Origin 头信息。CORS全称是 cross origin resource share 表示跨域资源共享。
出这个提示的原因是基于浏览器的同源策略,去判断是否跨域请求,同源策略是浏览器的一种安全机制,从一个地址请求另一个地址,如果协议、主机、端口三者全部一致则不属于跨域,否则有一个不一致就是跨域请求。
比如:
从http://localhost:8601 到 http://localhost:8602 由于端口不同,是跨域。
从http://192.168.101.10:8601 到 http://192.168.101.11:8601 由于主机不同,是跨域。
从http://192.168.101.10:8601 到 https://192.168.101.10:8601 由于协议不同,是跨域。
注意:服务器之间不存在跨域请求。
浏览器判断是跨域请求会在请求头上添加origin,表示这个请求来源哪里。
比如:
Plaintext GET / HTTP/1.1 Origin: http://localhost:8601 |
服务器收到请求判断这个Origin是否允许跨域,如果允许则在响应头中说明允许该来源的跨域请求,如下:
Plaintext Access-Control-Allow-Origin:http://localhost:8601 |
如果允许任何域名来源的跨域请求,则响应如下:
Plaintext Access-Control-Allow-Origin:* |
解决跨域的方法:
1、JSONP
通过script标签的src属性进行跨域请求,如果服务端要响应内容则首先读取请求参数callback的值,callback是一个回调函数的名称,服务端读取callback的值后将响应内容通过调用callback函数的方式告诉请求方。如下图:![](https://i-blog.csdnimg.cn/blog_migrate/3fd336b764ea0e57d4ce7863d73e2945.png)
2、添加响应头
服务端在响应头添加 Access-Control-Allow-Origin:*
3、通过nginx代理跨域
由于服务端之间没有跨域,浏览器通过nginx去访问跨域地址。![](https://i-blog.csdnimg.cn/blog_migrate/b396266548f1cc4a43bc9631a50320a4.png)
1)浏览器先访问http://192.168.101.10:8601 nginx提供的地址,进入页面
2)此页面要跨域访问http://192.168.101.11:8601 ,不能直接跨域访问http://www.baidu.com:8601 ,而是访问nginx的一个同源地址,比如:http://192.168.101.11:8601/api ,通过http://192.168.101.11:8601/api 的代理去访问http://www.baidu.com:8601。
这样就实现了跨域访问。
浏览器到http://192.168.101.11:8601/api 没有跨域
nginx到http://www.baidu.com:8601通过服务端通信,没有跨域。
我们准备使用方案2解决跨域问题。在内容管理的api工程config包下编写GlobalCorsConfig.java,
或直接从课程资料/项目工程下拷贝,
代码如下:
Java package com.xuecheng.system.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter;
/** * @description 跨域过虑器 * @author Mr.M * @date 2022/9/7 11:04 * @version 1.0 */ @Configuration public class GlobalCorsConfig {
/** * 允许跨域调用的过滤器 */ @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); //允许白名单域名进行跨域调用 config.addAllowedOrigin("*"); //允许跨越发送cookie config.setAllowCredentials(true); //放行全部原始头信息 config.addAllowedHeader("*"); //允许所有请求方法跨域调用 config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } } |
此配置类实现了跨域过虑器,在响应头添加Access-Control-Allow-Origin。
重启系统管理服务,前端工程可以正常进入http://localhost:8601,观察浏览器记录,成功解决跨域。
![](https://i-blog.csdnimg.cn/blog_migrate/86323b6bbb5b05ca3e168f20e9ee083b.png)
这里进行前后联调的目的是体会前后端联调的流程,测试的功能为课程查询功能。
1、启动前端工程,再启内容管理服务端。
2、修改服务端地址
前端默认连接的是项目的网关地址,由于现在网关工程还没有创建,这里需要更改前端工程的参数配置文件 ,修改网关地址为内容管理服务的地址。
![](https://i-blog.csdnimg.cn/blog_migrate/50519e1f28f473111517a2d10f3112aa.png)
访问前端首页,进入课程管理:http://localhost:8601/#/organization/course-list![](https://i-blog.csdnimg.cn/blog_migrate/a65172749ce72236cafa9f343969defc.png)
更改课程条件及分页参数测试课程查询列表是否正常显示。
跟踪内容管理服务的输出日志,查看是否正常。
到此基本完成了前后端连调。