256 lines
6.4 KiB
Markdown
256 lines
6.4 KiB
Markdown
---
|
|
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]]
|