Gin Framework 完整指南

目录

  1. 基础介绍
  2. 安装和配置
  3. 路由管理
  4. 请求处理
  5. 响应处理
  6. 中间件
  7. 参数验证
  8. 文件处理
  9. 数据库集成
  10. 认证与授权

1. 基础介绍

package main

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

func main() {
    // 创建默认的 gin 路由引擎
    // 包含了 Logger 和 Recovery 中间件
    r := gin.Default()

    // 定义一个简单的 GET 路由
    r.GET("/ping", func(c *gin.Context) {
        // 返回 JSON 响应
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
            "status": "success",
        })
    })

    // 定义一个处理错误的路由
    r.GET("/error", func(c *gin.Context) {
        // 使用 Error 方法添加错误
        c.Error(fmt.Errorf("这是一个错误"))
        // 返回错误响应
        c.JSON(http.StatusInternalServerError, gin.H{
            "error": "发生了一个错误",
        })
    })

    // 启动服务器,默认监听 0.0.0.0:8080
    // 可以通过环境变量 PORT 更改端口
    if err := r.Run(":8080"); err != nil {
        log.Fatal("启动服务器失败: ", err)
    }
}

2. 安装和配置

package main

import (
    "github.com/gin-gonic/gin"
    "io"
    "os"
    "time"
)

func main() {
    // 设置为发布模式
    // 可选值: debug, release, test
    gin.SetMode(gin.ReleaseMode)

    // 创建一个不带中间件的路由引擎
    r := gin.New()

    // 创建日志文件
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

    // 配置全局中间件
    r.Use(gin.Logger())   // 日志中间件
    r.Use(gin.Recovery()) // 恢复中间件,用于处理 panic

    // 配置信任代理
    r.SetTrustedProxies([]string{"192.168.1.2", "192.168.1.3"})

    // 配置路由超时
    r.GET("/timeout", func(c *gin.Context) {
        // 创建一个带超时的上下文
        ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
        defer cancel()

        // 将超时上下文设置到请求中
        c.Request = c.Request.WithContext(ctx)

        // 模拟耗时操作
        select {
        case <-time.After(5 * time.Second):
            c.JSON(http.StatusOK, gin.H{"message": "完成"})
        case <-ctx.Done():
            c.JSON(http.StatusGatewayTimeout, gin.H{"error": "请求超时"})
        }
    })

    // 配置自定义 HTTP 配置
    s := &http.Server{
        Addr:           ":8080",
        Handler:        r,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }

    // 启动服务器
    if err := s.ListenAndServe(); err != nil {
        log.Fatal("启动服务器失败: ", err)
    }
}

3. 路由管理

package main

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

// 用户结构体
type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

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

    // 1. 基本路由
    // GET 请求
    r.GET("/users", func(c *gin.Context) {
        c.JSON(200, []User{
            {ID: "1", Name: "张三"},
            {ID: "2", Name: "李四"},
        })
    })

    // POST 请求
    r.POST("/users", func(c *gin.Context) {
        var user User
        if err := c.ShouldBindJSON(&user); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        c.JSON(201, user)
    })

    // 2. 路由分组
    // API v1 版本分组
    v1 := r.Group("/v1")
    {
        // 添加中间件到分组
        v1.Use(AuthMiddleware())

        // 分组下的路由
        v1.GET("/users", listUsers)
        v1.POST("/users", createUser)
        v1.GET("/users/:id", getUser)
        v1.PUT("/users/:id", updateUser)
        v1.DELETE("/users/:id", deleteUser)
    }

    // 3. 路由参数
    // 必选参数
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.JSON(200, gin.H{"id": id})
    })

    // 可选参数
    r.GET("/users/*action", func(c *gin.Context) {
        action := c.Param("action")
        c.JSON(200, gin.H{"action": action})
    })

    // 查询参数
    r.GET("/search", func(c *gin.Context) {
        // 获取查询参数
        query := c.Query("q")          // 获取参数 q
        page := c.DefaultQuery("page", "1") // 获取参数 page,默认值为 1
        limit := c.DefaultQuery("limit", "10") // 获取参数 limit,默认值为 10

        c.JSON(200, gin.H{
            "query": query,
            "page": page,
            "limit": limit,
        })
    })

    // 4. 路由重定向
    // 内部重定向
    r.GET("/test", func(c *gin.Context) {
        c.Request.URL.Path = "/test2"
        r.HandleContext(c)
    })

    r.GET("/test2", func(c *gin.Context) {
        c.JSON(200, gin.H{"hello": "world"})
    })

    // HTTP 重定向
    r.GET("/redirect", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "https://www.google.com")
    })

    // 5. 路由组合
    // 支持多种 HTTP 方法
    r.Any("/any", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "method": c.Request.Method,
        })
    })

    // 没有路由匹配时的处理
    r.NoRoute(func(c *gin.Context) {
        c.JSON(404, gin.H{"message": "Page not found"})
    })

    if err := r.Run(":8080"); err != nil {
        log.Fatal("启动服务器失败: ", err)
    }
}

