Bootstrap

SpringBoot与Vue前后分离项目中返回结果为Long类型精度丢失问题处理

SpringBoot与Vue前后分离项目中返回结果为Long类型精度丢失问题处理

1. 前后分离环境

后端: SpringBoot + Mybatis-plus

前端: vue + elemnet-ui

数据库:Oracle12c

1. 表结构

create table TB_CONFIG
(
    ID     NUMBER(20) not null primary key,
    MODULE_NAME VARCHAR2(100),
    CONFIG      CLOB,
    NOTE        VARCHAR2(200),
    CREATED_BY  NUMBER(20),
    CREATE_TIME DATE        default SYSDATE  
)

2. 实体类

package com.yuan.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;


@Getter
@Setter
@TableName("TB_CONFIG")
@ApiModel(value = "Config对象", description = "配置表")
public class Config implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "ID")
    @TableId
    private Long id;

    @ApiModelProperty(value = "模块")
    private String moduleName;

    @ApiModelProperty(value = "配置项")
    private String config;

    @ApiModelProperty(value = "备注")
    private String note;

    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
}

2. 问题描述

数据库中存储的是Nubmer(20)类型的数据,前端接收后精度丢失

  1. 数据库记录

在这里插入图片描述

  1. 前端接收到的结果如下,最后两位会变成00
[
  {
    "id": 1779709862042493000,
    "moduleName": "顶顶顶顶",
    "config": null,
    "note": null,
    "createTime": "2024-04-15T11:19:59",
  },
  {
    "id": 1779709995329085400,
    "moduleName": "222",
    "config": null,
    "note": null,
    "createTime": "2024-04-15T11:19:59",
  },
  {
    "id": 1779711237837119500,
    "moduleName": "3",
    "config": null,
    "note": null,
    "createTime": "2024-04-15T11:19:59",
  },
  {
  "id": 1778942074839457800,
  "moduleName": "jinshengyuan",
  "config": null,
  "note": "备注",
  "createTime": "2024-04-13T08:23:36",
 }
]

3. 原因分析

MyBatis-Plus中使用 ASSIGN_ID(默认雪花算法) 生成的id作为主键时(或使用其他工具,如hutool生成雪花算法的ID值时),因为其长度为19位,而前端一般能处理16位,所以结果返回给前端会造成精度丢失,最后两位会变成00,有点类似四舍五入的效果

4. 处理方法(后端)

Spring Boot默认使用的是jackson对响应的JSON数据进行序列化的,后端处理方式就是返回结果给前端时,将Long类型转换为字符串即可解决,如果改变了默认的序列化框架jackson为其他框架,则需要按照对应框架的相关规则来处理,如fastjson等。下面以jackson序列化为例。

  • 局部解决: 通过在字段上标注@JsonSerialize@JsonFormat注解来解决
  • 全局解决:通过配置类,全局转换

1. 处理方式1(@JsonSerialize注解)

给需要转换的Long类型字段上面标注 @JsonSerialize(using = ToStringSerializer.class)来解决,如下

//......

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
//......
public class Config implements Serializable {

    private static final long serialVersionUID = 1L;

    @JsonSerialize(using = ToStringSerializer.class)//Long类型转String,解决前端精度丢失问题
    @ApiModelProperty(value = "ID")
    @TableId
    private Long id;

    //......
}

2. 处理方式2(@JsonFormat注解)

给需要转换的Long类型字段上面标注 @JsonSerialize(using = ToStringSerializer.class)来解决,如下

//......
import com.fasterxml.jackson.annotation.JsonFormat;
//......
public class Config implements Serializable {

    private static final long serialVersionUID = 1L;

    @JsonFormat(shape = JsonFormat.Shape.STRING)//Long类型转String,解决前端精度丢失问题
    @ApiModelProperty(value = "ID")
    @TableId
    private Long id;

    //......
}

3. 处理方式3推荐(全局解决)

新增一个配置类,然后配置需要转换序列化的类型即可,如下

package com.yuan.config;

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.math.BigInteger;

/**
 * <p>
 *
 * @Description: 解决数据库主键存储为雪花算法生成的ID值后,返回给前端时精度丢失的问题 <br>
 * </p>
 * @Datetime: 2024/4/14 16:33
 * @Author: Yuan · JinSheng <br>
 * @Since 2024/4/14 16:33
 */
@Configuration
public class Jackson2ObjectConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customizer() {
        return builder -> {
            builder.serializerByType(Long.class, ToStringSerializer.instance);
            builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
            //其他类型转换可以按需在这里添加......
        };
    }
}

5. 处理方法(前端)

前端可借助json-bigint插件来解决,一般请求都使用的Axios库,所以可以通过axios.defaults.transformResponse结合jsono-bigint来解决

1. 安装 json-bigint

pnpm add json-bigint
# 或
yarn add json-bigint

2. Axios处理

//......
import JSONBIG from 'json-bigint'
//......
axios.defaults.transformResponse = [
  function (data) {
    const jsonBig = JSONBIG({
      storeAsString: true
    })
    return jsonBig.parse(data)
  }
]
//......

6. 处理后输出结果如下

可看到id已经被转换为字符串了

[
    {
        "id": "1779709862042492928",
        "moduleName": "顶顶顶顶",
        "config": null,
        "note": null,
        "createTime": "2024-04-15T11:14:31"
    },
    {
        "id": "1779709995329085440",
        "moduleName": "222",
        "config": null,
        "note": null,
        "createTime": "2024-04-15T11:15:02"
    },
    {
        "id": "1779711237837119488",
        "moduleName": "3",
        "config": null,
        "note": null,      
        "createTime": "2024-04-15T11:19:59"
    },
    {
        "id": "1778942074839457792",
        "moduleName": "jinshengyuan",
        "config": "{}",
        "note": "备注",
        "createTime": "2024-04-13T08:23:36"
    }
]
;