现象
用 docker-compose 部署完 RuoYi-Vue-Plus 后,打开前端页面:
- ✅正常:按钮文字「新增」「修改」「删除」、表头「角色名称」「权限字符」
- ❌乱码:左侧菜单、面包屑、表格中的中文角色名
乱码长这样:系统管ç†、租户管ç†
看到这个 pattern 第一反应是编码问题,但问题出在哪一环?
排查过程
Step 1:排除 Nginx
先看 Nginx 的 Content-Type 头:
curl -sI http://127.0.0.1:88/ # Content-Type: text/html; charset=utf-8 ✅
Nginx 没问题。
Step 2:排除前端 JS 编码
检查编译产物的 UTF-8 字节:
# "系统" 的 UTF-8 应该是 e7 b3 bb xxd /usr/share/nginx/html/assets/index-*.js | grep "e7b3" # 找到了,UTF-8 字节是正确的
前端也没问题。
Step 3:检查 API 响应原始字节
这是关键一步。用 Python 直接读取 API 返回的原始字节:
import urllib.request # ... login 省略 ... resp = urllib.request.urlopen(req) raw = resp.read() print(repr(raw[:200]))
输出:
b'{"meta":{"title":"\\xc3\\xa7\\xc2\\xb3\\xc2\\xbb...'
发现问题了!正常"系统管理"的 UTF-8 应该是:
\xe7\xb3\xbb\xe7\xbb\x9f\xe7\xae\xa1\xe7\x90\x86
但实际返回的是:
\xc3\xa7\xc2\xb3\xc2\xbb\xc3\xa7\xc2\xbb...
每个原始 UTF-8 字节前面都多了一个\xc3或\xc2。
这就是经典的Mojibake(双重编码):
原始 UTF-8: E7 B3 BB → "系" 被当 Latin-1: ç ³ » → 3 个 Latin-1 字符 再 UTF-8 编码: C3 A7 C2 B3 C2 BB → 6 个字节
Step 4:确认是数据库层的问题
直接查 MySQL:
SELECT menu_name, HEX(menu_name), LENGTH(menu_name), CHAR_LENGTH(menu_name) FROM sys_menu WHERE menu_id = 1;
+----------+----------------------------------------------+--------+----------------+ | menu_name| HEX(menu_name) | LENGTH | CHAR_LENGTH | +----------+----------------------------------------------+--------+----------------+ | 系统管理 | C3A7C2B3C2BBC3A7C2BBC5B8C3A7C2AEC2A1C3A7... | 25 | 12 | +----------+----------------------------------------------+--------+----------------+
CHAR_LENGTH = 12:MySQL 以为这是 12 个"字符"(实际是 12 个 Latin-1 字符)LENGTH = 25:实际 25 个字节- 正确的"系统管理"应该是
CHAR_LENGTH = 4,LENGTH = 12,HEX 为E7B3BBE7BB9FE7AEA1E79086
数据库里存的就是双重编码的数据!
Step 5:定位 MySQL 初始化的 charset 问题
检查 MySQL 连接的字符集:
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!
问题链条:
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 可能已经配了:
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 是C或POSIX,映射到 MySQL 就是latin1。
所以即使你设了--character-set-server=utf8mb4,initdb 阶段的 mysql 客户端连接仍然是 latin1。
为什么--init-connect也不行?
你可能试过:
command: --init-connect="SET NAMES utf8mb4"
--init-connect对root 用户不生效(MySQL 安全策略)。而docker-entrypoint-initdb.d的 SQL 就是用 root 执行的,所以这个配置完全无效。
修复方案
在每个 SQL 文件的第一行加SET NAMES utf8mb4;:
SET NAMES utf8mb4; -- ---------------------------- -- 原来的内容... -- ---------------------------- create table sys_social ( ... );
然后删除数据卷,重新初始化:
docker compose down -v # 删除数据卷 docker compose up -d # 重新启动,重新执行 initdb.d
验证:
SELECT menu_name, HEX(menu_name), CHAR_LENGTH(menu_name) FROM sys_menu WHERE menu_id = 1;
+----------+----------------------------------+--------------+ | 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; | ✅ | 最简单、最可靠,不依赖任何外部配置 |
为什么这个问题很难排查?
- 静态文字正常:Vue 模板里的中文按钮不受影响,只有数据库返回的动态数据乱码
- 乱码不是问号:不是典型的
???或方块,而是系统这种"看起来像编码问题但又不太确定"的字符串 - server charset 误导:
SHOW VARIABLES显示character_set_server=utf8mb4,让人以为配置没问题 - 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;,否则数据会被双重编码写入数据库。