Spring Boot Web 全局异常处理器(基于JDK17,异常类型比较全面)

128 阅读9分钟

异常处理

常用异常

import jakarta.servlet.Servlet;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.unit.DataSize;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.*;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Set;

@Component
@RestControllerAdvice
public class GlobalExceptionAdvice {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionAdvice.class);

    // 替换为自己的业务异常
    @ExceptionHandler(value = BaseRuntimeException.class)
    public void handleBaseRuntimeException(BaseRuntimeException e, HttpServletResponse response) {
       e.log(log);
       ResponseUtils.writeExceptionToResponse(e, response, e.getHttpStatus());
    }

    // 请求方法未提供异常(如:请求方法要求GET,实际传递POST)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public Result<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
       return Result.failByMessage("预期的请求方法类型:" + StringUtils.join(e.getSupportedMethods(), "、"));
    }

    // 请求数据类型未提供异常(如:请求体类型要求为form,实际传递json)
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
    public Result<Void> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
       return Result.failByMessage("预期的请求数据类型:" + StringUtils.join(e.getSupportedMediaTypes(), "、"));
    }

    // 响应数据类型不接受异常(如:预期响应数据类型为json,实际为text)
    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
    @ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
    public Result<Void> handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException e) {
       return Result.failByMessage("预期的响应数据类型:" + StringUtils.join(e.getSupportedMediaTypes(), "、"));
    }

    // 路径参数缺失异常(如:未传递要求的@path变量)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = MissingPathVariableException.class)
    public Result<Void> handleMissingPathVariableException(MissingPathVariableException e) {
       log.error("缺少路径变量", e);
       return Result.failByMessage("缺少路径变量: " + e.getVariableName());
    }

    // 请求头参数缺失异常(如:缺少要求的请求头参数)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingRequestHeaderException.class)
    public Result<Void> handleMissingRequestHeaderException(MissingRequestHeaderException e) {
       return Result.failByMessage("缺少请求头:" + e.getHeaderName());
    }

    // 请求query参数缺失异常(如:缺少要求的请求query参数)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public Result<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
       return Result.failByMessage("缺少请求参数:" + e.getParameterName());
    }

    // 请参数缺失的基类异常
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingRequestValueException.class)
    public Result<Void> handleMissingRequestValueException(MissingRequestValueException e) {
       return Result.failByMessage(e.getMessage());
    }

    // 请求form参数缺失异常(如:缺少要求的请求form参数)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestPartException.class)
    public Result<Void> handleMissingServletRequestPartException(MissingServletRequestPartException e) {
       return Result.failByMessage("缺少请求文件:" + e.getRequestPartName());
    }

    // 请求参数绑定异常
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ServletRequestBindingException.class)
    public Result<Void> handleServletRequestBindingException(ServletRequestBindingException e) {
       log.error("请求参数绑定异常", e);
       return Result.failByMessage("请求参数不正确");
    }

    // 请求参数转换失败异常(如:缺少要求的请求参数无法转换至要求的类型)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ConversionNotSupportedException.class)
    public Result<Void> handleConversionNotSupportedException(ConversionNotSupportedException e) {
       log.error("请求参数转换异常", e);
       return Result.failByMessage("请求参数转换失败");
    }

    // 请求参数类型不正确异常(如:缺少要求的请求参数类型不正确)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public Result<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
       return Result.failByMessage("参数:" + StringUtils.defaultString(e.getName()) + "类型不正确");
    }

    // 请求体读取失败异常(如:缺失请求体json,或无法转换为要求的请求体时抛出)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public Result<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
       log.error("请求体读取失败", e);
       return Result.failByMessage("请求体读取失败");
    }

    // 上传文件大小超过限制异常
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MaxUploadSizeExceededException.class)
    public Result<Void> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
       log.error("上传文件大小超过限制", e);
       if (e.getMaxUploadSize() == -1) {
          if (Objects.nonNull(e.getCause()) &&
             e.getCause() instanceof IllegalStateException illegalStateException &&
             Objects.nonNull(illegalStateException.getCause()) &&
             illegalStateException.getCause() instanceof SizeLimitExceededException sizeLimitExceededException) {
             return Result.failByMessage("上传文件大小超过" + DataSize.ofBytes(sizeLimitExceededException.getPermittedSize()).toMegabytes() + "MB");
          }
          return Result.failByMessage("上传文件大小超过限制");
       }
       return Result.failByMessage("上传文件大小超过" + DataSize.ofBytes(e.getMaxUploadSize()).toMegabytes() + "MB");
    }

    // 上传文件异常
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MultipartException.class)
    public Result<Void> handleMultipartException(MultipartException e) {
       log.error("上传文件失败", e);
       return Result.failByMessage("上传文件失败");
    }

    // 响应体写入失败异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = HttpMessageNotWritableException.class)
    public Result<Void> handleHttpMessageNotWritableException(HttpMessageNotWritableException e) {
       log.error("响应体写入失败", e);
       return Result.failByMessage("响应体写入失败");
    }

    // 参数校验异常(校验对象参数失败时抛出)
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
       BindingResult bindingResult = e.getBindingResult();
       List<ObjectError> objectErrors = bindingResult.getAllErrors();
       if (!objectErrors.isEmpty()) {
          FieldError fieldError = (FieldError) objectErrors.iterator().next();
          return Result.fail(ConstantPool.VALIDATION_ERROR_RESPONSE_CODE, StringUtils.defaultString(fieldError.getDefaultMessage()));
       }
       return Result.fail(ConstantPool.VALIDATION_ERROR_RESPONSE_CODE, "请求参数验证不合法");
    }

    // 参数校验异常(校验方法参数失败时抛出)
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
       Set<ConstraintViolation<?>> constraints = e.getConstraintViolations();
       if (!constraints.isEmpty()) {
          ConstraintViolation<?> constraint = constraints.iterator().next();
          return Result.fail(ConstantPool.VALIDATION_ERROR_RESPONSE_CODE, StringUtils.defaultString(constraint.getMessage()));
       }
       return Result.fail(ConstantPool.VALIDATION_ERROR_RESPONSE_CODE, "请求参数验证不合法");
    }

    // 请求路径不存在时抛出
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = NoHandlerFoundException.class)
    public Result<Void> handleNoHandlerFoundException(NoHandlerFoundException e) {
       return Result.failByMessage("请求路径:" + e.getRequestURL() + "不存在");
    }

    // 请求资源不存在时抛出
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = NoResourceFoundException.class)
    public Result<Void> handleNoResourceFoundException(NoResourceFoundException e) {
       return Result.failByMessage("请求资源:" + e.getResourcePath() + "不存在");
    }

    // 异步请求超时时抛出
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = AsyncRequestTimeoutException.class)
    public Result<Void> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) {
       log.error("异步请求超时", e);
       return Result.failByMessage("异步请求超时");
    }

    // IO异常相关
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = IOException.class)
    public Result<Void> handleIOException(IOException e) {
       log.error("IO异常", e);
       return Result.fail(ConstantPool.SERVER_ERROR_RESPONSE_CODE, "服务器内部错误");
    }
    
    // 运行时异常相关
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = RuntimeException.class)
    public Result<Void> handleRuntimeException(RuntimeException e) {
       log.error("运行时异常", e);
       return Result.fail(ConstantPool.SERVER_ERROR_RESPONSE_CODE, "服务器内部错误");
    }

    // 其他异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public Result<Void> handleException(Exception e) {
       log.error("系统级异常", e);
       return Result.fail(ConstantPool.SERVER_ERROR_RESPONSE_CODE, "服务器内部错误");
    }
}

