Go基础(Gin)

go mod init my-gin-app 初始化一个 Go 项目,创建一个go.mod文件

go mod tidy 自动整理项目依赖,确保go.modgo.sum文件与代码实际使用的依赖一致

  • go mod init:创建项目的 "依赖说明书"。
  • go mod tidy:整理 "说明书",让依赖清单精确匹配代码。

代码基础

Go 复制代码
package main

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

func main() {
    // 创建默认引擎,包含日志和恢复中间件
    r := gin.Default()

    // 定义根路径的 GET 请求
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "Hello World",
        })
    })

    // 启动服务器,监听 8080 端口
    r.Run() // 默认监听 :8080
}

参数查询代码

Go 复制代码
package main

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

func main() {
    // 创建默认引擎,包含日志和恢复中间件
    r := gin.Default()

    // 处理带参数的 URL:/users/123
	r.GET("/users/:id", func(c *gin.Context) {
		id := c.Param("id")
		c.JSON(200, gin.H{
			"user_id": id,
		})
	})

    // 启动服务器,监听 8080 端口
    r.Run() // 默认监听 :8080
}

处理 POST 请求并验证账号密码

Go 复制代码
package main

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

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

    // 处理 POST 请求,验证账号密码
    r.POST("/login", func(c *gin.Context) {
        // 定义请求体结构
        type LoginRequest struct {
            Username string `json:"username" binding:"required"`
            Password string `json:"password" binding:"required"`
        }

        var req LoginRequest
        // 绑定 JSON 请求体并验证
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }

        // 验证账号密码
        if req.Username == "123" && req.Password == "123" {
            c.JSON(200, gin.H{"message": "验证正确"})
        } else {
            c.JSON(401, gin.H{"message": "账号或密码错误"})
        }
    })

    r.Run()
}
  • json:"password" 就是这个「翻译器」。
  • 当你收到一个 JSON 数据(比如 {"password": "123"}),Gin 会自动把 JSON 里的 password 字段,对应到 Go 代码里的 Password 变量。
  • binding:"required" 表示这个字段「必须存在」,如果 JSON 里没有这个字段,就会报错。

ShouldBindJSON 会做两件事

  1. 翻译 :把 JSON 里的字段名(比如 password),对应到 Go 结构体的变量(比如 Password)。
  2. 检查规则 :检查每个字段是否符合 binding:"required" 等规则。

JWT

Go 复制代码
package main

import (
    "github.com/dgrijalva/jwt-go"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

// 密钥(生产环境应从配置文件或环境变量获取)
var jwtKey = []byte("your-secret-key")

// Token中包含的用户信息
type Claims struct {
    Username string `json:"username"`
    jwt.StandardClaims
}

// 生成JWT Token
func generateToken(username string) (string, error) {
    expirationTime := time.Now().Add(1 * time.Hour)
    claims := &Claims{
        Username: username,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: expirationTime.Unix(),
            IssuedAt:  time.Now().Unix(),
            Issuer:    "your-app",
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtKey)
}

// 验证JWT Token
func validateToken(tokenStr string) (*Claims, error) {
    claims := &Claims{}
    token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
        return jwtKey, nil
    })
    if err != nil || !token.Valid {
        return nil, err
    }
    return claims, nil
}

// 登录接口 - 验证用户并生成Token
func loginHandler(c *gin.Context) {
    type LoginRequest struct {
        Username string `json:"username" binding:"required"`
        Password string `json:"password" binding:"required"`
    }
    var req LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    if req.Username != "admin" || req.Password != "password" {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的凭证"})
        return
    }
    token, err := generateToken(req.Username)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "生成Token失败"})
        return
    }
    c.JSON(http.StatusOK, gin.H{
        "token": token,
    })
}

// 认证中间件 - 检查请求中的Token
func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenStr := c.GetHeader("Authorization")
        if tokenStr == "" || len(tokenStr) < 7 || tokenStr[:7] != "Bearer " {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少或格式错误的Token"})
            c.Abort()
            return
        }
        tokenStr = tokenStr[7:]
        claims, err := validateToken(tokenStr)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的Token"})
            c.Abort()
            return
        }
        c.Set("username", claims.Username)
        c.Next()
    }
}

// 受保护的接口 - 需要有效的Token才能访问
func protectedHandler(c *gin.Context) {
    username := c.MustGet("username").(string)
    c.JSON(http.StatusOK, gin.H{
        "message": "欢迎回来," + username,
        "data":    "这是受保护的数据",
    })
}

