为什么要编码
URL 只允许有限的字符集。中文、空格、&、=、? 等字符必须转成 %XX 的形式才能安全地在 URL 中传输,否则会被解析器误认为是结构分隔符。
%20 与 + 的区别
在查询字符串(application/x-www-form-urlencoded)中,空格历史上用 + 表示;而在 URL 路径中空格只能是 %20。不同解码器对 + 的处理不一致,是“空格变加号”类 bug 的常见来源——参数值里的空格建议统一用 %20。
encodeURI vs encodeURIComponent
- encodeURI:保留 URL 结构字符(
: / ? # [ ] @等),适合编码完整 URL; - encodeURIComponent:编码更彻底,适合编码单个参数值。
对完整 URL 用 encodeURIComponent 会把 :/ 也编码掉,URL 反而失效——这是最常见的误用。
中文乱码的两大根源
- 编码/解码不对称:编码了两次却只解码一次(或反过来),结果残留 %XX;
- 字符集不一致:发送端按 UTF-8 编码、服务端按 GBK 解析,中文必然乱码。
常用字符编码对照
| 原字符 | 编码结果 | 说明 |
|---|---|---|
| 空格 | %20 或 + | 查询串历史上用 +,路径必须用 %20 |
| 中文“中” | %E4%B8%AD | UTF-8 的 3 个字节,各转成一个 %XX |
| & | %26 | 不编码会被当作参数分隔符 |
| = | %3D | 出现在参数值里必须转义 |
| # | %23 | 不编码会被截断成锚点 |
代码示例
// 前端:拼参数一律用 encodeURIComponent
const url = '/search?q=' + encodeURIComponent('中文 & 符号');
// => /search?q=%E4%B8%AD%E6%96%87%20%26%20%E7%AC%A6%E5%8F%B7
// Node:new URLSearchParams({ q: '中文 & 符号' }).toString()
// Python:urllib.parse.quote('中文 & 符号', safe='')
三个排查建议
- 拿不准时先解码一次,确认是不是被编码了两遍;
- 服务端收到
+时,先确认框架是否按表单规则把它解析成空格——在 URL 路径里它就是字面加号; - 中文乱码优先检查两端字符集是否都是 UTF-8,而不是反复编码解码。
实战案例:三个常见故障
- “搜索词里的空格变成了 +”:前端按表单规则编码、后端按路径规则解析。统一在参数值里使用
%20即可避免。 - “中文参数传到后端变乱码”:多半是两端字符集不一致(UTF-8 发送、GBK 解析)。先在两端统一 UTF-8,而不是反复编码解码。
- “链接里出现 %2520”:说明
%20被二次编码成了%2520(% 又被编码为 %25)。去掉重复编码的环节即可。
常见问题(FAQ)
路径里能用 + 表示空格吗?不能;只有查询串才可能把 + 当空格,路径里 + 就是加号。encodeURIComponent 能编码整条 URL 吗?不建议,它会把 :/?&= 一起编码,破坏结构。为什么“中”编码后有 3 组 %XX?因为 UTF-8 用 3 个字节表示它,逐字节编码即 %E4%B8%AD。浏览器地址栏里的中文会自动编码吗?显示不编码,实际发送时仍会转成 %XX,所以手动拼 URL 时务必自己编码。
动手试试:URL 编解码、Base64 编解码
在前后端协作中的约定
- 明确由谁编码:前端拼接链接时负责编码一次,后端只做解码与校验,双方都不要重复处理,避免双重编码造成内容错乱。
- 参数名与参数值分开处理:参数名通常由开发者定义,不需要编码;参数值来自用户输入,必须编码。混在一起处理会让可读的参数名也变成难以辨认的形式。
- 日志中保留原文:日志应同时记录解码后的可读内容与原始编码串,便于排查;只记录编码串会把排障变成手工解码的体力活。
- 测试覆盖边界字符:空格、加号、百分号、中文与表情符号都容易出错,应在接口测试中固定覆盖,而不是依赖人工抽查。
- 文档写清约定:接口文档应说明参数接受的是已编码值还是原始值,这一句话能省掉大量联调时间。
把编码责任划分清楚,并写进文档与测试,是避免链接类问题的长期解法。
与网关和代理的关系
请求经过反向代理或网关时,路径与查询串可能被重写。重写规则如果对已编码的百分号再处理一次,就会出现参数值被二次编码或反解不开的情况。排查这类问题时应同时查看客户端发出的原始地址与到达应用时的地址,对比两者差异,通常一眼就能看出是哪一层做了多余处理。