1.增加插入数据时的 createTime字段 为 LocalDateTime

2.自动序列和反序列化 CreateTime 字段
3.修改rabbitMQ序列化方法,使其支持 LocalDateTime
This commit is contained in:
2025-06-26 19:12:27 +08:00
parent 4f575c2eb9
commit a90c4a347d
14 changed files with 401 additions and 29 deletions

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yupi</groupId>

View File

@@ -0,0 +1,37 @@
package com.yupi.springbootinit.common;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import java.io.IOException;
/**
* Long 序列化规则
*
* 会将超长 long 值转换为 string解决前端 JavaScript 最大安全整数是 2^53-1 的问题
*
* @author 星语
*/
@JacksonStdImpl
public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer {
private static final long MAX_SAFE_INTEGER = 9007199254740991L;
private static final long MIN_SAFE_INTEGER = -9007199254740991L;
public static final NumberSerializer INSTANCE = new NumberSerializer(Number.class);
public NumberSerializer(Class<? extends Number> rawType) {
super(rawType);
}
@Override
public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 超出范围 序列化位字符串
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
super.serialize(value, gen, serializers);
} else {
gen.writeString(value.toString());
}
}
}

View File

@@ -0,0 +1,27 @@
package com.yupi.springbootinit.common;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 基于时间戳的 LocalDateTime 反序列化器
*
* @author 老五
*/
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// 将 Long 时间戳,转换为 LocalDateTime 对象
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
}
}

View File

@@ -0,0 +1,26 @@
package com.yupi.springbootinit.common;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 基于时间戳的 LocalDateTime 序列化器
*
* @author 老五
*/
public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 将 LocalDateTime 对象,转换为 Long 时间戳
gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
}
}

View File

@@ -1,6 +1,13 @@
package com.yupi.springbootinit.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.rabbitmq.client.ConnectionFactory;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
@@ -18,9 +25,20 @@ public class RabbitMQConfig {
public Queue hostInfoQueue(){
return new Queue(QUEUE,true);
}
//
// @Bean
// public MessageConverter messageConverter(){
// return new Jackson2JsonMessageConverter();
// }
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
public MessageConverter messageConverter() {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule());
return new Jackson2JsonMessageConverter(om);
}
}

View File

@@ -0,0 +1,53 @@
package com.yupi.springbootinit.config;
import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.yupi.springbootinit.common.NumberSerializer;
import com.yupi.springbootinit.common.TimestampLocalDateTimeDeserializer;
import com.yupi.springbootinit.common.TimestampLocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import com.yupi.springbootinit.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
@AutoConfiguration
@Slf4j
public class YudaoJacksonAutoConfiguration {
@Bean
@SuppressWarnings("InstantiationOfUtilityClass")
public JsonUtils jsonUtils(List<ObjectMapper> objectMappers) {
// 1.1 创建 SimpleModule 对象
SimpleModule simpleModule = new SimpleModule();
simpleModule
// 新增 Long 类型序列化规则,数值超过 2^53-1在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型
.addSerializer(Long.class, NumberSerializer.INSTANCE)
.addSerializer(Long.TYPE, NumberSerializer.INSTANCE)
.addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)
.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)
.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
// 新增 LocalDateTime 序列化、反序列化规则,使用 Long 时间戳
.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
// 1.2 注册到 objectMapper
objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule));
// 2. 设置 objectMapper 到 JsonUtils
JsonUtils.init(CollUtil.getFirst(objectMappers));
log.info("[init][初始化 JsonUtils 成功]");
return new JsonUtils();
}
}

View File

@@ -4,8 +4,17 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
/*
* @author: ziin
@@ -98,4 +107,8 @@ public class ServerBigBrother {
*/
@TableField(value = "tenant_id")
private Long tenantId;
@TableField(value = "create_time")
private LocalDateTime createTime;
}

View File

