项目构建-统一参数校验,统一结果响应,统一异常处理,统一错误处理,统一日志记录,统一生成api文档 一个后端接口大致分为四个部分组成:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response)。 这里用的是SpringBoot配置项目,本文讲解的重点是后端接口,所以只需要导入一个spring-boot-starter-web包就可以了: pom.xml: 本文还用了swagger来生成API文档,lombok来简化类,logback来生成日志。都不是必须的,可用可不用。 一个接口一般对参数(请求数据)都会进行安全校验,参数校验的重要性自然不必多说,那么如何对参数进行校验就有讲究了。 业务层校验 这还没有进行业务操作呢光是一个参数校验就已经这么多行代码,实在不够优雅。 使用Spring Validator 和 Hibernate Validator,这两套Validator来进行方便的参数校验! Validator可以非常方便的制定校验规则,并自动帮你完成校验。首先在入参里需要校验的字段加上注解,每个注解对应不同的校验规则,并可制定校验失败后的信息: 校验规则和错误提示信息配置完毕后,接下来只需要在接口需要校验的参数上加上@Valid注解,并添加BindResult参数 即可方便完成验证: 这样当请求数据传递到接口的时候Validator就自动完成校验了,校验的结果就会封装到BindingResult中去,如果有错误信息我们就直接返回给前端,业务逻辑代码也根本没有执行下去。 现在可以看一下参数校验效果。我们故意给这个接口传递一个不符合校验规则的参数,先传递一个错误数据给接口,故意将password这个字段不满足校验条件: 再来看一下接口的响应数据: 使用Validator+ BindingResult已经是非常方便实用的参数校验方式了,在实际开发中也有很多项目就是这么做的,不过这样还是不太方便,因为你每写一个接口都要添加一个BindingResult参数,然后再提取错误信息返回给前端。这样有点麻烦,并且重复代码很多(尽管可以将这个重复代码封装成方法)。 我们能否去掉BindingResult这一步呢?当然是可以的! 我们完全可以将BindingResult这一步给去掉: 去掉之后会发生什么事情呢?直接来试验一下,还是按照之前一样故意传递一个不符合校验规则的参数给接口。 此时我们观察控制台可以发现接口引发MethodArgumentNotValidException异常: 异常是引发了,可我们并没有编写返回错误信息的代码呀,那参数校验失败了会响应什么数据给前端呢? 我们来看一下刚才异常发生后接口响应的数据: 没错,是直接将整个错误对象相关信息都响应给前端了!这样就很难受,不过解决这个问题也很简单,就是我们接下来要讲的全局异常处理! (全局异常处理也叫统一异常处理) 该注解为spirngboot中统一异常处理的核心。 是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上。 该类中的设计思路: 我们现在就来演示一下对参数校验失败抛出的 MethodArgumentNotValidException 全局处理。 ExceptionControllerAdvice.java: 我们再来看下这次校验失败后的响应数据: 没错,这次返回的就是我们制定的错误提示信息! 以后我们再想写接口参数校验,就只需要在传参的成员变量上加上Validator校验规则的注解(比如 @NotNull(message = “用户id不能为空”) 在很多情况下,有一些异常我们需要手动抛出,比如在业务层中有些条件并不符合业务逻辑,这时候就可以手动抛出异常从而触发事务回滚。 我们现在就来开始写一个自定义异常:继承 RuntimeException。 APIException.java: 在刚才的全局异常处理类ExceptionControllerAdvice.java 中添加对我们自定义异常的声明(类似于注册原理): 这样就无论发生什么异常我们都能屏蔽掉,然后响应数据给前端。 现在我们规范好了参数校验方式和全局异常,自定义异常的处理方式,然而还没有规范响应数据! 比如我要获取一个分页信息数据,获取成功了呢,自然就返回的数据列表,获取失败了后台就会响应异常信息,即一个字符串,就是说前端开发者压根就不知道后端响应过来的数据会是啥样的!所以,统一响应数据是前后端规范中必须要做的! 在目前的前后端分离架构下,后端主要是一个RESTful API的数据接口。但是HTTP的状态码数量有限,而随着业务的增长,HTTP状态码无法很好地表示业务中遇到的异常情况。那么可以通过修改响应返回的JSON数据,让其带上一些固有的字段 这3个属性为固有属性,每次响应结果都会有带有它们。 统一数据响应第一步肯定要做的就是我们自己自定义一个响应体类,无论后台是运行正常还是发生异常,响应给前端的数据格式是不变的! 自定义一个统一的响应体类,ResultVO.java: 然后我们修改一下全局异常处理那的return的返回值: 我们再来看一下此时如果发生异常了会响应什么数据给前端: OK,这个异常信息响应就非常好了,状态码和响应说明还有错误提示数据都返给了前端,并且是所有异常都会返回相同的格式!异常这里搞定了,别忘了我们到接口那也要修改返回类型。 新增一个接口好来看看效果,视图层使用统一结果。 看一下如果响应正确返回的是什么效果: 这样无论是正确响应还是发生异常,响应数据的格式都是统一的,十分规范! 数据格式是规范了,不过响应码code和响应信息msg还没有规范呀! 自定义注解版: 要规范响应体中的响应码和响应信息用枚举简直再恰当不过了,我们现在就来创建一个响应码枚举类: ResultCode.java: 然后修改响应体的构造方法,让其只准接受响应码枚举来设置响应码和响应信息: 在ResultVO.java类中: 然后同时修改全局异常处理的响应码设置方式: 这样响应码和响应信息只能是枚举规定的那几个,就真正做到了响应数据格式、响应码和响应信息规范化、统一化! 接口返回统一响应体 + 异常也返回统一响应体,其实这样已经很好了,但还是有可以优化的地方。 Conrtoller增强的统一响应体处理类。 首先,先创建一个类加上注解使其成为全局处理类。然后继承ResponseBodyAdvice接口重写其中的方法,即可对我们的controller进行增强操作,具体看代码和注释。 新建ResponseControllerAdvice.java类: 注意:重写的这两个方法是用来在controller将数据进行返回前进行增强操作,supports方法要返回为true才会执行beforeBodyWrite方法,所以如果有些情况不需要进行增强操作可以在supports方法里进行判断。对返回数据进行真正的操作还是在beforeBodyWrite方法中,我们可以直接在该方法里包装数据,这样就不需要每个接口都进行数据包装了,省去了很多麻烦。 我们可以现在去掉接口的数据包装来看下效果: 然后我们来看下响应数据: getUser功能流程: 注意:上面方法里,beforeBodyWrite方法里包装数据无法对String类型的数据直接进行强转,所以要进行特殊处理。(注:只能捕获到Controller类里方法返回的结果) 特殊处理原因: 对于一个Controller中的方法来说,其返回值Data会被哪种MessageConverter处理取决于Data的类型: 对于@RestController下的方法来说,通常(除了String)的类型其返回的content-type=application/json,beforeBodyWrite返回值data将被converterType=MappingJackson2HttpMessageConverter处理 当Data为String类型时是特例,其content-type=text/plain, 可见,Data是String时converterType为StringHttpMessageConverter,若直接在beforeBodyWrite里将其包装为ResultVO类型的data,则会报错。 解决:如示例代码所示,根据converterType确定是否将data转为String类型。 SpringBoot 根据 HTTP 的请求头信息进行了不同的响应处理。 如果我们想,Web端发的请求则跳转到404,500或error等自定义页面,非Web端发的请求则返回统一JSON的结果。 浏览器访问一个不存在的页面时,springboot会默认返回一个错误页面。 比如: 随意访问不存在路径得到: 如果是其他客户端请求,如接口测试工具postman,会默认返回JSON数据。 根据SpringBoot 错误处理原理分析,得知最终返回的 JSON 信息是从一个 map 对象中转换出来的,那么只要能自定义 map 中的值,就可以自定义错误信息的 json 格式了。 自定义后,输入错误的地址响应是这样的: 项目总体结构: 日志的框架比较丰富,由于spring boot对logback的集成,因此推荐使用logback在项目中使用。 在yml配置里面规定相关的输出路径: 然后在需要的方法中使用 @Slf4j注解,和log对象来输出日志信息。 如图: knife4j 是为Java MVC框架集成Swagger 生成Api文档的增强解决方案,前身是swagger-bootstrap-ui。 所以我们推荐采用了swagger的新增强版knife4j来生成API接口文档。 pom.xml 引入依赖: 在这里我用的新版本的依赖。 springboot的配置文件和启动类不用做任何特殊配置,使用knife4j需要一个swagger的配置类。 knife4j/swagger的配置类: 可以看到,内容上和以前的swagger配置类没什么变化,唯一的变化是类注解需要比原来的swagger多加一个 @EnableSwaggerBootstrapUi。这样knife4j的所有配置都完成了。 以上有两个注解需要特别说明,如下: @EnableSwagger2 @EnableKnife4j swagger常用注解: 具体可参考:swagger常用注解说明。 默认访问地址:https:// 运行springboot项目,自动生成接口文档, 自此整个后端接口基本体系就构建完毕了 代码地址晚些会给出,如果觉得不错或者帮助到你,希望大家给个Star。 参考地址:
springboot/web项目优秀的后端接口体系,看一篇就够了
1. 前言
本文主要演示如何构建起一个优秀的后端接口体系,体系构建好了自然就有了规范,同时再构建新的后端接口也会十分轻松。2. 所需依赖包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.rudecrab</groupId> <artifactId>validation-and-exception-handler</artifactId> <version>0.0.1-SNAPSHOT</version> <name>validation-and-exception-handler</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--web依赖包,web应用必备--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--(新版本)swagger增强工具依赖包,方便生成接口文档。非必须导入--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <!--lombok依赖包,简化类。非必须导入--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3.统一参数校验
首先我们来看一下最常见的做法,就是在业务层进行参数校验:public String addUser(User user) { if (user == null || user.getId() == null || user.getAccount() == null || user.getPassword() == null || user.getEmail() == null) { return "对象或者对象字段不能为空"; } if (StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) { return "不能输入空字符串"; } if (user.getAccount().length() < 6 || user.getAccount().length() > 11) { return "账号长度必须是6-11个字符"; } if (user.getPassword().length() < 6 || user.getPassword().length() > 16) { return "密码长度必须是6-16个字符"; } if (!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$", user.getEmail())) { return "邮箱格式不正确"; } // 参数校验完毕后这里就写上业务逻辑 return "success"; }
(这两套Validator依赖包已经包含在前面所说的web依赖包里了,所以可以直接使用。)3.1 Validator + BindResult进行校验
@Data public class User { @NotNull(message = "用户id不能为空") private Long id; @NotNull(message = "用户账号不能为空") @Size(min = 6, max = 11, message = "账号长度必须是6-11个字符") private String account; @NotNull(message = "用户密码不能为空") @Size(min = 6, max = 11, message = "密码长度必须是6-16个字符") private String password; @NotNull(message = "用户邮箱不能为空") @Email(message = "邮箱格式不正确") private String email; }
@RestController @RequestMapping("user") public class UserController { @Autowired private UserService userService; @PostMapping("/addUser") public String addUser(@RequestBody @Valid User user, BindingResult bindingResult) { // 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里 for (ObjectError error : bindingResult.getAllErrors()) { return error.getDefaultMessage(); } return userService.addUser(user); } }
此时,传统的那种业务层里的校验代码就已经不需要了:public String addUser(User user) { // 直接编写业务逻辑 return "success"; }
{ "account": "12345678", "email": "123@qq.com", "id": 0, "password": "123" }
3.2 Validator + 自动抛出异常
@PostMapping("/addUser") public String addUser(@RequestBody @Valid User user) { return userService.addUser(user); }
4. 统一异常处理(全局异常处理)
参数校验失败会自动引发异常,又不想手动捕捉这个异常,又要对这个异常进行处理,那正好使用SpringBoot全局异常处理来达到一劳永逸的效果!4.1 @ControllerAdvice注解
4.2 全局异常处理类
@RestControllerAdvice public class ExceptionControllerAdvice { @ExceptionHandler(MethodArgumentNotValidException.class) public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { // 从异常对象中拿到ObjectError对象 ObjectError objectError = e.getBindingResult().getAllErrors().get(0); // 然后提取错误提示信息进行返回 return objectError.getDefaultMessage(); } }
,然后在参数上加上@Valid注解即可完成校验,校验失败会自动返回错误提示信息,无需任何其他代码!4.3 自定义全局异常类
那手动抛出异常最简单的方式就是throw new RuntimeException(“异常信息”)了,不过使用自定义异常 规范会更好一些。@Getter //只要getter方法,无需setter public class APIException extends RuntimeException { private int code; private String msg; public APIException() { this(1001, "接口错误"); } public APIException(String msg) { this(1001, msg); } public APIException(int code, String msg) { super(msg); this.code = code; this.msg = msg; } }
@ExceptionHandler(APIException.class) public String APIExceptionHandler(APIException e) { return e.getMsg(); }
5. 统一结果响应
5.1 自定义统一响应体
其中关键属性的用途如下:
@Getter public class ResultVO<T> { /** * 状态码,比如1000代表响应成功 */ private int code; /** * 响应信息,用来说明响应情况 */ private String msg; /** * 响应的具体数据 */ private T data; public ResultVO(T data) { this(1000, "success", data); } public ResultVO(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } }
@ExceptionHandler(APIException.class) public ResultVO<String> APIExceptionHandler(APIException e) { // 注意哦,这里返回类型是自定义响应体 return new ResultVO<>(e.getCode(), "响应失败", e.getMsg()); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResultVO<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { ObjectError objectError = e.getBindingResult().getAllErrors().get(0); // 注意哦,这里返回类型是自定义响应体 return new ResultVO<>(1001, "参数校验失败", objectError.getDefaultMessage()); }
(凡是你写了一个自定义的异常,就别忘了到接口那修改返回类型。)5.2 控制层返回测试
在controller中新增方法:@GetMapping("/getUser") public ResultVO<User> getUser() { User user = new User(); user.setId(1L); user.setAccount("12345678"); user.setPassword("12345678"); user.setEmail("123@qq.com"); return new ResultVO<>(user); }
基于Spring AOP的统一响应体的实现(注解版)6. 响应码枚举
@Getter public enum ResultCode { SUCCESS(1000, "操作成功"), FAILED(1001, "响应失败"), VALIDATE_FAILED(1002, "参数校验失败"), ERROR(5000, "未知错误"); private int code; private String msg; ResultCode(int code, String msg) { this.code = code; this.msg = msg; } }
// 返回具体数据 public ResultVO(T data) { this(ResultCode.SUCCESS, data); } // 返回对应的枚举响应码和响应信息,以及具体的数据。 public ResultVO(ResultCode resultCode, T data) { this.code = resultCode.getCode(); this.msg = resultCode.getMsg(); this.data = data; } // 返回对应的枚举响应码和响应信息。 public ResultVO(ResultCode resultCode) { this.code = resultCode.getCode(); this.msg = resultCode.getMsg(); }
@ExceptionHandler(APIException.class) public ResultVO<String> APIExceptionHandler(APIException e) { // 注意哦,这里传递的响应码枚举 return new ResultVO<>(ResultCode.FAILED, e.getMsg()); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResultVO<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { ObjectError objectError = e.getBindingResult().getAllErrors().get(0); // 注意哦,这里传递的响应码枚举 return new ResultVO<>(ResultCode.VALIDATE_FAILED, objectError.getDefaultMessage()); }
7. 全局处理响应数据
要知道一个项目下来定义的接口搞个几百个太正常不过了,要是每一个接口返回数据时都要用响应体来包装一下(return new ResultVO<>)好像有点麻烦,有没有办法省去这个包装过程呢?当然是有滴,还是要用到全局处理。7.1 响应增强类
package com.rudecrab.demo.config; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.rudecrab.demo.enums.ResultCode; import com.rudecrab.demo.exception.APIException; import com.rudecrab.demo.vo.ResultVO; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.Map; /** * @description 全局处理响应数据--响应增强类 * 接口返回统一响应体 + 异常也返回统一响应体, * 其实这样已经很好了,但还是有可以优化的地方。 * * 先创建一个类加上注解使其成为全局处理类。 * 然后继承ResponseBodyAdvice接口重写其中的方法, * 即可对我们的controller进行增强操作 */ // 可以改为@ControllerAdvice注解来拦截所有Controller的处理结果 @RestControllerAdvice(basePackages = {"com.rudecrab.demo.controller"}) // 注意哦,这里要加上需要扫描的包 public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> { // 1.关于哪些请求要执行beforeBodyWrite,返回true执行,返回false不执行 @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) { // 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作,返回false System.out.println("=================="+!returnType.getParameterType().equals(ResultVO.class)); return !returnType.getParameterType().equals(ResultVO.class); } // 2.如果接口返回的类型本身不是ResultVO,那就将原本的数据包装在ResultVO里再返回 @Override public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) { // String类型不能直接包装,所以要进行些特别的处理 if (returnType.getParameterType().equals(String.class)) { System.out.println(data instanceof String); ObjectMapper objectMapper = new ObjectMapper(); try { System.out.println("===========进入了包装数据======"); // 将String转换,再将数据包装在ResultVO里,再转换为json字符串响应给前端 return objectMapper.writeValueAsString(new ResultVO<>(data)); } catch (JsonProcessingException e) { throw new APIException("返回String类型错误"); } } // 将原本的数据包装在ResultVO里 return new ResultVO<>(data); } }
@GetMapping("/getUser") public User getUser() { User user = new User(); user.setId(1L); user.setAccount("12345678"); user.setPassword("12345678"); user.setEmail("123@qq.com"); // 注意哦,这里是直接返回的User类型,并没有用ResultVO进行包装 return user; } @ApiOperation("获得所有用户") @GetMapping("/getAllUser") public Map<String, List<User>> getAllUser() { User user1 = new User(); user1.setId(1L); user1.setAccount("12345678"); user1.setPassword("12345678"); user1.setEmail("123@qq.com"); User user2 = new User(); user2.setId(2L); user2.setAccount("9877986"); user2.setPassword("adasdasd"); user2.setEmail("456@qq.com"); List<User> list = new ArrayList<>(); list.add(user1); list.add(user2); Map<String, List<User>> map = new HashMap<>(); map.put("items", list); Set<String> keys = map.keySet(); //获取所有的key值 for(String key: keys){ System.out.println(key); } //用ResultVO进行包装了 //return new ResultVO<>(user); // 这里是直接返回的map,没用ResultVO进行包装,beforeBodyWrite会自动增强操作,给我们包装起来。 return map; }
成功对数据进行了包装!
前端发出接口请求》》controller处理请求》》同时ResponseControllerAdvice中添加了扫描控制层的全局异常处理类》》beforeBodyWrite规定了返回统一的结果体》》结果体ResultVO中定义了其内容包含有code,msg,data》》所以controller中处理的结果被增强操作,封装成了ResultVO中的格式》》由此达到了统一返回。
对于String类型springboot中默认会用org.springframework.http.converter.StringHttpMessageConverter处理,所以对String类型的数据是特殊情况,需特殊处理:
converterType=StringHttpMessageConverter
8. 统一错误处理
8.1 自定义错误页面:
如果我们需要自定义错误页面,就只需要在模版文件夹Template下的 error 文件夹下放4xx 或 5xx.html的网页即可。<!doctype html> <html lang="en" xmlns:th="https://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>[[${status}]]</title> <!-- Bootstrap core CSS --> <link href="/webjars/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="m-5" > <p>错误码:[[${status}]]</p> <p >信息:[[${message}]]</p> <p >时间:[[${#dates.format(timestamp,'yyyy-MM-dd hh:mm:ss ')}]]</p> <p >请求路径:[[${path}]]</p> </div> </body> </html>
8.2 自定义错误JSON返回:
默认是这样:
9. 统一日志记录
9.1 logback-spring.xml配置:
<?xml version="1.0" encoding="UTF-8" ?> <configuration debug="false" scan="true"> <!-- 日志级别,参考地址https://cloud.tencent.com/developer/article/1397693 --> <springProperty scope="context" name="LOG_ROOT_LEVEL" source="logging.level.root" defaultValue="DEBUG"/> <!-- 标识这个"STDOUT" 将会添加到这个logger, 使用springProperty才可使用application.yml中的值,也可以设置默认值 --> <springProperty scope="context" name="STDOUT" source="logging.stdout" defaultValue="STDOUT"/> <springProperty scope="context" name="logPath" source="logging.path" defaultValue="logs"/> <!-- 日志格式,%d:日期;%thread:线程名;%-5level:日志级别从左显示5个字符长度,列如:DEBUG; %logger{36}:java类名,例如:com.muses.taoshop.MyTest,36表示字符长度;%msg:日志内容;%d:换行 --> <property name="LOG_PATTERN" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <!-- root日志级别--> <property name="${LOG_ROOT_LEVEL}" value="DEBUG" /> <!-- 日志跟目录-对应了springProperty中对应的name,也是yml中对应设置的value --> <property name="LOG_HOME" value="${logPath}" /> <!-- 日志文件路径--> <property name="LOG_DIR" value="${LOG_HOME}/%d{yyyyMMdd}" /> <!-- 日志文件名称 --> <property name="LOG_PREFIX" value="portal" /> <!-- 日志文件编码 --> <property name="LOG_CHARSET" value="utf-8" /> <!-- 配置日志的滚动时间,保存时间为15天--> <property name="MAX_HISTORY" value="15" /> <!-- 文件大小,默认为10MB--> <property name="MAX_FILE_SIZE" value="10" /> <!-- 1.打印到控制台 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- 格式化日志内容--> <encoder> <pattern>${LOG_PATTERN}</pattern> </encoder> </appender> <!-- 2.打印所有日志,保存到文件--> <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--日志文件名--> <file>${LOG_HOME}/all_${LOG_PREFIX}.log</file> <!-- 设置滚动策略,当日志文件大小超过${MAX_FILE_SIZE}时,新的日志内容写到新的日志文件--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 新的日志文件路径名称,%d:日期 %i:i是变量 --> <fileNamePattern>${LOG_DIR}/all_${LOG_PREFIX}%d{yyyy-MM-dd}.%i.log</fileNamePattern> <!-- 保存日志15天 --> <maxHistory>${MAX_HISTORY}</maxHistory> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <!-- 日志文件的最大大小 --> <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 格式日志文件内容--> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>${LOG_PATTERN}</pattern> </layout> </appender> <!-- 3.打印错误日志,保存到文件--> <appender name="FILE_ERR" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--日志文件名--> <file>${LOG_HOME}/err_${LOG_PREFIX}.log</file> <!-- 设置滚动策略,当日志文件大小超过${MAX_FILE_SIZE}时,新的日志内容写到新的日志文件--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 新的日志文件路径名称,%d:日期 %i:i是变量 --> <fileNamePattern>${LOG_DIR}/err_${LOG_PREFIX}%d{yyyy-MM-dd}.%i.log</fileNamePattern> <!-- 保存日志15天 --> <maxHistory>${MAX_HISTORY}</maxHistory> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <!-- 日志文件的最大大小 --> <maxFileSize>${MAX_FILE_SIZE}</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 格式日志文件内容--> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>${LOG_PATTERN}</pattern> </layout> </appender> <!-- rest template logger--> <!--<logger name="org.springframework.web.client.RestTemplate" level="DEBUG" />--> <!--<logger name="org.springframework" level="DEBUG" />--> <!-- jdbc--> <!--<logger name="jdbc.sqltiming" level="DEBUG" />--> <logger name="org.mybatis" level="DEBUG" /> <!-- zookeeper--> <logger name="org.apache.zookeeper" level="ERROR" /> <!-- dubbo --> <logger name="com.alibaba.dubbo.monitor" level="ERROR"/> <logger name="com.alibaba.dubbo.remoting" level="ERROR" /> <!-- 日志输出级别,高到低:ERROR, WARN, INFO, DEBUG or TRACE 有时候我们要获取更多的日志信息,就可以降低日志级别 --> <root leve="${LOG_ROOT_LEVEL}"> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE_ALL" /> <appender-ref ref="FILE_ERR" /> </root> </configuration>
9.2 application.yml:
logging: config: classpath:./logback-spring.xml level: com.rudecrab.demo.controller: WARN path: ./logs
9.3 测试生成日志文件
import lombok.extern.slf4j.Slf4j; @Slf4j public class UserController { @PostMapping("/addUser") public String addUser(@RequestBody @Valid User user) { //日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出。 log.trace("日志输出 trace"); log.debug("日志输出 debug"); log.info("日志输出 info"); log.warn("日志输出 warn"); log.error("日志输出 error"); return userService.addUser(user); } }
以后项目运行,会在当前项目的./logs文件夹下生成日志文件。
10. knife4j生成API文档
knife4j的使用方法和swagger几乎一模一样。10.1 pom.xml 引入依赖
<!--(老版本)引用依赖包--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> <!--(新版本)swagger增强工具依赖包,方便生成接口文档。非必须导入--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency>
10.2 新建SwaggerConfig类:
package com.rudecrab.demo.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * 使用了其新版名称为:knife4j * @description swagger接口文档配置类 */ @Configuration @EnableSwagger2 public class SwaggerConfig { /** * 是否启用swagger文档 * enable 开启 disenable 关闭 * 默认访问地址:https://${yourhost}:${yourport}/doc.html */ @Value("${swagger.enable}") private boolean enable; @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .enable(enable) .apiInfo(apiInfo()) .select() // 这里配置要扫描的包,接口在哪个包就配置哪个包 .apis(RequestHandlerSelectors.basePackage("com.rudecrab.demo.controller")) .paths(PathSelectors.any()) .build(); } public ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("参数校验和统一异常处理Demo") .description("用来演示参数校验和统一异常处理") .termsOfServiceUrl("zoutao.info") .contact(new Contact("ZouTao", "", "zoutao6666@qq.com")) .version("1.0") .build(); } }
该注解是Springfox-swagger框架提供的使用Swagger注解,该注解必须加
该注解是knife4j提供的增强注解,Ui提供了例如动态参数、参数过滤、接口排序等增强功能,如果你想使用这些增强功能就必须加该注解,否则可以不用加10.3 添加对应的API信息:
10.4 查看生成的接口文档:
host:{port}/doc.html
访问地址: https://localhost:8080/doc.html
还可以在其中进行接口调试:
更多功能参考 knife4j官网。11. 总结
https://juejin.im/post/5e7ab0bae51d45271b749815#heading-13
https://juejin.im/post/5e073980f265da33f8653f2e#heading-2
https://segmentfault.com/a/1190000019795918
https://www.toutiao.com/a6755002314790011399/
https://www.cnblogs.com/zxg-6/p/12629821.html
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算