MyBatis和MyBatis Plus相关异常

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import jakarta.servlet.Servlet;
import org.apache.ibatis.exceptions.PersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.DispatcherServlet;

import java.util.Objects;
import java.util.Optional;

@Component
@RestControllerAdvice
public class MybatisExceptionAdvice {
    private static final Logger log = LoggerFactory.getLogger(MybatisExceptionAdvice.class);

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = CannotGetJdbcConnectionException.class)
    public Result<Void> handleMybatisPlusException(CannotGetJdbcConnectionException e) {
       log.error("JDBC连接超时", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "数据库连接超时");
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = MybatisPlusException.class)
    public Result<Void> handleMybatisPlusException(MybatisPlusException e) {
       log.error("mybatis-plus抛出异常", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "服务器内部错误");
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = PersistenceException.class)
    public Result<Void> handlePersistenceException(PersistenceException e) {
       log.error("mybatis抛出异常", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "服务器内部错误");
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = DataAccessException.class)
    public Result<Void> handleDataAccessException(DataAccessException e) {
       log.error("数据访问异常", e);
       if (Objects.nonNull(e.getCause())) {
          Class<?> clz = Optional.ofNullable(e.getCause())
             .map(Throwable::getCause)
             .map(Throwable::getClass)
             .orElse(null);
          if (Objects.nonNull(clz) && CannotGetJdbcConnectionException.class.isAssignableFrom(clz)) {
             return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "数据库连接超时");
          }
       }
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "服务器内部错误");
    }
}

