Introducción
En la era de la separación entre frontend y backend, nuestra vida está llena de innumerables oportunidades y desafíos, incluidos esos errores que aparecen de repente y nos sacan de quicio. Hoy hablaremos de un problema clásico que ha dejado a incontables desarrolladores sin saber si reír o llorar:Backend Long Un ID de tipo demasiado largo provoca pérdida de precisión en el frontend。Hablando de este problema, es de verdad el «origen de todos los males», y es que JavaScript Solo puede procesar de forma segura Number.MAX_SAFE_INTEGER (es decir, 9007199254740991) o inferiores?
Si alguna vez te has devanado los sesos con este problema, no te preocupes: no eres la única persona. Hoy no solo vamos a analizar con humor este «gran enigma del siglo», sino que también te ayudaremos a resolverlo de raíz para que tu código deje de «perder precisión».
Contexto del problema: por qué Long ¿Tan largo?
Para empezar, tenemos que partir de Long empezar hablando del tipo.Long,es decir, Java el tipo de entero de 64 bits de este, para quienes disfrutan trabajando con Macrodatos y los números grandes de Java, este tipo es una auténtica bendición. Sin embargo, el mundo del frontend anda algo «corto de miras»: solo sabe manejar enteros de hasta 53 bits. Sí, has oído bien, en este aspecto JavaScript Es como un viejo erudito que siempre va «medio paso por detrás»: cuando se encuentra con números más grandes, empieza a jugar con el punto decimal y los exponentes, y al final escupe un número que te lleva a la desesperación. Así, el problema de la pérdida de precisión empieza a deambular por tu proyecto como un fantasma.
Primer encuentro: cuestiones de pérdida de precisión
La historia comienza con un informe de Bug de cierto día: «Oye, desarrollador, mira, ¿por qué ha cambiado este ID? Este no es el que yo Base de datos¡Ese ID que aparece dentro!» Lo miras con el ceño fruncido y, sí, 9223372036854775807 se convirtió en 9223372036854776000,¡vaya!, esos dígitos de más parecen cosa de magia. ¡Pero si el servidor enviaba los datos correctos! Te rascas la cabeza mientras piensas:“¡Seguro que la culpa es del frontend!”
¿Es culpa del frontend o del backend?——En busca de la causa
En realidad, atribuir este problema al frontend tampoco carece por completo de fundamento. Veamos JavaScript el «punto débil» al procesar números.JavaScript de Number es un tipo con formato de coma flotante de doble precisión basado en el estándar IEEE 754, que solo puede representar de forma segura 53 dígitos binarios, es decir, Number.MAX_SAFE_INTEGER cuyo valor es 9007199254740991.
En otras palabras, los enteros que superan este intervalo,JavaScript empezará a “perder precisión” y su “capacidad cerebral” de pronto dejará de ser suficiente. Entonces, tu Long Los ID de ese tipo se convierten, a sus ojos, en un montón de valores numéricos menos «sexis». Por eso, cuando el frontend procesa estos ID desmesuradamente largos, se ve obligado a hacer un «sacrificio»: tu ID, que originalmente era fiel al valor real, acaba truncado y convertido en un nuevo número «interesante».
De la sorpresa a la calma:en busca de una solución
Ahora que hemos encontrado la raíz del problema, nos toca a los desarrolladores demostrar de lo que somos capaces. A continuación, te guiaré por varias soluciones y te explicaré las ventajas y desventajas de cada una; al fin y al cabo, todos los caminos llevan a Roma.
1. Convertir directamente en una cadena: sencillo y directo, pero eficaz
Ante un problema así, lo primero que se nos ocurre es, sin duda, el método más sencillo y drástico: directamente tomar Long del tipo Conversión de datos¡Bastaría con convertirlo en una cadena! Puesto que JavaScript es un fanático de los números de coma flotante, así que devolvámosle el problema y digámosle:“¡Solo tienes que tratar esto como una cadena de texto, sin preocuparte por su longitud!”
¿Cómo se consigue esto? En realidad, es muy sencillo, utilizando Jackson proporcionado por ToStringSerializer,podemos fácilmente convertir Long Convertir en una cadena de texto.
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;
// 其他字段
}De este modo, cuando el backend devuelva UserDto cuando,Long del tipo id se serializará como una cadena, y el frontend también recibirá una cadena, con lo que el problema de precisión quedará resuelto. ¡Es casi como pegarle una nota en la cabeza a JavaScript:“¡Esto es una cadena, no tienes que preocuparte!”
2. Uso en el frontend BigInt:Permitir cálculos precisos también con números grandes
Por supuesto, este método tan directo no siempre sirve para todas las situaciones. Imagina que el frontend necesita realizar alguna operación matemática con este ID: convertirlo directamente en una cadena no sería muy adecuado. Entonces, ¿cómo debería gestionar el frontend estos ID «excesivamente largos»?
Por suerte,JavaScript tampoco carece por completo de virtudes. Introdujo BigInt Después,JavaScript Por fin había dejado de ser aquel bobo que solo sabía trastear con decimales.BigInt es un nuevo tipo de dato primitivo, diseñado específicamente para manejar enteros de precisión arbitraria. Puedes hacer lo siguiente:
const id = BigInt("9223372036854775807");
console.log(id + 1n); // 输出:9223372036854775808nAsí, podrás procesar con precisión en el frontend Long datos de ese tipo, evitando la pérdida de precisión. Eso sí, aquí conviene hacer una pequeña advertencia:BigInt No todos los navegadores lo admiten, así que debes asegurarte de que el entorno de tu aplicación sea compatible.
3. Serialización personalizada:simplificar un problema complejo
Aunque el método anterior ya permite resolver la mayoría de los problemas, a veces nos encontramos con situaciones que requieren un control más detallado. En esos casos, Jackson es cuando resulta útil el serializador personalizado de
Podemos escribir un serializador personalizado para controlar de forma flexible, según nuestras necesidades, Long el proceso de serialización de los campos de ese tipo. Por ejemplo, durante la serialización podemos decidir si convertir Long convertirlo en una cadena o aplicar tratamientos distintos según condiciones 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());
}
}
}Después, aplica este serializador personalizado donde sea necesario:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class UserDto {
@JsonSerialize(using = CustomLongSerializer.class)
private Long id;
// 其他字段
}Así podrás controlar todo el proceso de serialización y garantizar que cada Long pueden procesarse del modo que desees. Por supuesto, aunque este método es flexible, también es algo más complejo:requiere escribir unas cuantas líneas adicionales de código y puede aumentar el coste de mantenimiento.
4. Tratamiento global:una solución que ahorra tiempo y esfuerzo
Si deseas resolver este problema de forma global y ahorrarte la molestia de configurar anotaciones en cada campo, puedes considerar una configuración global ObjectMapper. Mediante Spring Boot configurar globalmente en ObjectMapper,puedes hacer que todos los Long todos los campos de ese tipo se convierten automáticamente en cadenas de texto.
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 solución definitiva: no tendrás que preocuparte por olvidar configurar el serializador de algún campo, y todo el proyecto podrá disfrutar de la ausencia de pérdida de precisión. Eso sí, al utilizar una configuración global, ten presente que podría afectar a ciertos elementos que no deseas convertir Long campo, por lo que debe considerarse con cautela.
Caso práctico:Un camino lleno de contratiempos para resolver un bug
Para que todos comprendan mejor los resultados prácticos de estas soluciones, voy a compartir una historia de un proyecto real. Este proyecto implicaba la gestión de identificadores de usuario y, debido al gran número de usuarios, el backend utilizó Long como identificador único de ese tipo. A los pocos días de poner el proyecto en producción, llegaron comentarios de usuarios: «¿Por qué ha cambiado el aspecto de mi ID?».
Primer paso: rastrear la raíz del problema
Primero revisamos el código del frontend y descubrimos que JavaScript de Number El tipo de datos realmente no puede representar con exactitud este número de 19 dígitos, lo que provoca una pérdida de precisión. Después, revisamos el código del backend y descubrimos que, aunque Long Los datos de este tipo son correctos en el backend, pero al pasar por REST API Al devolver el número al frontend, se perdió precisión.
Segundo paso:Elegir la solución adecuada
Para resolver el problema rápidamente, decidimos adoptar primero @JsonSerialize(using = ToStringSerializer.class) Este método sencillo y eficaz. Con él, logramos evitar que el frontend recibiera números con pérdida de precisión.
Sin embargo, el problema no se había resuelto por completo. Los requisitos posteriores exigían que el frontend realizara ciertas operaciones con los ID, como ordenar los ID de los usuarios. En ese momento, las cadenas de texto empezaron a mostrar sus limitaciones.
Tercer paso: usar BigInt Resolver el problema de los cálculos en el frontend
Así pues, decidimos introducir en el frontend BigInt。Mediante el uso de BigInt,el frontend no solo puede almacenar con precisión estos ID extremadamente largos, sino también realizar las operaciones matemáticas necesarias. Las pruebas muestran que este método funciona bien en los principales navegadores; su única desventaja es la escasa compatibilidad con algunas versiones antiguas.
Paso cuatro: configuración global definitiva
Para evitar que vuelvan a producirse problemas similares, decidimos ToStringSerializer configurado para que surta efecto de forma global. Esto hace que todos los Long Los datos de este tipo se convierten automáticamente en cadenas durante la serialización, lo que garantiza la precisión de los datos en el frontend y reduce la configuración repetida en el código.
Conclusión:el exterminador de la pérdida de precisión
En este ejemplo se puede ver que, aunque Long El problema de la pérdida de precisión del tipo parece sencillo, pero puede ocasionar numerosos riesgos ocultos en proyectos reales. Tras comparar y probar varias soluciones, finalmente encontramos la más adecuada para nuestro proyecto.
Espero que este artículo de blog, humorístico y detallado, te ayude al manejar Long al afrontar problemas de pérdida de precisión de tipos, evitar rodeos innecesarios. Si también has vivido algo parecido, te invito a compartir tu historia en los comentarios; quizá tu experiencia pueda inspirar a otros desarrolladores.
Por último, recordad: el código es como la vida; alguna «pérdida» ocasional no tiene por qué asustarnos. La clave está en encontrar el «serializador» adecuado para devolverlo al buen camino. ¡Os deseo que vuestro código nunca vuelva a «perder precisión», se mantenga exacto y funcione sin tropiezos!