4. 路由与路径参数

任务说明

实现一个基于 net/http 标准库的 HTTP 服务,提供带路径参数的 RESTful 端点 /users/{id}。当客户端请求该端点时,服务端从 URL 路径中提取用户 ID,并返回包含该 ID 的 JSON 响应。同时提供 /users 端点用于创建用户,以构成完整的最小闭环。

实现方案

Go 1.22+ 的 net/http.ServeMux 原生支持路径参数(使用 {id} 语法),无需引入第三方库。通过 r.PathValue("id") 获取路径参数值。服务监听 :8080 端口,所有请求处理逻辑集中在 main.go 中。

代码实现

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strings"
)

// user 表示用户数据结构
type user struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

// 内存数据存储,模拟数据库
var users = map[string]user{
    "1": {ID: "1", Name: "Alice"},
    "2": {ID: "2", Name: "Bob"},
}

func main() {
    mux := http.NewServeMux()

    // 路径参数端点:GET /users/{id}
    mux.HandleFunc("GET /users/{id}", getUser)

    // 创建用户:POST /users
    mux.HandleFunc("POST /users", createUser)

    // 健康检查
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("ok"))
    })

    log.Println("Server listening on :8080")
    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatal(err)
    }
}

// getUser 处理 GET /users/{id} 请求
func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id") // 获取路径参数

    u, exists := users[id]
    if !exists {
        http.Error(w, `{"error":"user not found"}`, http.StatusNotFound)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(u)
}

// createUser 处理 POST /users 请求
func createUser(w http.ResponseWriter, r *http.Request) {
    var newUser user
    if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
        http.Error(w, `{"error":"invalid request body"}`, http.StatusBadRequest)
        return
    }

    // 简单校验:ID 不能为空且不能重复
    newUser.ID = strings.TrimSpace(newUser.ID)
    if newUser.ID == "" {
        http.Error(w, `{"error":"id is required"}`, http.StatusBadRequest)
        return
    }
    if _, exists := users[newUser.ID]; exists {
        http.Error(w, `{"error":"user already exists"}`, http.StatusConflict)
        return
    }

    users[newUser.ID] = newUser
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(newUser)
}

运行与验证

运行服务:

go run main.go

验证路径参数端点:

# 请求存在的用户
curl -i http://localhost:8080/users/1

# 预期响应(HTTP/1.1 200 OK)
# {"id":"1","name":"Alice"}

# 请求不存在的用户
curl -i http://localhost:8080/users/999

# 预期响应(HTTP/1.1 404 Not Found)
# {"error":"user not found"}

验证创建用户后再次通过路径参数访问:

# 创建新用户
curl -i -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"id":"3","name":"Charlie"}'

# 预期响应(HTTP/1.1 201 Created)
# {"id":"3","name":"Charlie"}

# 通过路径参数获取刚创建的用户
curl -i http://localhost:8080/users/3

# 预期响应(HTTP/1.1 200 OK)
# {"id":"3","name":"Charlie"}
HTTP 路由与路径参数请求流程 客户端 (curl) ServeMux 路由器 模式匹配: GET /users/{id} 提取 PathValue("id") getUser 处理器 内存数据 users map[string]user GET /users/1 id="1" 查询 返回 user HTTP 200 + JSON 响应 路径参数机制 • URL 模式: /users/{id} 中的 {id} 为路径参数占位符 • 匹配规则: /users/1 → id="1"; /users/999 → id="999" • 获取方式: r.PathValue("id") 返回字符串类型 成功响应示例 HTTP/1.1 200 OK Content-Type: application/json {"id":"1","name":"Alice"}

判定标准

  • 路径参数提取正确:请求 /users/1 返回 ID 为 "1" 的用户数据,而非返回所有用户或固定数据。
  • 动态路由生效:请求 /users/2 返回 Bob 的数据,证明同一路由模式可匹配不同 ID 值。
  • 错误处理有效:请求不存在的 ID(如 /users/999)返回 404 状态码和 JSON 错误信息。
  • 数据一致性:通过 POST /users 创建的新用户,可立即通过 GET /users/{id} 访问到,证明路径参数与数据存储联动正常。