Redis相关异常

import jakarta.servlet.Servlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.DispatcherServlet;

@Component
@RestControllerAdvice
public class RedisExceptionAdvice {
    private static final Logger log = LoggerFactory.getLogger(RedisExceptionAdvice.class);

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = RedisConnectionFailureException.class)
    public Result<Void> handleRedisConnectionFailureException(RedisConnectionFailureException e) {
       log.error("redis连接超时异常", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "Redis连接超时");
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = DataAccessException.class)
    public Result<Void> handleDataAccessException(DataAccessException e) {
       log.error("数据访问异常", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "服务器内部错误");
    }
}

MongoDB相关异常

import com.mongodb.MongoException;
import com.mongodb.MongoExecutionTimeoutException;
import com.mongodb.MongoSocketReadTimeoutException;
import com.mongodb.MongoTimeoutException;
import jakarta.servlet.Servlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.DispatcherServlet;

@Component
@RestControllerAdvice
public class MongoDBExceptionAdvice {
    private static final Logger log = LoggerFactory.getLogger(MongoDBExceptionAdvice.class);

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = MongoExecutionTimeoutException.class)
    public Result<Void> handleMongoExecutionTimeoutException(MongoExecutionTimeoutException e) {
       log.error("MongoDB执行超时", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "MongoDB执行超时");
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = MongoTimeoutException.class)
    public Result<Void> handleMongoTimeoutException(MongoTimeoutException e) {
       log.error("MongoDB连接超时", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "MongoDB连接超时");
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = MongoSocketReadTimeoutException.class)
    public Result<Void> handleMongoSocketReadTimeoutException(MongoSocketReadTimeoutException e) {
       log.error("MongoDB读取超时", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "MongoDB读取超时");
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = MongoException.class)
    public Result<Void> handleMongoException(MongoException e) {
       log.error("MongoDB抛出异常", e);
       return Result.fail(ConstantPool.DATA_ERROR_RESPONSE_CODE, "MongoDB错误");
    }
}

相关代码

响应结构(Result)

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(value = JsonInclude.Include.NON_NULL)
public record Result<T>(
        String message,
        Integer code,
        T data) {
    public static Result<Void> ok() {
        return new Result<>("请求成功", 0, null);
    }

    public static Result<Void> okByMessage(String message) {
        return new Result<>(message, 0, null);
    }

    public static <T> Result<T> ok(T data) {
        return new Result<>("请求成功", 0, data);
    }

    // 替换为自己的业务异常
    public static <E extends BaseRuntimeException> Result<Void> failByException(E e) {
        return new Result<>(e.getMessage(), e.getCode(), null);
    }

    public static Result<Void> fail() {
        return new Result<>("请求失败", -1, null);
    }

    public static Result<Void> failByMessage(String message) {
        return new Result<>(message, -1, null);
    }

    public static Result<Void> fail(int code, String message) {
        return new Result<>(message, code, null);
    }

    public static <T> Result<T> fail(T data) {
        return new Result<>("请求失败", -1, data);
    }

    public static <T> Result<T> fail(int code, String message, T data) {
        return new Result<>(message, code, data);
    }

    @Override
    public String toString() {
        // 将对象转为json字符串
        return JsonUtils.toString(this);
    }
}

自定义异常基类(BaseRuntimeException)

import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.event.Level;

public abstract class BaseRuntimeException extends RuntimeException {
    // 错误code,默认为-1
    private int code;
    // HTTP状态码,默认为200
    private int httpStatus;

    protected BaseRuntimeException(String message) {
        super(message);
        this.code = -1;
        this.httpStatus = 200;
    }