// 中间件函数
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 检查认证逻辑
        auth := c.GetHeader("Authorization")
        if auth == "" {
            c.JSON(401, gin.H{"error": "未授权"})
            c.Abort()
            return
        }
        c.Next()
    }
}

// 路由处理函数
func listUsers(c *gin.Context) {
    users := []User{
        {ID: "1", Name: "张三"},
        {ID: "2", Name: "李四"},
    }
    c.JSON(200, users)
}

func createUser(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    // 这里应该有数据库操作
    c.JSON(201, user)
}

func getUser(c *gin.Context) {
    id := c.Param("id")
    user := User{ID: id, Name: "张三"}
    c.JSON(200, user)
}

func updateUser(c *gin.Context) {
    id := c.Param("id")
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    user.ID = id
    c.JSON(200, user)
}

func deleteUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"message": "用户" + id + "已删除"})
}

4. 请求处理

package main

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

// 用户注册请求结构体
type RegisterRequest struct {
    Username string `json:"username" binding:"required,min=3,max=32"`
    Password string `json:"password" binding:"required,min=6"`
    Email    string `json:"email" binding:"required,email"`
}

// 用户信息结构体
type UserInfo struct {
    Name    string                `form:"name"`
    Age     int                   `form:"age"`
    Avatar  *multipart.FileHeader `form:"avatar"`
    Hobbies []string             `form:"hobbies[]"`
}

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

    // 1. 处理 JSON 请求
    r.POST("/json", func(c *gin.Context) {
        var req RegisterRequest
        // 绑定 JSON 数据
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        c.JSON(200, gin.H{
            "username": req.Username,
            "email": req.Email,
        })
    })

    // 2. 处理表单数据
    r.POST("/form", func(c *gin.Context) {
        var userInfo UserInfo
        // 绑定表单数据
        if err := c.ShouldBind(&userInfo); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }

        // 处理文件上传
        if userInfo.Avatar != nil {
            // 保存文件
            if err := c.SaveUploadedFile(userInfo.Avatar, "uploads/"+userInfo.Avatar.Filename); err != nil {
                c.JSON(500, gin.H{"error": "文件上传失败"})
                return
            }
        }

        c.JSON(200, userInfo)
    })

    // 3. 处理 URL 查询参数
    r.GET("/search", func(c *gin.Context) {
        // 获取查询参数
        query := c.Query("q")          // 必选参数
        page := c.DefaultQuery("page", "1") // 可选参数带默认值
        if exists := c.QueryArray("tags"); exists != nil {
            // 处理数组参数
            tags := c.QueryArray("tags")
            c.JSON(200, gin.H{
                "query": query,
                "page": page,
                "tags": tags,
            })
            return
        }
        c.JSON(200, gin.H{
            "query": query,
            "page": page,
        })
    })

    // 4. 处理路径参数
    r.GET("/users/:id/*action", func(c *gin.Context) {
        id := c.Param("id")
        action := c.Param("action")
        c.JSON(200, gin.H{
            "id": id,
            "action": action,
        })
    })

    // 5. 处理请求头
    r.GET("/headers", func(c *gin.Context) {
        // 获取特定请求头
        userAgent := c.GetHeader("User-Agent")
        // 获取所有请求头
        headers := c.Request.Header

        c.JSON(200, gin.H{
            "user_agent": userAgent,
            "headers": headers,
        })
    })

    // 6. 处理 Cookie
    r.GET("/cookies", func(c *gin.Context) {
        // 获取 cookie
        cookie, err := c.Cookie("session_id")
        if err != nil {
            // 设置 cookie
            c.SetCookie("session_id", "123456", 3600, "/", "localhost", false, true)
            cookie = "123456"
        }

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

    // 7. 处理原始数据
    r.POST("/raw", func(c *gin.Context) {
        // 读取原始数据
        data, err := c.GetRawData()
        if err != nil {
            c.JSON(400, gin.H{"error": "读取数据失败"})
            return
        }

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

    if err := r.Run(":8080"); err != nil {
        log.Fatal("启动服务器失败: ", err)
    }
}

5. 响应处理

package main

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

// 响应数据结构
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
}

// 用户数据结构
type User struct {
    ID    string `json:"id" xml:"id"`
    Name  string `json:"name" xml:"name"`
    Email string `json:"email" xml:"email"`
}

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

    // 1. JSON 响应
    r.GET("/json", func(c *gin.Context) {
        // 使用自定义响应结构
        c.JSON(http.StatusOK, Response{
            Code:    200,
            Message: "success",
            Data: User{
                ID:    "1",
                Name:  "张三",
                Email: "[email protected]",
            },
        })
    })

    // 2. XML 响应
    r.GET("/xml", func(c *gin.Context) {
        c.XML(http.StatusOK, User{
            ID:    "1",
            Name:  "张三",
            Email: "[email protected]",
        })
    })

    // 3. YAML 响应
    r.GET("/yaml", func(c *gin.Context) {
        c.YAML(http.StatusOK, gin.H{
            "name": "张三",
            "age":  20,
            "hobbies": []string{
                "reading",
                "music",
            },
        })
    })

    // 4. HTML 响应
    // 加载模板文件
    r.LoadHTMLGlob("templates/*")
    r.GET("/html", func(c *gin.Context) {
        // 渲染模板
        c.HTML(http.StatusOK, "index.html", gin.H{
            "title": "Gin HTML 示例",
            "content": "这是一个 HTML 页面",
        })
    })

    // 5. 文件下载
    r.GET("/download", func(c *gin.Context) {
        // 文件路径
        filePath := "files/example.pdf"
        // 获取文件名
        filename := filepath.Base(filePath)
        
        // 设置响应头,使浏览器下载文件
        c.Header("Content-Description", "File Transfer")
        c.Header("Content-Transfer-Encoding", "binary")
        c.Header("Content-Disposition", "attachment; filename="+filename)
        c.Header("Content-Type", "application/octet-stream")
        
        // 发送文件
        c.File(filePath)
    })

    // 6. 流式响应
    r.GET("/stream", func(c *gin.Context) {
        // 设置响应头
        c.Header("Transfer-Encoding", "chunked")
        c.Header("Content-Type", "text/event-stream")
        c.Header("Cache-Control", "no-cache")
        c.Header("Connection", "keep-alive")

        // 创建通道
        messageChan := make(chan string)

        // 在 goroutine 中发送数据
        go func() {
            defer close(messageChan)
            for i := 0; i < 5; i++ {
                messageChan <- fmt.Sprintf("message %d", i)
                time.Sleep(time.Second)
            }
        }()

        // 发送数据到客户端
        c.Stream(func(w io.Writer) bool {
            if msg, ok := <-messageChan; ok {
                c.SSEvent("message", msg)
                return true
            }
            return false
        })
    })

    // 7. 自定义响应
    r.GET("/custom", func(c *gin.Context) {
        // 设置自定义响应头
        c.Header("X-Custom-Header", "custom value")
        
        // 设置 Cookie
        c.SetCookie("session", "123456", 3600, "/", "localhost", false, true)
        
        // 自定义状态码和响应
        c.JSON(http.StatusOK, gin.H{
            "status": "success",
            "data": gin.H{
                "message": "这是自定义响应",
                "timestamp": time.Now().Unix(),
            },
        })
    })

    // 8. 错误响应处理
    r.GET("/error", func(c *gin.Context) {
        // 根据不同情况返回不同的错误响应
        err := someFunction()
        if err != nil {
            switch err.(type) {
            case *NotFoundError:
                c.JSON(http.StatusNotFound, Response{
                    Code:    404,
                    Message: err.Error(),
                })
            case *ValidationError:
                c.JSON(http.StatusBadRequest, Response{
                    Code:    400,
                    Message: err.Error(),
                })
            default:
                c.JSON(http.StatusInternalServerError, Response{
                    Code:    500,
                    Message: "服务器内部错误",
                })
            }
            return
        }

        c.JSON(http.StatusOK, Response{
            Code:    200,
            Message: "success",
        })
    })

    // 9. 重定向响应
    r.GET("/redirect", func(c *gin.Context) {
        // 临时重定向
        c.Redirect(http.StatusTemporaryRedirect, "/target")
    })

    // 10. 异步响应
    r.GET("/async", func(c *gin.Context) {
        // 复制上下文
        cCp := c.Copy()
        
        // 异步处理
        go func() {
            // 使用复制的上下文执行异步操作
            time.Sleep(5 * time.Second)
            log.Printf("异步操作完成: %s", cCp.Request.URL.Path)
        }()

        // 立即返回响应
        c.JSON(http.StatusOK, gin.H{
            "message": "异步操作已开始",
        })
    })

    if err := r.Run(":8080"); err != nil {
        log.Fatal("启动服务器失败: ", err)
    }
}

// 自定义错误类型
type NotFoundError struct {
    Message string
}

func (e *NotFoundError) Error() string {
    return e.Message
}

type ValidationError struct {
    Message string
}

func (e *ValidationError) Error() string {
    return e.Message
}

// 模拟业务函数
func someFunction() error {
    // 模拟业务逻辑
    return &ValidationError{Message: "验证失败"}
}

6. 中间件

package main

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

// 自定义日志格式
type LogFormatterParams struct {
    StatusCode  int
    Latency     time.Duration
    ClientIP    string
    Method      string
    Path        string
    ErrorMessage string
}

func main() {
    r := gin.New() // 创建不带中间件的引擎

    // 1. 全局中间件
    // 自定义日志中间件
    r.Use(LoggerMiddleware())
    // 恢复中间件
    r.Use(gin.Recovery())
    // CORS中间件
    r.Use(CORSMiddleware())

    // 2. 路由组中间件
    authorized := r.Group("/auth")
    authorized.Use(AuthMiddleware())
    {
        authorized.GET("/user", func(c *gin.Context) {
            // 从中间件获取用户信息
            user, exists := c.Get("user")
            if !exists {
                c.JSON(http.StatusUnauthorized, gin.H{"error": "未找到用户信息"})
                return
            }
            c.JSON(http.StatusOK, gin.H{"user": user})
        })
    }

    // 3. 单个路由中间件
    r.GET("/admin", AdminAuthRequired(), func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "管理员页面"})
    })

    // 4. 中间件链
    r.GET("/chain", 
        RateLimiter(),
        AuthMiddleware(),
        func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"message": "通过所有中间件"})
        },
    )

    r.Run(":8080")
}

