From 3eb651e039d19c3e391bb4f62c1ff7fa08c53a66 Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Wed, 26 Nov 2025 20:24:16 +0800 Subject: [PATCH] feat(common): add exception handling and response utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backend project initial!!! --- .idea/modules.xml | 2 + weblog-module-common/build.gradle.kts | 2 + .../common/config/JacksonConfig.java | 53 +++++++++++++ .../common/enums/ResponseCodeEnum.java | 23 ++++++ .../exception/BaseExceptionInterface.java | 7 ++ .../common/exception/BizException.java | 18 +++++ .../exception/GlobalExceptionHandler.java | 79 +++++++++++++++++++ .../com/hanserwei/common/utils/Response.java | 70 ++++++++++++++++ weblog-web/build.gradle.kts | 3 + .../web/controller/TestController.java | 21 ++++- .../java/com/hanserwei/web/model/User.java | 16 +++- 11 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/exception/BaseExceptionInterface.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/exception/BizException.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/utils/Response.java diff --git a/.idea/modules.xml b/.idea/modules.xml index dea5dca..3665080 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,8 @@ + + \ No newline at end of file diff --git a/weblog-module-common/build.gradle.kts b/weblog-module-common/build.gradle.kts index 3a9f1ed..8d502a5 100644 --- a/weblog-module-common/build.gradle.kts +++ b/weblog-module-common/build.gradle.kts @@ -15,4 +15,6 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-core") // aop implementation("org.springframework.boot:spring-boot-starter-aop") + // web + implementation("org.springframework.boot:spring-boot-starter-web") } diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java b/weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java new file mode 100644 index 0000000..9fc0ee9 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/config/JacksonConfig.java @@ -0,0 +1,53 @@ +package com.hanserwei.common.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.TimeZone; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + // 初始化一个 ObjectMapper 对象,用于自定义 Jackson 的行为 + ObjectMapper objectMapper = new ObjectMapper(); + + // 忽略未知字段(前端有传入某个字段,但是后端未定义接受该字段值,则一律忽略掉) + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + // JavaTimeModule 用于指定序列化和反序列化规则 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + + // 支持 LocalDateTime、LocalDate、LocalTime + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); + javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); + javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); + javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); + + objectMapper.registerModule(javaTimeModule); + + // 设置时区 + objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + + // 设置凡是为 null 的字段,返参中均不返回,请根据项目组约定是否开启 + // objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + return objectMapper; + } +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java b/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java new file mode 100644 index 0000000..b3b3a6c --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java @@ -0,0 +1,23 @@ +package com.hanserwei.common.enums; + +import com.hanserwei.common.exception.BaseExceptionInterface; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ResponseCodeEnum implements BaseExceptionInterface { + + // ----------- 通用异常状态码 ----------- + SYSTEM_ERROR("10000", "出错啦,后台小哥正在努力修复中..."), + PARAM_NOT_VALID("10001", "参数错误"), + + // ----------- 业务异常状态码 ----------- + ; + + // 异常码 + private final String errorCode; + // 错误信息 + private final String errorMsg; + +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/exception/BaseExceptionInterface.java b/weblog-module-common/src/main/java/com/hanserwei/common/exception/BaseExceptionInterface.java new file mode 100644 index 0000000..77297db --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/exception/BaseExceptionInterface.java @@ -0,0 +1,7 @@ +package com.hanserwei.common.exception; + +public interface BaseExceptionInterface { + String getErrorCode(); + + String getErrorMsg(); +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/exception/BizException.java b/weblog-module-common/src/main/java/com/hanserwei/common/exception/BizException.java new file mode 100644 index 0000000..aaad6fe --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/exception/BizException.java @@ -0,0 +1,18 @@ +package com.hanserwei.common.exception; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BizException extends RuntimeException { + // 异常码 + private String errorCode; + // 错误信息 + private String errorMsg; + + public BizException(BaseExceptionInterface baseExceptionInterface) { + this.errorCode = baseExceptionInterface.getErrorCode(); + this.errorMsg = baseExceptionInterface.getErrorMsg(); + } +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java b/weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0b8f107 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,79 @@ +package com.hanserwei.common.exception; + +import com.hanserwei.common.enums.ResponseCodeEnum; +import com.hanserwei.common.utils.Response; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Optional; + +@ControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + /** + * 捕获自定义业务异常 + * + * @return 错误信息 + */ + @ExceptionHandler({BizException.class}) + @ResponseBody + public Response handleBizException(HttpServletRequest request, BizException e) { + log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg()); + return Response.fail(e); + } + + /** + * 其他类型异常 + * + * @param request 请求 + * @param e 异常 + * @return 错误信息 + */ + @ExceptionHandler({Exception.class}) + @ResponseBody + public Response handleOtherException(HttpServletRequest request, Exception e) { + log.error("{} request error, ", request.getRequestURI(), e); + return Response.fail(ResponseCodeEnum.SYSTEM_ERROR); + } + + /** + * 捕获参数校验异常 + * + * @return 错误信息 + */ + @ExceptionHandler({MethodArgumentNotValidException.class}) + @ResponseBody + public Response handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) { + // 参数错误异常码 + String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode(); + + // 获取 BindingResult + BindingResult bindingResult = e.getBindingResult(); + + StringBuilder sb = new StringBuilder(); + + // 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com'; + Optional.of(bindingResult.getFieldErrors()).ifPresent(errors -> errors.forEach(error -> + sb.append(error.getField()) + .append(" ") + .append(error.getDefaultMessage()) + .append(", 当前值: '") + .append(error.getRejectedValue()) + .append("'; ") + )); + + // 错误信息 + String errorMessage = sb.toString(); + + log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage); + + return Response.fail(errorCode, errorMessage); + } + +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/utils/Response.java b/weblog-module-common/src/main/java/com/hanserwei/common/utils/Response.java new file mode 100644 index 0000000..a6c18a0 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/utils/Response.java @@ -0,0 +1,70 @@ +package com.hanserwei.common.utils; + +import com.hanserwei.common.exception.BaseExceptionInterface; +import com.hanserwei.common.exception.BizException; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class Response implements Serializable { + + // 是否成功,默认为 true + private boolean success = true; + // 响应消息 + private String message; + // 异常码 + private String errorCode; + // 响应数据 + private T data; + + // =================================== 成功响应 =================================== + public static Response success() { + return new Response<>(); + } + + public static Response success(T data) { + Response response = new Response<>(); + response.setData(data); + return response; + } + + // =================================== 失败响应 =================================== + public static Response fail() { + Response response = new Response<>(); + response.setSuccess(false); + return response; + } + + public static Response fail(String errorMessage) { + Response response = new Response<>(); + response.setSuccess(false); + response.setMessage(errorMessage); + return response; + } + + public static Response fail(String errorCode, String errorMessage) { + Response response = new Response<>(); + response.setSuccess(false); + response.setErrorCode(errorCode); + response.setMessage(errorMessage); + return response; + } + + public static Response fail(BizException bizException) { + Response response = new Response<>(); + response.setSuccess(false); + response.setErrorCode(bizException.getErrorCode()); + response.setMessage(bizException.getErrorMsg()); + return response; + } + + public static Response fail(BaseExceptionInterface baseExceptionInterface) { + Response response = new Response<>(); + response.setSuccess(false); + response.setErrorCode(baseExceptionInterface.getErrorCode()); + response.setMessage(baseExceptionInterface.getErrorMsg()); + return response; + } + +} \ No newline at end of file diff --git a/weblog-web/build.gradle.kts b/weblog-web/build.gradle.kts index 90c9014..6805b00 100644 --- a/weblog-web/build.gradle.kts +++ b/weblog-web/build.gradle.kts @@ -13,6 +13,9 @@ dependencies { // Test testImplementation("org.springframework.boot:spring-boot-starter-test") + // jsr380 + implementation("org.springframework.boot:spring-boot-starter-validation") + // 其他依赖… implementation(project(":weblog-module-common")) implementation(project(":weblog-module-admin")) diff --git a/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java b/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java index 3d5a892..b85c902 100644 --- a/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java +++ b/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java @@ -3,19 +3,36 @@ package com.hanserwei.web.controller; import com.hanserwei.common.aspect.ApiOperationLog; import com.hanserwei.web.model.User; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import java.util.stream.Collectors; + @RestController @Slf4j public class TestController { @PostMapping("/test") @ApiOperationLog(description = "测试接口") - public User test(@RequestBody User user) { + public ResponseEntity test(@RequestBody @Validated User user, BindingResult bindingResult) { + // 是否存在校验错误 + if (bindingResult.hasErrors()) { + // 获取校验不通过字段的提示信息 + String errorMsg = bindingResult.getFieldErrors() + .stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + + return ResponseEntity.badRequest().body(errorMsg); + } + // 返参 - return user; + return ResponseEntity.ok("参数没有任何问题"); } } \ No newline at end of file diff --git a/weblog-web/src/main/java/com/hanserwei/web/model/User.java b/weblog-web/src/main/java/com/hanserwei/web/model/User.java index f3d64d4..95eeb0f 100644 --- a/weblog-web/src/main/java/com/hanserwei/web/model/User.java +++ b/weblog-web/src/main/java/com/hanserwei/web/model/User.java @@ -1,11 +1,25 @@ package com.hanserwei.web.model; +import jakarta.validation.constraints.*; import lombok.Data; @Data public class User { - // 用户名 + // 用户名 + @NotBlank(message = "用户名不能为空") // 注解确保用户名不为空 private String username; // 性别 + @NotNull(message = "性别不能为空") // 注解确保性别不为空 private Integer sex; + + // 年龄 + @NotNull(message = "年龄不能为空") + @Min(value = 18, message = "年龄必须大于或等于 18") // 注解确保年龄大于等于 18 + @Max(value = 100, message = "年龄必须小于或等于 100") // 注解确保年龄小于等于 100 + private Integer age; + + // 邮箱 + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") // 注解确保邮箱格式正确 + private String email; } \ No newline at end of file