后端的Long型参数,让阿粉踩了个大坑

探讨JavaScript处理长整型(long)数据时的精度丢失问题,介绍如何通过修改后端数据类型、使用工具进行转化及前端处理等方法解决此问题。重点推荐使用Jackson工具序列化对象,将所有Long类型转换为String类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

每天早上七点三十,准时推送干货

最近几天一直在改造工程,采用雪花算法生成主键ID,突然踩到一个天坑,前端 JavaScript 在取 Long 型参数时,参数值有点不太对!

一、问题描述

最近在改造内部管理系统的时候, 发现了一个巨坑,就是前端 JS 在获取后端 Long 型参数时,出现精度丢失!

起初,用 postman 模拟接口请求,都很正常,但是用浏览器请求的时候,就出现问题了!

  • 问题复现

@RequestMapping("/queryUser")
public List<User> queryUser(){
    List<User> resultList = new ArrayList<>();
    User user = new User();
	//赋予一个long型用户ID
    user.setId(123456789012345678L);
    resultList.add(user);
    return resultList;
}

打开浏览器,请求接口,结果如下!

用 postman 模拟接口请求,结果如下!

刚开始的时候,还真没发现这个坑,结果当进行测试的时候,才发现前端传给后端的ID,与数据库中存的ID不一致,才发现 JavaScript 还有这个天坑!

由于 JavaScript 中 Number 类型的自身原因,并不能完全表示 Long 型的数字,在 Long 长度大于17位时会出现精度丢失的问题。

当我们把上面的用户 ID 改成 19 位的时候,我们再来看看浏览器请求返回的结果。

//设置用户ID,位数为19位
user.setId(1234567890123456789l);

浏览器请求结果!

当返回的结果超过17位的时候,后面的全部变成0!

二、解决办法

遇到这种情况,应该怎么办呢?

  • 第一种办法:在后台把 long 型改为String类型,但是代价有点大,只要涉及到的地方都需要改

  • 第二种办法:使用工具进行转化把 long 型改为String类型,这种方法可以实现全局转化(推荐)

  • 第三种办法:前端进行处理(目前没有很好的办法,不推荐)

因为项目涉及到的代码非常多,所以不可能把 long 型改为 String 类型,而且使用 Long 类型的方法非常多,改起来风险非常大,所以不推荐使用!

最理想的方法,就是使用aop代理拦截所有的方法,对返回参数进行统一处理,使用工具进行转化,过程如下!

2.1、Jackson 工具序列化对象

我们可以使用Jackson工具包来实现对象序列化。

  • 首先在 maven 中添加必须的依赖

<!--jackson依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>
  • 编写一个转化工具类JsonUtil

public class JsonUtil {


    private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);


    private static ObjectMapper objectMapper = new ObjectMapper();
    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";


    static {
        // 对象的所有字段全部列入
        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        // 取消默认转换timestamps形式
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        // 忽略空bean转json的错误
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        //设置为东八区
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        // 统一日期格式
        objectMapper.setDateFormat(new SimpleDateFormat(DATE_FORMAT));
        // 反序列化时,忽略在json字符串中存在, 但在java对象中不存在对应属性的情况, 防止错误
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 序列换成json时,将所有的long变成string
        objectMapper.registerModule(new SimpleModule().addSerializer(Long.class, ToStringSerializer.instance).addSerializer(Long.TYPE, ToStringSerializer.instance));
    }


    /**
     * 对象序列化成json字符串
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> String objToStr(T obj) {
        if (null == obj) {
            return null;
        }


        try {
            return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            log.warn("objToStr error: ", e);
            return null;
        }
    }


    /**
     * json字符串反序列化成对象
     * @param str
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T strToObj(String str, Class<T> clazz) {
        if (StringUtils.isBlank(str) || null == clazz) {
            return null;
        }


        try {
            return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
        } catch (Exception e) {
            log.warn("strToObj error: ", e);
            return null;
        }
    }


    /**
     * json字符串反序列化成对象(数组)
     * @param str
     * @param typeReference
     * @param <T>
     * @return
     */
    public static <T> T strToObj(String str, TypeReference<T> typeReference) {
        if (StringUtils.isBlank(str) || null == typeReference) {
            return null;
        }


        try {
            return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
        } catch (Exception e) {
            log.warn("strToObj error", e);
            return null;
        }
    }
}
  • 紧接着,编写一个实体类Person,用于测试