// 日志中间件
func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 开始时间
        startTime := time.Now()

        // 处理请求
        c.Next()

        // 结束时间
        endTime := time.Now()
        // 执行时间
        latencyTime := endTime.Sub(startTime)

        // 请求方式
        reqMethod := c.Request.Method
        // 请求路由
        reqUri := c.Request.RequestURI
        // 状态码
        statusCode := c.Writer.Status()
        // 请求IP
        clientIP := c.ClientIP()

        // 日志格式
        log.Printf("| %3d | %13v | %15s | %s | %s |",
            statusCode,
            latencyTime,
            clientIP,
            reqMethod,
            reqUri,
        )

        // 如果有错误,记录错误信息
        for _, err := range c.Errors.Errors() {
            log.Printf("Error: %v", err)
        }
    }
}

// CORS中间件
func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    }
}

// 认证中间件
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 获取认证token
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证token"})
            c.Abort()
            return
        }

        // 验证token(这里简化处理)
        if token != "valid-token" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的token"})
            c.Abort()
            return
        }

        // 设置用户信息到上下文
        c.Set("user", gin.H{"id": 1, "name": "测试用户"})
        c.Next()
    }
}

// 管理员认证中间件
func AdminAuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 获取用户角色
        role := c.GetHeader("User-Role")
        if role != "admin" {
            c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
            c.Abort()
            return
        }
        c.Next()
    }
}

