Golang不支持UTF8编码的原因分析

Golang不支持UTF8编码的原因分析 大家好,

我一直在绞尽脑汁地尝试让我的 Golang API 通过 gorm 解析 MySQL 数据库的列,但在处理包含外文字符的条目时却毫无进展。网上似乎没有任何关于此的指南,所以我希望能在这里得到一些帮助。

我有一个包含许多含有日文字符的表的数据库。例如,某一行中有这个字符串: 美味しい

然而,当在数据库中对此包含该文本值的条目执行 SELECT 查询时,它总是返回: 美味しい

我已经确保数据库是正确使用 utf8mb4 创建的,甚至将此特定列在表上设置为 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci。对于我的 gorm.Open 调用,我在末尾设置了以下参数以确保它解析 utf8mb4:?charset=utf8mb4&parseTime=true

当数据库已正确设置时,我该如何让 Golang 正确支持 UTF8?

如果有帮助的话,以下是数据库中预期的 UTF8 数据:

    mysql> select alt_text from picture_details where id=136;
    +--------------+
    | alt_text     |
    +--------------+
    | 美味しい |
    +--------------+
    1 row in set (0.00 sec)

是的,我也尝试过将文本手动编码为 \xe7\xbe\x8e\xe5\x91\xb3\xe3\x81\x97\xe3\x81\x84 字符串,看看是否能解决问题(结果并没有)。

谢谢!


更多关于Golang不支持UTF8编码的原因分析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

哇,我明白了……

我深入查看了我的数据库代码,发现数据库本身由于某些原因没有设置为utf8mb4。我显然漏掉了整个事情中最重要的一环。 由于这是一个测试数据库,我不得不重新创建它:

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

当然,我现有的数据库必须用以下命令修改:

ALTER DATABASE my_database CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

但这还不是全部。我还必须将以下内容添加到我的docker-compose命令中:

--skip-character-set-client-handshake

我曾经一度只设置了其中一项,但没有像刚才那样同时设置这两项。显然两者都是必需的。

感谢您的帮助,并指出了它变成字节数组这个有趣的事实。我的API现在终于能返回日语了!

更多关于Golang不支持UTF8编码的原因分析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


k71:

当数据库正确设置时,如何让 Golang 正确支持 UTF8?

这是 Go 编程语言:Go 编程语言规范

Rob 是 Go 和 UTF-8 的共同作者。因此,Go 正确地支持 UTF-8。

你没有提供一个小的、可复现的代码示例,所以我们不知道你的问题是什么。

然而,你似乎将字符解释为字节切片而不是字符串。

package main

import (
	"fmt"
)

func main() {
	jp := `美味しい` // UTF-8 as string
	fmt.Println(jp)
	bs := []byte(jp) // UTF-8 as byte slice
	fmt.Printf("%c\n", bs)
	s := string(bs) // UTF-8 as string
	fmt.Println(s)
}

Go Playground - The Go Programming Language

美味しい
[ç ¾ Ž å ‘ ³ 㠁 — 㠁 „]
美味しい

你好 petrus,

感谢你的回复。了解到它应该支持UTF8编码,这无疑是个好消息。由于数据是从数据库提取并通过 fmt.Println 直接打印的,除了GORM操作和模型定义外,我没有太多代码可以分享,但我会在下面提供我所拥有的内容。

正如你指出的,它是一个 []byte 数组,这很有趣。然而,字节数组中似乎出现了一些额外的字符或其他东西。我基本上以你展示的代码片段为例:

	s2 := string(pictures[0].AltText)
	fmt.Println(s2)

	jp := `美味しい` // UTF-8 字符串
	fmt.Println(jp)
	bs := []byte(jp) // UTF-8 字节切片
	fmt.Printf("%c\n", bs)
	s := string(bs) // UTF-8 字符串
	fmt.Println(s)

输出:

美味ã—ã„
美味しい
[ç ¾  å  ³ ã   ã
                  ]
美味しい

