【从零搭建SpringBoot3.x 项目脚手架】- 2. 项目通用配置

715 阅读7分钟

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