RuoYi-Vue-Plus Docker 部署踩坑:MySQL 中文双重编码的根因与修复

用 Docker 部署 RuoYi-Vue-Plus 后,中文全部变成乱码,但奇怪的是按钮文字正常、只有数据库返回的数据乱码。排查了一圈发现是 MySQL 官方镜像的一个"经典坑"------docker-entrypoint-initdb.d 执行 SQL 时 client charset 默认是 latin1。

现象

用 docker-compose 部署完 RuoYi-Vue-Plus 后,打开前端页面:

  • 正常:按钮文字「新增」「修改」「删除」、表头「角色名称」「权限字符」
  • 乱码:左侧菜单、面包屑、表格中的中文角色名

乱码长这样:ç³>>ç>>Ÿç®¡ç†ç§Ÿæˆ·ç®¡ç†

看到这个 pattern 第一反应是编码问题,但问题出在哪一环?

排查过程

Step 1:排除 Nginx

先看 Nginx 的 Content-Type 头:

bash 复制代码
curl -sI http://127.0.0.1:88/
# Content-Type: text/html; charset=utf-8 ✅

Nginx 没问题。

Step 2:排除前端 JS 编码

检查编译产物的 UTF-8 字节:

bash 复制代码
# "系统" 的 UTF-8 应该是 e7 b3 bb
xxd /usr/share/nginx/html/assets/index-*.js | grep "e7b3"
# 找到了,UTF-8 字节是正确的

前端也没问题。

Step 3:检查 API 响应原始字节

这是关键一步。用 Python 直接读取 API 返回的原始字节

python 复制代码
import urllib.request
# ... login 省略 ...
resp = urllib.request.urlopen(req)
raw = resp.read()
print(repr(raw[:200]))

输出:

swift 复制代码
b'{"meta":{"title":"\\xc3\\xa7\\xc2\\xb3\\xc2\\xbb...'

发现问题了!正常"系统管理"的 UTF-8 应该是:

复制代码
\xe7\xb3\xbb\xe7\xbb\x9f\xe7\xae\xa1\xe7\x90\x86

但实际返回的是:

erlang 复制代码
\xc3\xa7\xc2\xb3\xc2\xbb\xc3\xa7\xc2\xbb...

每个原始 UTF-8 字节前面都多了一个 \xc3\xc2

这就是经典的 Mojibake(双重编码)

yaml 复制代码
原始 UTF-8:  E7 B3 BB    → "系"
被当 Latin-1: ç ³ >>      → 3 个 Latin-1 字符
再 UTF-8 编码: C3 A7  C2 B3  C2 BB → 6 个字节

Step 4:确认是数据库层的问题

直接查 MySQL:

sql 复制代码
SELECT menu_name, HEX(menu_name), LENGTH(menu_name), CHAR_LENGTH(menu_name)
FROM sys_menu WHERE menu_id = 1;
sql 复制代码
+----------+----------------------------------------------+--------+----------------+
| menu_name| HEX(menu_name)                               | LENGTH | CHAR_LENGTH    |
+----------+----------------------------------------------+--------+----------------+
| 系统管理  | C3A7C2B3C2BBC3A7C2BBC5B8C3A7C2AEC2A1C3A7... | 25     | 12             |
+----------+----------------------------------------------+--------+----------------+
  • CHAR_LENGTH = 12:MySQL 以为这是 12 个"字符"(实际是 12 个 Latin-1 字符)
  • LENGTH = 25:实际 25 个字节
  • 正确的"系统管理"应该是 CHAR_LENGTH = 4LENGTH = 12,HEX 为 E7B3BBE7BB9FE7AEA1E79086

数据库里存的就是双重编码的数据!

Step 5:定位 MySQL 初始化的 charset 问题

检查 MySQL 连接的字符集:

sql 复制代码
SHOW SESSION VARIABLES LIKE 'character_set%';
复制代码
| character_set_client     | latin1 |
| character_set_connection | latin1 |
| character_set_results    | latin1 |
| character_set_server     | utf8mb4 |

虽然 character_set_server = utf8mb4,但 character_set_client = latin1

问题链条:

ini 复制代码
SQL 文件 (UTF-8: 系统管理 = E7B3BB E7BB9F E7AEA1 E79086)
    ↓
mysql 客户端 (character_set_client = latin1)
    ↓
MySQL 服务器认为输入是 Latin-1,按 Latin-1 存储
    ↓
每个 UTF-8 字节被当成独立的 Latin-1 字符
    ↓
存储结果变成双重编码: C3A7C2B3C2BB...

为什么 --character-set-server=utf8mb4 不够?

你的 docker-compose.yml 可能已经配了:

yaml 复制代码
mysql:
  image: mysql:8.0
  command:
    --character-set-server=utf8mb4
    --collation-server=utf8mb4_general_ci

但这只设置了服务端(server)的默认字符集。docker-entrypoint-initdb.d 执行 SQL 文件时,用的是mysql 客户端 连接,而 mysql 客户端的 character_set_client 默认取决于操作系统 locale

MySQL 官方 Docker 镜像基于 Oracle Linux / Debian,默认 locale 是 CPOSIX ,映射到 MySQL 就是 latin1

所以即使你设了 --character-set-server=utf8mb4,initdb 阶段的 mysql 客户端连接仍然是 latin1。

为什么 --init-connect 也不行?

你可能试过:

yaml 复制代码
command:
  --init-connect="SET NAMES utf8mb4"

--init-connectroot 用户不生效 (MySQL 安全策略)。而 docker-entrypoint-initdb.d 的 SQL 就是用 root 执行的,所以这个配置完全无效。

修复方案

在每个 SQL 文件的第一行加 SET NAMES utf8mb4;

sql 复制代码
SET NAMES utf8mb4;
-- ----------------------------
-- 原来的内容...
-- ----------------------------
create table sys_social
(
    ...
);

然后删除数据卷,重新初始化:

bash 复制代码
docker compose down -v    # 删除数据卷
docker compose up -d      # 重新启动,重新执行 initdb.d

验证:

sql 复制代码
SELECT menu_name, HEX(menu_name), CHAR_LENGTH(menu_name)
FROM sys_menu WHERE menu_id = 1;
sql 复制代码
+----------+----------------------------------+--------------+
| menu_name| HEX(menu_name)                   | CHAR_LENGTH  |
+----------+----------------------------------+--------------+
| 系统管理  | E7B3BBE7BB9FE7AEA1E79086         | 4            |
+----------+----------------------------------+--------------+

CHAR_LENGTH = 4,HEX 是正确的 UTF-8,搞定。

其他方案对比

方案 是否有效 原因
--character-set-server=utf8mb4 只影响 server 层,不影响 client 连接
--init-connect="SET NAMES utf8mb4" root 用户不执行 init_connect
-e LANG=C.UTF-8 ⚠️ 部分 只对 BMP 字符有效,emoji 等扩展字符仍有问题
[client] default-character-set=utf8mb4 在 my.cnf 里配置,但需要挂载自定义配置文件
SQL 文件头部加 SET NAMES utf8mb4; 最简单、最可靠,不依赖任何外部配置

为什么这个问题很难排查?

  1. 静态文字正常:Vue 模板里的中文按钮不受影响,只有数据库返回的动态数据乱码
  2. 乱码不是问号 :不是典型的 ??? 或方块,而是 ç³>>ç>>Ÿ 这种"看起来像编码问题但又不太确定"的字符串
  3. server charset 误导SHOW VARIABLES 显示 character_set_server=utf8mb4,让人以为配置没问题
  4. SQL 文件本身是 UTF-8 :用 file 命令检查 SQL 文件确实是 UTF-8,但写入时被 mysql 客户端转码了

总结

MySQL Docker 镜像的 docker-entrypoint-initdb.d 机制,mysql 客户端默认 charset 是 latin1 而不是 utf8mb4。 这是一个从 2016 年就有人报告的问题(docker-library/mysql#131#708),至今仍是默认行为。

如果你的 SQL 文件包含非 ASCII 字符(中文、日文、韩文、emoji 等),务必在每个 SQL 文件的第一行加 SET NAMES utf8mb4;,否则数据会被双重编码写入数据库。


参考:

相关推荐
Wzx1980122 小时前
Redis&ES——Retriever的抽象实现
数据库·人工智能·redis·elasticsearch
G.O.G.O.G2 小时前
《LeetCode SQL 从入门到进阶(MySQL)》06(下)
数据库·sql·oracle
zandy10113 小时前
多租户SaaS架构下的BI数据隔离与权限治理体系
数据库·架构·数据加密
多巴胺梦想家3 小时前
NoSQL 数据库:不只是关系型
数据库·nosql
数字孪生家族4 小时前
3DGS 数据编辑单体化技术:将高斯重建转化为结构化数字空间资产
数据库·3d·高斯泼溅·空间智能应用
广州灵眸科技有限公司4 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) 系统操作-线进程操作
数据库·单片机·嵌入式硬件·算法·php
石一峰6995 小时前
驱动:私有数据为什么要在三个地方各挂一遍?
数据库·python·算法
ClouGence6 小时前
跨云、跨地域数据同步,这样更省心
数据库·sql·saas
TDengine (老段)6 小时前
TDengine 函数完整参考 — 聚合、时序、字符串、时间、数学
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据