// 限流中间件
func RateLimiter() gin.HandlerFunc {
    // 创建令牌桶
    limiter := time.NewTicker(time.Second / 10) // 每秒10个请求
    return func(c *gin.Context) {
        select {
        case <-limiter.C:
            c.Next()
        default:
            c.JSON(http.StatusTooManyRequests, gin.H{"error": "请求过于频繁"})
            c.Abort()
        }
    }
}

// 错误处理中间件
func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                // 记录错误日志
                log.Printf("Panic: %v", err)
                
                // 返回500错误
                c.JSON(http.StatusInternalServerError, gin.H{
                    "error": "服务器内部错误",
                })
            }
        }()
        
        c.Next()

        // 处理请求过程中的错误
        if len(c.Errors) > 0 {
            c.JSON(http.StatusBadRequest, gin.H{
                "errors": c.Errors.Errors(),
            })
        }
    }
}

// 请求追踪中间件
func RequestTracer() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 生成请求ID
        requestID := fmt.Sprintf("%d", time.Now().UnixNano())
        
        // 设置请求ID到header
        c.Writer.Header().Set("X-Request-ID", requestID)
        
        // 设置到上下文
        c.Set("RequestID", requestID)
        
        // 记录请求开始
        log.Printf("Request started: %s %s", requestID, c.Request.URL.Path)
        
        c.Next()
        
        // 记录请求结束
        log.Printf("Request completed: %s %s %d", requestID, c.Request.URL.Path, c.Writer.Status())
    }
}

7. 参数验证

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
    "net/http"
    "time"
    "regexp"
)

