http.ServeMux不支持路径参数因其仅做前缀匹配,无法解析如/users/{id}的动态路由,需改用gorilla/mux或chi等第三方路由器。

用 net/http 搭建最简 RESTful 路由时,为什么 http.ServeMux 不支持路径参数?
http.ServeMux 是 Go 标准库自带的多路复用器,但它只支持前缀匹配(如 /users/),无法解析 /users/{id} 这类带变量的路径。直接写 r.HandleFunc("/users/123", handler) 无法覆盖所有 ID,硬编码路由也不可维护。
实际开发中必须换用第三方路由器,或自己实现路径解析逻辑。主流选择是 gorilla/mux 或 chi ——它们能提取路径段并注入 http.Request.Context 或通过 Vars() 获取。
-
gorilla/mux兼容性好、文档全,适合初学:安装后用router := mux.NewRouter()替代http.DefaultServeMux -
chi更轻量、性能略优,API 设计更函数式,但对中间件链式调用要求稍高 - 别在
http.HandleFunc里手动strings.Split(r.URL.Path, "/")解析 —— 容易漏掉 URL 编码、边界情况和双斜杠问题
处理 JSON 请求体时,json.Unmarshal 报 invalid character 的常见原因
这个错误通常不是 JSON 格式本身有问题,而是请求体没被正确读取。Go 的 http.Request.Body 是单次读取流,一旦被其他代码(比如日志中间件、鉴权逻辑)提前读过,后续再调 json.Decode 就会失败,返回空字节或乱码。
务必确保:只读一次 Body,且在解码前检查 Content-Type 是否为 application/json。
立即学习“go语言免费学习笔记(深入)”;
- 用
io.ReadAll(r.Body)一次性读完,再传给json.Unmarshal;不要用json.NewDecoder(r.Body).Decode()后又试图重复读 - 如果需要记录原始请求体(例如审计日志),必须用
io.NopCloser(bytes.NewReader(data))重建Body - 前端发请求时注意是否误加了 BOM 头、尾部逗号、单引号代替双引号 —— 这些都会触发该错误
如何让 chi 路由支持 OPTIONS 预检和跨域头?
浏览器发起跨域请求前会先发 OPTIONS 请求,若服务端没响应对应路径的 200 OK 并携带 Access-Control-Allow-* 头,后续请求会被拦截。标准 chi 不自动处理预检,需显式注册或使用中间件。
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
r.Use(corsMiddleware)
- 生产环境慎用
*通配Access-Control-Allow-Origin,尤其涉及 Cookie 时必须指定明确域名 -
chi自带的chi.Middleware不含 CORS,不要误以为r.Use(middleware.Logger)会附带跨域支持 - 如果 API 需要认证(如 JWT),记得把
Authorization加入Access-Control-Allow-Headers
用 database/sql 查询时,Scan 报 sql: expected 2 destination arguments 怎么定位?
这个错误说明 rows.Scan() 提供的变量数量与查询结果列数不一致。常见于 SELECT 字段动态变化、JOIN 导致列重复、或用了 * 却没同步更新结构体字段。
最稳妥的方式是显式列出字段名,并用结构体接收,避免靠顺序匹配。
type User struct {
ID int `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
var users []User
rows, _ := db.Query("SELECT id, name, email FROM users WHERE active = ?", true)
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
log.Println(err)
continue
}
users = append(users, u)
}
- 别用
SELECT *+struct{}组合 —— 表结构一变,运行时就 panic - 如果用
sqlx,可用db.Select(&users, "SELECT ...")自动按字段名绑定,但需确保 struct tag 和列名一致 -
Scan参数必须是指针;传值会导致类型不匹配,报错信息可能误导为“数量不对”