    protected BaseRuntimeException(int code, String message) {
        super(message);
        this.code = code;
        this.httpStatus = 200;
    }

    protected BaseRuntimeException(String message, Throwable cause) {
        super(message, cause);
        this.code = -1;
        this.httpStatus = 200;
    }

    protected BaseRuntimeException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.httpStatus = 200;
    }

    public int getCode() {
        return code;
    }

    protected void setCode(int code) {
        this.code = code;
    }

    public int getHttpStatus() {
        return httpStatus;
    }

    protected void setHttpStatus(int httpStatus) {
        this.httpStatus = httpStatus;
    }

    public void log(Logger logger) {
        logger.error(this.getMessage(), this);
    }

    public void log(Logger logger, Level level) {
        logger.atLevel(level)
                .setCause(this)
                .log(this.getMessage());
    }

    public Throwable getRootCause() {
        Throwable rootCause = null;
        Throwable cause = this.getCause();
        while (cause != null && cause != rootCause) {
            rootCause = cause;
            cause = cause.getCause();
        }
        return (rootCause != null ? rootCause : this);
    }

    public Throwable getMostSpecificCause() {
        return ObjectUtils.defaultIfNull(getRootCause(), this);
    }

    public boolean contains(Class<?> exType) {
        if (exType == null) {
            return false;
        }
        if (exType.isInstance(this)) {
            return true;
        }
        Throwable cause = getCause();
        if (cause == this) {
            return false;
        }
        if (cause instanceof BaseRuntimeException e) {
            return e.contains(exType);
        } else {
            while (cause != null) {
                if (exType.isInstance(cause)) {
                    return true;
                }
                if (cause.getCause() == cause) {
                    break;
                }
                cause = cause.getCause();
            }
            return false;
        }
    }
}

业务异常(ServiceException)

package cn.changtech.framework.core.exception.base;

import org.slf4j.Logger;
import org.slf4j.event.Level;

public class ServiceException extends BaseRuntimeException {
    private final String reason;

    public ServiceException(String message) {
        super(-40000, message);
        this.reason = message;
    }

    public ServiceException(String message, String reason) {
        super(-40000, message);
        this.reason = reason;
    }

    public ServiceException(String message, Throwable cause) {
        super(-40000, message, cause);
        this.reason = message;
    }

    public ServiceException(String message, String reason, Throwable cause) {
        super(-40000, message, cause);
        this.reason = reason;
    }

    protected ServiceException(int code, String message) {
        super(code, message);
        this.reason = message;
    }

    protected ServiceException(int code, String message, String reason) {
        super(code, message);
        this.reason = reason;
    }

    protected ServiceException(int code, String message, Throwable cause) {
        super(code, message, cause);
        this.reason = message;
    }

    protected ServiceException(int code, String message, String reason, Throwable cause) {
        super(code, message, cause);
        this.reason = reason;
    }

    public String getReason() {
        return reason;
    }

    @Override
    public void log(Logger logger) {
        logger.error(this.reason, this);
    }

    @Override
    public void log(Logger logger, Level level) {
        logger.atLevel(level)
                .setCause(this)
                .log(this.reason);
    }
}

服务端异常(ServerException)

import org.slf4j.Logger;
import org.slf4j.event.Level;

public class ServerException extends BaseRuntimeException {
    protected static final String DEFAULT_MESSAGE = "服务器内部错误";

    private final String reason;

    public ServerException() {
        super(-50000, DEFAULT_MESSAGE);
        this.reason = DEFAULT_MESSAGE;
        this.setHttpStatus(500);
    }

    public ServerException(String reason) {
        super(-50000, DEFAULT_MESSAGE);
        this.reason = reason;
        this.setHttpStatus(500);
    }

    public ServerException(Throwable cause) {
        super(-50000, DEFAULT_MESSAGE, cause);
        this.reason = DEFAULT_MESSAGE;
        this.setHttpStatus(500);
    }

    public ServerException(String reason, Throwable cause) {
        super(-50000, DEFAULT_MESSAGE, cause);
        this.reason = reason;
        this.setHttpStatus(500);
    }

    protected ServerException(int code, String reason) {
        super(code, DEFAULT_MESSAGE);
        this.reason = reason;
        this.setHttpStatus(500);
    }

