1. 程式人生 > >BigDecimal/Long 前後端互動失去精度解決方法

BigDecimal/Long 前後端互動失去精度解決方法

問題

發現一個詭異的現象,資料庫儲存的bigDecimal型別的資料,經過springboot返回給前端資料丟失了幾位小數,例如 222233334444.01134567(後端)>222233334444.01135(前端)。經過查資料,在Controller層通過@ResponseBody將返回資料自動轉換成json時,不做任何處理,而直接傳給前端的話,在BigDecimal長度大於17位(不包括小數點)會出現精度丟失,在Long長度大於17位時也會出現精度丟失的問題。
在這裡插入圖片描述
在這裡插入圖片描述

解決方案

  1. 類屬性直接定義成String型別,處理好之後再返回前端
  2. 自定義訊息轉換器
  3. 在待轉化的欄位之上加上@JsonSerialize(using=ToStringSerializer.class)

1和3是類似的做法,如果系統已經成型,這兩種做法會改動很多地方,這裡著重記錄一下第2種做法。
直接貼程式碼

package ***.***.***.config;


import ***.***.***.constant.PathPattern;
import ***.***.***.interceptor.TokenInterceptor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.List;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TokenInterceptor tokenInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * 新增請求鑑權攔截器
         */
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns(PathPattern.API_ENTRY_POINT)
                .excludePathPatterns(PathPattern.NO_AUTH_API_ENTRY_POINT);
    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(toStringConverter());
    }

    /**
     * BigDecimal Long 轉化為String
     * @return
     */
    @Bean
    public MappingJackson2HttpMessageConverter toStringConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(BigDecimal.class, BigDecimalToStringSerializer.instance);
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        simpleModule.addSerializer(long.class, ToStringSerializer.instance);
        mapper.registerModule(simpleModule);
        converter.setObjectMapper(mapper);
        return converter;
    }

    @JacksonStdImpl
    static class BigDecimalToStringSerializer extends ToStringSerializer {
        public final static BigDecimalToStringSerializer instance = new BigDecimalToStringSerializer();

        public BigDecimalToStringSerializer() {
            super(Object.class);
        }

        public BigDecimalToStringSerializer(Class<?> handledType) {
            super(handledType);
        }

        @Override
        public boolean isEmpty(SerializerProvider prov, Object value) {
            if (value == null) {
                return true;
            }
            String str = ((BigDecimal) value).stripTrailingZeros().toPlainString();
            return str.isEmpty();
        }

        @Override
        public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
                throws IOException {
            gen.writeString(((BigDecimal) value).stripTrailingZeros().toPlainString());
        }

        @Override
        public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
            return createSchemaNode("string", true);
        }

        @Override
        public void serializeWithType(Object value, JsonGenerator gen,
                                      SerializerProvider provider, TypeSerializer typeSer)
                throws IOException {
            // no type info, just regular serialization
            serialize(value, gen, provider);
        }
    }
}

  • addInterceptors:是配置攔截器
    addInterceptor:註冊攔截器
    addPathPatterns:新增已註冊攔截器應用於的URL
    excludePathPatterns:添加註冊攔截器不應該應用於的URL
  • configureContentNegotiation:ViewResolver使用所請求的媒體型別的一個實現
    favorPathExtension:是否應使用URL路徑中的路徑擴充套件來確定所請求的媒體型別。
  • configureMessageConverters:自定義轉換器
    toStringConverter:增加了序列化long及包裝類Long需要使用ToStringSerializer轉成String型別
    BigDecimalToStringSerializer:因為直接用ToStringSerializer,BigDecimal的值過小就會顯示科學計數法,也不能去除末尾無效的零。這邊自定義一個BigDecimalToStringSerializer繼承自ToStringSerializer,修改裡面的serialize、isEmpty方法。

在這裡還有種延伸的做法,如果前端對BigDecimal返回的小數位有多種要求,我們可以自定義幾種資料型別extends BigDecimal,自定義多種BigDecimalToStringSerializer,然後在訊息轉換器中分別按型別處理,避免在程式中頻繁去寫格式。