// 用户注册请求
type RegisterRequest struct {
    Username  string `json:"username" binding:"required,min=3,max=32"`
    Password  string `json:"password" binding:"required,min=6,max=32"`
    Email     string `json:"email" binding:"required,email"`
    Age       int    `json:"age" binding:"required,gte=18,lte=130"`
    Phone     string `json:"phone" binding:"required,phone"`
    Birthday  string `json:"birthday" binding:"required,datetime=2006-01-02"`
    Gender    string `json:"gender" binding:"required,oneof=male female other"`
    Agreement bool   `json:"agreement" binding:"required,agreed"`
}

// 产品信息
type Product struct {
    ID        string    `json:"id" binding:"required,uuid"`
    Name      string    `json:"name" binding:"required,min=2"`
    Price     float64   `json:"price" binding:"required,gt=0"`
    Stock     int       `json:"stock" binding:"required,gte=0"`
    CreatedAt time.Time `json:"created_at" binding:"required,ltefield=UpdatedAt"`
    UpdatedAt time.Time `json:"updated_at" binding:"required"`
}

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

    // 注册自定义验证器
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // 手机号验证器
        v.RegisterValidation("phone", phoneValidator)
        // 同意协议验证器
        v.RegisterValidation("agreed", agreedValidator)
    }

    // 1. 基本验证
    r.POST("/register", func(c *gin.Context) {
        var req RegisterRequest
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "error": translateValidationError(err),
            })
            return
        }
        c.JSON(http.StatusOK, gin.H{"message": "注册成功"})
    })

    // 2. 嵌套结构验证
    r.POST("/products", func(c *gin.Context) {
        var product Product
        if err := c.ShouldBindJSON(&product); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "error": translateValidationError(err),
            })
            return
        }
        c.JSON(http.StatusOK, product)
    })

    // 3. 查询参数验证
    r.GET("/search", validateQueryParams(), func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "查询参数验证通过",
        })
    })

    r.Run(":8080")
}

// 手机号验证器
func phoneValidator(fl validator.FieldLevel) bool {
    phone := fl.Field().String()
    // 简单的手机号验证规则
    match, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
    return match
}

// 协议同意验证器
func agreedValidator(fl validator.FieldLevel) bool {
    return fl.Field().Bool()
}

// 查询参数验证中间件
func validateQueryParams() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 验证页码
        page := c.DefaultQuery("page", "1")
        if _, err := strconv.Atoi(page); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "页码必须是数字"})
            c.Abort()
            return
        }

        // 验证每页数量
        limit := c.DefaultQuery("limit", "10")
        if limitNum, err := strconv.Atoi(limit); err != nil || limitNum <= 0 || limitNum > 100 {
            c.JSON(http.StatusBadRequest, gin.H{"error": "每页数量必须在1-100之间"})
            c.Abort()
            return
        }

        c.Next()
    }
}

// 验证错误翻译
func translateValidationError(err error) map[string]string {
    errors := make(map[string]string)
    
    if validationErrors, ok := err.(validator.ValidationErrors); ok {
        for _, e := range validationErrors {
            // 这里可以根据需要翻译成中文错误信息
            switch e.Tag() {
            case "required":
                errors[e.Field()] = "此字段是必需的"
            case "email":
                errors[e.Field()] = "请输入有效的电子邮件地址"
            case "min":
                errors[e.Field()] = "长度不能小于" + e.Param()
            case "max":
                errors[e.Field()] = "长度不能大于" + e.Param()
            case "phone":
                errors[e.Field()] = "请输入有效的手机号码"
            default:
                errors[e.Field()] = "验证失败"
            }
        }
        return errors
    }
    
    errors["general"] = err.Error()
    return errors
}

8. 文件处理

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
    "path/filepath"
    "fmt"
    "os"
    "io"
    "strings"
    "mime/multipart"
    "crypto/md5"
    "encoding/hex"
)

// 文件上传配置
const (
    MaxFileSize = 10 << 20 // 10MB
    UploadDir   = "./uploads"
    AllowedExts = ".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx"
)