    protected ServerException(int code, String reason, Throwable cause) {
        super(code, DEFAULT_MESSAGE, cause);
        this.reason = reason;
        this.setHttpStatus(500);
    }

    public String getReason() {
        return reason;
    }

    @Override
    public void log(Logger logger) {
        logger.error(this.reason, this);
    }

    @Override
    public void log(Logger logger, Level level) {
        logger.atLevel(level)
                .setCause(this)
                .log(this.reason);
    }
}

响应工具类(ResponseUtils)

import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

public class ResponseUtils {
    protected static final Logger logger = LoggerFactory.getLogger(ResponseUtils.class);

    protected ResponseUtils() {
    }

    public static void setAttachmentHeader(final HttpServletResponse response, final String filename) {
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8));
    }

    public static void writeBytesToResponse(final byte[] bytes, final HttpServletResponse response) {
        writeBytesToResponse(bytes, response, HttpStatus.OK.value());
    }

    public static void writeBytesToResponse(final byte[] bytes, final HttpServletResponse response, final HttpStatus status) {
        writeBytesToResponse(bytes, response, status.value());
    }

    public static void writeBytesToResponse(final byte[] bytes, final HttpServletResponse response, int status) {
        try (OutputStream outputStream = response.getOutputStream()) {
            response.setStatus(status);
            outputStream.write(bytes);
        } catch (IOException e) {
            throw new ServerException("Http Servlet 响应值写入失败", e);
        }
    }

    public static void writeInputStreamToResponse(final InputStream inputStream, final HttpServletResponse response) {
        writeInputStreamToResponse(inputStream, response, HttpStatus.OK.value());
    }

    public static void writeInputStreamToResponse(final InputStream inputStream, final HttpServletResponse response, final HttpStatus status) {
        writeInputStreamToResponse(inputStream, response, status.value());
    }

    public static void writeInputStreamToResponse(final InputStream inputStream, final HttpServletResponse response, int status) {
        try (OutputStream outputStream = response.getOutputStream()) {
            response.setStatus(status);
            inputStream.transferTo(outputStream);
        } catch (IOException e) {
            throw new ServerException("Http Servlet 响应值写入失败", e);
        }
    }

    public static <T> void writeBeanToResponse(final T bean, final HttpServletResponse response) {
        writeResultToResponse(Result.ok(bean), response, HttpStatus.OK.value());
    }

    public static <T> void writeBeanToResponse(final T bean, final HttpServletResponse response, final HttpStatus status) {
        writeResultToResponse(Result.ok(bean), response, status.value());
    }

    public static <E extends BaseRuntimeException> void writeExceptionToResponse(final E exception, final HttpServletResponse response) {
        exception.log(logger);
        writeResultToResponse(Result.failByException(exception), response, exception.getHttpStatus());
    }

    public static <E extends BaseRuntimeException> void writeExceptionToResponse(final E exception, final HttpServletResponse response, final HttpStatus httpStatus) {
        exception.log(logger);
        writeResultToResponse(Result.failByException(exception), response, httpStatus.value());
    }

    public static <E extends BaseRuntimeException> void writeExceptionToResponse(final E exception, final HttpServletResponse response, int httpStatus) {
        exception.log(logger);
        writeResultToResponse(Result.failByException(exception), response, httpStatus);
    }

    public static <T> void writeResultToResponse(final Result<T> data, final HttpServletResponse response, final HttpStatus httpStatus) {
        writeResultToResponse(data, response, httpStatus.value());
    }

    public static <T> void writeResultToResponse(final Result<T> data, final HttpServletResponse response, int status) {
        try (OutputStream outputStream = response.getOutputStream()) {
            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
            response.setStatus(status);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            outputStream.write(JsonUtils.toString(data).getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new ServerException("Http Servlet 响应值写入失败", e);
        }
    }
}

