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.Unmarshal 报 invalid character 的常见原因这个错误通常不是 JSON 格式本身有问题,而是请求体没被正确读取。Go 的 http.Request.Body 是单次读取流,一旦被其他代码(比如日志中间件、鉴权逻辑)提前读过,后续再调 json.Decode 就会失败,返回空字节或乱码。
务必确保:只读一次 Body,且在解码前检查 Content-Type 是否为 application/json。
io.ReadAll(r.Body) 一次性读完,再传给 json.Unmarshal;不要用 json.NewDecoder(r.Body).Decode() 后又试图重复读io.NopCloser(bytes.NewReader(data)) 重建 Body
chi 路由支持 OPTIONS 预检和跨域头?浏览器发起跨域请求前会先发 OPTIONS 请求,若服务端没响应对应路径的 200 OK 并携带 Access-Control-Allo 头,后续请求会被拦截。标准 
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) 会附带跨域支持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{} 组合 —— 表结构一变,运行时就 panicsqlx,可用 db.Select(&users, "SELECT ...") 自动按字段名绑定,但需确保 struct tag 和列名一致Scan 参数必须是指针;传值会导致类型不匹配,报错信息可能误导为“数量不对”