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

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

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

最近几天一直在改造工程,采用雪花算法生成主键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 >

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

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

<think>我们有一个需求:在 Vue 前端传递一个 List<Long> 类参数(例如 [1,2,3])给 Spring Boot 后端后端使用 @GetMapping 接收。 根据引用[1]和引用[3]的内容,我们知道在 GET 请求中,参数是通过 URL 传递的(查询参数)。由于 GET 请求不能有请求体,所以我们必须将列表参数转换为查询字符串。 引用[3]提到了一种方式:将参数转换为逗号分隔的字符串。例如,将列表 [1,2,3] 转换为 "1,2,3",然后作为查询参数传递。 因此,我们可以这样操作: 1. 前端(Vue)将 List<Long> 类参数转换为逗号分隔的字符串,然后作为查询参数传递。 2. 后端Spring Boot)使用 `@RequestParam` 接收这个字符串,然后将其转换为 List<Long>。 另一种方法是:前端将列表拆分成多个相同参数名的查询参数,例如:?ids=1&ids=2&ids=3。Spring MVC 支持这种形式自动转换为 List。 根据引用[3]的示例,他们使用了逗号分隔字符串的方式。而引用[2]中提到了在 DELETE 请求中使用了类似的方式(但那里是作为请求体,而 GET 请求没有请求体)。所以对于 GET 请求,我们只能使用查询参数。 参考引用[4],我们注意到字段名称必须对应。因此,前端传递的参数名必须与后端接收的参数名一致。 下面我们分别说明两种方法: 方法一:使用逗号分隔的字符串(前端处理为字符串,后端接收字符串并分割) 方法二:使用重复的查询参数(前端传递数组,axios 会自动处理为重复参数) 在 axios 中,当我们传递一个数组给 `params` 时,默认会将数组转换为重复的参数。例如: params: { ids: [1,2,3] } 将转换为 ?ids=1&ids=2&ids=3 而后端可以直接用 `@RequestParam List<Long> ids` 接收。 因此,我们有两种方式,下面分别给出示例: 方法一:逗号分隔字符串 - 前端:手动将数组转换为逗号分隔的字符串 - 后端:接收字符串再分割 方法二:重复参数 - 前端:直接传递数组,axios 会自动处理 - 后端:直接声明为 List<Long> 接收 我们更推荐方法二,因为 Spring MVC 原生支持这种方式,且代码更简洁。 下面分别给出前后端代码示例: 方法二示例(推荐): 前端(Vue + axios): ```javascript export function getSomething(ids) { return request({ url: '/api/something', method: 'get', params: { ids: ids // 这里 ids 是一个数组,如 [1,2,3] } }) } ``` 后端Spring Boot): ```java @GetMapping("/api/something") public ResponseEntity<?> getSomething(@RequestParam List<Long> ids) { // 使用 ids 处理业务 return ResponseEntity.ok("success"); } ``` 注意:这里后端使用 `@RequestParam` 直接接收 List<Long>,要求前端传递的每个值都能转换为 Long。 方法一示例(如果后端不想用 List 接收,或者有其他特殊要求,可以用这种方法): 前端(Vue + axios): ```javascript export function getSomething(ids) { // 将数组转换为逗号分隔的字符串 const idsString = ids.join(','); return request({ url: '/api/something', method: 'get', params: { ids: idsString } }) } ``` 后端Spring Boot): ```java @GetMapping("/api/something") public ResponseEntity<?> getSomething(@RequestParam String ids) { // 将逗号分隔的字符串分割为 Long 列表 List<Long> idList = Arrays.stream(ids.split(",")) .map(Long::valueOf) .collect(Collectors.toList()); // 使用 idList 处理业务 return ResponseEntity.ok("success"); } ``` 根据引用[3]的例子,他们使用了逗号分隔字符串的方式,并且在前端手动将数组转换为逗号分隔的字符串(通过 `join` 方法)。但是,方法二更为简洁,且不需要手动转换(除了无法避免的URL编码外,axios会自动处理数组参数格式)。 因此,我们推荐使用方法二。 但是需要注意:方法二中,如果数组中的元素包含特殊字符(例如逗号)可能会出现问题,但我们的元素是Long,所以不会有问题。 另外,引用[4]提醒我们,前后端字段名要一致。在这个例子中,前后端都使用了参数名 `ids`。 总结: - 使用 GET 请求传递 List<Long> 类参数,推荐使用重复参数的方式(即方法二)。 - 前端直接传递数组给 axios 的 params,后端使用 `@RequestParam List<Long>` 接收。 如果遇到特殊情况(如个别旧框架不支持重复参数),则可以使用方法一(逗号分隔字符串)。 生成相关问题: 1. 在 POST 请求中,前端如何传递 List<Long> 参数? 2. 使用 GET 请求传递数组时,URL 长度有限制怎么办? 3. 如何传递其他复杂类(如对象数组)给后端? </think>### Vue 前端传递 `List<Long>` 参数Spring Boot `@GetMapping` 的方法 #### 1. **前端(Vue + Axios)参数传递** 在 GET 请求中,需将 `List<Long>` 转换为 **URL 查询参数**。有两种主流方式: ##### (1) **逗号分隔字符串(推荐)** 将列表转换为逗号分隔的字符串,通过 `params` 传递: ```javascript // 前端代码 export function fetchData(ids) { return request({ url: '/api/data', method: 'get', params: { // 将数组转换为逗号分隔的字符串,如 "1,2,3" ids: ids.join(',') } }) } ``` ##### (2) **重复参数模式** Axios 支持直接传递数组,自动转换为 `?ids=1&ids=2&ids=3` 格式: ```javascript params: { ids: [1, 2, 3] // Axios 自动处理为重复参数 } ``` > **注**:后端需使用 `@RequestParam List<Long>` 接收(见下文)。若遇类转换问题,优先使用逗号分隔法[^3]。 --- #### 2. **后端Spring Boot)参数接收** 通过 `@RequestParam` 注解接收,并处理类转换: ##### (1) **直接接收 `List<Long>`** ```java @GetMapping("/api/data") public ResponseEntity<?> getData( @RequestParam("ids") List<Long> idList // 自动转换逗号分隔或重复参数 ) { // 业务逻辑 } ``` > **要求**:参数名必须与前端一致(如 `ids`)。 ##### (2) **手动转换(备用方案)** 若自动转换失败,可接收字符串后手动处理: ```java @RequestParam("ids") String idsStr List<Long> idList = Arrays.stream(idsStr.split(",")) .map(Long::valueOf) .collect(Collectors.toList()); ``` --- #### 关键注意事项 1. **参数命名一致** 前端 `params` 的键名(如 `ids`)需与后端 `@RequestParam` 的值一致[^4]。 2. **GET 请求限制** URL 长度有限制(通常 2KB),超长列表建议改用 POST + `@RequestBody`[^1]。 3. **URL 编码** 若 ID 值含特殊字符(如逗号),前端需调用 `encodeURIComponent` 处理。 --- #### 完整示例 ##### 前端(Vue) ```javascript // 调用 API const ids = [101, 102, 103]; fetchData(ids).then(res => { /* 处理响应 */ }); // API 函数 export function fetchData(ids) { return axios({ url: 'http://localhost:8080/api/data', method: 'get', params: { ids: ids.join(',') } // 转换为 "101,102,103" }); } ``` ##### 后端Spring Boot) ```java @RestController public class DataController { @GetMapping("/api/data") public String getData(@RequestParam List<Long> ids) { return "Received IDs: " + ids; // 输出: [101,102,103] } } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值