This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
obsidian/BACKEND/Go 后端基础.md

6.4 KiB

tags, create time
tags create time
后端
Go
Gin
基础
2026-04-24 18:41

Go 后端基础

概述

Go 是一门适合写后端的语言:编译速度快、并发模型简单(goroutine + channel)、标准库完善。结合 Gin 框架,可以快速构建高性能的 RESTful API。

思考题:Go 的 goroutine 和线程有什么区别?为什么百万级 goroutine 不会撑爆内存?

正文

1. 基础 HTTP 服务

package main

import "net/http"

func main() {
    // 最简单的 HTTP 服务
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })

    http.ListenAndServe(":8080", nil)
}

2. 使用 Gin 框架

package main

import "github.com/gin-gonic/gin"

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    r := gin.Default()  // 默认中间件(日志 + 恢复)

    // 路由分组:/api/users
    users := r.Group("/api/users")
    {
        users.GET("", listUsers)
        users.GET("/:id", getUser)
        users.POST("", createUser)
        users.PUT("/:id", updateUser)
        users.DELETE("/:id", deleteUser)
    }

    r.Run(":8080")
}

路由分组的作用与原理

思考题:如果把所有路由都写成 r.GET("/api/users/...") 而不是用 .Group(),会有什么缺点?

维度 说明
作用 自动为一批路由添加公共前缀(prefix),避免重复写相同的路径片段
中间件继承 分组上 Use() 的中间件只作用于该分组下的路由
路由注册 Group() 返回一个新的 IRoute,内部维护一个前缀累加器

底层原理(Gin 源码级别):

// gin/group.go 简化逻辑

func (group *routerGroup) Group(path string) RouterGroup {
    // 新前缀 = 当前前缀 + 传入的路径
    newPath := group.prefix + path
    return RouterGroup{
        handlers: group.handlers, // 继承已注册的中间件
        prefix:   newPath,        // 累加前缀
        engine:   group.engine,   // 共享同一个路由引擎
    }
}

func (group *routerGroup) registerHandler(method, relativePath string, handlers ...HandlerFunc) {
    // 最终路径 = 分组前缀 + 路由路径
    absolutePath := group.prefix + relativePath
    group.engine.handle(method, absolutePath, handlers)
}

路由匹配示意:

r.Group("/api/users")
├── users.GET("")            →  /api/users         (GET)
├── users.GET("/:id")        →  /api/users/:id     (GET)
├── users.POST("")           →  /api/users         (POST)
├── users.PUT("/:id")        →  /api/users/:id     (PUT)
└── users.DELETE("/:id")     →  /api/users/:id     (DELETE)

嵌套分组示例:

v1 := r.Group("/api/v1")
{
    users := v1.Group("/users")
    {
        users.Use(v1AuthMiddleware())  // 仅 v1 的 /users 生效
        users.GET("", listUsers)
        users.GET("/:id", getUser)
    }
    // 嵌套后前缀是: /api/v1/users
}

提问: 为什么分组中间件(group.Use())只能作用于该分组及其子分组内的路由,而不能影响 r.Group() 之外的路由?(提示:中间件本质上是 HTTP 处理链,和路由树是正交的)

3. 中间件 — 横切关注点的利器

// 认证中间件
func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(401, gin.H{"code": 1003, "message": "未认证"})
            c.Abort()
            return
        }
        // 验证 token...
        c.Next()
    }
}

// 使用
r.Use(authMiddleware())  // 全局中间件
r.GET("/profile", getProfile)

// 局部中间件
admin := r.Group("/api/admin")
admin.Use(adminMiddleware())
admin.GET("/stats", adminStats)

常见中间件类型:

中间件 作用
CORS 跨域处理
RateLimit 限流
Logger 请求日志
Recovery 异常恢复
Auth 身份认证

4. 结构体标签与 JSON 序列化

type CreateUserRequest struct {
    Name  string `json:"name" binding:"required,min=2,max=50"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"required,min=1,max=150"`
}

// Gin 自动绑定和校验
func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"code": 1002, "message": "参数校验失败", "errors": err.Error()})
        return
    }
    // ...
}

提问: binding:"required,email" 中的标签是 Gin 内置的校验规则。如果需要自定义校验逻辑(比如检查用户名是否已存在),应该怎么实现?

5. 错误处理

// Go 风格的错误处理
func getUser(c *gin.Context) {
    id := c.Param("id")

    user, err := db.GetUser(id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            c.JSON(404, gin.H{"code": 1001, "message": "用户不存在"})
            return
        }
        c.JSON(500, gin.H{"code": 2001, "message": "服务器错误"})
        return
    }

    c.JSON(200, gin.H{"data": user})
}

6. 完整 API 示例

package main

import (
    "log"
    "net/http"
    "github.com/gin-gonic/gin"
)

// 统一响应
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

func main() {
    r := gin.Default()

    // CORS 中间件
    r.Use(func(c *gin.Context) {
        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE")
        c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }
        c.Next()
    })

    // 用户路由
    r.GET("/api/health", func(c *gin.Context) {
        c.JSON(200, Response{Code: 0, Message: "ok"})
    })

    r.GET("/api/users", listUsers)
    r.POST("/api/users", createUser)

    log.Println("Server starting on :8080")
    r.Run(":8080")
}

关联笔记