Chuyển đến nội dung chính

Tìm trên CooLWJ

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

Ngôn ngữ

Dài quá cũng là lỗi?——Hành trình sửa lỗi mất độ chính xác của ID kiểu Long ở phía máy chủ đầy kỳ thú

Lời mở đầu

Trong thời đại frontend và backend tách biệt, cuộc sống của chúng ta đầy vô vàn cơ hội và thách thức—bao gồm cả những Bug bất ngờ xuất hiện khiến người ta phát điên. Hôm nay, chúng ta sẽ nói về một vấn đề kinh điển khiến vô số lập trình viên dở khóc dở cười:Backend Long ID kiểu quá dài khiến phía giao diện mất độ chính xác。Nói đến vấn đề này thì đúng là “nguồn gốc của mọi tội lỗi”, ai bảo JavaScript chỉ có thể xử lý an toàn Number.MAX_SAFE_INTEGER(tức là 9007199254740991)thì sao?

Nếu bạn từng vắt óc suy nghĩ vì vấn đề này, đừng lo, bạn không phải người duy nhất. Hôm nay, chúng ta không chỉ phân tích “bài toán thế kỷ” này theo cách hài hước, mà còn cùng bạn giải quyết nó tận gốc, để mã của bạn không còn “mất tinh” — mất độ chính xác — nữa.

Bối cảnh vấn đề: Tại sao Long Dài đến vậy sao?

Trước tiên, chúng ta phải bắt đầu từ Long bắt đầu nói từ loại hình.Long,chính là Java kiểu số nguyên 64 bit trong đó, đối với những ai thích xử lý Dữ liệu lớn、với Java xử lý các số lớn, kiểu này quả là một món quà trời ban. Thế nhưng thế giới frontend lại có phần “hiểu biết hạn hẹp”, chỉ biết xử lý số nguyên trong phạm vi 53 bit. Đúng vậy, bạn không nghe nhầm đâu, về điểm này JavaScript Giống như một ông đồ già “chậm nửa nhịp”, khi đối mặt với những con số lớn hơn, nó bắt đầu loay hoay với dấu thập phân và số mũ, cuối cùng nhả ra một con số khiến bạn tuyệt vọng. Thế là vấn đề mất độ chính xác bắt đầu lởn vởn trong dự án của bạn như một bóng ma.

Lần đầu chạm trán:Những chuyện mất độ chính xác

Câu chuyện phải bắt đầu từ một phản hồi Bug vào một ngày nọ: “Anh lập trình ơi, xem này, sao ID này lại đổi dạng rồi? Đây đâu phải cái em Cơ sở dữ liệu cái ID trong đó ấy!” Bạn cau mày nhìn, đúng vậy,9223372036854775807 trở thành 9223372036854776000,ôi chao, những con số dư ra này cứ như phép thuật vậy. Rõ ràng phía backend trả về đúng mà!Bạn vừa vò đầu vừa nghĩ:“Chắc chắn lỗi ở frontend rồi!”

Lỗi ở frontend hay backend?——Truy tìm căn nguyên

Thật ra, đổ lỗi vấn đề này cho phía giao diện cũng không hẳn là vô lý. Hãy cùng xem JavaScript “điểm yếu” khi xử lý các con số.JavaScript của Number Kiểu này là định dạng số dấu phẩy động độ chính xác kép dựa trên tiêu chuẩn IEEE 754, chỉ có thể biểu diễn an toàn 53 chữ số nhị phân, tức là Number.MAX_SAFE_INTEGER có giá trị——9007199254740991.

Nói cách khác, những số nguyên vượt quá phạm vi này,JavaScript sẽ bắt đầu “giảm độ chính xác”, “dung lượng não” của nó đột nhiên không còn đủ nữa. Vì vậy, của bạn Long ID thuộc kiểu này liền trở thành một đống giá trị số chẳng mấy “hấp dẫn” trong mắt nó. Chính vì vậy, khi xử lý những ID dài quá mức này, phía giao diện buộc phải “hy sinh” đôi chút, kết quả là ID vốn trung thành của bạn bị cắt cụt, biến thành một con số mới “thú vị”.

Từ sửng sốt đến bình tĩnh: Tìm kiếm giải pháp

Khi đã tìm ra căn nguyên của vấn đề, giờ là lúc những lập trình viên chúng ta trổ tài. Tiếp theo, tôi sẽ giúp bạn tìm hiểu sâu về một số giải pháp và nêu ưu nhược điểm của từng phương án, bởi suy cho cùng, mọi con đường đều dẫn đến thành Rome.

