博客 / 詳情

返回

記錄一個關於 GBK 編碼的問題

背景

區分 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 接口進行處理。需要支持下面幾種請求情況。

  1. POST 對方的請求內容 GBK 編碼並且請求頭裏指定了編碼方式 application/json;charset=GBK
  2. POST 對方的請求內容 GBK 編碼並且請求頭裏未指定編碼方式 application/json
  3. 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;
    }
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.