From 8139c890073fbcc17dd8135d8b4fea411abbb52d Mon Sep 17 00:00:00 2001 From: smallchill Date: Wed, 2 Jul 2025 19:36:18 +0800 Subject: [PATCH] =?UTF-8?q?:tada:=20=E5=A2=9E=E5=BC=BAjackson=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=A7?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E8=87=AA=E5=8A=A8=E8=BD=AC=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=EF=BC=8C=E4=BB=A5=E5=8F=8A=E5=BC=80=E5=90=AF=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E7=A9=BA=E6=95=B0=E5=AD=97=E7=B1=BB=E5=9E=8B=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E4=B8=BA-1=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tool/jackson/BigNumberSerializer.java | 59 +++++++++++++++++++ .../tool/jackson/BladeJacksonProperties.java | 9 +++ .../core/tool/jackson/BladeNumberModule.java | 47 +++++++++++++++ ...appingApiJackson2HttpMessageConverter.java | 14 +++-- 4 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java create mode 100644 blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java new file mode 100644 index 0000000..732ef25 --- /dev/null +++ b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BigNumberSerializer.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.tool.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.ser.std.NumberSerializer; + +import java.io.IOException; + +/** + * 大数值序列化,避免超过js的精度,造成精度丢失 + * + * @author L.cm + */ +@JacksonStdImpl +public class BigNumberSerializer extends NumberSerializer { + + /** + * js 最大值为 Math.pow(2, 53),十进制为:9007199254740992 + */ + private static final long JS_NUM_MAX = 0x20000000000000L; + /** + * js 最小值为 -Math.pow(2, 53),十进制为:-9007199254740992 + */ + private static final long JS_NUM_MIN = -0x20000000000000L; + /** + * Static instance that is only to be used for {@link Number}. + */ + public final static BigNumberSerializer instance = new BigNumberSerializer(Number.class); + + public BigNumberSerializer(Class rawType) { + super(rawType); + } + + @Override + public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { + long longValue = value.longValue(); + if (longValue < JS_NUM_MIN || longValue > JS_NUM_MAX) { + gen.writeString(value.toString()); + } else { + super.serialize(value, gen, provider); + } + } +} diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java index 6759fb9..ea37749 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java +++ b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeJacksonProperties.java @@ -29,6 +29,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("blade.jackson") public class BladeJacksonProperties { + /** + * null 转为 空,字符串转成"",数组转为[],对象转为{},数字转为-1 + */ + private Boolean nullToEmpty = Boolean.TRUE; + /** + * 响应到前端,大数值自动写出为 String,避免精度丢失 + */ + private Boolean bigNumToString = Boolean.TRUE; + /** * 支持 MediaType text/plain,用于和 blade-api-crypto 一起使用 */ diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java new file mode 100644 index 0000000..7e7eeed --- /dev/null +++ b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/BladeNumberModule.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.tool.jackson; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * 大整数序列化为 String 字符串,避免浏览器丢失精度 + * + *

+ * 前端建议采用: + * bignumber 库: https://github.com/MikeMcl/bignumber.js + * decimal.js 库: https://github.com/MikeMcl/decimal.js + *

+ * + * @author L.cm + */ +public class BladeNumberModule extends SimpleModule { + public static final BladeNumberModule INSTANCE = new BladeNumberModule(); + + public BladeNumberModule() { + super(BladeNumberModule.class.getName()); + // Long 和 BigInteger 采用定制的逻辑序列化,避免超过js的精度 + this.addSerializer(Long.class, BigNumberSerializer.instance); + this.addSerializer(Long.TYPE, BigNumberSerializer.instance); + this.addSerializer(BigInteger.class, BigNumberSerializer.instance); + // BigDecimal 采用 toString 避免精度丢失,前端采用 decimal.js 来计算。 + this.addSerializer(BigDecimal.class, ToStringSerializer.instance); + } +} diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java index b3a9b09..816e150 100644 --- a/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java +++ b/blade-core-tool/src/main/java/org/springblade/core/tool/jackson/MappingApiJackson2HttpMessageConverter.java @@ -49,7 +49,7 @@ public class MappingApiJackson2HttpMessageConverter extends AbstractReadWriteJac * @see Jackson2ObjectMapperBuilder#json() */ public MappingApiJackson2HttpMessageConverter(ObjectMapper objectMapper, BladeJacksonProperties properties) { - super(objectMapper, initWriteObjectMapper(objectMapper), initMediaType(properties)); + super(objectMapper, initWriteObjectMapper(objectMapper, properties), initMediaType(properties)); } private static List initMediaType(BladeJacksonProperties properties) { @@ -63,12 +63,18 @@ public class MappingApiJackson2HttpMessageConverter extends AbstractReadWriteJac return supportedMediaTypes; } - private static ObjectMapper initWriteObjectMapper(ObjectMapper readObjectMapper) { + private static ObjectMapper initWriteObjectMapper(ObjectMapper readObjectMapper, BladeJacksonProperties properties) { // 拷贝 readObjectMapper ObjectMapper writeObjectMapper = readObjectMapper.copy(); + // 大数字 转 字符串 + if (Boolean.TRUE.equals(properties.getBigNumToString())) { + writeObjectMapper.registerModules(BladeNumberModule.INSTANCE); + } // null 处理 - writeObjectMapper.setSerializerFactory(writeObjectMapper.getSerializerFactory().withSerializerModifier(new BladeBeanSerializerModifier())); - writeObjectMapper.getSerializerProvider().setNullValueSerializer(BladeBeanSerializerModifier.NullJsonSerializers.STRING_JSON_SERIALIZER); + if (Boolean.TRUE.equals(properties.getNullToEmpty())) { + writeObjectMapper.setSerializerFactory(writeObjectMapper.getSerializerFactory().withSerializerModifier(new BladeBeanSerializerModifier())); + writeObjectMapper.getSerializerProvider().setNullValueSerializer(BladeBeanSerializerModifier.NullJsonSerializers.STRING_JSON_SERIALIZER); + } return writeObjectMapper; }