1. Chuyển trực tiếp thành chuỗi: đơn giản, thẳng tay nhưng hiệu quả

Đối mặt với vấn đề này, điều đầu tiên chúng ta nghĩ đến chắc chắn là cách đơn giản và thẳng tay nhất:trực tiếp đem Long thuộc kiểu Chuyển đổi dữ liệu thành chuỗi là được rồi mà! Nếu đã JavaScript là fan cuồng của số dấu phẩy động, vậy chúng ta cứ ném ngược vấn đề lại cho nó, nói với nó:“Bạn chỉ cần coi đây là một chuỗi, đừng lo nó dài bao nhiêu!”

Làm thế nào để thực hiện điều này?Thực ra rất đơn giản, sử dụng Jackson được cung cấp ToStringSerializer,chúng ta có thể dễ dàng đưa Long Chuyển thành chuỗi.

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;
    // 其他字段
}


Như vậy, khi phía máy chủ trả về UserDto khi đó,Long thuộc kiểu id sẽ được tuần tự hóa thành chuỗi, phía frontend cũng nhận được chuỗi, nhờ đó vấn đề độ chính xác được giải quyết dễ dàng. Cứ như dán một tờ giấy ghi chú lên đầu JavaScript: “Đây là chuỗi nhé, khỏi cần lo!”

2. Frontend sử dụng BigInt: giúp cả số lớn cũng được tính toán chính xác

Dĩ nhiên, cách đơn giản và thẳng tay không phải lúc nào cũng phù hợp với mọi tình huống. Hãy tưởng tượng, nếu phía giao diện cần thực hiện một phép toán nào đó với ID này, thì chuyển thẳng thành chuỗi sẽ không ổn lắm. Vậy phía giao diện nên xử lý những ID “siêu dài” này như thế nào?

May mắn thay,JavaScript Cũng không phải hoàn toàn vô dụng. Đã đưa vào BigInt sau đó,JavaScript Cuối cùng cũng không còn là kẻ ngốc chỉ biết loay hoay với dấu thập phân nữa.BigInt là một kiểu dữ liệu nguyên thủy mới, chuyên dùng để xử lý số nguyên có độ chính xác tùy ý. Bạn có thể làm như sau:

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

Như vậy, bạn có thể xử lý chính xác ở phía frontend Long dữ liệu thuộc kiểu này, tránh vấn đề mất độ chính xác. Tất nhiên, ở đây có một lưu ý nhỏ:BigInt Không phải trình duyệt nào cũng hỗ trợ, vì vậy bạn phải đảm bảo môi trường ứng dụng của mình tương thích.

3. Tuần tự hóa tùy chỉnh:Đơn giản hóa vấn đề phức tạp

Mặc dù phương pháp trên đã có thể giải quyết phần lớn vấn đề, đôi khi chúng ta vẫn gặp những tình huống cần kiểm soát chi tiết hơn. Lúc này,Jackson thì bộ tuần tự hóa tùy chỉnh của nó sẽ phát huy tác dụng.

Chúng ta có thể viết một bộ tuần tự hóa tùy chỉnh để linh hoạt kiểm soát theo nhu cầu Long quá trình tuần tự hóa trường thuộc kiểu này。Ví dụ, khi tuần tự hóa, chúng ta có thể quyết định có đưa Long chuyển thành chuỗi, hoặc xử lý khác nhau tùy theo điều kiện cụ thể:

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());
        }
    }
}


Sau đó áp dụng bộ tuần tự hóa tùy chỉnh này ở nơi cần thiết:

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

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


Như vậy, bạn có thể kiểm soát toàn bộ quá trình tuần tự hóa, bảo đảm mỗi Long đều có thể được xử lý theo cách bạn mong muốn. Tất nhiên, cách này tuy linh hoạt nhưng hơi phức tạp, cần viết thêm vài dòng mã và cũng có thể làm tăng chi phí bảo trì.

4. Xử lý toàn cục: Giải pháp tiết kiệm thời gian và công sức

Nếu muốn giải quyết vấn đề này trên toàn hệ thống, tránh phải cấu hình annotation trên từng trường, bạn có thể cân nhắc cấu hình toàn cục ObjectMapper。Thông qua việc tại Spring Boot cấu hình thiết lập toàn cục trong ObjectMapper,bạn có thể cho phép tất cả Long các trường thuộc kiểu này đều tự động chuyển thành chuỗi.

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;
    }
}


