优秀的编程知识分享平台

网站首页 > 技术文章 正文

统一异常处理(统一异常处理器)

nanyue 2024-09-03 16:19:22 技术文章 11 ℃

在当前rest-api的大行下,还有微服务的加持下。统一异常处理组件非常重要。那么怎么样来进行统一异常处理呢?首先异常按产生阶段分为进入controller前和service中;异常也会分类如自定义异常(UserNotFoundException\ParameterCheckException)和系统提供异常等;

指导原则:所以异常处理就是统一对不同阶段、不同异常进行处理。

目标消灭95%以上的 try catch 代码块,以优雅的 Assert(断言) 方式来校验业务的异常情况,只关注业务逻辑,而不用花费大量精力写冗余的 try catch 代码块。

优雅抛出异常用 Assert(断言) 替换 throw exception。使用断言进行异常抛出让我们写的代码有一种非一般丝滑的感觉。

陌生异常类

controller上一层相关异常类:

NoHandlerFoundException:
HttpRequestMethodNotSupportedException:如后台需要get接收,前台axios调用使用post会出此异常
HttpMediaTypeNotSupportedException:
MissingPathVariableException:
MissingServletRequestParameterException:
TypeMismatchException:
HttpMessageNotReadableException:
HttpMessageNotWritableException:
BindException:
MethodArgumentNotValidException:
HttpMediaTypeNotAcceptableException:
ServletRequestBindingException:
ConversionNotSupportedException:
MissingServletRequestPartException:
AsyncRequestTimeoutException:

ClassCastException:http://localhost:8080/api/user/findUser/1 请求会出此异常

/**
 * 验证异常
 * @param id
 * @return
 */
@GetMapping("findUser")
public R<User> findUser(Long id) {
    return R.ok(userService.findUser(id));
}

自定义异常类:

UserNotFoundException:

ParameterCheckException:

ValidException:

统一异常处理相关:

package com.batsoft.common.exception;

import com.batsoft.common.core.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author: leo
 * @data: 2022/4/4
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ParameterCheckException.class)
    public R parameterCheckHandle(ParameterCheckException e) {
        log.info("error:{}", e.getMessage());
        return R.fail(e.getMessage());
    }

    @ExceptionHandler(UserNotFoundException.class)
    public R userNotFoundHandle(UserNotFoundException e) {
        log.info("error:{}", e.getMessage());
        return R.fail(e.getMessage());
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R methodNotSupport(HttpRequestMethodNotSupportedException e) {
        log.info("error:{}", e.getMessage());
        return R.fail(e.getMessage());
    }
}
package com.batsoft.common.exception;

/**
 * @author: leo
 * @data: 2022/4/4
 */
public class ParameterCheckException extends RuntimeException{
    public ParameterCheckException(String message) {
        super(message);
    }
}
package com.batsoft.common.exception;

/**
 * @author: leo
 * @data: 2022/4/4
 */
public class UserNotFoundException extends RuntimeException{
    public UserNotFoundException() {
        super("user not found...");
    }

    public UserNotFoundException(String message) {
        super(message);
    }
}

业务模块中使用:

package com.batsoft.vip.service;

import com.batsoft.vip.entity.User;

/**
 * @author: leo
 * @data: 2022/3/12
 */
public interface UserService {
    User findUserById(long id);

    User create(long id);

    User findUser(Long id);
}
package com.batsoft.vip.service.impl;

import cn.hutool.core.lang.Assert;
import com.batsoft.common.exception.ParameterCheckException;
import com.batsoft.common.exception.UserNotFoundException;
import com.batsoft.vip.entity.User;
import com.batsoft.vip.service.UserService;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * @author: leo
 * @data: 2022/3/12
 */
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User findUserById(long id) {
        return User.builder()
                .id(id)
                .age(30)
                .name("yzj")
                .build();
    }

    @Override
    public User create(long id) {
        User user = new User();
        user.setId(id);
        user.setAge(28);
        user.setName("yzj");
        return user;
    }

    @Override
    public User findUser(Long id) {
        Assert.isTrue(id>0,()->new ParameterCheckException("parameter check error..."));
        User u1 = new User(1L, "u1", 1);
        User u2 = new User(2L, "u2", 2);
        User u3 = new User(3L, "u3", 3);
        User u4 = new User(4L, "u4", 4);
        User u5 = new User(5L, "u5", 5);
        List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
        Optional<User> userOptional = users.stream().filter(user -> user.getId().equals(id)).findFirst();
        Assert.isTrue(userOptional.isPresent(),()->new UserNotFoundException("user not found..."));
        return userOptional.get();
    }
}
package com.batsoft.vip.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author: leo
 * @data: 2022/3/12
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private Long id;
    private String name;
    private Integer age;
}
package com.batsoft.vip.controller;

import com.batsoft.common.core.util.R;
import com.batsoft.common.core.util.ThreadPool;
import com.batsoft.mail.autoconfigure.MailService;
import com.batsoft.mail.autoconfigure.annotation.Log;
import com.batsoft.vip.entity.User;
import com.batsoft.vip.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author: leo
 * @data: 2022/3/12
 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserController {
    private final UserService userService;

    @Autowired(required = false)
    private MailService mailService;

    @PostMapping("create")
    public R<User> create(long id) {
        User user = userService.create(id);
        return R.ok(user);
    }

    @Log(content = "#id")
    @GetMapping("find/{id}")
    public R<User> find(@PathVariable long id) {
        ThreadPool.exec(new Runnable() {
            @Override
            public void run() {
                log.info("要查找的用户为:{}",id);
            }
        });
        return R.ok(userService.findUserById(id));
    }

    @Log(content = "获取host")
    @PostMapping("getHost")
    public R<String> getHost(@RequestBody User user) {
        return R.ok(mailService.getHost());
    }

    @GetMapping("getProtocol")
    public R<String> getProtocol() {
        return R.ok(mailService.getProtocol());
    }

    /**
     * 验证异常
     * @param id
     * @return
     */
    @GetMapping("findUser/{id}")
    public R<User> findUser(@PathVariable Long id) {
        return R.ok(userService.findUser(id));
    }
}

小知识:如果接口要返回json对象,则需要在方法上添加@ResponseBody注解或在类上添加@RestControllerAdvice;同理如果要接收vue传来的json对象需要在方法参数添加@RequestBody注解;

4-5 logback-spring

<?xml version="1.0" encoding="UTF-8"?>
<!--
    小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
    <properties>
        <log-path>/Users/batsoft</log-path>
    </properties>
    1. 其他模块加日志输出,直接copy本文件放在resources 目录即可
    2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
-->
<configuration debug="false" scan="false">
    <property name="log.path" value="logs"/>
    <!-- 彩色日志格式 -->
    <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"/>
    <!-- Console log output -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- Log file debug output -->
    <appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Log file error output -->
    <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <logger name="org.activiti.engine.impl.db" level="DEBUG">
        <appender-ref ref="debug"/>
    </logger>

    <!--nacos 心跳 INFO 屏蔽-->
    <logger name="com.alibaba.nacos" level="OFF">
        <appender-ref ref="error"/>
    </logger>
    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="debug"/>
    </root>
</configuration>
最近发表
标签列表