Ir para o conteúdo principal

Buscar no CooLWJ

选择搜索范围,输入关键词后按回车。

Idioma

Ser longo demais também é um erro?——Uma curiosa jornada para corrigir a perda de precisão de IDs do tipo Long no backend

Introdução

Na era da separação entre frontend e backend, nossa vida está repleta de inúmeras oportunidades e desafios — incluindo aqueles bugs que surgem de repente e nos deixam à beira de um ataque de nervos. Hoje vamos falar de um problema clássico que deixa inúmeros desenvolvedores sem saber se riem ou choram:Backend Long ID de tipo longo demais causa perda de precisão no frontend。Falando desse problema, ele é mesmo a “origem de todos os males”, afinal, quem mandou JavaScript Só é possível processar com segurança Number.MAX_SAFE_INTEGER (ou seja, 9007199254740991) para valores dentro desse limite?

Se você já quebrou a cabeça com esse problema, não se preocupe: você não está sozinho. Hoje, além de analisar com humor esse “problema do século”, vamos ajudar você a resolvê-lo pela raiz, para que seu código pare de “perder a precisão”.

Contexto do problema:por que Long Tão longo assim?

Primeiro, precisamos começar por Long começar falando do tipo.Long,é justamente Java o tipo inteiro de 64 bits em questão, para quem gosta de trabalhar com Big data e números grandes em Java, esse tipo é uma verdadeira bênção. No entanto, o mundo do frontend tem um conhecimento um tanto “limitado”: só sabe lidar com inteiros de até 53 bits. Sim, você ouviu direito, nesse aspecto JavaScript É como um velho erudito que está sempre “um passo atrasado”:diante de números maiores, começa a mexer no ponto decimal e nos expoentes e, no fim, cospe um número que deixa você desesperado. Assim, o problema da perda de precisão começa a assombrar seu projeto como um fantasma.

O primeiro encontro: histórias de perda de precisão

A história começa com um relato de bug em certo dia: “Meu querido desenvolvedor, olha como esse ID mudou de aparência! Não foi isso que eu Banco de dados aquele ID ali dentro!” Você franze a testa e confere: sim, 9223372036854775807 transformou-se em 9223372036854776000,nossa, esses dígitos extras parecem até mágica. Mas o backend tinha enviado tudo certo! Você coça a cabeça e pensa: “Isso só pode ser culpa do frontend!”

A culpa é do frontend ou do backend?——Investigando a origem

Na verdade, culpar o frontend por esse problema não é totalmente sem fundamento. Vamos dar uma olhada em JavaScript O “ponto fraco” ao lidar com números.JavaScript de Number O tipo usa o formato de ponto flutuante de precisão dupla baseado no padrão IEEE 754 e só consegue representar com segurança 53 dígitos binários, ou seja, Number.MAX_SAFE_INTEGER o valor de——9007199254740991。

Em outras palavras, os números inteiros fora desse intervalo,JavaScript começará a “perder precisão”, e sua “capacidade cerebral” de repente deixará de ser suficiente. Assim, seu Long os IDs desse tipo passam a ser, aos seus olhos, um monte de valores nada tão “sexy”. Justamente por isso, ao lidar com esses IDs absurdamente longos, o frontend acaba tendo de fazer um pequeno “sacrifício”: seu ID, antes fiel ao original, é truncado e se transforma em um novo número “interessante”.

Do choque à calma:em busca de uma solução

Agora que a raiz do problema foi encontrada, chegou a nossa vez, como desenvolvedores, de mostrar do que somos capazes. A seguir, vou apresentar em profundidade algumas soluções e explicar as vantagens e desvantagens de cada uma, afinal, todos os caminhos levam a Roma.

1. Converter diretamente em string: simples e bruto, mas eficiente

Diante desse tipo de problema, a primeira solução que certamente nos ocorre é a mais simples e direta: pegar diretamente Long do tipo Conversão de dados Basta converter em uma string! Já que JavaScript é fã incondicional dos números de ponto flutuante, então vamos simplesmente devolver o problema e dizer: “Você só precisa tratar isso como uma string, sem se preocupar com o comprimento!”

Como fazer isso? Na verdade, é muito simples, basta usar Jackson fornecido por ToStringSerializer,podemos facilmente Long Converter em uma string.

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

public class UserDto {
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;
    // 其他字段
}


Assim, quando o backend retornar UserDto quando,Long do tipo id será serializado como uma string, e o frontend também receberá uma string, resolvendo de imediato o problema de precisão. É como colar um lembrete na cabeça do JavaScript: “Isto é uma string, pode deixar que não precisa se preocupar!”

2. Uso no frontend BigInt:permitindo cálculos precisos também com números grandes

É claro que uma abordagem simples e direta nem sempre serve para todos os cenários. Imagine que o frontend precise realizar alguma operação matemática com esse ID: convertê-lo diretamente em uma string não seria uma boa ideia. Então, como o frontend deve lidar com esses IDs “longos demais”?

Felizmente,JavaScript Também tem seus méritos. Introduziu BigInt Depois,JavaScript Finalmente deixei de ser aquele tolo que só sabia mexer com casas decimais.BigInt é um novo tipo de dado primitivo, criado especificamente para lidar com inteiros de precisão arbitrária. Você pode fazer assim:

const id = BigInt("9223372036854775807");
console.log(id + 1n); // 输出:9223372036854775808n

