Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

How to handle this pit when the precision is lost when the back end receives the long type parameters


Jun 01, 2021 Article blog


Table of contents


This article was reproduced from the public number: Java Geek Technology Author: Duck Blood Fans

In recent days has been in the transformation project, the use of snowflake algorithm to generate the primary key ID, suddenly stepped on a pit, front JavaScript in taking Long type parameters, the parameter value is a bit wrong!

First, the description of the problem

Recently in the transformation of the internal management system, found a huge pit, that is, the front-end JavaScript in the acquisition of back-end Long parameters, the accuracy of the loss!

At first, it was normal to simulate an interface request with postman but when you use a browser request, something goes wrong!

  • The problem recurs

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

Open the browser and request the interface, the result is as follows!

 How to handle this pit when the precision is lost when the back end receives the long type parameters1

Simulate interface requests with postman, and the results are as follows!

 How to handle this pit when the precision is lost when the back end receives the long type parameters2

At the beginning, I really did not find this pit, the results of testing, only to find that the front end passed to the back end of the ID, and the ID stored in the database is not consistent, only to find JavaScript and this pit!

For its own Number type in JavaScript does not fully represent Long type of number, and there is a loss of precision when Long length is greater than 17 bits.

When we change the user ID above to 19 bits, let's look at the results returned by the browser request.

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

Browser request results!

 How to handle this pit when the precision is lost when the back end receives the long type parameters3

When the returned result exceeds 17 bits, the back all becomes 0!

Second, the solution

What should I do if I encounter this situation?

  • The first option: change the long type to String type in the background, but it's a bit expensive and needs to be changed wherever it's involved
  • The second approach: Use tools to convert to change the long type to String type, which enables global conversion (recommended)
  • Third approach: front-end processing (currently not a good approach, not recommended)

Because the project involves a lot of code, it is not possible to change the long type to String type, and there are so many ways to use the Long type that it is very risky to change, so it is not recommended!

The ideal method is to use aop代理 to intercept all methods, the return parameters for unified processing, the use of tools for conversion, the process is as follows!

2.1, Jackson tool serialization objects

We can use Jackson toolkit to serialize objects.

  • Start by adding the necessary dependencies to 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>

  • Write a conversion tool 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;
        }
    }
}

  • Next, write an entity Person for testing

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

  • Finally, let's write a test class to test the effect

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

The output is as follows:

 How to handle this pit when the precision is lost when the back end receives the long type parameters4

One of the most critical lines of code is to register this conversion class to enable all long to be string

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

If you want to format a date, you can set it globally.

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

Alternatively, set a property individually, such as formatting the createTime property to yyyy-MM-dd with just the following annotations.

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

Once the tool conversion class is written, it's very simple, and you only need to serialize the parameters returned by aop intercept method to automatically turn all long into string

2.2, SpringMVC configuration

If it's a SpringMVC project, it's easy to do.

  • Customize an implementation class that is inherited from 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);
    }
}

  • Add the following configuration to SpringMVC's profile

<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 configuration

In the case of a SpringBoot project, the operation is similar.

  • Write a WebConfig configuration class and implement the configureMessageConverters method from WebMvcConfigurer

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

Third, summary

In the actual project development, many services are pure microservices development, do not use SpringMVC in this case, using JsonUtil tool class to serialize objects, may be a very good choice.

If there is something wrong with understanding, welcome netizens to criticize and point out!

The above is W3Cschool编程狮 about the back end to receive long type parameters when the accuracy is lost, how to deal with this pit related to the introduction, I hope to help you.