有一次營運在群組裡發了一條活動落地頁連結,說「手機上打開搜尋詞是空的」。連結長這樣:?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 裡能直接寫中文嗎?
現代瀏覽器會在你按下 Enter 時自動做編碼,所以「能寫」。但伺服端日誌、抓包工具、以及跨系統傳遞時都會以編碼形態存在,寫文件和範例連結時建議直接給出編碼後的形式,減少歧義。
Q:參數值裡帶 # 會怎樣?
# 之後的內容不會發給伺服器,瀏覽器只把它當片段標識。這是排查「參數莫名丟失」時最容易忽略的一條。
回頭看那次「手機上搜尋詞是空的」,兩個原因都不在業務邏輯裡,而是空格與 & 這兩個字元在傳輸鏈路上的歸屬沒約定清楚。編碼規則記不全沒關係,抓到出問題的原始串,三種模式各解一次,答案通常就顯示在螢幕上。