// 文件信息结构体
type FileInfo struct {
    Filename    string `json:"filename"`
    Size        int64  `json:"size"`
    ContentType string `json:"content_type"`
    MD5         string `json:"md5"`
    Path        string `json:"path"`
}

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

    // 设置文件上传大小限制
    r.MaxMultipartMemory = MaxFileSize

    // 创建上传目录
    if err := os.MkdirAll(UploadDir, 0755); err != nil {
        panic(err)
    }

    // 1. 单文件上传
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败"})
            return
        }

        // 验证文件
        if err := validateFile(file); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }

        // 生成文件信息
        fileInfo, err := saveFile(file)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"})
            return
        }

        c.JSON(http.StatusOK, fileInfo)
    })

    // 2. 多文件上传
    r.POST("/uploads", func(c *gin.Context) {
        form, err := c.MultipartForm()
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "获取文件失败"})
            return
        }

        files := form.File["files"]
        fileInfos := make([]FileInfo, 0)

        for _, file := range files {
            // 验证文件
            if err := validateFile(file); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
                return
            }

            // 保存文件
            fileInfo, err := saveFile(file)
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"})
                return
            }

            fileInfos = append(fileInfos, fileInfo)
        }

        c.JSON(http.StatusOK, fileInfos)
    })

    // 3. 文件下载
    r.GET("/download/:filename", func(c *gin.Context) {
        filename := c.Param("filename")
        filepath := filepath.Join(UploadDir, filename)

        // 检查文件是否存在
        if _, err := os.Stat(filepath); os.IsNotExist(err) {
            c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
            return
        }

        // 设置下载头
        c.Header("Content-Description", "File Transfer")
        c.Header("Content-Transfer-Encoding", "binary")
        c.Header("Content-Disposition", "attachment; filename="+filename)
        c.Header("Content-Type", "application/octet-stream")
        c.File(filepath)
    })

    // 4. 文件预览
    r.GET("/preview/:filename", func(c *gin.Context) {
        filename := c.Param("filename")
        filepath := filepath.Join(UploadDir, filename)

        // 检查文件是否存在
        if _, err := os.Stat(filepath); os.IsNotExist(err) {
            c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
            return
        }

        // 获取文件类型
        contentType := getContentType(filename)
        c.Header("Content-Type", contentType)
        c.File(filepath)
    })

    // 5. 文件删除
    r.DELETE("/files/:filename", func(c *gin.Context) {
        filename := c.Param("filename")
        filepath := filepath.Join(UploadDir, filename)

        // 检查文件是否存在
        if _, err := os.Stat(filepath); os.IsNotExist(err) {
            c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
            return
        }

        // 删除文件
        if err := os.Remove(filepath); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "文件删除失败"})
            return
        }

        c.JSON(http.StatusOK, gin.H{"message": "文件删除成功"})
    })

    r.Run(":8080")
}

// 验证文件
func validateFile(file *multipart.FileHeader) error {
    // 检查文件大小
    if file.Size > MaxFileSize {
        return fmt.Errorf("文件大小超过限制")
    }

    // 检查文件扩展名
    ext := strings.ToLower(filepath.Ext(file.Filename))
    if !strings.Contains(AllowedExts, ext) {
        return fmt.Errorf("不支持的文件类型")
    }

    return nil
}

// 保存文件
func saveFile(file *multipart.FileHeader) (FileInfo, error) {
    src, err := file.Open()
    if err != nil {
        return FileInfo{}, err
    }
    defer src.Close()

    // 计算MD5
    hash := md5.New()
    if _, err := io.Copy(hash, src); err != nil {
        return FileInfo{}, err
    }
    md5sum := hex.EncodeToString(hash.Sum(nil))

    // 重新打开文件
    src.Seek(0, 0)

    // 生成唯一文件名
    ext := filepath.Ext(file.Filename)
    newFilename := fmt.Sprintf("%s%s", md5sum, ext)
    dst := filepath.Join(UploadDir, newFilename)

    // 创建目标文件
    out, err := os.Create(dst)
    if err != nil {
        return FileInfo{}, err
    }
    defer out.Close()

    // 复制文件内容
    src.Seek(0, 0)
    if _, err = io.Copy(out, src); err != nil {
        return FileInfo{}, err
    }

    return FileInfo{
        Filename:    file.Filename,
        Size:        file.Size,
        ContentType: file.Header.Get("Content-Type"),
        MD5:         md5sum,
        Path:        dst,
    }, nil
}

// 获取文件Content-Type
func getContentType(filename string) string {
    ext := strings.ToLower(filepath.Ext(filename))
    switch ext {
    case ".jpg", ".jpeg":
        return "image/jpeg"
    case ".png":
        return "image/png"
    case ".gif":
        return "image/gif"
    case ".pdf":
        return "application/pdf"
    case ".doc":
        return "application/msword"
    case ".docx":
        return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    default:
        return "application/octet-stream"
    }
}

9. 数据库集成

package main

import (
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
    "gorm.io/driver/mysql"
    "time"
    "log"
)

// 数据库配置
const (
    DBUser     = "root"
    DBPassword = "password"
    DBHost     = "localhost"
    DBPort     = "3306"
    DBName     = "testdb"
)