@Data
public class Person implements Serializable {


    private static final long serialVersionUID = 1L;


    private Integer id;


    //Long型参数
    private Long uid;
    private String name;
    private String address;
    private String mobile;


    private Date createTime;
}
  • 最后,我们编写一个测试类测试一下效果

public static void main(String[] args) {
    Person person = new Person();
    person.setId(1);
    person.setUid(1111L);
    person.setName("hello");
    person.setAddress("");
    System.out.println(JsonUtil.objToStr(person));
}

输出结果如下:

其中最关键一行代码,是注册了这个转换类,从而实现将所有的 long 变成 string。

// 序列换成json时,将所有的long变成string
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);

如果想对某个日期进行格式化,可以全局设置。

//全局统一日期格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

也可以,单独对某个属性进行设置,例如对createTime属性格式化为yyyy-MM-dd,只需要加上如下注解即可。

@JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8")
private Date createTime;

工具转化类写好之后,就非常简单了,只需要对 aop 拦截的方法返回的参数,进行序列化就可以自动实现将所有的 long 变成 string。

2.2、SpringMVC 配置

如果是 SpringMVC 项目,操作也很简单。

  • 自定义一个实现类,继承自ObjectMapper

package com.example.util;


/**
 * 继承ObjectMapper
 */
public class CustomObjectMapper extends ObjectMapper {


    public CustomObjectMapper() {
        super();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        registerModule(simpleModule);
    }
}
  • 在 SpringMVC 的配置文件中加上如下配置

<mvc:annotation-driven >
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <constructor-arg index="0" value="utf-8" />
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json;charset=UTF-8</value>
                    <value>text/plain;charset=UTF-8</value>
                </list>
            </property>
        </bean>          
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="com.example.util.CustomObjectMapper">
                    <property name="dateFormat">
						<-对日期进行统一转化->
                        <bean class="java.text.SimpleDateFormat">
                            <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" />
                        </bean>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
2.3、SpringBoot 配置

如果是 SpringBoot 项目,操作也类似。

  • 编写一个WebConfig配置类,并实现自WebMvcConfigurer,重写configureMessageConverters方法

/**
 * WebMvc配置
 */
