第一章:RESTful API 设计原则回顾
1.1 资源导向设计
-
URI 表示资源 ,而非动作
✅/users(用户集合)
✅/users/123(ID 为 123 的用户)
❌/getUsers、/deleteUser?id=123 -
HTTP 方法表示操作
方法 语义 幂等 安全 GET| 获取资源 | 是 | 是POST| 创建子资源 | 否 | 否PUT| 全量更新 | 是 | 否PATCH| 部分更新 | 否 | 否DELETE| 删除 | 是 | 否
-
状态码标准化
场景 状态码 - 成功创建 |
201 Created - 请求成功 |
200 OK - 无内容返回 |
204 No Content - 资源不存在 |
404 Not Found - 输入无效 |
400 Bad Request - 未认证 |
401 Unauthorized - 无权限 |
403 Forbidden - 服务器错误 |
500 Internal Server Error
- 成功创建 |
第二章:中间件(Middleware)------ 请求处理的横切关注点
2.1 什么是中间件?
中间件是包装 HTTP 处理器的函数 ,用于处理与业务逻辑无关的通用功能:
- 日志记录
- 身份认证
- 请求限流
- CORS 头设置
- Panic 恢复
- 请求 ID 追踪
2.2 中间件签名(标准库风格)
// Middleware 是中间件类型
type Middleware func(http.Handler) http.Handler
// Chain 组合多个中间件
func Chain(mw ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for i := len(mw) - 1; i >= 0; i-- {
next = mw[i](next)
}
return next
}
}
执行顺序:从右到左(最外层最先执行)
2.3 实现核心中间件
(1) LoggingMiddleware(结构化日志)
// internal/middleware/logging.go
func LoggingMiddleware(logger *logrus.Logger) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 生成唯一请求 ID
reqID := uuid.New().String()
ctx := context.WithValue(r.Context(), "req_id", reqID)
// 调用下一个处理器
next.ServeHTTP(w, r.WithContext(ctx))
// 记录日志
logger.WithFields(logrus.Fields{
"req_id": reqID,
"method": r.Method,
"path": r.URL.Path,
"ip": r.RemoteAddr,
"latency": time.Since(start),
"status": w.(*responseWrapper).StatusCode(), // 需包装 ResponseWriter
}).Info("Request completed")
})
}
}
关键 :需包装
ResponseWriter以捕获状态码(见 2.4)。
(2) RecoveryMiddleware(Panic 恢复)
func RecoveryMiddleware(logger *logrus.Logger) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
logger.WithField("panic", err).Error("Handler panicked")
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
}
作用:防止单个请求 panic 导致整个服务崩溃。
(3) CORSMiddleware(跨域支持)
func CORSMiddleware() Middleware {
return func(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, PATCH, 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)
})
}
}
2.4 包装 ResponseWriter 捕获状态码
标准库 http.ResponseWriter 不提供 StatusCode() 方法,需自定义:
// internal/middleware/response_wrapper.go
type responseWrapper struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWrapper) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWrapper) StatusCode() int {
if rw.statusCode == 0 {
return http.StatusOK
}
return rw.statusCode
}
// 在中间件中使用
wrapped := &responseWrapper{ResponseWriter: w, statusCode: 0}
next.ServeHTTP(wrapped, r)
第三章:路由选择 ------ 标准库 vs chi vs gin
3.1 标准库 net/http 的局限
- 无路径参数(如
/users/{id}) - 无嵌套路由
- 无中间件链原生支持
适用场景:极简服务、学习原理
3.2 推荐:chi ------ 轻量、标准库兼容、中间件友好
-
特点 :
- 基于标准库
http.Handler - 支持路径参数、中间件、子路由
- 无魔法,代码可读性强
- 社区活跃(K8s 生态广泛使用)
go get github.com/go-chi/chi/v5
- 基于标准库
路由示例
r := chi.NewRouter()
r.Use(middleware.Logger) // chi 内置中间件
r.Route("/api/v1", func(r chi.Router) {
r.Get("/healthz", healthHandler)
r.Route("/users", func(r chi.Router) {
r.Use(authMiddleware) // 仅对 /users 子树生效
r.Get("/", listUsers)
r.Post("/", createUser)
r.Route("/{userID}", func(r chi.Router) {
r.Get("/", getUser)
r.Put("/", updateUser)
r.Delete("/", deleteUser)
})
})
})
优势 :中间件可作用于特定路由子树,权限控制更精细。
3.3 对比:gin ------ 高性能但"非标准"
- 优点:性能略高(基准测试)、内置验证
- 缺点 :
- 自定义
*gin.Context,与标准库不兼容 - "魔法"较多(如
c.BindJSON隐式处理) - 升级 Breaking Changes 频繁
- 自定义
建议 :新项目优先选 chi,除非有极致性能需求。
第四章:请求验证与结构化错误
4.1 使用 validator.v10 校验输入
go get github.com/go-playground/validator/v10
定义带标签的结构体
// internal/model/user.go
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2,max=50"`
Email string `json:"email" validate:"required,email"`
Role string `json:"role" validate:"required,oneof=admin user"`
}
在 Handler 中验证
// internal/handler/user.go
func CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "Invalid JSON")
return
}
validate := validator.New()
if err := validate.Struct(req); err != nil {
errs := make([]string, 0)
for _, e := range err.(validator.ValidationErrors) {
errs = append(errs, fmt.Sprintf("%s: %s", e.Field(), e.Tag()))
}
respondError(w, http.StatusBadRequest, strings.Join(errs, "; "))
return
}
// ... 业务逻辑
}
4.2 统一错误响应格式
// internal/handler/error.go
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
func respondError(w http.ResponseWriter, code int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(ErrorResponse{
Code: code,
Message: message,
})
}
响应示例:
{
"code": 400,
"message": "Email: email"
}
第五章:OpenAPI 文档自动化 ------ swaggo
5.1 为什么需要自动生成文档?
- 手写 Swagger YAML 易过时
- 自动生成确保代码与文档一致
- 提供交互式 UI(Swagger UI)
5.2 集成 swaggo
go install github.com/swaggo/swag/cmd/swag@latest
go get github.com/swaggo/http-swagger
在代码中添加注释
// @Summary 创建用户
// @Description 创建新用户,需 admin 权限
// @Tags users
// @Accept json
// @Produce json
// @Param user body CreateUserRequest true "用户信息"
// @Success 201 {object} User
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Router /api/v1/users [post]
func CreateUser(w http.ResponseWriter, r *http.Request) {
// ...
}
生成文档
swag init --dir cmd/my-gateway,internal/handler --generalInfo cmd/my-gateway/main.go
生成 docs/ 目录,包含 swagger.json 和 swagger.yaml。
暴露 Swagger UI
// main.go
import "github.com/swaggo/http-swagger"
func main() {
r := chi.NewRouter()
r.Get("/swagger/*", httpSwagger.WrapHandler)
// ...
}
访问 http://localhost:8080/swagger/index.html 即可查看交互式文档。
第六章:实战 ------ 带认证与权限的用户管理 API
6.1 功能清单
POST /api/v1/auth/login→ 返回 JWT TokenGET /api/v1/users→ 列出所有用户(admin only)POST /api/v1/users→ 创建用户(admin only)GET /api/v1/users/{id}→ 获取用户(本人或 admin)PUT /api/v1/users/{id}→ 更新用户(本人或 admin)DELETE /api/v1/users/{id}→ 删除用户(admin only)
6.2 JWT 认证实现
生成 Token
// internal/auth/jwt.go
func GenerateToken(userID string, role string) (string, error) {
claims := jwt.MapClaims{
"user_id": userID,
"role": role,
"exp": time.Now().Add(24 * time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte("your-secret-key"))
}
认证中间件
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
respondError(w, http.StatusUnauthorized, "Missing Authorization header")
return
}
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
return []byte("your-secret-key"), nil
})
if err != nil || !token.Valid {
respondError(w, http.StatusUnauthorized, "Invalid token")
return
}
claims := token.Claims.(jwt.MapClaims)
ctx := context.WithValue(r.Context(), "user_id", claims["user_id"])
ctx = context.WithValue(ctx, "role", claims["role"])
next.ServeHTTP(w, r.WithContext(ctx))
})
}
6.3 RBAC 权限中间件
func RequireRole(requiredRole string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
role := r.Context().Value("role").(string)
if role != requiredRole {
respondError(w, http.StatusForbidden, "Insufficient permissions")
return
}
next.ServeHTTP(w, r)
})
}
}
路由配置
r.Route("/api/v1", func(r chi.Router) {
r.Post("/auth/login", loginHandler)
r.Route("/users", func(r chi.Router) {
r.Use(AuthMiddleware) // 所有 /users 需认证
r.Get("/", RequireRole("admin")(listUsers)) // 仅 admin 可列出
r.Post("/", RequireRole("admin")(createUser))
r.Route("/{userID}", func(r chi.Router) {
r.Use(CheckOwnership) // 自定义中间件:检查是否本人
r.Get("/", getUser)
r.Put("/", updateUser)
r.Delete("/", RequireRole("admin")(deleteUser))
})
})
})
CheckOwnership :比较
userID路径参数与ctx.Value("user_id")。
第七章:测试策略
7.1 Handler 单元测试
func TestCreateUser_InvalidEmail(t *testing.T) {
reqBody := `{"name":"Alice","email":"invalid","role":"user"}`
req := httptest.NewRequest("POST", "/api/v1/users", strings.NewReader(reqBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router := setupRouter() // 返回 chi.Router
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "email")
}
7.2 集成测试(含数据库)
- 使用
testify/suite管理测试生命周期 - 在
TestMain中启动临时 PostgreSQL(Docker) - 每个测试用例运行前清空数据
结语:API 是系统的门面
一个设计良好的 API,不仅是功能的入口,更是用户体验、安全边界、系统契约 的体现。
通过本篇,你已具备构建企业级 Go Web 服务的核心能力。