Jackson 序列化配置
基础配置
package com.bootemp.boot3.infrastructur.core.jackson;
import com.bootemp.boot3.common.constants.DateFormatConstant;
import com.bootemp.boot3.infrastructur.core.jackson.deserializer.CustomLongDeserializer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
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 lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Jackson配置
*
* @author 十八
*/
@Slf4j
public class ObjectMapperConfig {
public static final ObjectMapper OBJECT_MAPPER = configureObjectMapper();
private static ObjectMapper configureObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// LocalDateTime 时间序列化
JavaTimeModule javaTimeModule = configureJavaTimeModule();
objectMapper.registerModule(javaTimeModule);
// Jackson 其他配置
configureOtherSettings(objectMapper);
return objectMapper;
}
private static JavaTimeModule configureJavaTimeModule() {
// 初始化JavaTimeModule
JavaTimeModule javaTimeModule = new JavaTimeModule();
//处理LocalDateTime
DateTimeFormatter localDateTimeFormatter = DateFormatConstant.LocalDateFormatter.DATE_TIME_FORMAT;
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(localDateTimeFormatter));
//处理LocalDate
DateTimeFormatter localDateFormatter = DateFormatConstant.LocalDateFormatter.DATE_FORMAT;
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(localDateFormatter));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(localDateFormatter));
//处理LocalTime
DateTimeFormatter localTimeFormatter = DateFormatConstant.LocalDateFormatter.TIME_FORMAT;
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(localTimeFormatter));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter));
return javaTimeModule;
}
private static void configureOtherSettings(ObjectMapper objectMapper) {
objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, false);
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
objectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY);
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<>() {
@Override
public void serialize(Object o, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
matchDataTypeDefaultValue(gen);
}
});
}
private static void matchDataTypeDefaultValue(JsonGenerator gen) throws IOException {
// 检查 getCurrentValue 是否为 null
if (gen.getCurrentValue() == null) {
gen.writeString("");
return;
}
String fieldName = gen.getOutputContext().getCurrentName();
if (fieldName == null || fieldName.trim().isEmpty()) {
throw new IllegalArgumentException("fieldName cannot be null or empty");
}
try {
Field field = gen.getCurrentValue().getClass().getDeclaredField(fieldName);
if (isStringType(field)) {
gen.writeString("");
} else if (isListType(field)) {
gen.writeStartArray();
gen.writeEndArray();
} else if (isMapType(field)) {
gen.writeStartObject();
gen.writeEndObject();
}
} catch (NoSuchFieldException e) {
log.error("JSON 序列化默认值填充错误: {}", e.getMessage(), e);
gen.writeString("");
}
}
private static boolean isStringType(Field field) {
return Objects.equals(field.getType(), String.class);
}
private static boolean isListType(Field field) {
return List.class.isAssignableFrom(field.getType());
}
private static boolean isMapType(Field field) {
return Map.class.isAssignableFrom(field.getType());
}
}
Long类型数据处理
在项目开发中,通常会存在如雪花ID,订单号等 Long
类型数据,但是前端直接解析后端返回的Long类型数据会存在精度溢出的问题,所以在后端统一将 Long
类型数据转换为字符串进行传输。
自定义Long类型数据序列化器
package com.bootemp.boot3.infrastructur.core.jackson.deserializer;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
/**
* @author 十八
* @description 自定义Long2String反序列化器
*/
public class CustomLongDeserializer extends JsonDeserializer<Long> {
@Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getText();
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return null;
}
}
}
配置自定义序列化器
修改后的配置文件如下:
package com.bootemp.boot3.infrastructur.core.jackson;
import com.bootemp.boot3.common.constants.DateFormatConstant;
import com.bootemp.boot3.infrastructur.core.jackson.deserializer.CustomLongDeserializer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
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 lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Jackson配置
*
* @author 十八
* @since 2024-07-02 01:50
*/
@Slf4j
public class ObjectMapperConfig {
public static final ObjectMapper OBJECT_MAPPER = configureObjectMapper();
private static ObjectMapper configureObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// LocalDateTime 时间序列化
JavaTimeModule javaTimeModule = configureJavaTimeModule();
objectMapper.registerModule(javaTimeModule);
// Long转String
SimpleModule longToStringModule = configureLongToStringModule();
objectMapper.registerModule(longToStringModule);
// Jackson 其他配置
configureOtherSettings(objectMapper);
return objectMapper;
}
private static JavaTimeModule configureJavaTimeModule() {
// 初始化JavaTimeModule
JavaTimeModule javaTimeModule = new JavaTimeModule();
//处理LocalDateTime
DateTimeFormatter localDateTimeFormatter = DateFormatConstant.LocalDateFormatter.DATE_TIME_FORMAT;
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(localDateTimeFormatter));
//处理LocalDate
DateTimeFormatter localDateFormatter = DateFormatConstant.LocalDateFormatter.DATE_FORMAT;
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(localDateFormatter));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(localDateFormatter));
//处理LocalTime
DateTimeFormatter localTimeFormatter = DateFormatConstant.LocalDateFormatter.TIME_FORMAT;
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(localTimeFormatter));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter));
return javaTimeModule;
}
private static SimpleModule configureLongToStringModule() {
SimpleModule longToString = new SimpleModule();
longToString.addSerializer(Long.class, new ToStringSerializer());
longToString.addSerializer(Long.TYPE, new ToStringSerializer());
longToString.addDeserializer(Long.class, new CustomLongDeserializer());
return longToString;
}
private static void configureOtherSettings(ObjectMapper objectMapper) {
objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, false);
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
objectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY);
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<>() {
@Override
public void serialize(Object o, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
matchDataTypeDefaultValue(gen);
}
});
}
private static void matchDataTypeDefaultValue(JsonGenerator gen) throws IOException {
// 检查 getCurrentValue 是否为 null
if (gen.getCurrentValue() == null) {
gen.writeString("");
return;
}
String fieldName = gen.getOutputContext().getCurrentName();
if (fieldName == null || fieldName.trim().isEmpty()) {
throw new IllegalArgumentException("fieldName cannot be null or empty");
}
try {
Field field = gen.getCurrentValue().getClass().getDeclaredField(fieldName);
if (isStringType(field)) {
gen.writeString("");
} else if (isListType(field)) {
gen.writeStartArray();
gen.writeEndArray();
} else if (isMapType(field)) {
gen.writeStartObject();
gen.writeEndObject();
}
} catch (NoSuchFieldException e) {
log.error("JSON 序列化默认值填充错误: {}", e.getMessage(), e);
gen.writeString("");
}
}
private static boolean isStringType(Field field) {
return Objects.equals(field.getType(), String.class);
}
private static boolean isListType(Field field) {
return List.class.isAssignableFrom(field.getType());
}
private static boolean isMapType(Field field) {
return Map.class.isAssignableFrom(field.getType());
}
}
全局配置
package com.bootemp.boot3.config;
import com.bootemp.boot3.infrastructur.core.jackson.ObjectMapperConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 十八
*/
@Slf4j
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper getJacksonObjectMapper() {
return ObjectMapperConfig.OBJECT_MAPPER;
}
}
JsonUtil
工具类
使用到了Hutool工具类,引入如下依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
实现代码:
package com.bootemp.boot3.common.utils;
import cn.hutool.json.JSONUtil;
import com.bootemp.boot3.infrastructur.core.jackson.ObjectMapperConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Json 工具类
*
* @author 十八
*/
@Slf4j
public class JsonUtil {
public static final ObjectMapper OBJECT_MAPPER = ObjectMapperConfig.OBJECT_MAPPER;
private JsonUtil() {
}
/**
* 对象转换
*
* @param obj 源对象
* @param clazz 目标实体类型
* @return 目标实体对象
*/
public static <T> T cov(Object obj, Class<T> clazz) {
return OBJECT_MAPPER.convertValue(obj, clazz);
}
/**
* 将object对象转成JSON字符串
*
* @param object 源对象
* @return JSON字符串
*/
public static String toJsonStr(Object object) {
if (Objects.isNull(object)) {
return null;
}
try {
return OBJECT_MAPPER.writeValueAsString(object);
} catch (Exception e) {
log.error("Object to Json exception ", e);
return null;
}
}
/**
* JSON字符串反序列化为目标对象
*
* @param jsonStr JSON字符串
* @param cls 目标实体类型
* @return 目标实体对象
*/
public static <T> T toObj(String jsonStr, Class<T> cls) {
if (StringUtils.isBlank(jsonStr) || cls == null) {
return null;
}
try {
return OBJECT_MAPPER.readValue(jsonStr, cls);
} catch (Exception e) {
log.error("Json to Bean exception", e);
return null;
}
}
/**
* JSON字符串反序列化为Map
*
* @param jsonStr JSON字符串
* @param kClazz Map集合-Key类型
* @param vClazz Map集合-Value类型
* @return {@link Map}<{@link K}, {@link V}>
*/
public static <K, V> Map<K, V> toMap(String jsonStr, Class<K> kClazz, Class<V> vClazz) {
if (StringUtils.isBlank(jsonStr) || kClazz == null || vClazz == null) {
return Collections.emptyMap();
}
Map<K, V> map;
try {
map = OBJECT_MAPPER.readValue(jsonStr, OBJECT_MAPPER.getTypeFactory().constructParametricType(Map.class, kClazz, vClazz));
} catch (JsonProcessingException e) {
log.error("Json to Map exception", e);
return Collections.emptyMap();
}
return map;
}
/**
* Map集合JSON字符串,反序列化为List<Map<>>
*
* @param jsonStr mapJSON字符串
* @param clz 实体类型
* @return List<Map <>>对象
*/
@NotNull
public static <T> List<Map<Object, T>> toListMap(String jsonStr, Class<T> clz) {
if (StringUtils.isBlank(jsonStr) || clz == null) {
return Collections.emptyList();
}
List<String> mapJsonStr = JSONUtil.toList(jsonStr, String.class);
assert mapJsonStr != null;
List<Map<Object, T>> list = new ArrayList<>(mapJsonStr.size());
for (String mapStr : mapJsonStr) {
list.add(toMap(mapStr, Object.class, clz));
}
return list;
}
/**
* 创建一个JSON对象
*
* @return JSON对象
*/
public static ObjectNode createObj() {
return OBJECT_MAPPER.createObjectNode();
}
/**
* 创建JSON数组
*
* @return JSON数组
*/
public static ArrayNode createArr() {
return OBJECT_MAPPER.createArrayNode();
}
}
项目接口统一返回结构
涉及到分页数据,需要在
pom.xml
中先添加 MyBatisPlus 依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
基础类
BaseResult
:基础返回实体
package com.bootemp.boot3.infrastructur.core.model.result;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 基础返回实体
*
* @author 十八
*/
@Data
public abstract class BaseResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private int code;
private String message;
private Boolean success;
public BaseResult() {
}
public BaseResult(int code, String message) {
this.code = code;
this.message = message;
}
public BaseResult(int code, String message, boolean success) {
this.code = code;
this.message = message;
this.success = success;
}
}
PageResult
:分页返回实体
package com.bootemp.boot3.infrastructur.core.model.result;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import org.apache.commons.lang3.ObjectUtils;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 分页返回实体
*
* @author 十八
*/
@Data
public class PageResult<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Integer current;
private Integer pageSize;
private Integer total;
private List<T> records;
public static <T> PageResult<T> page2Result(IPage<T> result) {
PageResult<T> data = new PageResult<>();
data.setCurrent((int) result.getCurrent());
data.setPageSize((int) result.getSize());
data.setTotal((int) (ObjectUtils.isNotEmpty(result.getTotal()) ? result.getTotal() : 0L));
data.setRecords(result.getRecords());
return data;
}
public static <T> PageResult<T> page2Result(Integer total, List<T> records) {
PageResult<T> data = new PageResult<>();
data.setTotal(total);
data.setRecords(records);
return data;
}
public static <T, R> PageResult<R> page2Result(List<R> records, IPage<T> result) {
PageResult<R> data = new PageResult<>();
data.setCurrent((int) result.getCurrent());
data.setPageSize((int) result.getSize());
data.setTotal((int) (ObjectUtils.isNotEmpty(result.getTotal()) ? result.getTotal() : 0L));
data.setRecords(records);
return data;
}
}
ApiResult
:接口统一返回
package com.bootemp.boot3.infrastructur.core.model.result;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.bootemp.boot3.common.constants.AppConstant;
import com.bootemp.boot3.common.enums.StatusType;
import com.bootemp.boot3.common.exception.ResponseCode;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 接口统一返回
*
* @author 十八
*/
@Data
@EqualsAndHashCode(callSuper = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiResult<T> extends BaseResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonProperty("data")
private T data;
private ApiResult() {
}
public ApiResult(int code, String message, boolean success, T data) {
super(code, message, success);
this.data = data;
}
public static ApiResult<String> ok() {
return success(AppConstant.API_OPERATION_SUCCESS_MESSAGE);
}
public static <T> ApiResult<T> success(int code, String message, T data) {
return new ApiResult<>(code, message, StatusType.YES.getValue(), data);
}
public static <T> ApiResult<T> success(ResponseCode status, T data) {
return success(status.getCode(), status.getMessage(), data);
}
public static <T> ApiResult<T> success(ResponseCode status) {
return success(status.getCode(), status.getMessage(), null);
}
public static <T> ApiResult<T> success(T data) {
return success(ResponseCode.SUCCESS, data);
}
public static <T> ApiResult<T> success(String message, T data) {
return success(ResponseCode.SUCCESS.getCode(), message, data);
}
public static <T> ApiResult<T> success() {
return success(ResponseCode.SUCCESS, null);
}
public static <T> ApiResult<T> error(int code, String message, T data) {
return new ApiResult<>(code, message, StatusType.NO.getValue(), data);
}
public static <T> ApiResult<T> error(int code, String message) {
return error(code, message, null);
}
public static <T> ApiResult<T> error(ResponseCode status, T data) {
return error(status.getCode(), status.getMessage(), data);
}
public static <T> ApiResult<T> error(ResponseCode status, String message) {
return error(status.getCode(), message, null);
}
public static <T> ApiResult<T> error(ResponseCode status) {
return error(status.getCode(), status.getMessage(), null);
}
public static <T> ApiResult<T> error(T data) {
return error(ResponseCode.ERROR, data);
}
public static <T> ApiResult<T> error() {
return error(ResponseCode.ERROR, null);
}
// ==================== 特定操作类型返回 ====================
public static ApiResult<String> created() {
return success(AppConstant.DEFAULT_CREATE_OPTION_SUCCESS_MESSAGE);
}
public static <T> ApiResult<T> created(T data) {
return success(AppConstant.DEFAULT_CREATE_OPTION_SUCCESS_MESSAGE, data);
}
public static ApiResult<String> updated() {
return success(AppConstant.DEFAULT_UPDATE_OPTION_SUCCESS_MESSAGE);
}
public static <T> ApiResult<T> updated(T data) {
return success(AppConstant.DEFAULT_UPDATE_OPTION_SUCCESS_MESSAGE, data);
}
public static ApiResult<String> deleted() {
return success(AppConstant.DEFAULT_DELETE_OPTION_SUCCESS_MESSAGE);
}
public static <T> ApiResult<T> deleted(T data) {
return success(AppConstant.DEFAULT_DELETE_OPTION_SUCCESS_MESSAGE, data);
}
// ==================== 以下为分页数据封装返回 ====================
public static <T> ApiResult<PageResult<T>> success(IPage<T> data) {
return success(PageResult.page2Result(data));
}
public static <T> ApiResult<PageResult<T>> success(Integer total, List<T> data) {
return success(PageResult.page2Result(total, data));
}
public static <T, R> ApiResult<PageResult<R>> success(List<R> data, IPage<T> page) {
return success(PageResult.page2Result(data, page));
}
} z
测试
修改 HelloController
package com.bootemp.boot3.trigger.controller;
import com.bootemp.boot3.infrastructur.core.model.result.ApiResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public ApiResult<String> hello() {
return ApiResult.success("Hello, SpringBoot 3.2.5!");
}
}
![[assets/2. 项目通用配置/image.png]]
全局异常处理
基础类
ResponseCode
:接口返回码
package com.bootemp.boot3.common.exception;
import lombok.Getter;
/**
* 业务状态码枚举
*
* @author 十八
*/
@Getter
public enum ResponseCode {
SUCCESS(200, "操作成功"),
ERROR(-1, "系统异常,操作失败"),
BIZ_ERROR(500, "通用业务异常"),
UNAUTHORIZED(401, "认证失败请重新登录"),
FORBIDDEN(403, "权限不足无法访问"),
RESOURCE_NOT_FOUNT(404, "请求资源不存在");
/**
* 状态码
*/
private final int code;
/**
* 状态信息
*/
private final String message;
ResponseCode(int code, String message) {
this.code = code;
this.message = message;
}
}
BizException
:自定义系统统一异常
package com.bootemp.boot3.common.exception;
import lombok.Getter;
import java.io.Serial;
/**
* 自定义异常类
*
* @author 十八
* @since 2022/11/9
*/
@Getter
public class BizException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private final int errorCode;
/**
* 错误信息
*/
private final String errorMessage;
public BizException() {
super();
this.errorMessage = ResponseCode.BIZ_ERROR.getMessage();
this.errorCode = ResponseCode.BIZ_ERROR.getCode();
}
public BizException(Throwable cause) {
super(cause);
this.errorMessage = ResponseCode.BIZ_ERROR.getMessage();
this.errorCode = ResponseCode.BIZ_ERROR.getCode();
}
public BizException(String message, Throwable cause) {
super(message, cause);
this.errorMessage = message;
this.errorCode = ResponseCode.BIZ_ERROR.getCode();
}
public BizException(String errorMessage) {
super();
this.errorMessage = errorMessage;
this.errorCode = ResponseCode.BIZ_ERROR.getCode();
}
public BizException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public BizException(ResponseCode errorInfoInterface) {
super(errorInfoInterface.getMessage());
this.errorCode = errorInfoInterface.getCode();
this.errorMessage = errorInfoInterface.getMessage();
}
public BizException(ResponseCode errorInfoInterface, String message) {
super(errorInfoInterface.getMessage());
this.errorCode = errorInfoInterface.getCode();
this.errorMessage = errorInfoInterface.getMessage() + " : " + message;
}
public BizException(ResponseCode errorInfoInterface, Throwable cause) {
super(errorInfoInterface.getMessage(), cause);
this.errorCode = errorInfoInterface.getCode();
this.errorMessage = errorInfoInterface.getMessage();
}
public BizException(int errorCode, String errorMessage, Throwable cause) {
super(errorMessage, cause);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
BizAssert
:断言处理类,用于统一 API 异常抛出
package com.bootemp.boot3.common.exception;
import jakarta.annotation.Nullable;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
/**
* 断言处理类,用于抛出各种API异常
*
* @author 十八
*/
public class BizAssert {
private BizAssert() {
}
public static void error(String message) {
throw new BizException(message);
}
public static void error(int code, String message) {
throw new BizException(code, message);
}
public static void error(ResponseCode errorCode) {
throw new BizException(errorCode);
}
public static void error(ResponseCode errorCode, String message) {
throw new BizException(errorCode, message);
}
public static void error() {
throw new BizException(ResponseCode.BIZ_ERROR);
}
public static void error(boolean flag, String errorMessage) {
if (flag) {
throw new BizException(errorMessage);
}
}
public static void error(boolean flag, ResponseCode errorCode) {
if (flag) {
throw new BizException(errorCode);
}
}
public static void isNull(@Nullable Object object, ResponseCode status) {
if (ObjectUtils.isNotEmpty(object)) {
throw new BizException(status);
}
}
public static void isNull(@Nullable Object object, String message) {
if (ObjectUtils.isNotEmpty(object)) {
throw new BizException(message);
}
}
public static void notNull(@Nullable Object object, ResponseCode status) {
if (ObjectUtils.isEmpty(object)) {
throw new BizException(status);
}
}
public static void notNull(@Nullable Object object, String message) {
if (ObjectUtils.isEmpty(object)) {
throw new BizException(message);
}
}
public static void notBlank(String object, ResponseCode status) {
if (StringUtils.isBlank(object)) {
throw new BizException(status);
}
}
public static void notBlank(String object, String message) {
if (StringUtils.isBlank(object)) {
throw new BizException(message);
}
}
public static void isTrue(boolean flag, String message) {
if (!flag) {
throw new BizException(message);
}
}
public static void isTrue(boolean flag, ResponseCode status) {
if (!flag) {
throw new BizException(status);
}
}
public static void isFalse(boolean flag, String message) {
if (flag) {
throw new BizException(message);
}
}
public static void isFalse(boolean flag, ResponseCode status) {
if (flag) {
throw new BizException(status);
}
}
}
全局异常配置
全局异常处理包括了参数校验异常捕获,需要添加如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
GlobalExceptionHandler
:全局异常处理
package com.bootemp.boot3.config.exception;
import com.bootemp.boot3.common.exception.BizException;
import com.bootemp.boot3.common.exception.ResponseCode;
import com.bootemp.boot3.common.utils.JsonUtil;
import com.bootemp.boot3.infrastructur.core.model.result.ApiResult;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* 全局异常处理
*
* @author 十八
*/
@Order(1)
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
public static final String EXCEPTION_LOG = """
\n\r============================= {}
请求地址: {}
请求方式: {}
请求参数: {}
异常信息: {}
============================= """;
/**
* 处理自定义的业务异常
*/
@ExceptionHandler(value = BizException.class)
public ApiResult<BizException> bizExceptionHandler(@NotNull HttpServletRequest req, @NotNull BizException e) {
log.error(EXCEPTION_LOG,
"业务异常",
req.getRequestURL(),
req.getMethod(),
JsonUtil.toJsonStr(req.getParameterMap()),
e.getErrorCode() + ": " + e.getErrorMessage());
return ApiResult.error(e.getErrorCode(), e.getErrorMessage());
}
/**
* 参数异常信息返回
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ApiResult<Map<String, String>> methodArgumentNotValidExceptionHandler(HttpServletRequest req, @NotNull MethodArgumentNotValidException e) {
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
Map<String, String> paramExceptionInfo = new TreeMap<>();
StringBuilder builder = new StringBuilder();
for (ObjectError objectError : allErrors) {
FieldError fieldError = (FieldError) objectError;
builder.append(fieldError.getDefaultMessage()).append("\r\n");
paramExceptionInfo.put(fieldError.getField(), fieldError.getDefaultMessage());
} log.error(EXCEPTION_LOG,
"请求参数校验错误",
req.getRequestURL(),
req.getMethod(),
JsonUtil.toJsonStr(req.getParameterMap()),
paramExceptionInfo);
return ApiResult.error(ResponseCode.ARGUMENT_ERROR, builder.toString());
}
/**
* 参数异常信息返回
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public ApiResult<Map<String, String>> constraintViolationExceptionHandler(HttpServletRequest req, @NotNull ConstraintViolationException e) {
log.error(EXCEPTION_LOG,
"请求参数校验错误",
req.getRequestURL(),
req.getMethod(),
JsonUtil.toJsonStr(req.getParameterMap()),
e.getMessage());
return ApiResult.error(ResponseCode.ARGUMENT_ERROR, e.getMessage().split(":")[1]);
}
}
RuntimeExceptionHandler
:未定义异常处理
package com.bootemp.boot3.config.exception;
import com.bootemp.boot3.common.exception.ResponseCode;
import com.bootemp.boot3.common.utils.JsonUtil;
import com.bootemp.boot3.infrastructur.core.model.result.ApiResult;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
*
* @author 十八
*/
@Order(2)
@RestControllerAdvice
@Slf4j
public class RuntimeExceptionHandler {
/**
* 处理其他异常
*
* @param req the req
* @param e the e
* @return ApiResult
*/
@ExceptionHandler(value = RuntimeException.class)
public ApiResult<String> exceptionHandler(HttpServletRequest req, Exception e) {
log.error(GlobalExceptionHandler.EXCEPTION_LOG,
"未定义异常",
req.getRequestURL(),
req.getMethod(),
JsonUtil.toJsonStr(req.getParameterMap()),
e.getMessage(),
e);
return ApiResult.error(ResponseCode.ERROR);
}
}