feat(common): add exception handling and response utilities

backend project initial!!!
This commit is contained in:
2025-11-26 20:24:16 +08:00
parent 531d99b3df
commit 3eb651e039
11 changed files with 291 additions and 3 deletions

2
.idea/modules.xml generated
View File

@@ -3,6 +3,8 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/weblog-springboot.iml" filepath="$PROJECT_DIR$/weblog-springboot.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.main.iml" />
</modules>
</component>
</project>

View File

@@ -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")
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,7 @@
package com.hanserwei.common.exception;
public interface BaseExceptionInterface {
String getErrorCode();
String getErrorMsg();
}

View File

@@ -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();
}
}

View File

@@ -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<Object> 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<Object> 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<Object> 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);
}
}

View File

@@ -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<T> implements Serializable {
// 是否成功,默认为 true
private boolean success = true;
// 响应消息
private String message;
// 异常码
private String errorCode;
// 响应数据
private T data;
// =================================== 成功响应 ===================================
public static <T> Response<T> success() {
return new Response<>();
}
public static <T> Response<T> success(T data) {
Response<T> response = new Response<>();
response.setData(data);
return response;
}
// =================================== 失败响应 ===================================
public static <T> Response<T> fail() {
Response<T> response = new Response<>();
response.setSuccess(false);
return response;
}
public static <T> Response<T> fail(String errorMessage) {
Response<T> response = new Response<>();
response.setSuccess(false);
response.setMessage(errorMessage);
return response;
}
public static <T> Response<T> fail(String errorCode, String errorMessage) {
Response<T> response = new Response<>();
response.setSuccess(false);
response.setErrorCode(errorCode);
response.setMessage(errorMessage);
return response;
}
public static <T> Response<T> fail(BizException bizException) {
Response<T> response = new Response<>();
response.setSuccess(false);
response.setErrorCode(bizException.getErrorCode());
response.setMessage(bizException.getErrorMsg());
return response;
}
public static <T> Response<T> fail(BaseExceptionInterface baseExceptionInterface) {
Response<T> response = new Response<>();
response.setSuccess(false);
response.setErrorCode(baseExceptionInterface.getErrorCode());
response.setMessage(baseExceptionInterface.getErrorMsg());
return response;
}
}

View File

@@ -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"))

View File

@@ -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<String> 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("参数没有任何问题");
}
}

View File

@@ -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;
}