6.4 KiB
6.4 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
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")
}
关联笔记
- HTTP 协议 — Gin 处理 HTTP 请求的基础
- API 设计 — 按照设计的 API 规范实现
- 数据库基础 — Go 操作数据库的方法
- 部署与运维基础 — Go 应用部署
- 30.areas/finance/Investment lessons/2024.Current trading lessons.md