Introduzione
Nell'era della separazione tra frontend e backend, la nostra vita è piena di opportunità e sfide, compresi quei bug esasperanti che spuntano all'improvviso. Oggi parleremo di un problema classico che ha fatto ridere e piangere innumerevoli sviluppatori:Backend Long ID del tipo troppo lungo che causa una perdita di precisione nel frontend。A proposito di questo problema, è proprio la “radice di tutti i mali”, del resto JavaScript può gestire in sicurezza soltanto Number.MAX_SAFE_INTEGER(ossia 9007199254740991)come limite massimo per i valori numerici?
Se vi siete mai scervellati su questo problema, tranquilli: non siete soli. Oggi analizzeremo con umorismo questo “problema del secolo” e vi guideremo a risolverlo alla radice, affinché il vostro codice non perda più “colpi di precisione”.
Contesto del problema: perché Long Così lungo?
Per prima cosa, dobbiamo partire da Long iniziare dal tipo.Long,ossia Java il tipo intero a 64 bit in esso contenuto, per chi ama elaborare Big data、per Java, che tratta numeri grandi, questo tipo è una vera benedizione. Il mondo del frontend, però, è un po' “di vedute ristrette”: sa gestire soltanto interi entro 53 bit. Sì, avete capito bene, su questo punto JavaScript È come un vecchio erudito “sempre un passo indietro”: quando si trova davanti a numeri più grandi, comincia a trafficare con virgole decimali ed esponenti, per poi sputare fuori un numero che ti fa disperare. Ed ecco che il problema della perdita di precisione comincia ad aggirarsi nel tuo progetto come un fantasma.
Il primo incontro: storie di perdita di precisione
La storia comincia con la segnalazione di un bug, un giorno:“Ehi, sviluppatore, guarda: perché questo ID è cambiato? Questo non è quello che io Database quell'ID lì dentro!» Guardi aggrottando la fronte: sì,9223372036854775807 Si trasformò in 9223372036854776000,accidenti, queste cifre in più sembrano proprio un trucco di magia。Eppure il backend aveva fornito il valore giusto!Ti gratti la testa e pensi:“È sicuramente colpa del frontend!”
È colpa del frontend o del backend?——Risalire alla radice
A dire il vero, attribuire questo problema al frontend non è del tutto privo di fondamento. Diamo un'occhiata a JavaScript il suo “punto debole” nella gestione dei numeri.JavaScript di Number Il tipo utilizza il formato dei numeri in virgola mobile a doppia precisione basato sullo standard IEEE 754 e può rappresentare in modo sicuro soltanto 53 cifre binarie, ossia Number.MAX_SAFE_INTEGER il cui valore è——9007199254740991.
In altre parole, per gli interi che superano questo intervallo,JavaScript inizierà a “perdere precisione” e la sua “capacità cerebrale” diventerà improvvisamente insufficiente. Così, il tuo Long L'ID del tipo diventa ai suoi occhi un insieme di valori numerici poco “sexy”. Proprio per questo, quando il frontend gestisce questi ID incredibilmente lunghi, deve fare un piccolo “sacrificio”: il risultato è che il tuo ID, in origine fedele, viene troncato e diventa un nuovo numero “interessante”.
Dallo sconcerto alla calma: alla ricerca di una soluzione
Ora che abbiamo individuato la radice del problema, tocca a noi sviluppatori mostrare di cosa siamo capaci. Di seguito ti guiderò nell'esplorazione di diverse soluzioni, illustrando i vantaggi e gli svantaggi di ciascuna: dopotutto, tutte le strade portano a Roma.
1. Conversione diretta in stringa: semplice e brutale, ma efficiente
Di fronte a questo problema, il primo metodo che ci viene in mente è sicuramente quello più semplice e diretto:prendere direttamente Long di tipo Conversione dei dati basta convertirlo in una stringa! Dato che JavaScript è un fan sfegatato dei numeri in virgola mobile, allora rimandiamogli direttamente il problema dicendogli:“Devi solo trattarlo come una stringa, senza preoccuparti della sua lunghezza!”
Come si può ottenere questo risultato? In realtà è molto semplice, usando Jackson fornito da ToStringSerializer,possiamo facilmente Long Convertire in stringa.
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;
// 其他字段
}In questo modo, quando il backend restituisce UserDto quando,Long di tipo id verrà serializzato come stringa, e anche il frontend riceverà una stringa, risolvendo così il problema della precisione. È proprio come appiccicare un promemoria sulla testa di JavaScript: “Questa è una stringa, non devi preoccupartene!”
2. Il frontend usa BigInt:per eseguire calcoli precisi anche con numeri grandi
Naturalmente, una soluzione così semplice e diretta non è sempre adatta a ogni scenario. Immaginate che il frontend debba eseguire qualche operazione matematica su questo ID: convertirlo direttamente in una stringa non sarebbe una buona idea. Come dovrebbe quindi gestire il frontend questi ID “lunghissimi”?
Fortunatamente,JavaScript non è nemmeno del tutto privo di meriti. Ha introdotto BigInt Successivamente,JavaScript Finalmente non è più quello sciocco capace soltanto di armeggiare con la virgola decimale.BigInt è un nuovo tipo di dato primitivo, pensato appositamente per gestire interi a precisione arbitraria. Puoi fare così:
const id = BigInt("9223372036854775807");
console.log(id + 1n); // 输出:9223372036854775808nIn questo modo, puoi gestire con precisione nel frontend Long dati di questo tipo, evitando il problema della perdita di precisione. Naturalmente, qui c'è una piccola avvertenza:BigInt non è supportato da tutti i browser, quindi devi assicurarti che l’ambiente della tua applicazione sia compatibile.
3. Serializzazione personalizzata: semplificare i problemi complessi
Sebbene i metodi sopra descritti possano già risolvere la maggior parte dei problemi, a volte incontriamo situazioni che richiedono un controllo più preciso. In questi casi,Jackson entra in gioco il serializzatore personalizzato di
Possiamo scrivere un serializzatore personalizzato per controllare in modo flessibile, secondo le esigenze, Long il processo di serializzazione dei campi di questo tipo. Per esempio, durante la serializzazione possiamo decidere se convertire Long Convertire in stringa, oppure applicare trattamenti diversi in base a condizioni specifiche:
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());
}
}
}Poi applica questo serializzatore personalizzato dove necessario:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class UserDto {
@JsonSerialize(using = CustomLongSerializer.class)
private Long id;
// 其他字段
}In questo modo potrai controllare l'intero processo di serializzazione, assicurandoti che ogni Long possono essere tutti gestiti nel modo desiderato. Naturalmente, pur essendo flessibile, questo metodo è un po' più complesso: richiede qualche riga di codice in più e potrebbe anche aumentare i costi di manutenzione.
4. Gestione globale: una soluzione che fa risparmiare tempo e fatica
Se desideri risolvere questo problema globalmente, evitando di configurare annotazioni su ogni campo, puoi valutare una configurazione globale ObjectMapper。Attraverso l’uso in Spring Boot configurare quello globale in ObjectMapper,puoi far sì che tutti i Long i campi di questo tipo vengono tutti convertiti automaticamente in stringhe.
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;
}
}Una soluzione definitiva: non dovrete più preoccuparvi di dimenticare il serializzatore per qualche campo e l'intero progetto potrà godere del piacere di non perdere precisione. Naturalmente, usando una configurazione globale, bisogna considerare che potrebbe influire su alcuni elementi che non desiderate convertire Long campo, quindi occorre valutarlo con attenzione.
Caso reale: il tortuoso percorso verso la risoluzione di un bug
Per aiutarvi a comprendere meglio gli effetti concreti di queste soluzioni, vi racconto un episodio tratto da un progetto reale. Il progetto riguardava la gestione degli ID utente e, dato l'elevato numero di utenti, il backend adottava Long come identificatore univoco. Pochi giorni dopo la messa online del progetto, sono arrivate le prime segnalazioni degli utenti: “Come mai il mio ID è cambiato?”.
Primo passo: individuare la causa del problema
Abbiamo innanzitutto esaminato il codice frontend e scoperto che JavaScript di Number il tipo non riesce effettivamente a rappresentare con esattezza questo numero di ben 19 cifre, causando così una perdita di precisione. Poi abbiamo esaminato il codice del backend e scoperto che, sebbene Long I dati di questo tipo sono corretti nel backend, ma quando passano attraverso REST API Quando il numero viene restituito al frontend, perde precisione.
Secondo passo:Scegliere la soluzione adatta
Per risolvere rapidamente il problema, abbiamo deciso di adottare inizialmente @JsonSerialize(using = ToStringSerializer.class) Questo metodo semplice ed efficace. Grazie a questo metodo, siamo riusciti a evitare che il frontend ricevesse numeri che avevano perso precisione.
Tuttavia, il problema non era del tutto risolto. Nei requisiti successivi, il frontend doveva eseguire alcune operazioni sugli ID, per esempio ordinare gli ID degli utenti. A quel punto, le stringhe iniziavano a mostrare i propri limiti.
Terzo passaggio:usare BigInt Risolvere il problema dei calcoli nel frontend
Abbiamo quindi deciso di introdurre nel frontend BigInt。Utilizzando BigInt,il frontend può non soltanto memorizzare con precisione questi ID lunghissimi, ma anche eseguire le operazioni matematiche necessarie. I test hanno mostrato che questo metodo funziona bene su tutti i principali browser; l'unico svantaggio è il supporto poco soddisfacente per alcuni browser meno recenti.
Quarto passaggio: configurazione globale finale
Per evitare che problemi simili si ripetano in futuro, abbiamo deciso di ToStringSerializer configurato affinché abbia effetto globale. Questo fa sì che tutti i Long i dati di questo tipo vengono convertiti automaticamente in stringhe durante la serializzazione, garantendo l'accuratezza dei dati sul frontend e riducendo le configurazioni ripetitive nel codice.
Conclusione:la fine della perdita di precisione
Da questo esempio si può vedere che, sebbene Long Il problema della perdita di precisione dei tipi sembra semplice, ma nei progetti reali può nascondere numerose insidie. Confrontando e provando diverse soluzioni, alla fine abbiamo trovato quella più adatta al nostro progetto.
Spero che questo articolo di blog, umoristico e dettagliato, possa aiutarti quando devi gestire Long per evitare giri a vuoto quando si affrontano problemi di perdita di precisione dei tipi. Se anche tu hai vissuto un’esperienza simile, raccontala nei commenti: potrebbe offrire ulteriori spunti ad altri sviluppatori.
Infine, ricordate: il codice è come la vita, qualche occasionale «perdita» non deve spaventare; ciò che conta è trovare il «serializzatore» adatto per rimetterlo sulla giusta strada. Auguro a tutti che il vostro codice non «perda più precisione», resti accurato e proceda senza intoppi!