根本原因是原始URL的RawQuery未合法编码,如?name=张三会导致url.Parse()丢弃整个query,使Query()返回空map;Query()自动解码,但前提是RawQuery格式正确(key=value、&分隔、值已编码)。
根本原因不是 Go 解析错了,而是原始 URL 的 RawQuery 本身就不合法 —— 比如手拼的 ?name=张三 或 ?q=hello world,空格、中文、&、= 等字符未经 url.QueryEscape 编码,url.Parse() 会直接丢弃整个 query 部分,导致 Query() 返回空 map。
真正起作用的是 Query() 方法:它内部调用 url.ParseQuery(u.RawQuery) 并自动解码值。但前提是 RawQuery 格式正确(key=value 形式、& 分隔、值已编码)。
https://a.com/?q=%E4%B8%AD%E6%96%87&tag=go → u.Query().Get("q") 得到 "中文"
https://a.com/?q=中文 → u.Query() 返回空 map,u.RawQuery 可能是空或只含部分参数Query(),直接对 u.RawQuery 调用 url.ParseQuery(),它容忍度更高,且返回 map[string][]string
写 urlStr := "https://api.com/search?q=" + keyword + "&sort=asc" 是高危操作 —— keyword 里只要出现空格、中文、&、=、/,生成的 URL 就非法,服务端收不到完整参数,甚至被截断或注入。
url.Values 是唯一安全的中转层,它内部强制调用 url.QueryEscape 对每个值编码,且自动处理重复 key、多值等边界情况。
立即学习“go语言免费学习笔记(深入)”;
v.Set("q", "Go语言")
v.Add("tag", "backend")、v.Add("tag", "cli")
v.Encode() 得到 query 字符串,再赋给 u.RawQuery
u.RawQuery 后,要调用 u.String() 才能得到完整合法 URL,不能直接拼接路径中的特殊字符(如 /、?、#)和查询参数中的特殊字符(如 &、=、空格)编码规则不同,用错函数会导致解析失败或服务端 400。
url.PathEscape 专用于路径段(path segment),它保留 / 不编码;url.QueryEscape 专用于 query 值,它把 / 编成 %2F,把空格编成 %20。
url.QueryEscape("/api/v1") → %2Fapi%2Fv1,塞进 u.Path 会导致路径被当成字面量,找不到路由url.PathEscape("user/张三") → user%2F%E5%BC%A0%E4%B8%89,再拼进 u.Path
url.QueryEscape,哪怕它是 URL 字符串(如 url.QueryEscape("https://x.com/a?b=c"))url.URL 结构体字段(Scheme、Host、Path、RawQuery 等)只是数据容器,它们之间没有自动同步逻辑。改了 u.Path 不等于 URL 字符串变了,也不影响 u.String() 的输出结果 —— 直到你显式调用它。
常见误区是改完字段就直接打印 u 或传给 HTTP client,结果发出去的还是旧 URL。
finalURL := u.String()
RawQuery,别忘了它必须是 url.Values.Encode() 的结果,不是原始键值对字符串Fragment 字段不参与网络传输,但会影响前端路由,修改后同样要走 String()
最易被忽略的是:路径和 query 的编码责任完全在使用者手上,net/url 不会帮你补救“看起来差不多”的字符串 —— 它只认 RFC 3986 合规输入。