Debug và khắc phục lỗi hiển thị ký tự Unicode của ứng dụng Web

Bài viết này ngầm định rằng bạn biết “bảng mã” nghĩa là gì, và bạn quen thuộc với các thành phầncủa mô hình ứng dụng web cũng như mô hình trình diễn MVC. Mặc dù mã ở đây được trình bày dưới dạng thức của ngôn ngữ Java, framework Spring MVC và database MySQL, các vấn đề được nhắc đến là chung, các khái niệm được nhắc đến là phổ biến, và các cách giải quyết là tổng quát.

Model mang dữ liệu Unicode nhưng View thì không

Bạn có model như thế này.

Nhưng bạn nhận được kết quả như thế này:

Là vì View của bạn có nội dung như thế này:

<input type="text" name="name" value="Nh?t">

Bạn cần làm cho view engine render cho bạn một view mà trong đó các ký tự unicode của templatemodel được bảo toàn. Ví dụ sau đây là cấu hình cho view engine Thymeleaf.

@Bean
public ViewResolver viewResolver() {
// ...
viewResolver.setCharacterEncoding("UTF-8");
return viewResolver;
}

Template mang ký tự Unicode nhưng View thì không

Bạn có view template như thế này:

<tr>
<td>Tên</td>
<td>
<input type="text" name="name" th:value="${customer.name}">
</td>
</tr>
<tr>
<td>Email</td>
<td>
<input type="text" name="email" th:value="${customer.email}">
</td>
</tr>
<tr>
<td>Địa chỉ</td>
<td>
<input type="text" name="address" th:value="${customer.address}">
</td>
</tr>

Nhưng bạn nhận được view như thế này:

Nếu bạn đã xử lý vấn đề encoding trong lúc render như ở trên rồi, thì có thể vấn đề là do template resorver đã dùng một encoding khác để đọc tài liệu template. Hãy cấu hình lại cho cả template resolver nữa.

@Bean
public ITemplateResolver templateResolver() {
// ...
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}

Không thể chuyển tải ký tự Unicode qua tầng giao vận

Giả sử cần submit form sau:

Tầng giao vận TCP/IP không quan tâm với bảng mã, nó đơn giản và vận chuyển gói tin, từng byte một. Phần lớn web server, trừ khi là web server do bạn tự viết, decode các bytes này để có các parametter theo lối như thể rằng các bytes đó trước kia là ký tự của bảng mã ISO-8859–1. Nếu bạn nhìn các ký tự đã được decode ra dưới con mắt của bảng mã UTF-8 (trớ trêu rằng, phần lớn các ngôn ngữ lập trình làm như thế), bạn sẽ nhận được kết quả không mong muốn.

Cách xử lý luôn luôn là encode chuỗi ký tự ngược lại thành dòng bytes (theo bảng mã ISO-8859–1, tất nhiên), và sau đó decode lại theo bảng mã UTF-8. Cho dù là thủ công như sau:

public String createCustomer(Customer customer) {
try {
byte[] bytes = customer.getName().getBytes("ISO-8859-1");
String decodedName = new String(bytes, "UTF-8");
customer.setName(decodedName);
customerService.save(customer);
return "redirect:/customers";
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "500";
}
}

… hay tự động, bằng cách sử dụng filter, như sau chẳng hạn:

public class AppInit extends AbstractAnnotationConfigDispatcherServletInitializer {
// ...
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
FilterRegistration.Dynamic filterRegistration =
servletContext.addFilter("endcoding-filter", new CharacterEncodingFilter());
filterRegistration.setInitParameter("encoding", "UTF-8");
filterRegistration.setInitParameter("forceEncoding", "true");

//make sure encodingFilter is matched most first, by "false" arg
filterRegistration.addMappingForUrlPatterns(null, false, "/*");

super.onStartup(servletContext);
}
}

Entity mang thông tin Unicode, vào đến Database thì mất dấu

Bạn có form như thế này:

Entity ngon nghẻ như thế này:

Vào tới database thì loạn cào cào hết cả:

Hầu hết các client của các dbms, khi kết nối tới dbms server, đều chọn mặc định một bảng mã để làm việc với nhau. “Mặc định” này đôi khi là một giá trị cố định, đôi khi là lấy dynamic. Dù là trường hợp nào đi chăng nữa, đôi khi charset đó hoàn toàn khác với charset được dùng cho Schema/Table/Column. Nghĩa là “anh nói tiếng của anh, nhưng tôi hiểu theo tiếng của tôi”.

Cách xử lý luôn là cố định lại bảng mã dùng trong cuộc nói chuyện giữa client và dbms server:

dataSource.setUrl("jdbc:mysql://localhost:3306/cms?characterEncoding=utf8");

Và đồng thời (điều này rất quan trọng), sử dụng cùng một bảng mã đó cho Schema/Table/Column:

CREATE DATABASE IF NOT EXISTS cms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Nếu database cùng với dữ liệu đã tồn tại, theo một bảng mã khác, cách xử lý luôn là sử dụng một kỹ thuật nào đó được dbms hỗ trợ để convert dữ liệu từ bảng mã cũ sang bảng mã mong muốn.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade