背景
區分 UTF-8 和 GBK
GBK 是在國家標準 GB2312 基礎上擴容後兼容 GB2312 的標準,專門用來解決中文編碼的,是雙字節的,不論中英文都是雙字節的。
UTF-8 是一種國際化的編碼方式,包含了世界上大部分的語種文字(簡體中文字、繁體中文字、英文、日文、韓文等語言),也兼容 ASCII 碼。
雖然 GBK 比 UTF-8 少節省兩個字節,但是 GBK 只包含中文,UTF-8 則包含全世界所有國家需要用到的字符,所以現在很多系統或者框架處理都默認使用 UTF-8 。
不過業務開發對接系統接口的時候,經常會碰到一些老系統使用 GBK 編碼,特別是國內支付,這個時候需要兼容 GBK 和 UTF-8 編碼。
如何讓項目兼容 UTF-8 和 GBK
我們使用 spring boot 2.7 版本。我們默認API使用 UTF-8 ,特別的 API 使用 GBK。在 spring boot 中,當收到請求解析前會通過 CharacterEncodingFilter 進行內容編碼格式指定。
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String encoding = getEncoding();
if (encoding != null) {
// 請求進來設置編碼(設置了請求強制轉編碼或請求頭未設置編碼時)
if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
// 設置了響應強制轉編碼
if (isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
通過以下方式可以修改項目全局的編碼方式
server:
servlet:
encoding:
charset: UTF-8
force-request: false
force-response: false
看下默認情況下,編碼格式 UTF-8 並且每次都強制轉換,什麼意思?也就是就算你請求頭 application/json;charset=GBK 這樣也不會按照頭的編碼解析,會強制給你轉成 UTF-8,如果是 GBK 過來的內容,你就等着吃亂碼吧!
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
public boolean shouldForce(Type type) {
Boolean force = (type != Type.REQUEST) ? this.forceResponse : this.forceRequest;
if (force == null) {
force = this.force;
}
if (force == null) {
force = (type == Type.REQUEST);
}
return force;
}
現在有一個支付系統,它請求的內容是 GBK 編碼,並且有 GET 和 POST 兩種方式過來,我們系統接口默認是 UTF-8,所以只有針對特定的 GBK 接口進行處理。需要支持下面幾種請求情況。
- POST 對方的請求內容 GBK 編碼並且請求頭裏指定了編碼方式
application/json;charset=GBK。 - POST 對方的請求內容 GBK 編碼並且請求頭裏未指定編碼方式
application/json。 - GET 對方的請求內容 GBK 編碼。
第一種情況,我們只需要關閉強制轉換,帶了charset=gbk,就會使用 gbk 編碼進行解析,默認不帶則使用 utf-8 解析。
server:
servlet:
encoding:
force: false
第二種和第三種情況,我們需要先關閉強制轉換,然後添加一個優先級很高的過濾器將指定的請求設置為 GBK 編碼格式(也就是在進入spring 解析前就要處理),如果使用 tomcat 容器的話如下處理。
@Slf4j
@Configuration
public class GBKFilterConfig {
@Bean
public FilterRegistrationBean gbkFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
RequestFacade req = (RequestFacade) request;
Class clazz = req.getClass();
log.info("GBK Filter...");
try {
Field field = clazz.getDeclaredField("request");
field.setAccessible(true);
Request r = (Request) field.get(req);
org.apache.coyote.Request p = r.getCoyoteRequest();
// GET 請求參數強使用 GBK 編碼。
p.getParameters().setQueryStringCharset(Charset.forName("GBK"));
// POST 請求帶頭未指定編碼,強制使用 GBK
p.getParameters().setCharset(Charset.forName("GBK"));
p.setCharset(Charset.forName("GBK"));
chain.doFilter(request, response);
} catch (Exception e) {
log.error("error", e)
}
}
@Override
public void destroy() {
}
});
registration.addUrlPatterns("/api/gbk/**");
registration.setName("gbkFilter");
registration.setOrder(Integer.MIN_VALUE);
return registration;
}
}