refactor(core): 移动全局异常处理器到核心模块

This commit is contained in:
2024-01-18 22:34:07 +08:00
parent 6d9e8b43eb
commit ec0ebd00e4
10 changed files with 159 additions and 59 deletions

View File

@@ -55,6 +55,13 @@
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!-- Swagger 注解 -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
<optional>true</optional>
</dependency>
<!-- 第三方封装 Ip2region离线 IP 数据管理框架和定位库支持亿级别的数据段10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) -->
<dependency>
<groupId>net.dreamlu</groupId>

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.core.annotation;
import org.springframework.context.annotation.Import;
import top.charles7c.continew.starter.core.autoconfigure.GlobalExceptionHandlerAutoConfiguration;
import java.lang.annotation.*;
/**
* 全局异常、错误处理器启用注解
*
* @author Charles7c
* @since 1.2.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({GlobalExceptionHandlerAutoConfiguration.class})
public @interface EnableGlobalExceptionHandler {
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.core.autoconfigure;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import top.charles7c.continew.starter.core.exception.GlobalErrorHandler;
import top.charles7c.continew.starter.core.exception.GlobalExceptionHandler;
/**
* 全局异常处理器自动配置
*
* @author Charles7c
* @since 1.0.0
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
@Import({GlobalExceptionHandler.class, GlobalErrorHandler.class})
@ConditionalOnMissingBean(BasicErrorController.class)
@ComponentScan("top.charles7c.continew.starter.**.exception")
public class GlobalExceptionHandlerAutoConfiguration {
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Extension-Global Exception Handler' completed " + "initialization.");
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.core.exception;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import top.charles7c.continew.starter.core.model.R;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 全局错误处理器
*
* @author Charles7c
* @since 1.0.0
*/
@Slf4j
@RestController
public class GlobalErrorHandler extends BasicErrorController {
@Resource
private ObjectMapper objectMapper;
public GlobalErrorHandler(ErrorAttributes errorAttributes,
ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
}
@Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
String path = (String)errorAttributeMap.get("path");
HttpStatus status = super.getStatus(request);
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
result.setData(path);
try {
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), result);
} catch (IOException e) {
log.error("请求地址 [{}],默认错误处理时发生 IO 异常。", path, e);
}
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
return null;
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.ALL));
String path = (String)errorAttributeMap.get("path");
HttpStatus status = super.getStatus(request);
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
result.setData(path);
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
return new ResponseEntity<>(BeanUtil.beanToMap(result), HttpStatus.OK);
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.core.exception;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.core.model.R;
import top.charles7c.continew.starter.core.util.ExceptionUtils;
import java.util.Objects;
/**
* 全局异常处理器
*
* @author Charles7c
* @since 1.1.0
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 拦截自定义验证异常-错误请求
*/
@ExceptionHandler(BadRequestException.class)
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
/**
* 拦截校验异常-违反约束异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
String errorMsg = CollUtil.join(e
.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-绑定异常
*/
@ExceptionHandler(BindException.class)
public R handleBindException(BindException e, HttpServletRequest request) {
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
String errorMsg = CollUtil.join(e
.getAllErrors(), StringConstants.CHINESE_COMMA, DefaultMessageSourceResolvable::getDefaultMessage);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-方法参数无效异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
String errorMsg = ExceptionUtils.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError())
.getDefaultMessage());
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-方法参数类型不匹配异常
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
HttpServletRequest request) {
String errorMsg = StrUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter().getParameterType());
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截文件上传异常-超过上传大小限制
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
String errorMsg = String.format("请上传小于 %sMB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-请求方式不支持异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
}
/**
* 拦截业务异常
*/
@ExceptionHandler(BusinessException.class)
public R handleServiceException(BusinessException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
return R.fail(e.getMessage());
}
/**
* 拦截未知的系统异常
*/
@ExceptionHandler(Throwable.class)
public R handleException(Throwable e, HttpServletRequest request) {
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
return R.fail(e.getMessage());
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.core.model;
import cn.hutool.core.date.DateUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;
import java.io.Serial;
import java.io.Serializable;
/**
* 响应信息
*
* @author Charles7c
* @since 1.0.0
*/
@Data
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Schema(description = "响应信息")
public class R<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final int SUCCESS_CODE = HttpStatus.OK.value();
private static final int FAIL_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();
/**
* 是否成功
*/
@Schema(description = "是否成功", example = "true")
private boolean success;
/**
* 业务状态码
*/
@Schema(description = "业务状态码", example = "200")
private int code;
/**
* 业务状态信息
*/
@Schema(description = "业务状态信息", example = "操作成功")
private String msg;
/**
* 响应数据
*/
@Schema(description = "响应数据")
private T data;
/**
* 时间戳
*/
@Schema(description = "时间戳", example = "1691453288")
private long timestamp = DateUtil.currentSeconds();
private R(boolean success, int code, String msg, T data) {
this.success = success;
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 操作成功
*
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok() {
return new R<>(true, SUCCESS_CODE, "操作成功", null);
}
/**
* 操作成功
*
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok(T data) {
return new R<>(true, SUCCESS_CODE, "操作成功", data);
}
/**
* 操作成功
*
* @param msg 业务状态信息
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok(String msg) {
return new R<>(true, SUCCESS_CODE, msg, null);
}
/**
* 操作成功
*
* @param msg 业务状态信息
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok(String msg, T data) {
return new R<>(true, SUCCESS_CODE, msg, data);
}
/**
* 操作失败
*
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail() {
return new R<>(false, FAIL_CODE, "操作失败", null);
}
/**
* 操作失败
*
* @param msg 业务状态信息
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(String msg) {
return new R<>(false, FAIL_CODE, msg, null);
}
/**
* 操作失败
*
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(T data) {
return new R<>(false, FAIL_CODE, "操作失败", data);
}
/**
* 操作失败
*
* @param msg 业务状态信息
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(String msg, T data) {
return new R<>(false, FAIL_CODE, msg, data);
}
/**
* 操作失败
*
* @param code 业务状态码
* @param msg 业务状态信息
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(int code, String msg) {
return new R<>(false, code, msg, null);
}
}