// 用户模型
type User struct {
    ID        uint      `gorm:"primarykey" json:"id"`
    Username  string    `gorm:"size:32;uniqueIndex;not null" json:"username"`
    Email     string    `gorm:"size:128;uniqueIndex;not null" json:"email"`
    Password  string    `gorm:"size:128;not null" json:"-"` // 密码不返回给客户端
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

// 文章模型
type Article struct {
    ID        uint      `gorm:"primarykey" json:"id"`
    Title     string    `gorm:"size:128;not null" json:"title"`
    Content   string    `gorm:"type:text" json:"content"`
    UserID    uint      `json:"user_id"`
    User      User      `gorm:"foreignKey:UserID" json:"user"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

var db *gorm.DB

func main() {
    // 初始化数据库连接
    initDB()

    r := gin.Default()

    // 用户相关路由
    users := r.Group("/users")
    {
        users.POST("", createUser)
        users.GET("", listUsers)
        users.GET("/:id", getUser)
        users.PUT("/:id", updateUser)
        users.DELETE("/:id", deleteUser)
    }

    // 文章相关路由
    articles := r.Group("/articles")
    {
        articles.POST("", createArticle)
        articles.GET("", listArticles)
        articles.GET("/:id", getArticle)
        articles.PUT("/:id", updateArticle)
        articles.DELETE("/:id", deleteArticle)
    }

    r.Run(":8080")
}

// 初始化数据库
func initDB() {
    var err error
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        DBUser, DBPassword, DBHost, DBPort, DBName)
    
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }

    // 自动迁移
    db.AutoMigrate(&User{}, &Article{})
}

// 用户相关处理函数
func createUser(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // 创建用户
    if err := db.Create(&user).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "创建用户失败"})
        return
    }

    c.JSON(http.StatusCreated, user)
}

func listUsers(c *gin.Context) {
    var users []User
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))

    // 分页查询
    offset := (page - 1) * limit
    if err := db.Offset(offset).Limit(limit).Find(&users).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败"})
        return
    }

    c.JSON(http.StatusOK, users)
}

func getUser(c *gin.Context) {
    id := c.Param("id")
    var user User

    if err := db.First(&user, id).Error; err != nil {
        if err == gorm.ErrRecordNotFound {
            c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
            return
        }
        c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败"})
        return
    }

    c.JSON(http.StatusOK, user)
}

func updateUser(c *gin.Context) {
    id := c.Param("id")
    var user User

    // 检查用户是否存在
    if err := db.First(&user, id).Error; err != nil {
        if err == gorm.ErrRecordNotFound {
            c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
            return
        }
        c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败"})
        return
    }

    // 绑定更新数据
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // 更新用户
    if err := db.Save(&user).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "更新用户失败"})
        return
    }

    c.JSON(http.StatusOK, user)
}

func deleteUser(c *gin.Context) {
    id := c.Param("id")
    
    if err := db.Delete(&User{}, id).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "删除用户失败"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "用户已删除"})
}

// 文章相关处理函数
// ... 类似的 CRUD 操作 ...

10. 认证与授权

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v4"
    "golang.org/x/crypto/bcrypt"
    "gorm.io/gorm"
    "time"
    "errors"
    "strings"
)

// JWT配置
const (
    JWT_SECRET = "your-secret-key"
    JWT_EXPIRE = 24 * time.Hour
)

// 用户模型
type User struct {
    gorm.Model
    Username string `gorm:"uniqueIndex;not null" json:"username"`
    Password string `gorm:"not null" json:"-"`
    Email    string `gorm:"uniqueIndex;not null" json:"email"`
    Role     string `gorm:"default:user" json:"role"` // admin or user
}

// JWT Claims
type Claims struct {
    UserID uint   `json:"user_id"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

// 登录请求
type LoginRequest struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

// 注册请求
type RegisterRequest struct {
    Username string `json:"username" binding:"required,min=3,max=32"`
    Password string `json:"password" binding:"required,min=6"`
    Email    string `json:"email" binding:"required,email"`
}

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

    // 公开路由
    public := r.Group("/api")
    {
        public.POST("/register", register)
        public.POST("/login", login)
    }

    // 需要认证的路由
    authorized := r.Group("/api")
    authorized.Use(AuthMiddleware())
    {
        // 用户路由
        authorized.GET("/profile", getProfile)
        authorized.PUT("/profile", updateProfile)
        authorized.PUT("/password", changePassword)

        // 管理员路由
        admin := authorized.Group("/admin")
        admin.Use(AdminRequired())
        {
            admin.GET("/users", listUsers)
            admin.DELETE("/users/:id", deleteUser)
        }
    }

    r.Run(":8080")
}

// 注册处理
func register(c *gin.Context) {
    var req RegisterRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // 检查用户名是否已存在
    var existingUser User
    if err := db.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
        c.JSON(400, gin.H{"error": "用户名已存在"})
        return
    }

    // 加密密码
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
    if err != nil {
        c.JSON(500, gin.H{"error": "密码加密失败"})
        return
    }

    // 创建用户
    user := User{
        Username: req.Username,
        Password: string(hashedPassword),
        Email:    req.Email,
        Role:     "user", // 默认角色
    }

    if err := db.Create(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": "用户创建失败"})
        return
    }

    c.JSON(201, gin.H{"message": "注册成功"})
}

