• 项目统一处理


    统一结果返回

    1、统一自定义返回状态码

    /**
     * 统一自定义返回状态码
     * @author bigfairy
     * @date  2021-01-28
     */
    @Getter
    @AllArgsConstructor
    public enum ResultEnum {
        /**
         * 自定义状态码
         */
        SUCCESS(1000, "操作成功"),
    	FAILURE(1001,"操作失败"),
    	UNKNOWN_ERROR(1002,"未知错误"),
        SERVER_ERROR(1003,"服务端异常"),
    	NULL_POINT(1004,"空指针异常"),
        PARAM_ERROR(1005, "参数校验异常:%s");
    
        /** 响应码 */
        private final Integer code;
        /** 响应消息 */
        private final String msg;
    }
    

    2、统一封装返回对象

    /**
     * 统一封装返回对象
     * @author bigfairy
     * @date  2021-01-28
     */
    @Data
    public class ResultVO implements Serializable {
        /** 响应码 */
        private Integer code;
    
        /** 响应消息 */
        private String msg;
    
        /** 响应内容 */
        private Object data;
    
       /**
        * 成功
        * @return result
        */
       public static ResultVO success() {
          return  createResult(null,ResultEnum.SUCCESS);
       }
    
       /**
        * 成功返回数据
        * @param object 响应内容
        * @return result
        */
       public static ResultVO success(Object object) {
          return  createResult(object,ResultEnum.SUCCESS);
       }
       /**
        * 失败返回未知错误
        * @return result
        */
       public static ResultVO fail() {
          return  createResult(null,ResultEnum.FAILURE);
       }
    
       /**
        * 失败返回未知错误
        * @param data 响应内容
        * @return result
        */
       public static ResultVO fail(Object data) {
          return  createResult(data,ResultEnum.FAILURE);
       }
    
       /**
        * 失败返回自定义错误信息
        * @param resultEnum  自定义枚举
        * @return result
        */
       public static ResultVO fail(ResultEnum resultEnum) {
          return  createResult(null,resultEnum);
       }
    
       /**
        * 失败返回自定义错误信息
        * @param data 响应内容
        * @param resultEnum  自定义枚举
        * @return result
        */
       public static ResultVO fail(Object data,ResultEnum resultEnum) {
          return  createResult(data,resultEnum);
       }
    
       /**
        * 使用链式编程 自定义响应消息
        * @param msg 自定义响应消息
        * @return result
        */
       public ResultVO msg(String msg) {
          this.setMsg(msg);
          return this;
       }
    
       /**
        *
        * @param data 响应内容
        * @param resultEnum 自定义枚举
        * @return result
        */
       private static ResultVO createResult(Object data,ResultEnum resultEnum){
          ResultVO result = new ResultVO();
          result.setData(data);
          result.setCode(resultEnum.getCode());
          result.setMsg(resultEnum.getMsg());
          return result;
       }
    }
    

    3、自定义响应消息

    /**
     * 自定义响应消息
     * @author bigfairy
     * @date  2021-01-28
     */
    public interface ResultMsg {
       String DEFAULT_MESSAGE = "自定义响应消息";
    }
    

    4、测试

    /**
     * 全局统一返回测试
     */
    @RestController
    @RequestMapping("/results")
    public class ResultController {
    
       @GetMapping("/test1")
       public ResultVO test1(){
          return ResultVO.success();
       }
    
       @GetMapping("/test2")
       public ResultVO test2(){
          UserVO userVO = new UserVO();
          userVO.setUserName("测试");
          userVO.setMobileNum("13141314520");
          userVO.setSex(1);
          userVO.setAge(23);
          userVO.setEmail("592188043@qq.com");
          return ResultVO.success(userVO);
       }
    
       @GetMapping("/test3")
       public ResultVO test3(){
          UserVO userVO = new UserVO();
          userVO.setUserName("测试");
          userVO.setMobileNum("13141314520");
          userVO.setSex(1);
          userVO.setAge(23);
          userVO.setEmail("592188043@qq.com");
          List<UserVO> userVOList = new ArrayList<>();
          for (int i = 0; i < 4; i++) {
             userVOList.add(userVO);
          }
          return ResultVO.success(userVOList);
       }
    
       @GetMapping("/test4")
       public ResultVO test4(){
          return ResultVO.fail();
       }
    
       @GetMapping("/test5")
       public ResultVO test5(){
          return ResultVO.fail(ResultEnum.SERVER_ERROR);
       }
    
       @GetMapping("/test6")
       public ResultVO test6(){
          return ResultVO.success().msg(ResultMsg.DEFAULT_MESSAGE);
       }
    }
    

    统一异常处理

    1、全局异常处理

    /**
     * 全局异常处理
     * @author bigfairy
     * @date  2021-01-28
     */
    public class GlobalException extends RuntimeException{
    
        private static final long serialVersionUID = -4204734806795919275L;
    
        public GlobalException(ResultEnum resultEnum) {
            super(resultEnum.getMsg());
        }
        public GlobalException(String message) {
            super(message);
        }
    
        @Override
        public String toString() {
            return "GlobalException{message=" + this.getMessage() + '}';
        }
    }
    

    2、将异常信息写到日志文件中

    /**
     * 将异常信息写到日志文件中
     * @author bigfairy
     * @date  2021-01-28
     */
    @Slf4j
    public class GlobalExceptionUtil {
        /**
         * 打印异常信息 基于JDK 1.7之后,实现正确关闭流资源方法
         * try - with - resource
         */
        public static String getMessage(Exception e) {
            String swStr = null;
            try (
             StringWriter sw = new StringWriter();
             PrintWriter pw = new PrintWriter(sw)
            ) {
                e.printStackTrace(pw);
                pw.flush();
                sw.flush();
                swStr = sw.toString();
                log.error(swStr);
            } catch (IOException ex) {
                ex.printStackTrace();
                log.error(ex.getMessage());
            }
            return swStr;
        }
    }
    

    3、全局异常处理返回信息

    /**
     * 全局异常处理返回信息
     * @author bigfairy
     * @date  2021-01-28
     */
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        /**-------- 通用异常处理方法 --------**/
        @ExceptionHandler(Exception.class)
        public ResultVO error(Exception e) {
            String message = GlobalExceptionUtil.getMessage(e);
            return ResultVO.fail(message,ResultEnum.UNKNOWN_ERROR);
        }
    
        /**-------- 指定异常处理方法 --------**/
        @ExceptionHandler(NullPointerException.class)
        public ResultVO error(NullPointerException e) {
            String message = GlobalExceptionUtil.getMessage(e);
            return ResultVO.fail(message,ResultEnum.NULL_POINT);
        }
    
        /**-------- 自定义定异常处理方法 --------**/
        @ExceptionHandler(GlobalException.class)
        public ResultVO error(GlobalException e) {
            String message = GlobalExceptionUtil.getMessage(e);
            return ResultVO.fail(message).msg(e.getMessage());
        }
    
        /**--------  body raw参数校验处理方法 --------**/
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResultVO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
            return handleBodyException(e);
        }
        /**--------  body form-data参数校验处理方法 --------**/
        @ExceptionHandler(BindException.class)
        public ResultVO handleBindException(BindException e) {
            return handleBodyException(e);
        }
    
        private ResultVO handleBodyException(BindException e) {
            GlobalExceptionUtil.getMessage(e);
            List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
            Map<String,String> map = new HashMap<>();
            for (FieldError fieldError : fieldErrors) {
                map.put(fieldError.getField(),fieldError.getDefaultMessage());
            }
            return ResultVO.fail(map).msg("参数验证错误");
        }
    
        /**--------  Query Params参数校验处理方法 --------**/
        @ExceptionHandler(ConstraintViolationException.class)
        public ResultVO handleConstraintViolationException(ConstraintViolationException e) {
            GlobalExceptionUtil.getMessage(e);
            Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
            Map<String,String> map = new HashMap<>();
            for (ConstraintViolation<?> violation : violations) {
                String[] split = violation.getPropertyPath().toString().split("\.");
                String message = violation.getMessage();
                map.put(split[1],message);
            }
            return ResultVO.fail(map).msg("参数验证错误");
        }
    }
    

    4、测试

    /**
     * 全局异常测试
     */
    @RestController
    @RequestMapping("/exceptions")
    public class ExceptionController {
       @GetMapping("/test1")
       public ResultVO test1(){
          int a =  1/0;
          return ResultVO.success();
       }
    
       @GetMapping("/test2")
       public ResultVO test2(){
    //    throw new GlobalException(ResultEnum.SERVER_ERROR);
          throw new GlobalException("自定义异常提示");
       }
    
       @GetMapping("/test3")
       public ResultVO test3(){
          throw new NullPointerException();
       }
    }
    

    统一参数校验

    1、添加依赖

    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>
    

    2、注解介绍

    validator内置注解

    注解 详细信息
    @Null 被注释的元素必须为 null
    @NotNull 被注释的元素必须不为 null
    @AssertTrue 被注释的元素必须为 true
    @AssertFalse 被注释的元素必须为 false
    @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Size(max, min) 被注释的元素的大小必须在指定的范围内
    @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past 被注释的元素必须是一个过去的日期
    @Future 被注释的元素必须是一个将来的日期
    @Pattern(value) 被注释的元素必须符合指定的正则表达式

    Hibernate Validator 附加的 constraint

    注解 详细信息
    @Email 被注释的元素必须是电子邮箱地址
    @Length 被注释的字符串的大小必须在指定的范围内
    @NotEmpty 被注释的字符串的必须非空
    @NotBlank 被验证字符串非null,且长度必须大于0
    @Range 被注释的元素必须在合适的范围内

    注意

    • @NotNull 适用于任何类型被注解的元素必须不能与NULL
    • @NotEmpty 适用于String Map或者数组不能为Null且长度必须大于0
    • @NotBlank 只能用于String上面 不能为null,调用trim()后,长度必须大于0

    3、全局异常处理

    如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。代码如下

    /**--------  body raw参数校验处理方法 --------**/
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultVO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        return handleBodyException(e);
    }
    /**--------  body form-data参数校验处理方法 --------**/
    @ExceptionHandler(BindException.class)
    public ResultVO handleBindException(BindException e) {
        return handleBodyException(e);
    }
    
    private ResultVO handleBodyException(BindException e) {
        GlobalExceptionUtil.getMessage(e);
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        Map<String,String> map = new HashMap<>();
        for (FieldError fieldError : fieldErrors) {
            map.put(fieldError.getField(),fieldError.getDefaultMessage());
        }
        return ResultVO.fail(map).msg("参数验证错误");
    }
    
    /**--------  Query Params参数校验处理方法 --------**/
    @ExceptionHandler(ConstraintViolationException.class)
    public ResultVO handleConstraintViolationException(ConstraintViolationException e) {
        GlobalExceptionUtil.getMessage(e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        Map<String,String> map = new HashMap<>();
        for (ConstraintViolation<?> violation : violations) {
            String[] split = violation.getPropertyPath().toString().split("\.");
            String message = violation.getMessage();
            map.put(split[1],message);
        }
        return ResultVO.fail(map).msg("参数验证错误");
    }
    

    4、常用校验

    通常有这三种方式获取参数,所以对这些形式进行验证

    1、请求路径参数

    • @PathVariable(不能为空,不能设置默认值,因为null对于url是无意义的)

      获取路径参数。即url/{id}这种形式。

    • @RequestParam

      获取查询参数。即url?name=这种形式

    2、Body参数

    • @PostMapping组合@RequestBody注解

    • @PostMapping无@RequestBody注解

    3、请求头参数以及Cookie

    • @RequestHeader
    • @CookieValue

    测试demo如下:

    @Getter
    @Setter
    public class UserVO {
    
       @NotBlank(message = "用户姓名不能为空1")
       @NotNull(message = "用户姓名不能为空2")
       private String userName;
    
       @NotBlank(message = "手机号不能为空1")
       @NotNull(message = "手机号不能为空2")
       private String mobileNum;
    
       @NotNull(message = "性别不能为空")
       private Integer sex;
    
       @NotNull(message = "年龄不能为空")
       private Integer age;
    
       @NotBlank(message = "邮箱不能为空")
       @NotNull(message = "邮箱不能为空")
       @Email(message = "邮箱格式错误")
       private String email;
    }
    
    /**
     * 全局参数校验测试
     */
    @Log4j2
    @Validated
    @RestController
    @RequestMapping("/validators")
    public class ValidatorController {
    	/**
    	 * 验证@PostMapping+@RequestBody形式
    	 * @param userVO
    	 * @return
    	 */
    	@PostMapping("/test1")
    	public ResultVO test1(@RequestBody @Validated UserVO userVO){
    		return ResultVO.success();
    	}
    
    	/**
    	 * 验证@PostMapping无@RequestBody形式
    	 * @param userVO
    	 * @return
    	 */
    	@PostMapping("/test2")
    	public ResultVO test2(@Validated UserVO userVO){
    		return ResultVO.success();
    	}
    
    	/**
    	 * 验证@RequestParam接收参数形式
    	 * @param userId
    	 * @return
    	 */
    	@PostMapping("/test3")
    	public ResultVO test3(@NotBlank(message = "用户ID不能为空") @RequestParam("userId") String userId){
    		log.info(userId);
    		return ResultVO.success();
    	}
    
    	/**
    	 * 验证@RequestParam接收参数形式
    	 * @param userId
    	 * @return
    	 */
    	@GetMapping("/test4")
    	public ResultVO test4(@NotBlank(message = "用户ID不能为空") @RequestParam("userId")  String userId){
    		log.info(userId);
    		return ResultVO.success();
    	}
    
    	/**
    	 * 验证@RequestParam接收多个参数形式
    	 * @param userId
    	 * @return
    	 */
    	@GetMapping("/test5")
    	public ResultVO test5(@NotBlank(message = "用户ID不能为空") @RequestParam("userId") String userId,
    						  @NotBlank(message = "用户名字不能为空") @RequestParam("userName") String userName){
    		log.info(userId);
    		return ResultVO.success();
    	}
    
    
    	/**
    	 * 验证@PathVariable接收参数形式
    	 * @param userId
    	 * @return
    	 */
    	@GetMapping("/test6/{userId}")
    	public ResultVO test6(@Length(min = 6, max = 20,message = "用户Id长度需要在6和20之间")  @PathVariable String userId){
    		log.info(userId);
    		return ResultVO.success();
    	}
    
    	/**
    	 * 验证@PathVariable接收参数形式
    	 * @param userId
    	 * @return
    	 */
    	@GetMapping("/test7/{userId}/{mobilePhone}")
    	public ResultVO test7(@Length(min = 8,message = "userId长度最短为8位") @PathVariable String userId,
    						  @Length(min = 11, max = 11, message = "手机号只能为11位") @PathVariable String mobilePhone){
    		log.info("用户id:{},用户电话:{}",userId,mobilePhone);
    		return ResultVO.success();
    	}
    }
    

    注意:

    对@PostMapping请求验证参数,直接在参数上添加@Validated或者@Valid

    对@RequestParam验证参数,直接在参数上添加@Validated是无效的,使用@Valid也无效,需要在类上添加@Validated,然后在参数上使用注解验证。

    5、分组校验

    在实际项目中,可能多个方法需要使用同一个对象来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在对象的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。比如保存User的时候,UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。

    1、约束注解上声明适用的分组信息groups

    @Getter
    @Setter
    public class UserVO {
       @Email(message = "邮箱格式错误", groups = {Save.class, Update.class})
       private String email;
    
       /**
        * 保存的时候校验分组
        */
       public interface Save {
       }
    
       /**
        * 更新的时候校验分组
        */
       public interface Update {
       }
    }
    

    2、@Validated注解上指定校验分组

    /**
     * 分组校验测试
     * @param userVO
     * @return
     */
    @PostMapping("/test8")
    public ResultVO test8(@RequestBody @Validated(Save.class) UserVO userVO){
       return ResultVO.success();
    }
    
    /**
     * 分组校验测试
     * @param userVO
     * @return
     */
    @PutMapping("/test9")
    public ResultVO test9(@RequestBody @Validated(Update.class) UserVO userVO){
       return ResultVO.success();
    }
    

    6、嵌套校验

    在嵌套的类上必须标记@Valid注解,如要对UserVO的属性CarVO的carNum校验,UserVO引入CarV0须标记@Valid注解

    @Getter
    @Setter
    public class CarVO {
    	@NotBlank(message = "车牌号",groups = Save.class)
    	private String carNum;
    }
    
    @Getter
    @Setter
    public class UserVO {
    
       @NotBlank(message = "用户姓名不能为空1")
       @NotNull(message = "用户姓名不能为空2")
       private String userName;
    
       @Valid
       @NotNull(message = "car不能为空" ,groups = Save.class)
       private CarVO car;
    }
    
    /**
     * 嵌套校验测试
     * @param userList
     * @return
     */
    @PostMapping("/test11")
    public ResultVO test11(@RequestBody @Validated(Save.class) ValidationList<UserVO> userList) {
       // 校验通过,才会执行业务逻辑处理
       return ResultVO.success();
    }
    

    7、集合校验

    如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数:

    包装List类型,并声明@Valid注解

    /**
     * 集合校验
     * @param <E>
     */
    @Getter
    @Setter
    public class ValidationList<E> implements List<E> {
    
    	@Delegate
    	@Valid
    	public List<E> list = new ArrayList<>();
    }
    
    /**
     * 集合校验测试
     * @param userList
     * @return
     */
    @PostMapping("/test10")
    public ResultVO test10(@RequestBody @Validated(Save.class) ValidationList<UserVO> userList) {
       // 校验通过,才会执行业务逻辑处理
       return ResultVO.success();
    }
    

    8、自定义校验

    Hibernate Validator提供的校验无法满足当前需求时,可以使用Validator自定义注解进行特殊校验。

    1、自定义电话校验

    @Documented
    @Target({ElementType.PARAMETER, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = PhoneValid.class)
    public @interface Phone {
    
        String message() default "电话格式错误";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    

    这个注解是作用在Field字段上,运行时生效,触发的是PhoneValid这个验证类。

    • message 定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制
    • groups 这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作
    • payload 主要是针对bean的,使用不多。

    2、电话真正进行验证的逻辑代码

    public class PhoneValid implements ConstraintValidator<Phone,String> {
    
        @Override
        public void initialize(Phone phone) {
    
        }
        @Override
        public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
            if(StringUtils.isNotEmpty(value)) {
                return isMobile(value);
            }else{
                return false;
            }
        }
        public static boolean isMobile(String str) {
            Pattern p = null;
            Matcher m = null;
            boolean b = false;
            String s2="^[1](([3|5|8][\d])|([4][4,5,6,7,8,9])|([6][2,5,6,7])|([7][^9])|([9][1,8,9]))[\d]{8}$";// 验证手机号
            if(StringUtils.isNotEmpty(str)){
                p = Pattern.compile(s2);
                m = p.matcher(str);
                b = m.matches();
            }
            return b;
        }
    }
    

    要对异常MethodArgumentNotValidException进行拦截处理。

    3、测试

    /**
     * 自定义校验测试
     * @param userVO
     * @return
     */
    @PostMapping("/test12")
    public ResultVO test12(@RequestBody @Validated UserVO userVO) {
       // 校验通过,才会执行业务逻辑处理
       return ResultVO.success();
    }
    

    补充身份证验证

    @Documented
    @Target({ElementType.PARAMETER, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = IdCardValid.class)
    public @interface IdCard {
    
        String message() default "身份证号码不合法";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
    }
    
    /**
     * 身份证校验
     */
    @Log4j2
    public class IdCardValid implements ConstraintValidator<IdCard, String> {
        @Override
        public void initialize(IdCard idCard) {
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
          log.info("验证的身份证号码:{}",value);
          // 使用HuTool身份证验证工具
          return IdcardUtil.isValidCard(value);
        }
    }
    

    统一日志处理

    日志的框架比较丰富,spring boot集成了logback,因此推荐使用logback在项目中使用。

    配置

    在resources里创建日志配置文件logback-spring.xml,配置内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
    	日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出
    	scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
    	scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
    	debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
    -->
    <configuration debug="true" scanPeriod="60 seconds" scan="false">
    	<springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>
    	<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
    	<property name="log.path" value="/DevEnvironment/workspace/IdeaProjects/demo/logs/${spring.application.name}"/>
    	<!-- 彩色日志格式 -->
    	<property name="CONSOLE_LOG_PATTERN"
    			  value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    	<!-- 彩色日志依赖的渲染类 -->
    	<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    	<conversionRule conversionWord="wex"
    					converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    	<conversionRule conversionWord="wEx"
    					converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    	<!-- 1、控制台日志输出 -->
    	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    		<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
    		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    			<level>debug</level>
    		</filter>
    		<encoder>
    			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
    			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
    		</encoder>
    	</appender>
    	<!-- 2、输出到文档 -->
    	<!-- 2.1 level为 DEBUG 日志,时间滚动输出  -->
    	<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<!-- 记录日志文件的路径及文件名 -->
    		<file>${log.path}/debug.log</file>
    		<!--日志文档输出格式-->
    		<encoder>
    			<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
    			<charset>UTF-8</charset> <!-- 设置字符集 -->
    		</encoder>
    		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    			<!--
    			   file节点指定当前写日志文件的路径
    			   fileNamePattern节点指定归档的日志文件的路径
    			   从而将当前日志文件或归档日志文件置不同的目录。
    			   %d{yyyy-MM, aux}每天的日志按月份划分存储在不同的路径下,aux说明是并非主要的滚动参数,主要是用来做文件夹分割。
    			   %d{yyyy-MM-dd}指定日期格式,
    			   %i指定索引
    			   下面表示每天的日志按照月份进行归档,超过10M,日志文件会以索引0开始分开归档
               -->
    			<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
    			<!--
                   日志文件不能超过10M,若超过10M,日志文件会以索引0开始,
                   命名日志文件,例如log-error-2019-06-03.0.log
               -->
    			<maxFileSize>10MB</maxFileSize>
    			<!--日志文件保留天数-->
    			<maxHistory>15</maxHistory>
    		</rollingPolicy>
    		<!-- 此日志文档只记录debug级别的 -->
    		<filter class="ch.qos.logback.classic.filter.LevelFilter">
    			<level>debug</level>
    			<onMatch>ACCEPT</onMatch>
    			<onMismatch>DENY</onMismatch>
    		</filter>
    	</appender>
    
    	<!-- 2.2 level为 INFO 日志,时间滚动输出  -->
    	<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<!-- 正在记录的日志文档的路径及文档名 -->
    		<file>${log.path}/info.log</file>
    		<!--日志文档输出格式-->
    		<encoder>
    			<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
    			<charset>UTF-8</charset>
    		</encoder>
    		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    			<!-- 每天日志归档路径以及格式 -->
    			<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
    			<maxFileSize>10MB</maxFileSize>
    			<!--日志文档保留天数-->
    			<maxHistory>15</maxHistory>
    		</rollingPolicy>
    		<!-- 此日志文档只记录info级别的 -->
    		<filter class="ch.qos.logback.classic.filter.LevelFilter">
    			<level>info</level>
    			<onMatch>ACCEPT</onMatch>
    			<onMismatch>DENY</onMismatch>
    		</filter>
    	</appender>
    
    	<!-- 2.3 level为 WARN 日志,时间滚动输出  -->
    	<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<!-- 正在记录的日志文档的路径及文档名 -->
    		<file>${log.path}/warn.log</file>
    		<!--日志文档输出格式-->
    		<encoder>
    			<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
    			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
    		</encoder>
    		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    			<!-- 每天日志归档路径以及格式 -->
    			<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/warn.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
    			<maxFileSize>10MB</maxFileSize>
    			<!--日志文档保留天数-->
    			<maxHistory>15</maxHistory>
    		</rollingPolicy>
    		<!-- 此日志文档只记录warn级别的 -->
    		<filter class="ch.qos.logback.classic.filter.LevelFilter">
    			<level>warn</level>
    			<onMatch>ACCEPT</onMatch>
    			<onMismatch>DENY</onMismatch>
    		</filter>
    	</appender>
    
    	<!-- 2.4 level为 ERROR 日志,时间滚动输出  -->
    	<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<!-- 正在记录的日志文档的路径及文档名 -->
    		<file>${log.path}/error.log</file>
    		<!--日志文档输出格式-->
    		<encoder>
    			<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
    			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
    		</encoder>
    		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    			<!-- 每天日志归档路径以及格式 -->
    			<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
    			<maxFileSize>100MB</maxFileSize>
    			<!--日志文档保留天数-->
    			<maxHistory>15</maxHistory>
    		</rollingPolicy>
    		<!-- 此日志文档只记录ERROR级别的 -->
    		<filter class="ch.qos.logback.classic.filter.LevelFilter">
    			<level>ERROR</level>
    			<onMatch>ACCEPT</onMatch>
    			<onMismatch>DENY</onMismatch>
    		</filter>
    	</appender>
    	<!--
                <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
                以及指定<appender>。<logger>仅有一个name属性,
                一个可选的level和一个可选的addtivity属性。
                name:用来指定受此logger约束的某一个包或者具体的某一个类。
                level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
                      还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
                      如果未设置此属性,那么当前logger将会继承上级的级别。
                addtivity:是否向上级logger传递打印信息。默认是true。
                <logger name="org.springframework.web" level="info"/>
                <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
            -->
    
    	<!--
            使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
            第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
            第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
            【logging.level.org.mybatis=debug logging.level.dao=debug】
         -->
    
    	<!--
            root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
            不能设置为INHERITED或者同义词NULL。默认是DEBUG
            可以包含零个或多个元素,标识这个appender将会添加到这个logger。
        -->
    	<!-- 4. 最终的策略 -->
    	<!-- 4.1 开发环境:打印控制台-->
    	<springProfile name="dev">
    		<logger name="com.example.demo" level="info"/>
    		<root level="debug">
    			<appender-ref ref="CONSOLE" />
    			<appender-ref ref="DEBUG_FILE" />
    			<appender-ref ref="INFO_FILE" />
    			<appender-ref ref="WARN_FILE" />
    			<appender-ref ref="ERROR_FILE" />
    		</root>
    	</springProfile>
    
    	<!-- 4.2 生产环境:输出到文档-->
    	<springProfile name="pro">
    		<logger name="com.example.demo" level="warn"/>
    		<root level="info">
    			<appender-ref ref="ERROR_FILE" />
    			<appender-ref ref="WARN_FILE" />
    		</root>
    	</springProfile>
    </configuration>
    

    归档日志效果

    alt

    注意

    • 日志的环境即spring.profiles.acticve,跟随项目启动;
    • 启动后,即可到自定目录查找到生成的日志文件;
    • 本地idea调试时,推荐Grep Console插件可实现控制台的自定义颜色输出,插件地址:https://plugins.jetbrains.com/plugin/7125-grep-console

    统一处理Web请求日志

    统一处理Web请求的日志,方便排查问题

    /**
     * AOP统一处理Web请求日志
     */
    @Log4j2
    @Aspect
    @Order(1)
    @Component
    public class WebLogAspect {
       // 记录请求执行的开始时间
       ThreadLocal<Long> startTime = new ThreadLocal<>();
    
        @Pointcut("execution(public * com.bigfairy.springboot.common.controller.*.*(..))")
        public void webLogPoint(){}
    
       /**
        * 记录Web请求日志
        * @param joinPoint
        */
        @Before("webLogPoint()")
        public void doBefore(JoinPoint joinPoint){
          startTime.set(System.currentTimeMillis());
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
          Optional.ofNullable(attributes).ifPresent(a->{
             HttpServletRequest request = attributes.getRequest();
             // 记录下请求内容
             log.info("URL : {}",request.getRequestURL().toString());
             log.info("HTTP_METHOD : {}",request.getMethod());
             log.info("IP : {}",request.getRemoteAddr());
             log.info("CLASS_METHOD : {}",joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
             log.info("ARGS : {}",JSONUtil.toJsonStr(joinPoint.getArgs()));
          });
        }
        
       /**
        * 记录Web响应日志
        * @param result 返回结果
        */
        @AfterReturning(returning = "result", pointcut = "webLogPoint()")
        public void doAfterReturning(Object result){
            // 处理完请求,返回内容
            log.info("RESPONSE : {} ",JSONUtil.toJsonStr(result));
          log.info("SPEND_TIME : {}ms",System.currentTimeMillis() - startTime.get());
        }
    }
    

    统一空指针处理

    Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

    Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

    Optional 类的引入很好的解决空指针异常。

    类方法

    序号 方法 & 描述
    1 static Optional empty()返回空的 Optional 实例。
    2 boolean equals(Object obj)判断其他对象是否等于 Optional。
    3 Optional filter(Predicate<? super predicate)如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
    4 Optional flatMap(Function<? super T,Optional> mapper)如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
    5 T get()如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
    6 int hashCode()返回存在值的哈希码,如果值不存在 返回 0。
    7 void ifPresent(Consumer<? super T> consumer)如果值存在则使用该值调用 consumer , 否则不做任何事情。
    8 boolean isPresent()如果值存在则方法会返回true,否则返回 false。
    9 Optional map(Function<? super T,? extends U> mapper)如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
    10 static Optional of(T value)返回一个指定非null值的Optional。
    11 static Optional ofNullable(T value)如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
    12 T orElse(T other)如果存在该值,返回值, 否则返回 other。
    13 T orElseGet(Supplier<? extends T> other)如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。
    14 T orElseThrow(Supplier<? extends X> exceptionSupplier)如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
    15 String toString()返回一个Optional的非空字符串,用来调试

    实战案例

    demo1

    public String getCity(User user)  throws Exception{
        if(user!=null){
            if(user.getAddress()!=null){
                Address address = user.getAddress();
                if(address.getCity()!=null){
                	return address.getCity();
                }
            }
        }
        throw new Excpetion("取值错误"); 
    }
    
    public String getCity(User user) throws Exception{
        return Optional.ofNullable(user)
                       .map(u-> u.getAddress())
                       .map(a->a.getCity())
                       .orElseThrow(()->new Exception("取指错误"));
    }
    

    demo2

    if(user!=null){
        dosomething(user);
    }
    
     Optional.ofNullable(user)
        .ifPresent(u->{
            dosomething(u);
    });
    

    demo3

    public User getUser(User user) throws Exception{
        if(user!=null){
            String name = user.getName();
            if("zhangsan".equals(name)){
                return user;
            }
        }else{
            user = new User();
            user.setName("zhangsan");
            return user;
        }
    }
    
    public User getUser(User user) {
        return Optional.ofNullable(user)
                       .filter(u->"zhangsan".equals(u.getName()))
                       .orElseGet(()-> {
                            User user1 = new User();
                            user1.setName("zhangsan");
                            return user1;
                       });
    }
    

    采用这种链式编程,虽然代码优雅了。但是,逻辑性没那么明显,可读性有所降低。

  • 相关阅读:
    数据表格优化
    vue数组和对象的监听变化
    python flask框架搭建以及大佬代码参考
    简单爬虫
    srs的基本配置
    记录飞天程序库调用
    面试题 递归算法1+2+....+100求和
    下载列表组件
    Prometheus之系统安装,启动
    nginx之日志
  • 原文地址:https://www.cnblogs.com/bigfairy/p/14373457.html
Copyright © 2020-2023  润新知