--- tags: [后端, Go, Gin, 基础] create time: 2026-04-24 18:41 --- # Go 后端基础 ## 概述 Go 是一门适合写后端的语言:编译速度快、并发模型简单(goroutine + channel)、标准库完善。结合 Gin 框架,可以快速构建高性能的 RESTful API。 思考题:Go 的 goroutine 和线程有什么区别?为什么百万级 goroutine 不会撑爆内存? ## 正文 ### 1. 基础 HTTP 服务 ```go 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 框架 ```go 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 源码级别):** ```go // 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) ``` **嵌套分组示例:** ```go 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. 中间件 — 横切关注点的利器 ```go // 认证中间件 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 序列化 ```go 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 // 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 示例 ```go 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") } ``` ## 关联笔记 - [[HTTP 协议]] — Gin 处理 HTTP 请求的基础 - [[API 设计]] — 按照设计的 API 规范实现 - [[数据库基础]] — Go 操作数据库的方法 - [[部署与运维基础]] — Go 应用部署 - [[30.areas/finance/Investment lessons/2024.Current trading lessons.md]]