JSON工具类(JsonUtils)

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class JsonUtils {
    public static final Gson DEFAULT_GSON;

    static {
       DEFAULT_GSON = new GsonBuilder()
             .setPrettyPrinting()
             .create();
    }

    protected JsonUtils() {
    }

    public static JsonElement parseString(final String json) {
       if (StringUtils.isBlank(json)) {
          return JsonNull.INSTANCE;
       }
       return JsonParser.parseString(json);
    }

    public static <T> T fromString(final String json) {
       return fromString(json, DEFAULT_GSON, new TypeToken<T>() {
       });
    }

    public static <T> T fromString(final String json, final TypeToken<T> typeToken) {
       return fromString(json, DEFAULT_GSON, typeToken);
    }

    public static <T> T fromString(final String json, final Gson gson) {
       return fromString(json, gson, new TypeToken<T>() {
       });
    }

    public static <T> T fromString(final String json, final Gson gson, final TypeToken<T> typeToken) {
       if (StringUtils.isBlank(json)) {
          return null;
       }
       return gson.fromJson(json, typeToken.getType());
    }

    public static <T> String toString(final T src) {
       return toString(src, DEFAULT_GSON, new TypeToken<T>() {
       });
    }

    public static <T> String toString(final T src, final TypeToken<T> typeToken) {
       return toString(src, DEFAULT_GSON, typeToken);
    }

    public static <T> String toString(final T src, final Gson gson) {
       return toString(src, gson, new TypeToken<T>() {
       });
    }

    public static <T> String toString(final T src, final Gson gson, final TypeToken<T> typeToken) {
       if (Objects.isNull(src)) {
          return StringUtils.EMPTY;
       }
       return gson.toJson(src, typeToken.getType());
    }

    public static <T> T fromJson(final JsonElement json) {
       return fromJson(json, DEFAULT_GSON, new TypeToken<T>() {
       });
    }

    public static <T> T fromJson(final JsonElement json, final TypeToken<T> typeToken) {
       return fromJson(json, DEFAULT_GSON, typeToken);
    }

    public static <T> T fromJson(final JsonElement json, final Gson gson) {
       return fromJson(json, gson, new TypeToken<T>() {
       });
    }

    public static <T> T fromJson(final JsonElement json, final Gson gson, final TypeToken<T> typeToken) {
       return gson.fromJson(json, typeToken.getType());
    }

    public static <T> JsonElement toJson(final T src) {
       return toJson(src, DEFAULT_GSON, new TypeToken<T>() {
       });
    }

    public static <T> JsonElement toJson(final T src, final TypeToken<T> typeToken) {
       return toJson(src, DEFAULT_GSON, typeToken);
    }

    public static <T> JsonElement toJson(final T src, final Gson gson) {
       return toJson(src, gson, new TypeToken<T>() {
       });
    }

    public static <T> JsonElement toJson(final T src, final Gson gson, final TypeToken<T> typeToken) {
       if (Objects.isNull(src)) {
          return JsonNull.INSTANCE;
       }
       return gson.toJsonTree(src, typeToken.getType());
    }

    public static <T> List<T> fromJsonArray(final JsonArray array) {
       return fromJsonArray(array, DEFAULT_GSON, new TypeToken<List<T>>() {
       });
    }

    public static <T> List<T> fromJsonArray(final JsonArray array, final TypeToken<? extends List<T>> typeToken) {
       return fromJsonArray(array, DEFAULT_GSON, typeToken);
    }

    public static <T> List<T> fromJsonArray(final JsonArray json, final Gson gson) {
       return fromJsonArray(json, gson, new TypeToken<List<T>>() {
       });
    }

    public static <T> List<T> fromJsonArray(final JsonArray json, final Gson gson, final TypeToken<? extends List<T>> typeToken) {
       if (json == null || json.isEmpty()) {
          return Collections.emptyList();
       }
       return gson.fromJson(json, typeToken.getType());
    }

    public static <T> JsonArray toJsonArray(final Collection<T> collection) {
       return toJsonArray(collection, DEFAULT_GSON, new TypeToken<Collection<T>>() {
       });
    }

    public static <T> JsonArray toJsonArray(final Collection<T> collection, final Gson gson) {
       return toJsonArray(collection, gson, new TypeToken<Collection<T>>() {
       });
    }

    public static <T> JsonArray toJsonArray(final Collection<T> collection, final Gson gson, final TypeToken<Collection<T>> typeToken) {
       if (CollectionUtils.isEmpty(collection)) {
          return new JsonArray();
       }
       JsonElement jsonElement = gson.toJsonTree(collection, typeToken.getType());
       return jsonElement.getAsJsonArray();
    }
}