// 登录处理
func login(c *gin.Context) {
    var req LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // 查找用户
    var user User
    if err := db.Where("username = ?", req.Username).First(&user).Error; err != nil {
        c.JSON(401, gin.H{"error": "用户名或密码错误"})
        return
    }

    // 验证密码
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
        c.JSON(401, gin.H{"error": "用户名或密码错误"})
        return
    }

    // 生成Token
    token, err := generateToken(user)
    if err != nil {
        c.JSON(500, gin.H{"error": "Token生成失败"})
        return
    }

    c.JSON(200, gin.H{
        "token": token,
        "user": gin.H{
            "id":       user.ID,
            "username": user.Username,
            "email":    user.Email,
            "role":     user.Role,
        },
    })
}

// JWT认证中间件
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 获取Token
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(401, gin.H{"error": "未提供认证token"})
            c.Abort()
            return
        }

        // 解析Token
        tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
        claims, err := parseToken(tokenString)
        if err != nil {
            c.JSON(401, gin.H{"error": "无效的token"})
            c.Abort()
            return
        }

        // 将用户信息存储到上下文
        c.Set("userID", claims.UserID)
        c.Set("role", claims.Role)
        c.Next()
    }
}

// 管理员权限中间件
func AdminRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        role, exists := c.Get("role")
        if !exists || role != "admin" {
            c.JSON(403, gin.H{"error": "需要管理员权限"})
            c.Abort()
            return
        }
        c.Next()
    }
}

// 生成Token
func generateToken(user User) (string, error) {
    claims := Claims{
        UserID: user.ID,
        Role:   user.Role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(JWT_EXPIRE)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            NotBefore: jwt.NewNumericDate(time.Now()),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(JWT_SECRET))
}

// 解析Token
func parseToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return []byte(JWT_SECRET), nil
    })

    if err != nil {
        return nil, err
    }

    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }

    return nil, errors.New("invalid token")
}

// 获取个人资料
func getProfile(c *gin.Context) {
    userID, _ := c.Get("userID")

    var user User
    if err := db.First(&user, userID).Error; err != nil {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }

    c.JSON(200, gin.H{
        "id":       user.ID,
        "username": user.Username,
        "email":    user.Email,
        "role":     user.Role,
    })
}

// 更新个人资料
func updateProfile(c *gin.Context) {
    userID, _ := c.Get("userID")

    var user User
    if err := db.First(&user, userID).Error; err != nil {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }

    // 只允许更新某些字段
    type UpdateRequest struct {
        Email string `json:"email" binding:"omitempty,email"`
    }

    var req UpdateRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // 更新用户信息
    if req.Email != "" {
        user.Email = req.Email
    }

    if err := db.Save(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": "更新失败"})
        return
    }

    c.JSON(200, gin.H{
        "id":       user.ID,
        "username": user.Username,
        "email":    user.Email,
        "role":     user.Role,
    })
}

// 修改密码
func changePassword(c *gin.Context) {
    userID, _ := c.Get("userID")

    type PasswordRequest struct {
        OldPassword string `json:"old_password" binding:"required"`
        NewPassword string `json:"new_password" binding:"required,min=6"`
    }

    var req PasswordRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    var user User
    if err := db.First(&user, userID).Error; err != nil {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }

    // 验证旧密码
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
        c.JSON(400, gin.H{"error": "旧密码错误"})
        return
    }

    // 加密新密码
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
    if err != nil {
        c.JSON(500, gin.H{"error": "密码加密失败"})
        return
    }

    // 更新密码
    user.Password = string(hashedPassword)
    if err := db.Save(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": "密码更新失败"})
        return
    }

    c.JSON(200, gin.H{"message": "密码修改成功"})
}

这个认证与授权模块包含了:

  1. JWT token 的生成和验证
  2. 用户注册和登录
  3. 密码加密和验证
  4. 角色基础的访问控制(RBAC)
  5. 个人资料管理
  6. 密码修改功能
  7. 中间件实现的权限控制

主要特点:

  1. 使用 bcrypt 进行密码加密
  2. 使用 JWT 进行身份验证
  3. 实现了基本的角色权限控制
  4. 提供了完整的用户管理功能
  5. 包含了必要的安全措施
每日更新-免费小火箭账号
不要错过任何机会,探索最新的应用和游戏,就在我们的平台。
立即访问
最后修改:2024 年 12 月 27 日
如果觉得我的文章对你有用,请随意赞赏