我决定找出这些字节的十六进制/二进制表示,看看是否有相似之处。对于来自数据库的条目和硬编码的字符串,我只是打印出它们的值并进行比较,但除了它们长度相同之外,没有发现任何真正相似的地方:

pictures[0].AltText: c3 c2 c5 c3 e2 c2 c3 c2 e2 c3 c2 e2
bs: e7 be 8e e5 91 b3 e3 81 97 e3 81 84

基本上,以下是我关于模型和正在运行的GORM命令的内容(我已将其简化为仅关注 alt_text,忽略了模型和扫描的其他细节):

type PictureDetails struct {
	AltText  string `json:"altText"`
	Language string `json:"language"`
	...
}

type Picture struct {
	AltText        string           `gorm:"<-:false" json:"altText,omitempty"`
	Language       string           `gorm:"<-:false" json:"language,omitempty"`
	PictureDetails []PictureDetails `gorm:"-" json:"pictureDetails,omitempty"`
	...
}
	var pictures []models.Picture
	db.Table("pictures").
		Select("pictures.*, picture_details.alt_text, picture_details.picture_language").
		Joins("left join picture_details on picture_details.picture_id = pictures.id").
		Where("picture_language = ?", language).
		Scan(&pictures)

API为给定语言返回的数组如下:

[
 {
  altText: "<Japanese here>"
  ...
 },
 ...
]

Go语言原生支持UTF-8编码,你遇到的问题实际上是数据库连接字符集配置问题。以下是解决方案:

package main

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func main() {
    // 正确的DSN格式
    dsn := "user:password@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"
    
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    
    // 验证连接字符集
    var result string
    db.Raw("SHOW VARIABLES LIKE 'character_set_connection'").Scan(&result)
    fmt.Printf("Connection charset: %s\n", result) // 应该显示utf8mb4
    
    // 查询示例
    type PictureDetail struct {
        ID      uint
        AltText string
    }
    
    var detail PictureDetail
    db.Where("id = ?", 136).First(&detail)
    fmt.Printf("AltText: %s\n", detail.AltText) // 应该正确显示"美味しい"
}

如果问题仍然存在,检查数据库连接的collation设置:

// 设置会话级别的字符集
db.Exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci")

// 或者使用连接后配置
sqlDB, err := db.DB()
if err != nil {
    panic(err)
}
defer sqlDB.Close()

// 设置连接参数
sqlDB.SetConnMaxLifetime(time.Minute * 3)
sqlDB.SetMaxOpenConns(10)
sqlDB.SetMaxIdleConns(10)

// 验证当前连接的字符集
var charset, collation string
row := db.Raw("SELECT @@character_set_connection, @@collation_connection").Row()
row.Scan(&charset, &collation)
fmt.Printf("Charset: %s, Collation: %s\n", charset, collation)

确保你的MySQL版本支持utf8mb4(5.5.3+),并且表的字符集确实正确设置:

// 检查表字符集
type TableCharset struct {
    Table string
    Charset string
    Collation string
}

var tableInfo TableCharset
db.Raw(`
    SELECT 
        TABLE_NAME as Table,
        CHARACTER_SET_NAME as Charset,
        COLLATION_NAME as Collation
    FROM information_schema.COLUMNS 
    WHERE TABLE_SCHEMA = DATABASE() 
        AND TABLE_NAME = 'picture_details'
        AND COLUMN_NAME = 'alt_text'
`).Scan(&tableInfo)

fmt.Printf("Table: %s, Charset: %s, Collation: %s\n", 
    tableInfo.Table, tableInfo.Charset, tableInfo.Collation)

如果以上配置都正确,问题可能出现在数据插入阶段。确保插入时也使用正确的字符集:

// 插入数据示例
newDetail := PictureDetail{
    AltText: "美味しい",
}
db.Create(&newDetail)

Go语言的字符串内部使用UTF-8编码,只要数据库连接配置正确,就能正确处理多语言字符。

回到顶部