在线教育项目研发之讲师模块业务操作
课程目标
1)Swagger2介绍
2)讲师CRUD操作【重点】
3)统一异常处理
4)日志处理
1、 SpringBoot整合Swagger2
在团队开发中,一个好的 API 文档不但可以减少大量的沟通成本,还可以帮助一位新人快速上手业务。传统的做法是由开发人员创建一份 RESTful API 文档来记录所有的接口细节,并在程序员之间代代相传。
这种做法存在以下几个问题:
第一:API 接口众多,细节复杂,需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等,想要高质量的完成这份文档需要耗费大量的精力;
第二:难以维护。随着需求的变更和项目的优化、推进,接口的细节在不断地演 变,接口描述文档也需要同步修订,可是文档和代码处于两个不同的媒介, 除非有严格的管理机制,否则很容易出现文档、接口不一致的情况
Swagger2 的出现就是为了从根本上解决上述问题。它作为一个规范和完整的框架,可以用于生成、描述、调用和可视化 RESTful 风格的 Web 服务:
1.接口文档在线自动生成,文档随接口变动实时更新,节省维护成本
2.支持在线接口测试,不依赖第三方工具
1.1配置Swagger2
parent中已经指定swagger版本,所以再teacher模块中直接引用:
<!--swagger--> |
1.2 创建Swagger2配置文件
package com.yxzx.ebs.teacher.config; |
1.3 重启服务器查看接口
http://localhost:8001/swagger-ui.html
1.4 API模型
MP的代码生成器已经在entity的实体类中生成了模型的定义
@ApiModelProperty(value = "…")
可以添加一些自定义设置,例如:
定义样例数据
@ApiModelProperty(value = "创建时间", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
1.5 定义接口说明和参数说明
定义在类上:@Api
定义在方法上:@ApiOperation
定义在参数上:@ApiParam
例如:
@Api(description="讲师管理")
@RestController
@RequestMapping("/teacher")
public class EbsTeacherController {
@Autowired
private EbsTeacherService ebsTeacherService;
@ApiOperation(value = "所有讲师列表")
@GetMapping
private List<EbsTeacher> getTeacherList(){
List<EbsTeacher> list = ebsTeacherService.list(null);
return list;
}
}
2、 讲师逻辑删除
2.1 在Controller中添加删除方法
@ApiOperation(value = "根据ID删除讲师")
@DeleteMapping("{id}")
public boolean removeById(
@ApiParam(name = "id", value = "讲师ID", required = true)
@PathVariable String id){
return ebsTeacherService.removeById(id);
}
2.2 配置逻辑删除插件
MyBatisPlusConfig 中配置
@Configuration
public class MyBatisPlusConfig {
/**
* 逻辑删除插件
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}
2.3 在EbsTeacher.java文件中
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
@TableLogic
@TableField(fill = FieldFill.INSERT, value = "is_delete")
private Boolean isDeleted;
因为在添加之前需要自动填充:FieldFill.INSERT,所以我们需要配置Handler处理;
2.4 配置Handler
创建 com.guli.teacher.handler.TeacherMetaObjectHandler.java
@Component
public class TeacherMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("isDelete", 0, metaObject);
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}
2.5 配置application.properties文件
配置指定删除和不删除的状态
# 设置指定删除状态
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
2.6 讲师逻辑删除测试
使用Swagger-ui.html链接:测试;
显示:
数据库显示:
3、 统一返回结果集
项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android, Web)对数据的操作更直观、轻松。
一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。
例如,我们的系统要求返回的基本数据格式如下:
列表:
{
"success": true,
"code": 20000,
"message": "成功",
"data": {
"items": [
{
"memberId": "1",
"mobile": "18911893513",
"createTime": "2018-05-15 01:20:52"
}
]
}
}
分页:
{
"success": true,
"code": 20000,
"message": "成功",
"data": {
"total": 17,
"rows": [
{
"memberId": "1",
"mobile": "18911893513",
"createTime": "2018-05-15 01:20:52"
}
]
}
}
没有返回数据:
{
"success": true,
"code": 20000,
"message": "成功",
"data": {“”:””,””:””}
失败:
{
"success": false,
"code": 20001,
"message": "失败",
"data": {}
}
因此,我们定义统一结果
{
"success": 布尔, //响应是否成功
"code": 数字, //响应码
"message": 字符串, //返回消息
"data": HashMap //返回数据,放在键值对中
}
3.1 创建模块yxzx_common
3.1.1 创建模块
在 yxzx_common项目下初始化一个普通的maven模块
Artifact:yxzx_common
<packaging>jar</packaging>
3.1.2 配置 pom.xml
<dependencies>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--开发者工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3.1.3 建返回码定义类
一个类:定义状态码
public interface ResultCode {
int OK = 20000;//成功
int ERROR = 20001;//失败
}
一个类:定义返回结果对象;
@Data
@ApiModel(value = "全局统一返回结果")
public class Result {
@ApiModelProperty(value = "是否成功")
private boolean success;
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String, Object> data = new HashMap<String, Object>();
private Result(){}
public static Result ok(){
Result r = new Result();
r.setSuccess(true);
r.setCode(ResultCode.OK);
r.setMessage("成功");
return r;
}
public static Result error(){
Result r = new Result();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
return r;
}
public Result message(String message){
this.setMessage(message);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
public Result data(String key, Object value){
this.data.put(key, value);
return this;
}
public Result data(Map<String, Object> map){
this.setData(map);
return this;
}
}
3.2 添加依赖
3.2.1 在yxzx_teacher 模块中添加依赖
dependency>
<groupId>com.yxzx</groupId>
<artifactId>yxzx_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
3.2.2 代码重构
@ApiOperation(value = "所有讲师列表")
@GetMapping
private Result getTeacherList(){
List<EbsTeacher> list = ebsTeacherService.list(null);
return Result.ok().data("items",list);
}
@ApiOperation(value = "根据ID删除讲师")
@DeleteMapping("{id}")
public Result removeById(
@ApiParam(name = "id", value = "讲师ID", required = true)
@PathVariable String id){
boolean flag = ebsTeacherService.removeById(id);
if(flag){
return Result.ok();
}else{
return Result.error();
}
}
4、 分页和条件查询
4.1 MyBatisPlusConfig中配置分页插件
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
4.2 分页Controller方法
接口文档数据
EbsTeacherController中添加分页方法
@ApiOperation(value = "分页讲师列表")
@GetMapping("{page}/{limit}")
public Result pageList(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Long page,
@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Long limit){
//创建分页提交对象
Page<EbsTeacher> pageParam = new Page<>(page, limit);
//执行分页
ebsTeacherService.page(pageParam, null);
//获取分页结果
List<EbsTeacher> records = pageParam.getRecords();
long total = pageParam.getTotal();
return Result.ok().data("total", total).data("rows", records);
}
4.3 Swagger中测试
4.4 条件查询
根据讲师名称name,讲师头衔level、讲师入驻时间gmt_create(时间段)查询
4.4.1 创建查询对象
创建com.yxzx.ebs.teacher.entity.query包,创建TeacherQuery.java查询对象
@ApiModel(value = "EbsTeacher查询对象", description = "讲师查询对象封装")
@Data
public class EbsTeacherQuery implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "教师名称,模糊查询")
private String name;
@ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
private Integer level;
@ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换
@ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
private String end;
}
4.4.2 service
接口EbsTeacherService
public interface EbsTeacherService extends IService<EbsTeacher> {
/**
* 根据多个条件查询讲师列表
* @param pageParam
* @param teacherQuery
*/
void pageQuery(Page<EbsTeacher> pageParam, EbsTeacherQuery teacherQuery);
}
实现EbsTeacherServiceImpl
/**
* 根据多个条件查询讲师列表
*
* @param pageParam
* @param teacherQuery
*/
@Override
public void pageQuery(Page<EbsTeacher> pageParam, EbsTeacherQuery teacherQuery) {
QueryWrapper<EbsTeacher> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("sort");
if (teacherQuery == null){
baseMapper.selectPage(pageParam, queryWrapper);
return;
}
String name = teacherQuery.getName();
Integer level = teacherQuery.getLevel();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
if (!StringUtils.isEmpty(name)) {
queryWrapper.like("name", name);
}
if (!StringUtils.isEmpty(level) ) {
queryWrapper.eq("level", level);
}
if (!StringUtils.isEmpty(begin)) {
queryWrapper.ge("gmt_create", begin);
}
if (!StringUtils.isEmpty(end)) {
queryWrapper.le("gmt_create", end);
}
baseMapper.selectPage(pageParam, queryWrapper);
}
4.4.3 controller
EbsTeacherController中修改 pageList方法:
增加参数EbsTeacherQuery ebsTeacherQuery,非必选
@ApiOperation(value = "分页讲师列表")
@GetMapping("{page}/{limit}")
public Result pageList(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Long page,
@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Long limit,
@ApiParam(name = "teacherQuery", value = "查询对象", required = false)
EbsTeacherQuery ebsTeacherQuery){
//创建分页提交对象
Page<EbsTeacher> pageParam = new Page<>(page, limit);
//执行分页
ebsTeacherService.pageQuery(pageParam, ebsTeacherQuery);
//获取分页结果
List<EbsTeacher> records = pageParam.getRecords();
long total = pageParam.getTotal();
return Result.ok().data("total", total).data("rows", records);
}
4.4.4 Swagger中测试
5、 讲师新增和修改
5.1 新增
在Controller中编写代码
@ApiOperation(value = "新增讲师")
@PostMapping
public Result save(
@ApiParam(name = "teacher", value = "讲师对象", required = true)
@RequestBody EbsTeacher teacher){
ebsTeacherService.save(teacher);
return Result.ok();
}
5.2 根据id查询
@ApiOperation(value = "根据ID查询讲师")
@GetMapping("{id}")
public Result getById(
@ApiParam(name = "id", value = "讲师ID", required = true)
@PathVariable String id){
EbsTeacher teacher = ebsTeacherService.getById(id);
return Result.ok().data("item", teacher);
}
5.3 根据id修改
@ApiOperation(value = "根据ID修改讲师")
@PutMapping("{id}")
public Result updateById(
@ApiParam(name = "id", value = "讲师ID", required = true)
@PathVariable String id,
@ApiParam(name = "teacher", value = "讲师对象", required = true)
@RequestBody EbsTeacher teacher){
ebsTeacherService.updateById(teacher);
return Result.ok();
}
6、 统一异常处理
6.1 测试系统对错误的响应
例如输入两个不合法的分页参数
6.2 测试
查看结果
6.3 全局异常处理
我们想让异常结果也统一,并且在集中的地方处理系统的异常信息,那么需要统一异常处理
yxzx_teacher中创建统一异常处理类:
package com.yxzx.ebs.teacher.handler;
import com.yxzx.ebs.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 统一异常处理类
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.error().message("执行出现了异常");
}
}
返回统一错误结果
注解说明:
@ControllerAdvice ,很多初学者可能都没有听说过这个注解,实际上,这是一个非常有用的注解,顾名思义,这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:
全局异常处理
全局数据绑定
全局数据预处理
灵活使用这三个功能,可以帮助我们简化很多工作,需要注意的是,这是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用.
@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为NullpointerException,则数组越界异常就不会进到这个方法中来。
6.4 特定异常配置
如果是SQL语句的问题;那么需要配置捕获SQL语句的异常:
6.4.1 定义状态码
yxzx_common中添加
package com.yxzx.ebs.common.Exception;
public interface ResultCode {
int SUCCESS=20000;//成功
int ERROR=20001;//统一异常
int SQL_ERROR = 20006;//sql错误
}
6.4.2 在Handler中配置捕获指定SQL异常
/**
* 统一异常处理类
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 统一异常处理器
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.error().message("执行出现了异常");
}
/**
* 特定异常处理器
* @param e
* @return
*/
@ExceptionHandler(MySQLSyntaxErrorException.class)
@ResponseBody
public Result error(MySQLSyntaxErrorException e){
e.printStackTrace();
return Result.error().code(ResultCode.SQL_ERROR).message("SQL语法错误");
}
}
6.5 自定义异常
6.5.1 EbsException通用异常类
在yxzx_common中创建Exception的异常类
注意:需要在ebs启动类添加注解
@SpringBootApplication(scanBasePackages = {"com.yxzx"})
package com.yxzx.ebs.common.Exception;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 自定义异常处理类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "全局异常")
public class EbsException extends RuntimeException {
@ApiModelProperty(value = "状态码")
private Integer code;
@ApiModelProperty(value = "异常消息")
private String msg;
}
6.5.2 创建捕获自定义异常类
package com.yxzx.ebs.common.Exception;
import com.yxzx.ebs.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
*自定义异常捕捉类
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EbsException.class)
@ResponseBody
public Result error(EbsException e){
e.printStackTrace();
return Result.error().code(e.getCode()).message(e.getMsg());
}
}
6.5.3 测试
在业务中需要的位置抛出EbsException,举例子在查询列表中出错:
@ApiOperation(value = "所有讲师列表")
@GetMapping
private Result getTeacherList(){
//测试自定义异常
try {
int i=1/0;
}catch (Exception e){
throw new EbsException(ResultCode.ERROR,"除零错误!");
}
List<EbsTeacher> list = ebsTeacherService.list(null);
return Result.ok().data("list",list);
}
7、 日志
7.1 配置日志级别
日志记录器(Logger)的行为是分等级的。如下表所示:
分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
DEBUG:输出调试信息;指出细粒度信息事件对调试应用程序是非常有帮助的。
INFO: 输出提示信息;消息在粗粒度级别上突出强调应用程序的运行过程。
WARN: 输出警告信息;表明会出现潜在错误的情形。
ERROR:输出错误信息;指出虽然发生错误事件,但仍然不影响系统的继续运行。
FATAL: 输出致命错误;指出每个严重的错误事件将会导致应用程序的退出。
ALL level:打开所有日志记录开关;是最低等级的,用于打开所有日志记录。
OFF level:关闭所有日志记录开关;是最高等级的,用于关闭所有日志记录。
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别
# 设置日志级别
logging.level.root=WARN
这种方式只能将日志打印在控制台上
7.2 Logback日志
spring boot内部使用Logback作为日志实现的框架。
Logback和log4j非常相似,如果你对log4j很熟悉,那对logback很快就会得心应手。
7.2.1 配置logback日志
删除application.properties中的日志配置
安装idea彩色日志插件:grep-console
resources 中创建 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> |
7.3 将错误日志输出到文件
GlobalExceptionHandler.java 中
类上添加注解
@Slf4j
异常输出语句
log.error(e.getMessage());
7.4 将日志堆栈信息输出到文件
7.4.1 定义工具类
Yxzx_common下创建util包,创建ExceptionUtil.java工具类
package com.yxzx.ebs.common;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
public class ExceptionUtil {
public static String getMessage(Exception e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
// 将出错的栈信息输出到printWriter中
e.printStackTrace(pw);
pw.flush();
sw.flush();
} finally {
if (sw != null) {
try {
sw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (pw != null) {
pw.close();
}
}
return sw.toString();
}
}
7.4.2 调用
在全局异常处理类GlobalExceptionHandler中调用
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
.....
/**
* 自定义异常处理器
* @param e
* @return
*/
@ExceptionHandler(EbsException.class)
@ResponseBody
public Result error(EbsException e){
e.printStackTrace();
log.error(ExceptionUtil.getMessage(e));
return Result.error().code(e.getCode()).message(e.getMsg());
}
}
7.4.3 修改EduException类中
EbsException中创建toString方法
@Override
public String toString() {
return "EduException{" +
"message=" + this.getMsg() +
", code=" + code +
"}";
}
- 点赞
- 收藏
- 关注作者
评论(0)