@Configuration
@Slf4j
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {


    /**
     *添加消息转化类
     * @param list
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = jsonConverter.getObjectMapper();
		//序列换成json时,将所有的long变成string
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        list.add(jsonConverter);
    }
}

三、总结

在实际的项目开发中,很多服务都是纯微服务开发,没有用到SpringMVC,在这种情况下,使用JsonUtil工具类实现对象序列化,可能是一个非常好的选择。

如果有理解不对的地方,欢迎网友批评指出!

四、参考

1、优快云 - Java下利用Jackson进行JSON解析和序列化

< END >

如果大家喜欢我们的文章,欢迎大家转发,点击在看让更多的人看到。也欢迎大家热爱技术和学习的朋友加入的我们的知识星球当中,我们共同成长,进步。

<think>首先,用户的问题是:如何解决JavaScript前端处理long数值精度丢失的问题。这涉及到处理超过53位的整数时,JavaScript的Number类会丢失精度的问题。 关键点: - JavaScript使用IEEE 754双精度浮点数表示数字,整数范围是-2^53到2^53。 - 当后端返回的long数值(如Javalong,64位)超过这个范围时,前端解析时会丢失精度。 - 解决方案包括使用字符串表示、BigInt、或第三方库。 解决方案步骤: 1. **后端配合**:确保后端返回数值时以字符串形式发送,而不是直接作为JSON数字。这样前端可以解析为字符串,避免精度丢失。 2. **前端处理**: - 使用BigInt:现代浏览器支持BigInt,可以直接处理大整数。 - 使用第三方库:如json-bigint,它可以解析JSON时自动处理大整数。 - 手动处理:解析JSON时,使用reviver函数将特定字段转换为字符串或BigInt。 系统指令要求: - 行内数学表达式用$...$,例如数字范围。 - 独立公式用$$...$$,但这里可能不需要。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。参考引用是[^1],但它是关于长整处理的,我需要自然地引用它。 回答结构: - 先解释问题原因。 - 然后提供解决方案。 - 包括代码示例。 - 最后生成相关问题。 参考引用: - 引用[1]是关于克服JavaScript长整精度丢失的挑战,所以我应该在相关部分引用它,如"如引用[1]所述"。 代码示例: - 展示如何使用BigInt或json-bigint。 生成相关问题: - 基于主题,生成3-4个相关问题。 完整回答: 1. **问题解释**:简要说明为什么精度丢失发生。 2. **解决方案**: - 后端修改:建议后端返回字符串。 - 前端方法: - 使用BigInt。 - 使用json-bigint库。 - 手动解析。 3. **代码示例**:提供JavaScript代码。 4. **相关问题**:在最后生成相关问题。 确保语言流畅、中文。</think>### JavaScript 前端处理 long数值精度丢失问题 在 JavaScript 中,数字类基于 IEEE 754 双精度浮点数标准,其整数范围被限制在 $[-2^{53}, 2^{53}]$(约 $-9e15$ 到 $9e15$)之间。当后端返回的 long数值(如 Java 中的 64 位整数)超出此范围时,前端解析 JSON 数据时会出现精度丢失问题。例如,数值 $1234567890123456789$ 可能被错误解析为 $1234567890123456800$。以下是系统的解决方案: #### 1. **问题原因分析** - JavaScript 的 `Number` 类无法精确表示超过 53 位的整数,导致高位数字被截断或四舍五入[^1]。 - 常见场景:后端(如 Java、C#)返回包含 long字段的 JSON 数据,前端通过 `JSON.parse()` 解析时发生精度丢失。 #### 2. **解决方案** ##### (1) 后端配合:返回字符串格式 - 建议后端long数值以字符串形式返回,避免直接作为 JSON 数字。例如: ```json // 后端响应示例(Java/Spring Boot) { "id": "1234567890123456789", // 字符串格式 "name": "example" } ``` - 优点:简单可靠,兼容所有浏览器。 ##### (2) 前端处理:使用 BigInt 类 - **适用场景**:现代浏览器(Chrome 67+、Firefox 68+、Edge 79+)支持 `BigInt`,可直接处理大整数。 ```javascript // 解析 JSON 时手动转换字符串为 BigInt const response = '{"id": "1234567890123456789"}'; const data = JSON.parse(response, (key, value) => { if (key === 'id') return BigInt(value); // 指定字段转换 return value; }); console.log(data.id.toString()); // 输出: "1234567890123456789" ``` - **注意**:`BigInt` 无法直接与 `Number` 运算,需调用 `.toString()` 或使用 `BigInt` 运算符(如 `+` 改为 `+n`)。 ##### (3) 前端处理:使用第三方库(推荐) - **json-bigint 库**:自动解析 JSON 中的大整数为 `BigInt` 或字符串。 1. 安装库: ```bash npm install json-bigint ``` 2. 使用示例: ```javascript import JSONBig from 'json-bigint'; const response = '{"id": 1234567890123456789}'; const data = JSONBig({ storeAsString: true }).parse(response); // 转为字符串 console.log(data.id); // 输出: "1234567890123456789" ``` - **选项**: - `storeAsString: true`:将大整数转为字符串。 - 默认行为:转为 `BigInt` 对象。 - 其他库:`bignumber.js` 或 `decimal.js`,适用于复杂数学运算。 ##### (4) 手动解析:正则替换法 - 如果无法修改后端或使用库,可通过正则表达式预处理 JSON 字符串: ```javascript const response = '{"id": 1234567890123456789}'; const safeResponse = response.replace(/"(\w+)":\s*(\d{15,})/g, '"$1": "$2"'); const data = JSON.parse(safeResponse); // id 自动转为字符串 ``` - **缺点**:可能误替换非 ID 字段,需谨慎使用。 #### 3. **最佳实践** - **优先方案**:后端返回字符串 + 前端使用 `json-bigint` 库,兼顾兼容性和安全性。 - **测试建议**:使用 Jest 等工具验证边界值,如数值 $2^{53} + 1$($9007199254740993$)。 - **性能注意**:`BigInt` 运算比 `Number` 慢,仅在必要时使用。 通过以上方法,可有效避免 long数值的精度丢失问题,确保数据完整性[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值