@@ -12,6 +12,7 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@@ -37,10 +38,11 @@ public class MQSender {
public void bigBrotherSend(ServerBigBrother bigBrothers){
try {
// log.info("{} 接收到的消息数量----------->{}", DateTime.now(),list.size());
this.rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
// this.rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
//指定你队列的名字
rabbitTemplate.convertAndSend("BIG_BROTHER_QUEUE",bigBrothers);
}catch (Exception e){
log.error(e.getMessage() );
throw new BusinessException(ErrorCode.QUEUE_ERROR);
}
}

View File

@@ -33,13 +33,16 @@ public class ServerBigBrotherServiceImpl extends ServiceImpl<ServerBigBrotherMap
.eq(ServerBigBrother::getTenantId, bigBrother.getTenantId()));
if(serverBigBrother == null){
save(bigBrother);
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("当前存储花费: {}ms",totalTimeMillis);
} else {
bigBrother.setId(serverBigBrother.getId());
bigBrother.setTotalGiftCoins(bigBrother.getHistoricHighCoins()+serverBigBrother.getTotalGiftCoins());
updateById(bigBrother);
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("当前存储花费: {}ms",totalTimeMillis);
log.info("当前更新花费: {}ms",totalTimeMillis);
}
}
}

View File

@@ -0,0 +1,210 @@
package com.yupi.springbootinit.utils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* JSON 工具类
*
* @author 芋道源码
*/
@Slf4j
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
}
/**
* 初始化 objectMapper 属性
* <p>
* 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
*
* @param objectMapper ObjectMapper 对象
*/
public static void init(ObjectMapper objectMapper) {
JsonUtils.objectMapper = objectMapper;
}
@SneakyThrows
public static String toJsonString(Object object) {
return objectMapper.writeValueAsString(object);
}
@SneakyThrows
public static byte[] toJsonByte(Object object) {
return objectMapper.writeValueAsBytes(object);
}
@SneakyThrows
public static String toJsonPrettyString(Object object) {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}
public static <T> T parseObject(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, Type type) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 将字符串解析成指定类型的对象
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
*
* @param text 字符串
* @param clazz 类型
* @return 对象
*/
public static <T> T parseObject2(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
return JSONUtil.toBean(text, clazz);
}
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
try {
return objectMapper.readValue(bytes, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", bytes, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
*
* @param text 字符串
* @param typeReference 类型引用
* @return 指定类型的对象
*/
public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
return null;
}
}
public static <T> List<T> parseArray(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(String text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(byte[] text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static boolean isJson(String text) {
return JSONUtil.isTypeJSON(text);
}
/**
* 判断字符串是否为 JSON 类型的字符串
* @param str 字符串
*/
public static boolean isJsonObject(String str) {
return JSONUtil.isTypeJSONObject(str);
}
}

View File

@@ -0,0 +1 @@
com.yupi.springbootinit.config.YudaoJacksonAutoConfiguration

View File

@@ -38,7 +38,12 @@ spring:
username: root
password: 123asd
jackson:
date-format: yyyy-MM-dd HH:mm:ss
# date-format: yyyy-MM-dd HH:mm:ss
serialization:
write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
servlet:
multipart:
# 大小限制

View File

@@ -18,10 +18,6 @@
<result column="host_display_id" jdbcType="VARCHAR" property="hostDisplayId" />
<result column="owner_id" jdbcType="VARCHAR" property="ownerId" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="creator" jdbcType="BIGINT" property="creator" />
<result column="updater" jdbcType="VARCHAR" property="updater" />
<result column="deleted" jdbcType="BOOLEAN" property="deleted" />
<result column="tenant_id" jdbcType="BIGINT" property="tenantId" />
</resultMap>
<sql id="Base_Column_List">

View File

@@ -1,19 +0,0 @@
package com.yupi.springbootinit;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 主类测试
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
@SpringBootTest
class MainApplicationTests {
@Test
void contextLoads() {
}
}