Assim, você pode tratar com precisão no frontend Long desse tipo de dados, evitando a perda de precisão. Claro, cabe aqui um pequeno lembrete:BigInt não é compatível com todos os navegadores, por isso você precisa garantir a compatibilidade com o ambiente da sua aplicação.

3. Serialização personalizada: simplificando problemas complexos

Embora o método acima já resolva a maioria dos problemas, às vezes encontramos cenários que exigem um controle mais preciso. Nessas situações,Jackson é aí que seu serializador personalizado entra em ação.

Podemos escrever um serializador personalizado para controlar com flexibilidade, conforme a necessidade, Long o processo de serialização de campos desse tipo. Por exemplo, durante a serialização, podemos decidir se vamos Long converter em uma string, ou realizar tratamentos diferentes para condições específicas:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class CustomLongSerializer extends JsonSerializer<Long> {

    @Override
    public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value != null) {
            gen.writeString(value.toString());
        }
    }
}


Em seguida, aplique esse serializador personalizado onde for necessário:

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public class UserDto {
    @JsonSerialize(using = CustomLongSerializer.class)
    private Long id;
    // 其他字段
}


Assim, você poderá controlar todo o processo de serialização e garantir que cada Long todos podem ser tratados da maneira que você desejar. Naturalmente, embora esse método seja flexível, ele é um pouco mais complexo, exige algumas linhas adicionais de código e também pode aumentar o custo de manutenção.

4. Tratamento global:uma solução que economiza tempo e esforço

Se você quiser resolver esse problema globalmente e evitar o trabalho de configurar anotações em cada campo, pode considerar uma configuração global ObjectMapper。Ao inserir em Spring Boot configurar globalmente em ObjectMapper,você pode fazer com que todos os Long os campos desse tipo são todos convertidos automaticamente em strings.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        mapper.registerModule(module);
        return mapper;
    }
}


Uma solução definitiva: você não precisa se preocupar em esquecer de configurar o serializador de algum campo, e todo o projeto pode desfrutar da satisfação de não perder precisão. Naturalmente, ao usar uma configuração global, é preciso ter atenção, pois ela pode afetar alguns elementos que você não deseja converter, como Long campo, portanto é preciso avaliar com cautela.

Caso prático: o caminho cheio de reviravoltas para resolver um bug

Para ajudar todos a entender melhor os efeitos práticos dessas soluções, vou compartilhar uma história de um projeto real. Esse projeto envolvia a gestão de IDs de usuários e, devido ao grande número de usuários, o backend adotou Long um identificador único desse tipo. Poucos dias depois de o projeto entrar em produção, começaram a chegar relatos de usuários: “Por que meu ID ficou diferente?”.

Primeiro passo: investigar a origem do problema

Primeiro verificamos o código do front-end e descobrimos JavaScript de Number o tipo realmente não consegue representar com precisão esse número de 19 dígitos, o que levou à perda de precisão. Em seguida, examinamos o código do backend e descobrimos que, embora Long os dados desse tipo estão corretos no backend, mas, ao passar por REST API Ao retornar o número ao frontend, sua precisão foi perdida.

Segundo passo:Escolher a solução adequada

Para resolver o problema rapidamente, decidimos primeiro adotar @JsonSerialize(using = ToStringSerializer.class) esse método simples e eficaz. Com ele, conseguimos evitar que o frontend recebesse números com perda de precisão.

No entanto, o problema não foi completamente resolvido. Nas necessidades posteriores, o front-end precisava realizar certas operações com os IDs, como ordenar os IDs dos usuários. Nesse momento, as strings se mostraram um tanto insuficientes.

Terceiro passo:usar BigInt Resolver problemas de cálculo no frontend

Assim, decidimos introduzir no front-end BigInt。Ao usar BigInt,o frontend consegue tanto armazenar com precisão esses IDs extremamente longos quanto realizar os cálculos matemáticos necessários. Os testes mostraram que esse método funciona bem nos principais navegadores; a única desvantagem é a compatibilidade limitada com algumas versões antigas de navegadores.

Quarto passo: configuração global final

Para evitar que problemas semelhantes voltem a ocorrer, decidimos ToStringSerializer configurado para ter efeito global. Isso faz com que todos os Long Os dados desse tipo são convertidos automaticamente em strings durante a serialização, garantindo a precisão dos dados no frontend e reduzindo a configuração repetitiva no código.

Conclusão: o exterminador da perda de precisão

Podemos ver neste caso que, embora Long O problema de perda de precisão do tipo parece simples, mas pode trazer diversos riscos em projetos reais. Comparando e experimentando várias soluções, acabamos encontrando a mais adequada para o nosso projeto.

Espero que este artigo bem-humorado e detalhado do blog ajude você a lidar com Long evitar desvios ao lidar com problemas de perda de precisão de tipos. Se você também passou por algo semelhante, compartilhe sua história nos comentários; talvez sua experiência traga mais ideias para outros desenvolvedores.

Por fim, lembrem-se: o código é como a vida; uma “perda” ocasional não é motivo para medo. O essencial é encontrar o “serializador” adequado para colocá-lo de volta nos trilhos. Que o código de vocês nunca mais “perca a precisão” e siga preciso e sem obstáculos!


Comentários

Enviando

Escolha seu tema

O tema é aplicado a todo o site.