Làm một lần là xong, không cần lo quên cấu hình bộ tuần tự hóa cho một trường nào đó, cả dự án đều có thể hưởng cảm giác thoải mái khi không mất độ chính xác. Tất nhiên, khi sử dụng cấu hình toàn cục cũng cần lưu ý rằng nó có thể ảnh hưởng đến một số thứ bạn không muốn chuyển đổi thuộc Long trường, vì vậy cần cân nhắc thận trọng.

Trường hợp thực tế: hành trình giải quyết Bug đầy trắc trở

Để mọi người hiểu rõ hơn hiệu quả thực tế của những giải pháp này, tôi xin chia sẻ một câu chuyện trong dự án có thật. Dự án này liên quan đến việc quản lý ID người dùng, do số lượng người dùng rất lớn, phía máy chủ đã sử dụng Long mã định danh duy nhất của kiểu này. Dự án mới đưa vào hoạt động được vài ngày đã có người dùng phản ánh: “Sao ID của tôi lại đổi dạng rồi?”.

Bước đầu tiên:Truy tìm căn nguyên vấn đề

Trước tiên chúng tôi kiểm tra mã frontend, phát hiện JavaScript của Number kiểu dữ liệu quả thực không thể biểu diễn chính xác con số dài tới 19 chữ số này, từ đó gây mất độ chính xác. Tiếp theo, chúng tôi xem mã phía backend và phát hiện rằng mặc dù Long dữ liệu thuộc kiểu này ở phía máy chủ là chính xác, nhưng khi thông qua REST API Khi trả về cho frontend, độ chính xác của số đã bị mất.

Bước hai:Chọn giải pháp phù hợp

Để nhanh chóng giải quyết vấn đề, chúng tôi quyết định trước tiên áp dụng @JsonSerialize(using = ToStringSerializer.class) cách đơn giản và hiệu quả này. Nhờ cách này, chúng ta đã tránh thành công việc frontend nhận những con số bị mất độ chính xác.

Tuy nhiên, vấn đề vẫn chưa được giải quyết hoàn toàn. Trong các yêu cầu về sau, frontend cần thực hiện một số phép toán trên ID, chẳng hạn sắp xếp ID người dùng. Lúc này, chuỗi có phần không đáp ứng nổi.

Bước thứ ba:Sử dụng BigInt Giải quyết vấn đề tính toán ở frontend

Vì vậy, chúng tôi quyết định đưa vào phía frontend BigInt。Thông qua việc sử dụng BigInt,phía máy khách không chỉ có thể lưu trữ chính xác những ID cực dài này mà còn có thể thực hiện các phép toán cần thiết. Qua thử nghiệm, cách này hoạt động tốt trên các trình duyệt phổ biến;nhược điểm duy nhất là hỗ trợ chưa tốt cho một số phiên bản trình duyệt cũ.

Bước bốn: Cấu hình toàn cục cuối cùng

Để tránh những vấn đề tương tự tái diễn trong tương lai, chúng tôi quyết định đưa ToStringSerializer cấu hình để có hiệu lực toàn cục. Điều này khiến tất cả Long dữ liệu thuộc kiểu này đều tự động được chuyển thành chuỗi khi tuần tự hóa, vừa bảo đảm tính chính xác của dữ liệu ở phía giao diện, vừa giảm cấu hình lặp lại trong mã nguồn.

Lời kết:Kẻ chấm dứt tình trạng mất độ chính xác

Qua trường hợp này có thể thấy, tuy Long Vấn đề mất độ chính xác của kiểu dữ liệu tưởng chừng đơn giản, nhưng trong dự án thực tế có thể tiềm ẩn nhiều rủi ro. Qua việc so sánh và thử nhiều giải pháp, cuối cùng chúng tôi đã tìm được phương án tốt nhất cho dự án của mình.

Hy vọng bài blog hài hước mà chi tiết này có thể giúp bạn khi xử lý Long khi gặp vấn đề mất độ chính xác của kiểu dữ liệu, tránh những đường vòng không cần thiết. Nếu bạn cũng có trải nghiệm tương tự, hãy chia sẻ câu chuyện của mình trong phần bình luận;có thể kinh nghiệm của bạn sẽ đem lại thêm gợi mở cho các lập trình viên khác.

Cuối cùng, hãy nhớ rằng mã cũng như cuộc đời, thỉnh thoảng “mất mát” không đáng sợ, điều quan trọng là tìm được “bộ tuần tự hóa” phù hợp để đưa nó trở lại đúng hướng. Chúc mã của mọi người không bao giờ còn “mất độ chính xác”, luôn chuẩn xác và chạy thông suốt!


Bình luận

Đang gửi

Chọn giao diện yêu thích

Giao diện đã chọn áp dụng cho toàn trang.