有一次运营在群里发了条活动落地页链接,说"手机上打开搜索词是空的"。链接长这样:?keyword=儿童 书包&from=wx。我在自己电脑上复制粘贴打开正常,发到微信里再点就断了。查了半小时才定位到两处问题:一是空格在传输链路里被换成了 +,后端按 %20 的规则解码,搜索词首字符变成了加号;二是链接里 & 写成裸的 &,某些客户端会把后面的内容当实体吞掉。链接看起来只是"一串字符",实际上它是最早被严格约束的格式之一——超出允许范围的字符必须换成 %XX 的形式,这就是百分号编码。
一、为什么网址不能想放什么就放什么
URL 的骨架只接受有限的 ASCII 字符。中文、空格、引号属于"必须转义"的内容;而 : / ? & = # 这些符号本身在语法里有职位——? 之后是查询串、& 分隔参数、= 分隔键值、# 之后是片段。参数值里出现这些字符时如果不转义,解析器就会按语法重新切分你的值。中文"中"的 UTF-8 字节是 E4 B8 AD,逐个加上百分号就是 %E4%B8%AD,你在链接里看到的乱码串其实是三个字节。
解码同理:拿到一串 %E4%B8%AD%E5%9B%BD,按百分号切字节再按 UTF-8 组装,就是"中国"。这件事没有任何模糊空间,唯一的变量是"当初用哪套编码规则编的"。
二、三种规则的区别:空格,以及 / ? & = 归谁
| 方式 | 空格编成 | 对 : / ? & = # | 典型用途 | 常见误用 |
|---|---|---|---|---|
encodeURI | %20 | 保留,视作结构 | 整条链接已经拼好,只想把中文、空格等非法字符转义 | 拿它编参数值,值里的 & 原样留下,把后面的参数拆开 |
encodeURIComponent | %20 | 一并转义 | 拼接参数值,前后端最常见约定 | 拿它编整条链接,协议里的 :/ 也被转义,链接直接失效 |
PHP urlencode | + | 一并转义(- . _ 除外) | 与 PHP 后端对接、老式表单提交 | 和按 %20 约定实现的一端混用,空格与加号互相串味 |
三者都会把字面量的 % 编成 %25,这正是双重编码的来源:参数已经被编过一次,某个环节又对整个查询串编了一次,%E4 就成了 %25E4,解码一遍只拿回 %E4 这个字符串而不是汉字。判断方法很直接——结果里出现 %25 就是编了两回。另外要区分查询串和路径:表单编码 application/x-www-form-urlencoded 规定空格写成 +,而路径里的 + 就是加号本身,空格必须用 %20;两套规则混用时,"搜索词首字符变成加号"这种怪事就会出现,一端按 + 解、另一端按 %20 编。
三、我处理这类问题的固定顺序
- 先看症状:值是整段乱码、只有空格出错、还是参数从某个位置被截断。截断几乎都是没转义的
&或#。 - 把出问题的原始串和后端的接收值同时抓出来(浏览器地址栏、代理日志、框架的请求对象),确认差异发生在编码环节还是解码环节。
- 在URL 编解码里用三种模式分别解一次,看哪一种能得到可读的中文——能解对的那种就是当初实际使用的规则。这一步比猜后端配置快得多。
- 修复时统一约定:拼参数值一律用
encodeURIComponent,整条链接做兜底转义才用encodeURI,并在接口文档里写清空格按%20还是+。
如果内容不是要塞进 URL,而是要塞进 HTML 或 JS 字符串,那属于另一套转义体系:\u4e2d 这类写法对应的是中文 Unicode 互转,两者结果不能混用,这也是排查时容易串线的地方。
四、几个容易被忽略的边界
- 路径里的中文:浏览器地址栏看起来是正常的中文,实际请求发出的已经是编码后的字节;复制到聊天软件时有的客户端会再编码一次,导致对方拿到
%25版本。 - 字符集不统一:如果服务端按 GBK 解码而内容是 UTF-8 编码的,解出来是"涓枃"这类怪字,而不是原始中文。换模式解决不了,得先把两端字符集对齐。
- 已经是编码串的内容再编码:跳转链接里嵌套 URL(
?target=https://...?a=1&b=2)时,内层整条必须用encodeURIComponent处理,否则&b=2会被外层当成自己的参数吃掉。 - SEO:中文 URL 搜索引擎能识别,但会显示成编码串,分享和日志里可读性差,页面 slug 用英文或拼音更省心。
- 别把编码当过滤:URL 编码只是传输格式转换,不构成任何安全校验;参数还是要按类型严格验证。
五、常见疑问
Q:%20 和 + 都表示空格,能随便换吗?
不能。查询串里两者在不少实现里都能被接受,但表单编码(x-www-form-urlencoded)规定空格是 +,路径里则必须是 %20。混用就会出现"搜索词首字符变成加号"或"加号变成空格"这类双向错误。
Q:一个汉字为什么是三个百分号段?
因为百分号编码是按字节来的,常用汉字在 UTF-8 里占 3 字节,每个字节写成 %XX,所以是 9 个字符。这也可以用来粗判编码用的字符集:3 段基本是 UTF-8,2 段往往是 GBK。
Q:解码出来还是乱码怎么办?
按顺序排查:先确认是不是双重编码(结果里有 %25 就是),再换一种编码模式解,最后检查两端字符集是否一致。三种方式各解一次,成本极低。
Q:URL 里能直接写中文吗?
现代浏览器会在你按下回车时自动做编码,所以"能写"。但服务端日志、抓包工具、以及跨系统传递时都会以编码形态存在,写文档和示例链接时建议直接给出编码后的形式,减少歧义。
Q:参数值里带 # 会怎样?
# 之后的内容不会发给服务器,浏览器只把它当片段标识。这是排查"参数莫名丢失"时最容易忽略的一条。
回头看那次"手机上搜索词是空的",两个原因都不在业务逻辑里,而是空格与 & 这两个字符在传输链路上的归属没约定清楚。编码规则记不全没关系,抓到出问题的原始串,三种模式各解一次,答案通常就显示在屏幕上。