func main() {
    r := gin.Default()
    r.POST("/login", loginHandler)
    
    authGroup := r.Group("/api")
    authGroup.Use(authMiddleware())
    {
        authGroup.GET("/protected", protectedHandler)
    }
    
    r.Run()
}

Apipost测试

Go 复制代码
curl -X POST http://localhost:8080/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "password"}'

响应

Go 复制代码
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjk5OTk5OTk5LCJpYXQiOjE2OTk5OTk2OTksImlzcyI6InlvdXItYXBwIn0.abcdefghijklmnopqrstuvwxyz123456"
}
然后
Go 复制代码
curl -X GET http://localhost:8080/api/protected \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

var jwtKey = []byte("your-secret-key") your-secret-key 可以随便写吗?

❶ 开发测试时:可以写简单的,但要注意
❷ 正式环境:必须用复杂、安全的密码
    • 比如用 your-secret-key123456,方便测试代码是否正常运行。
    • 但千万不要在正式环境用这种简单密码!就像日记本用 "123456" 当密码,很容易被别人破解。
    • 规则
      • 长度至少 16 位,包含字母、数字、特殊符号(比如 !@#$%^&*())。
      • 不能是任何人都能猜到的内容(比如生日、名字)。

在 JWT 验证流程中,用密钥 "解锁" Token 的核心代码是 jwt.ParseWithClaims 函数。这个函数会验证 Token 的签名,并解析出其中的内容(Claims)。

Go 复制代码
// 定义Claims结构体用于存储解析结果
claims := &Claims{}

// 用密钥"解锁"Token(验证签名并解析内容)
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
    return jwtKey, nil  // 返回密钥,用于验证签名
})

以下代码模拟了一个被篡改的 Token 的验证过程:

Go 复制代码
package main

import (
    "fmt"
    "github.com/dgrijalva/jwt-go"
)

var jwtKey = []byte("your-secret-key")

func main() {
    // 1. 生成一个合法的Token
    claims := jwt.MapClaims{
        "username": "alice",
        "exp":      jwt.TimeFunc().Add(time.Hour * 24).Unix(), // 24小时后过期
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenStr, _ := token.SignedString(jwtKey)
    fmt.Println("原始Token:", tokenStr)

    // 2. 模拟篡改Token(修改Payload中的username)
    // 注意:实际中无法直接修改,这里仅为演示验证失败的效果
    tamperedTokenStr := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MTY5OTQwNjQwMH0.abc123" // 伪造的Token

    // 3. 验证原始Token(正常情况)
    validToken, validErr := jwt.ParseWithClaims(tokenStr, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtKey, nil
    })
    fmt.Println("原始Token验证结果:", validToken.Valid, validErr)

    // 4. 验证篡改后的Token
    tamperedToken, tamperedErr := jwt.ParseWithClaims(tamperedTokenStr, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtKey, nil
    })
    fmt.Println("篡改Token验证结果:", tamperedToken.Valid, tamperedErr)
}

输出结果

Go 复制代码
原始Token验证结果: true <nil>
篡改Token验证结果: false signature is invalid

从 Token 中获取 username 的核心代码

1. 定义 Claims 结构体

首先需要定义一个结构体,用于存储 Token 中的数据:

Go 复制代码
type Claims struct {
    Username string `json:"username"`  // 对应Token中的username字段
    jwt.StandardClaims                  // 包含标准字段(如过期时间、签发者等)
}
2. 验证并解析 Token
Go 复制代码
// 验证Token并获取Claims
claims, err := validateToken(tokenStr)
if err != nil {
    // 处理验证失败的情况
    log.Fatal("Token验证失败:", err)
}

// 从Claims中获取username
username := claims.Username
fmt.Println("用户名:", username)
3. 完整的验证函数示例
Go 复制代码
func validateToken(tokenStr string) (*Claims, error) {
    claims := &Claims{}
    
    token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
        // 验证签名方法
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("无效的签名方法: %v", token.Header["alg"])
        }
        
        // 返回密钥
        return jwtKey, nil
    })
    
    // 检查验证错误
    if err != nil {
        return nil, err
    }
    
    // 检查Token有效性
    if !token.Valid {
        return nil, errors.New("无效的Token")
    }
    
    return claims, nil
}