过程描述

记录一个文字编码的问题

在请求支付宝支付的接口时,发现返回的Response如果有中文的话,print出来后会有乱码。第一反应肯定是编码有问题,那就先转一下编码吧。

一开始的代码大概如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    resp, err := client.Post(aliPayBillURL, "application/x-www-form-urlencoded", bytes.NewBufferString(output))
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    //...
    err = json.Unmarshal(body, &queryResp)
    if err != nil {
        panic(err)
    }
    data := queryResp.Data
    // data.SubMsg里含有中文,会乱码
    if data.Code != "10000" {
        fmt.Printf("download failed: msg: %s, sub_code: %s, sub_msg: %s\n", data.Msg, data.SubCode, data.SubMsg)
        panic("download failed")
    } 

改为了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    resp, err := client.Post(aliPayBillURL, "application/x-www-form-urlencoded", bytes.NewBufferString(output))
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    //...
    err = json.Unmarshal(body, &queryResp)
    if err != nil {
        panic(err)
    }
    data := queryResp.Data
    converted, err := gbkToUtf8([]byte(data.SubMsg))
    if err != nil {
        panic(err)
    }
    if data.Code != "10000" {
        fmt.Printf("download failed: msg: %s, sub_code: %s, sub_msg: %s\n", data.Msg, data.SubCode, converted)
        panic("download failed")
    }

结果是依旧乱码。。难道不是编码的问题?

%s%x看看具体字节:

1
2
3
4
    if data.Code != "10000" {
        fmt.Printf("download failed: msg: %s, sub_code: %s, sub_msg: %x\n", data.Msg, data.SubCode, converted)
        panic("download failed")
    }

然后看到了这个efbfbdefbfbdd0a7efbfbdefbfbd 4170704944 efbfbdefbfbdefbfbd,之所以分成了三段贴出来,是因为我发现中间那段对应了部分正常显示的字符“Appid”,然后尝试手动解码一下其它两段,不管是GBK还是utf8都没有找到对应的字符。

然后发现第一段和最后一段的模式有点类似啊,都是efbfbd啥的,有点奇怪。尝试直接搜索efbfbd,定位到一篇文章,里面主要讲到两点:

  • 这个序列就是utf8编码的,但它比较特殊,它是专门用来替换那些在解码时不认识的码点的,显示出来就是「�」
  • 某些语言,比如 Goang,会自动进行这个转换,不会报错

借用一下原文的例子:

1
2
3
now := time.Now().Unix() // 一个无效的码点值
str := string(now) // golang是utf-8编码,会对无效码点进行替换
fmt.Printf("%X", []byte(str)) // EFBFBD,即字符「�」

这一下坚定了认为是编码的问题,那就是转码出问题了,再一看,我是在json decode之后再转码的,json decode的时候可能已经把数据按utf8解码了,而数据如果不是utf8编码的话,那就都被替换成efbfbd了,改为在json decode之前就转码试试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    resp, err := client.Post(aliPayBillURL, "application/x-www-form-urlencoded", bytes.NewBufferString(output))
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    converted, err := gbkToUtf8([]byte(body))
    if err != nil {
        panic(err)
    }
    fmt.Println(string(converted))
    err = json.Unmarshal(converted, &queryResp)
    if err != nil {
        panic(err)
    }
    data := queryResp.Data
    if data.Code != "10000" {
        fmt.Printf("download failed: msg: %s, sub_code: %s, sub_msg: %s\n", data.Msg, data.SubCode, data.SubMsg)
        panic("download failed")
    }

这次没问题了。

反思 && 教训

  • 获取到数据后不要做任何操作,先做转码
  • 记住efbfbd ,它是一个信号,说明源数据一定不是utf8编码的

最后,记录一下 Golang 中如何从 GBK 转为 utf8

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import (
golang.org/x/text/encoding/simplifiedchinese
golang.org/x/text/transform
)

func gbkToUtf8(s []byte) ([]byte, error) {
    reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())
    d, err := ioutil.ReadAll(reader)
    if err != nil {
        return nil, err
    }
    return d, nil
}

参考