diff --git a/forgeboot-webmvc/exception/.gitattributes b/forgeboot-webmvc/exception/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/forgeboot-webmvc/exception/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/forgeboot-webmvc/exception/.gitignore b/forgeboot-webmvc/exception/.gitignore new file mode 100644 index 0000000..5a979af --- /dev/null +++ b/forgeboot-webmvc/exception/.gitignore @@ -0,0 +1,40 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Kotlin ### +.kotlin diff --git a/forgeboot-webmvc/exception/build.gradle.kts b/forgeboot-webmvc/exception/build.gradle.kts new file mode 100644 index 0000000..b7f51f7 --- /dev/null +++ b/forgeboot-webmvc/exception/build.gradle.kts @@ -0,0 +1,18 @@ +plugins{ + alias(libs.plugins.forgeboot.i18n.keygen) + alias(libs.plugins.kotlin.plugin.spring) +} +dependencies { + implementation(project(Modules.Core.EXTENSION)) + api(project(Modules.I18n.STARTER)) + api(project(Modules.TRACE.STARTER)) + implementation(project(Modules.Webmvc.DTO)) + compileOnly(libs.springBootStarter.validation) + compileOnly(libs.springBootStarter.web) + kapt(libs.springBoot.configuration.processor) +} +i18nKeyGen { + rootPackage.set("com.gewuyou.forgeboot.webmvc.extension.i18n") + readPath.set("src/main/resources/i18n/${project.name}") + level.set(3) +} diff --git a/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/config/WebMvcExceptionAutoConfiguration.kt b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/config/WebMvcExceptionAutoConfiguration.kt new file mode 100644 index 0000000..d8e3b1d --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/config/WebMvcExceptionAutoConfiguration.kt @@ -0,0 +1,54 @@ +package com.gewuyou.forgeboot.webmvc.exception.config + +import com.gewuyou.forgeboot.i18n.api.MessageResolver +import com.gewuyou.forgeboot.trace.api.RequestIdProvider +import com.gewuyou.forgeboot.webmvc.exception.config.entities.WebMvcExceptionProperties +import com.gewuyou.forgeboot.webmvc.exception.handler.GlobalExceptionHandler +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +/** + *Web MVC 异常自动配置 + * + * @since 2025-05-13 11:48:01 + * @author gewuyou + */ +@EnableConfigurationProperties(WebMvcExceptionProperties::class) +@Configuration +class WebMvcExceptionAutoConfiguration { + /** + *默认消息解析器 + * + * @since 2025-05-03 16:21:43 + * @author gewuyou + */ + @Bean + @ConditionalOnMissingBean + fun defaultMessageResolver(): MessageResolver = MessageResolver { code, _ -> code } + + /** + *默认请求ID提供商 + * + * @since 2025-05-03 16:22:18 + * @author gewuyou + */ + @Bean + @ConditionalOnMissingBean + fun defaultRequestIdProvider(): RequestIdProvider = RequestIdProvider { "" } + + @Bean + @ConditionalOnMissingBean + fun globalExceptionHandler( + webMvcExceptionProperties: WebMvcExceptionProperties, + messageResolver: MessageResolver, + requestIdProvider: RequestIdProvider, + ): GlobalExceptionHandler { + return GlobalExceptionHandler( + webMvcExceptionProperties, + messageResolver, + requestIdProvider + ) + } +} \ No newline at end of file diff --git a/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/config/entities/WebMvcExceptionProperties.kt b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/config/entities/WebMvcExceptionProperties.kt new file mode 100644 index 0000000..41cfa1e --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/config/entities/WebMvcExceptionProperties.kt @@ -0,0 +1,79 @@ +package com.gewuyou.forgeboot.webmvc.exception.config.entities + +import com.gewuyou.forgeboot.webmvc.extension.i18n.I18nKeys +import org.springframework.boot.context.properties.ConfigurationProperties + +/** + * Web Mvc异常属性 + * + * @author gewuyou + * @since 2025-05-13 11:06:46 + */ +@ConfigurationProperties("forgeboot.webmvc.exception") +class WebMvcExceptionProperties { + /** + * 设置其他通用外部异常的错误代码 + * + * @param otherGeneralExternalExceptionErrorCode 其他通用外部异常的错误代码 + */ + var otherGeneralExternalExceptionErrorCode: Int = 500 + + + /** + * 设置其他通用外部异常的错误消息 + * + * @param otherGeneralExternalExceptionErrorMessage 其他通用外部异常的错误消息 + */ + var otherGeneralExternalExceptionErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.INVALID_SERVER_ERROR + + /** + * 设置默认验证异常的错误代码 + * + * @param defaultValidationExceptionErrorCode 默认验证异常的错误代码 + */ + var defaultValidationExceptionErrorCode: Int = 400 + + + /** + * 设置默认验证异常的错误消息 + * + * @param defaultValidationExceptionErrorMessage 默认验证异常的错误消息 + */ + var defaultValidationExceptionErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.ILLEGAL_PARAMETER + + /** + * 设置默认验证异常的字段错误消息 + * + * @param defaultValidationExceptionFieldErrorMessage 默认验证异常的字段错误消息 + */ + var defaultValidationExceptionFieldErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.ILLEGAL_PARAMETER + + /** + * 设置默认无效参数异常的错误代码 + * + * @param defaultInvalidParameterErrorCode 默认无效参数异常的错误代码 + */ + var defaultInvalidParameterErrorCode: Int = 400 + + /** + * 设置默认无效参数异常的错误消息 + * + * @param defaultInvalidParameterErrorMessage 默认无效参数异常的错误消息 + */ + var defaultInvalidParameterErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.ILLEGAL_PARAMETER + + /** + * 设置默认内部服务器错误异常的错误代码 + * + * @param defaultInternalServerErrorCode 默认内部服务器错误异常的错误代码 + */ + var defaultInternalServerErrorCode: Int = 500 + + + /** + * 设置默认内部服务器错误异常的错误消息 + * + * @param defaultInternalServerErrorMessage 默认内部服务器错误异常的错误消息 + */ + var defaultInternalServerErrorMessage: String = I18nKeys.Forgeboot.Webmvc.Exception.INVALID_SERVER_ERROR +} diff --git a/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/core/GlobalException.kt b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/core/GlobalException.kt new file mode 100644 index 0000000..1da1f8f --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/core/GlobalException.kt @@ -0,0 +1,28 @@ +package com.gewuyou.forgeboot.webmvc.exception.core + +import com.gewuyou.forgeboot.i18n.api.ResponseInformation +import com.gewuyou.forgeboot.i18n.impl.exception.I18nBaseException + + +/** + * 全局异常: 继承I18nBaseException,如果其它模块需要抛出全局异常,则继承此类 + * + * @author gewuyou + * @since 2024-11-23 16:45:10 + */ +open class GlobalException : I18nBaseException { + /** + * 构造函数:初始化全局异常 + * + * @param responseInformation 响应信息,包含错误代码和消息 + */ + constructor(responseInformation: ResponseInformation) : super(responseInformation) + + /** + * 构造函数:初始化全局异常,并包含原始异常 + * + * @param responseInformation 响应信息,包含错误代码和消息 + * @param cause 原始异常 + */ + constructor(responseInformation: ResponseInformation, cause: Throwable) : super(responseInformation, cause) +} diff --git a/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/core/InternalException.kt b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/core/InternalException.kt new file mode 100644 index 0000000..24027fd --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/core/InternalException.kt @@ -0,0 +1,71 @@ +package com.gewuyou.forgeboot.webmvc.exception.core + +import com.gewuyou.forgeboot.i18n.api.InternalInformation + + +/** + * 内部异常 + * + * @author gewuyou + * @since 2024-11-24 21:14:03 + */ +open class InternalException : RuntimeException { + /** + * 错误信息 + */ + val errorMessage: String + + /** + * 可选(国际化内部错误信息码) + */ + @Transient + val internalInformation: InternalInformation? + + /** + * 构造一个新的运行时异常,其详细消息为`null`。 + * 原因尚未初始化,可以随后通过调用[.initCause]进行初始化。 + * + * @param errorMessage 详细消息 + */ + constructor(errorMessage: String) { + this.errorMessage = errorMessage + this.internalInformation = null + } + + /** + * 使用指定的详细消息和原因构造新的运行时异常。 + * + * @param errorMessage 详细消息 + * @param cause 异常的原因 + */ + constructor(errorMessage: String, cause: Throwable?) : super(errorMessage, cause) { + this.errorMessage = errorMessage + this.internalInformation = null + } + + /** + * 使用指定的详细消息和国际化内部错误信息码构造新的运行时异常。 + * + * @param errorMessage 详细消息 + * @param internalInformation 国际化内部错误信息码 + */ + constructor(errorMessage: String, internalInformation: InternalInformation?) { + this.errorMessage = errorMessage + this.internalInformation = internalInformation + } + + /** + * 使用指定的详细消息、原因和国际化内部错误信息码构造新的运行时异常。 + *GlobalExceptionHandler + * @param errorMessage 详细消息 + * @param cause 异常的原因 + * @param internalInformation 国际化内部错误信息码 + */ + constructor(errorMessage: String, cause: Throwable?, internalInformation: InternalInformation?) : super( + errorMessage, + cause + ) { + this.errorMessage = errorMessage + this.internalInformation = internalInformation + } +} diff --git a/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/handler/GlobalExceptionHandler.kt b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/handler/GlobalExceptionHandler.kt new file mode 100644 index 0000000..9709436 --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/kotlin/com/gewuyou/forgeboot/webmvc/exception/handler/GlobalExceptionHandler.kt @@ -0,0 +1,165 @@ +package com.gewuyou.forgeboot.webmvc.exception.handler + + +import com.gewuyou.forgeboot.core.extension.log +import com.gewuyou.forgeboot.i18n.api.MessageResolver +import com.gewuyou.forgeboot.trace.api.RequestIdProvider +import com.gewuyou.forgeboot.webmvc.dto.R +import com.gewuyou.forgeboot.webmvc.exception.config.entities.WebMvcExceptionProperties +import com.gewuyou.forgeboot.webmvc.exception.core.GlobalException +import com.gewuyou.forgeboot.webmvc.exception.core.InternalException + + +import jakarta.validation.ConstraintViolationException +import org.springframework.http.HttpStatus +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice + +/** + * 全局异常处理类 + * + * 该类用于统一处理整个应用中抛出的各种异常,以提供统一的错误响应格式 + * 它通过使用@RestControllerAdvice注解来标识这是一个全局异常处理类 + * + * @author gewuyou + * @since 2024-04-13 上午12:22:18 + */ +@RestControllerAdvice +class GlobalExceptionHandler( + private val webMvcExceptionProperties: WebMvcExceptionProperties, + private val messageResolver: MessageResolver, + private val requestIdProvider: RequestIdProvider, +) { + /** + * 异常处理器 + * + * 用于处理除特定异常外的所有其他异常 + * + * @param e 异常 + * @return 响应 + * @since 2024/4/13 上午12:29 + */ + @ExceptionHandler(Exception::class) + fun handleOtherException(e: Exception): R { + log.error("other exception:", e) + return R.failure( + webMvcExceptionProperties.otherGeneralExternalExceptionErrorCode, + webMvcExceptionProperties.otherGeneralExternalExceptionErrorMessage, + null, null, messageResolver, requestIdProvider + ) + } + + /** + * 处理 @Valid 和 @Validated 校验失败抛出的 MethodArgumentNotValidException 异常 + * + * 该方法首先尝试返回字段错误信息,如果没有字段错误则尝试返回全局错误信息, + * 如果都没有则返回默认的校验异常信息 + * + * @param ex 异常 + * @return 响应信息 + */ + @ExceptionHandler(MethodArgumentNotValidException::class) + fun handleValidationExceptions(ex: MethodArgumentNotValidException): R { + // 返回字段错误 + for (fieldError in ex.bindingResult.fieldErrors) { + return R.failure( + HttpStatus.BAD_REQUEST.value(), + fieldError.defaultMessage ?: webMvcExceptionProperties.defaultValidationExceptionFieldErrorMessage, + null, + null, + messageResolver, + requestIdProvider + ) + } + // 返回全局错误 + for (objectError in ex.bindingResult.globalErrors) { + return R.failure( + HttpStatus.BAD_REQUEST.value(), + objectError.defaultMessage ?: webMvcExceptionProperties.defaultValidationExceptionErrorMessage, + null, + null, + messageResolver, + requestIdProvider + ) + } + return R.failure( + webMvcExceptionProperties.defaultValidationExceptionErrorCode, + webMvcExceptionProperties.defaultValidationExceptionErrorMessage, + null, + null, + messageResolver, + requestIdProvider + ) + } + + /** + * 处理 JSR 303/JSR 380 校验失败抛出的 ConstraintViolationException 异常 + * + * 该方法遍历约束违规信息,并返回第一个错误信息如果存在多个错误, + * 否则返回默认的无效参数错误信息 + * + * @param ex 异常 + * @return 响应信息 + */ + @ExceptionHandler(ConstraintViolationException::class) + fun handleConstraintViolationException(ex: ConstraintViolationException): R { + for (constraintViolation in ex.constraintViolations) { + return R.failure( + HttpStatus.BAD_REQUEST.value(), constraintViolation.message, + null, null, messageResolver, requestIdProvider + ) + } + return R.failure( + webMvcExceptionProperties.defaultInvalidParameterErrorCode, + webMvcExceptionProperties.defaultInvalidParameterErrorMessage, + null, + null, + messageResolver, + requestIdProvider + ) + } + + /** + * 全局异常处理器 + * + * 用于处理全局异常(GlobalException),返回错误代码和国际化消息代码 + * + * @param e 异常 + * @return 返回的结果 + * @since 2024/4/13 下午1:56 + */ + @ExceptionHandler(GlobalException::class) + fun handleGlobalException(e: GlobalException): R { + return R.failure( + e.errorCode, e.errorI18nMessageCode, + null, e.errorI18nMessageArgs, messageResolver, requestIdProvider + ) + } + + /** + * 内部异常处理器 + * + * 用于处理内部异常(InternalException),记录异常信息并返回默认的内部服务器错误信息 + * + * @param e 异常 + * @return 返回的结果 + */ + @ExceptionHandler(InternalException::class) + fun handleGlobalException(e: InternalException): R { + log.error("内部异常: 异常信息: {}", e.errorMessage, e) + e.internalInformation?.responseI8nMessageCode?.let { + log.error( + "i18nMessage: {}", messageResolver.resolve(it, e.internalInformation.responseI8nMessageArgs) + ) + } + return R.failure( + webMvcExceptionProperties.defaultInternalServerErrorCode, + webMvcExceptionProperties.defaultInternalServerErrorMessage, + null, + null, + messageResolver, + requestIdProvider + ) + } +} diff --git a/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages.properties b/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages.properties new file mode 100644 index 0000000..fb4a26e --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages.properties @@ -0,0 +1,2 @@ +forgeboot.webmvc.exception.invalid_server_error=内部执行错误,请将本次请求的请求id反馈\! +forgeboot.webmvc.exception.illegal_parameter=非法参数,请检查输入\! diff --git a/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages_en.properties b/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages_en.properties new file mode 100644 index 0000000..a494392 --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages_en.properties @@ -0,0 +1,2 @@ +forgeboot.webmvc.exception.invalid_server_error=If there is an internal execution error, please report the request ID of this request\! +forgeboot.webmvc.exception.illegal_parameter=Illegal parameters, please check the input\! diff --git a/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages_zh_CN.properties b/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages_zh_CN.properties new file mode 100644 index 0000000..fb4a26e --- /dev/null +++ b/forgeboot-webmvc/exception/src/main/resources/i18n/forgeboot-webmvc-exception-spring-boot-starter/messages_zh_CN.properties @@ -0,0 +1,2 @@ +forgeboot.webmvc.exception.invalid_server_error=内部执行错误,请将本次请求的请求id反馈\! +forgeboot.webmvc.exception.illegal_parameter=非法参数,请检查输入\!