Introduction
À l’ère de la séparation entre frontend et backend, notre quotidien regorge d’occasions et de défis — y compris ces bugs exaspérants qui surgissent sans prévenir. Aujourd’hui, nous allons parler d’un problème classique qui laisse d’innombrables développeurs partagés entre le rire et les larmes :Côté serveur Long Un identifiant de type trop long provoque une perte de précision côté frontend。Ah, ce problème, c’est vraiment la « source de tous les maux », puisque JavaScript ne peut traiter de manière sûre que Number.MAX_SAFE_INTEGER(c’est-à-dire 9007199254740991)pour les valeurs ne dépassant pas cette limite ?
Si vous vous êtes déjà creusé la tête sur ce problème, rassurez-vous, vous n’êtes pas seul. Aujourd’hui, nous allons non seulement décortiquer avec humour cette « énigme du siècle », mais aussi vous aider à la résoudre à la racine, pour que votre code ne « perde plus en précision ».
Contexte du problème : pourquoi Long Aussi long ?
Tout d’abord, il nous faut partir de Long commencer par parler de ce type.Long, c’est Java le type entier sur 64 bits de , pour ceux qui aiment traiter Données massives、pour Java, qui traite de grands nombres, ce type est une véritable aubaine. Pourtant, le monde du développement frontal reste un peu « ignorant » : il ne sait traiter que les entiers de 53 bits au maximum. Oui, vous avez bien entendu, sur ce point JavaScript Tel un vieux lettré qui a toujours un temps de retard, face aux grands nombres, il se met à manipuler les virgules décimales et les exposants, avant de recracher un nombre qui vous plonge dans le désespoir. Le problème de perte de précision commence alors à hanter votre projet comme un fantôme.
Première rencontre : histoires de perte de précision
Tout commence par un signalement de bug, un beau jour : « Dis, cher développeur, regarde cet ID, pourquoi a-t-il changé d’allure ? Ce n’est pas celui que je Base de données cet ID qui se trouve dedans ! » Vous regardez en fronçant les sourcils : en effet,9223372036854775807 est devenu 9223372036854776000,ah là là, ces chiffres supplémentaires semblent surgir comme par magie. Pourtant, les données envoyées par le backend sont correctes ! Tu te grattes la tête en te disant : « C’est forcément la faute du frontend ! »
La faute au frontend ou au backend ? —— Remonter à la source
À vrai dire, attribuer ce problème au côté interface n’est pas entièrement dénué de fondement. Regardons JavaScript le « point faible » lors du traitement des nombres.JavaScript de Number est un format de nombre à virgule flottante double précision fondé sur la norme IEEE 754, qui ne peut représenter sans risque que 53 chiffres binaires, soit Number.MAX_SAFE_INTEGER a pour valeur — 9007199254740991.
Autrement dit, les entiers qui dépassent cette plage,JavaScript commencera à « perdre en précision », sa « capacité cérébrale » devenant soudain insuffisante. Ainsi, votre Long Les ID de ce type deviennent alors, à ses yeux, un tas de valeurs numériques moins « sexy ». C’est précisément pour cette raison que, lorsqu’il traite ces ID démesurément longs, le frontend doit faire un petit « sacrifice » : votre ID, jusque-là fidèle à l’original, se retrouve tronqué et devient un nouveau nombre « amusant ».
Du choc au calme : à la recherche d’une solution
Maintenant que la source du problème est identifiée, c’est à nous, développeurs, de montrer ce dont nous sommes capables. Je vais vous présenter en détail plusieurs solutions et vous expliquer les avantages et les inconvénients de chacune : après tout, tous les chemins mènent à Rome.
1. Conversion directe en chaîne : une méthode rudimentaire mais efficace
Face à ce problème, la première idée qui nous vient est certainement la méthode la plus simple et la plus radicale : directement Long de type Conversion des données Il suffit de le convertir en chaîne de caractères ! Puisque JavaScript est un fan inconditionnel des nombres à virgule flottante, alors autant lui renvoyer le problème en lui disant : « Tu n’as qu’à considérer cela comme une chaîne de caractères, sans te soucier de sa longueur ! »
Comment y parvenir ? C’est très simple, utilisez Jackson fourni par ToStringSerializer,nous pouvons facilement Long convertir en chaîne de caractères.
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;
// 其他字段
}Ainsi, lorsque le backend renvoie UserDto au moment où,Long de type id sera sérialisé sous forme de chaîne de caractères ; le frontend recevra lui aussi une chaîne, ce qui résout le problème de précision. C’est comme si l’on collait un pense-bête sur la tête de JavaScript : « Ceci est une chaîne de caractères, inutile de t’en préoccuper ! »
2. Utilisation côté frontend BigInt:permettre aussi des calculs exacts sur les grands nombres
Bien sûr, cette méthode simple et expéditive ne convient pas à toutes les situations. Imaginez que le frontend doive effectuer une opération mathématique sur cet ID : le convertir directement en chaîne de caractères serait peu judicieux. Comment le frontend doit-il donc gérer ces ID « extrêmement longs » ?
Heureusement,JavaScript n’est pas non plus dépourvu d’intérêt. Il a introduit BigInt Ensuite,JavaScript Enfin, ce n’est plus cet idiot qui ne savait que déplacer la virgule.BigInt est un nouveau type de données primitif spécialement conçu pour manipuler des entiers de précision arbitraire. Vous pouvez procéder ainsi :
const id = BigInt("9223372036854775807");
console.log(id + 1n); // 输出:9223372036854775808nAinsi, vous pouvez traiter avec précision côté interface Long données de ce type, afin d’éviter les pertes de précision. Bien sûr, voici un petit rappel :BigInt n’est pas pris en charge par tous les navigateurs ; vous devez donc vous assurer de sa compatibilité avec l’environnement de votre application.
3. Sérialisation personnalisée : simplifier les problèmes complexes
Bien que les méthodes précédentes permettent déjà de résoudre la plupart des problèmes, certaines situations nécessitent parfois un contrôle plus fin. Dans ce cas,Jackson le sérialiseur personnalisé devient alors utile.
Nous pouvons écrire un sérialiseur personnalisé afin de contrôler avec souplesse, selon nos besoins, Long le processus de sérialisation des champs de ce type. Par exemple, nous pouvons décider, lors de la sérialisation, s’il faut Long convertir en chaîne de caractères, ou appliquer des traitements différents selon certaines conditions :
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());
}
}
}Appliquez ensuite ce sérialiseur personnalisé là où vous en avez besoin :
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class UserDto {
@JsonSerialize(using = CustomLongSerializer.class)
private Long id;
// 其他字段
}Ainsi, vous pourrez maîtriser l’ensemble du processus de sérialisation et vous assurer que chaque Long peuvent tous être traités comme vous le souhaitez. Bien sûr, cette méthode, quoique souple, est un peu plus complexe : elle nécessite quelques lignes de code supplémentaires et peut également augmenter les coûts de maintenance.
4. Traitement global : une solution qui économise du temps et des efforts
Si vous souhaitez résoudre ce problème globalement et éviter de configurer des annotations sur chaque champ, vous pouvez envisager une configuration globale ObjectMapper。En Spring Boot configurer globalement dans ObjectMapper,vous pouvez faire en sorte que tous les Long Les champs de type sont tous automatiquement convertis en chaînes de caractères.
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;
}
}Une solution définitive : plus besoin de craindre d’oublier de configurer le sérialiseur d’un champ, et tout le projet peut profiter d’une précision préservée. Bien entendu, lorsque vous utilisez une configuration globale, prenez garde au fait qu’elle peut affecter certains éléments que vous ne souhaitez pas convertir Long champ, ce qui nécessite donc une réflexion prudente.
Cas concret : un parcours semé de rebondissements pour résoudre un bug
Pour vous aider à mieux comprendre les effets concrets de ces solutions, je vais partager une expérience vécue dans un véritable projet. Ce projet concernait la gestion des identifiants utilisateur ; le nombre d’utilisateurs étant très élevé, le backend avait adopté Long comme identifiant unique. Quelques jours à peine après la mise en production du projet, des utilisateurs ont signalé : « Pourquoi mon ID a-t-il changé d’apparence ? ».
Première étape : remonter à la source du problème
Nous avons d’abord examiné le code du frontend et constaté que JavaScript de Number Le type ne peut effectivement pas représenter avec exactitude ce nombre de 19 chiffres, ce qui entraîne une perte de précision. Nous avons ensuite examiné le code du backend et constaté que, bien que Long Les données de ce type sont correctes côté serveur, mais lorsqu’elles passent par REST API Lors du renvoi au frontend, le nombre a perdu en précision.
Deuxième étape : choisir une solution adaptée
Pour résoudre rapidement le problème, nous avons décidé de commencer par utiliser @JsonSerialize(using = ToStringSerializer.class) cette méthode simple et efficace. Grâce à elle, nous avons réussi à éviter que le frontend reçoive des nombres ayant perdu en précision.
Cependant, le problème n’était pas entièrement résolu. De nouveaux besoins ont ensuite imposé au code côté client d’effectuer certaines opérations sur les ID, par exemple de trier les ID des utilisateurs. Les chaînes de caractères ont alors montré leurs limites.
Troisième étape:utiliser BigInt Résoudre les problèmes de calcul côté frontend
Nous avons donc décidé d’introduire côté interface BigInt。En utilisant BigInt, le frontal peut stocker avec précision ces identifiants extrêmement longs et effectuer les opérations mathématiques nécessaires. Les tests montrent que cette méthode fonctionne bien dans les principaux navigateurs. Son seul inconvénient est une prise en charge assez limitée dans certaines anciennes versions.
Quatrième étape : configuration globale finale
Pour éviter que des problèmes similaires ne se reproduisent, nous avons décidé de ToStringSerializer configuré pour s’appliquer globalement. Cela permet à tous les Long sont automatiquement converties en chaînes lors de la sérialisation, ce qui garantit l’exactitude des données côté interface tout en réduisant les configurations répétitives dans le code.
Conclusion : en finir avec la perte de précision
Cet exemple montre que, bien que Long Le problème de perte de précision de ce type paraît simple, mais il peut entraîner de nombreux risques cachés dans les projets réels. En comparant et en essayant plusieurs solutions, nous avons finalement trouvé celle qui convenait le mieux à notre projet.
J’espère que cet article à la fois humoristique et détaillé vous permettra, lorsque vous traiterez Long lorsque vous rencontrez un problème de perte de précision des types, afin d’éviter des détours inutiles. Si vous avez vécu une expérience similaire, partagez votre histoire dans les commentaires ; votre expérience pourra peut-être inspirer d’autres développeurs.
Enfin, rappelez-vous : le code est comme la vie ; une « perte » occasionnelle n’a rien d’effrayant, l’essentiel est de trouver le bon « sérialiseur » pour le remettre sur les rails. Que votre code ne « perde plus jamais en précision », qu’il reste exact et que tout fonctionne sans accroc !