Skip to content

Gin, JWT, API Design ​

Phase 03 — Go Web 涵盖:Gin · JWT · API design


1. 学习目标 ​

完成本知识点后,你应该能够:

  • 了解 Gin 框架的基本用法,并理解其与 net/http 的关系
  • 说明何时用标准库、何时引入 Gin
  • 理解 JWT 的结构(Header.Payload.Signature)和无状态认证流程
  • 在 Go 侧签发和验证 JWT
  • 应用 API 设计最佳实践(版本、命名、错误码、分页)
  • 规划 Project 01 — Go REST API 的整体结构

2. 为什么需要 ​

net/http 能完成所有工作,但路由分组、JSON binding、中间件链在大型项目中会显得繁琐。Gin 是 Go 最流行的 Web 框架之一,语法类似 Express,适合快速开发——但路线要求你先理解标准库,Gin 作为效率工具引入。

JWT 是前后端分离项目的常见认证方案。Vue 前端登录后存 token,后续请求带 Authorization: Bearer <token>;Go 后端验证签名和过期时间。你前端可能已用过 JWT,本节点重点在 Go 侧实现。

规范的 API design 让 Vue + Three.js 团队协作有明确契约:URL 怎么命名、错误怎么返回、分页怎么传参——减少联调时的口头对齐成本。


3. 核心概念 ​

3.1 Gin 简介 ​

go
r := gin.Default()
r.GET("/api/devices", listDevices)
r.POST("/api/devices", createDevice)
r.Run(":8080")
特性Ginnet/http
路由r.GET, GroupServeMux
JSONc.JSON(200, obj)手动 Encode
参数c.Param("id"), c.Query("page")PathValue, URL.Query
Bindingc.ShouldBindJSON(&req)json.Decoder
中间件r.Use(mw)手动 wrap

建议:练习项目先用 net/http;Project 01 可选用 Gin 提高效率。

3.2 JWT 结构 ​

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature
  ─── Header ───   ───── Payload ─────   ── Sig ──
部分内容
Header算法(如 HS256)
Payloadclaims(sub, exp, iat 等)
SignatureHMAC(header.payload, secret)

Payload 是 Base64 编码,不是加密——任何人可解码,不要放密码或密钥。

3.3 认证流程 ​

1. POST /api/login { username, password }
2. 服务端验证 → 返回 { token: "eyJ..." }
3. 前端存 localStorage / cookie
4. 后续请求 Header: Authorization: Bearer eyJ...
5. 中间件验证 token → 注入 user 到 context

3.4 API Design 要点 ​

  • 版本前缀:/api/v1/
  • 名词复数资源:/devices
  • 统一响应 envelope
  • 错误码与 HTTP status 对齐
  • 分页、过滤、排序约定
  • OpenAPI 文档(可选)

4. 基础语法 ​

Gin 示例 ​

见 workspace/phase-03/gin-jwt-api-design/gin_example.go(可选运行)。

go
package main

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

type Device struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

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

    api := r.Group("/api/v1")
    {
        api.GET("/devices", func(c *gin.Context) {
            c.JSON(http.StatusOK, []Device{
                {ID: "AGV-001", Name: "Alpha"},
            })
        })
        api.GET("/devices/:id", func(c *gin.Context) {
            id := c.Param("id")
            c.JSON(http.StatusOK, Device{ID: id, Name: "Demo"})
        })
    }

    r.Run(":8080")
}

JWT 示例(golang-jwt) ​

go
import (
    "time"
    jwt "github.com/golang-jwt/jwt/v5"
)

var secret = []byte("dev-secret-change-in-production")

func signToken(userID string) (string, error) {
    claims := jwt.MapClaims{
        "sub": userID,
        "exp": time.Now().Add(24 * time.Hour).Unix(),
        "iat": time.Now().Unix(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(secret)
}

func verifyToken(tokenStr string) (string, error) {
    token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (any, error) {
        return secret, nil
    })
    if err != nil || !token.Valid {
        return "", err
    }
    claims := token.Claims.(jwt.MapClaims)
    return claims["sub"].(string), nil
}

Auth 中间件(Gin) ​

go
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        auth := c.GetHeader("Authorization")
        if len(auth) < 8 || auth[:7] != "Bearer " {
            c.AbortWithStatusJSON(401, gin.H{"message": "unauthorized"})
            return
        }
        userID, err := verifyToken(auth[7:])
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"message": "invalid token"})
            return
        }
        c.Set("userID", userID)
        c.Next()
    }
}

5. 代码解析 ​

go
api := r.Group("/api/v1")

路由分组:共享前缀和中间件(如 auth)。类似 Express Router()。

go
c.ShouldBindJSON(&req)

Gin 自动解析 JSON 到 struct,失败返回 400。底层仍是 json + reflection。

go
jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

HS256 对称加密:签发和验证用同一 secret。生产环境 secret 放环境变量;多服务场景可考虑 RSA。

go
"exp": time.Now().Add(24 * time.Hour).Unix()

过期时间必须校验,否则 token 永久有效。

go
c.AbortWithStatusJSON(401, ...)

认证失败时 Abort 阻止后续 handler 执行。


6. JavaScript / TypeScript 对比 ​

概念Go GinVue / Express
框架Gin(可选)Express / 无(axios 直调)
JWT 库golang-jwtjsonwebtoken
存 token不涉及(服务端签发)localStorage / cookie
路由gin.RouterGroupvue-router(前端)
拦截器auth middlewareaxios interceptors

Vue axios 示例:

typescript
// 请求拦截器
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

// 登录
const { data } = await axios.post('/api/v1/login', { username, password })
localStorage.setItem('token', data.token)

关键差异:

  1. Go 负责 sign 和 verify;前端只存和发送
  2. JWT payload 可解码,只放 user id、role 等非敏感标识
  3. Gin 是第三方依赖;net/http 是标准库

7. 常见错误 ​

错误 1:过早引入 Gin ​

未理解 net/http 就直接用 Gin,遇到问题时不知底层行为。

错误 2:JWT secret 硬编码 ​

go
var secret = []byte("123456")  // ❌ 提交到 git
// ✅ os.Getenv("JWT_SECRET")

错误 3:不验证 exp ​

只 parse 不检查 token.Valid 和 exp claim。

错误 4:在 JWT 存敏感信息 ​

password、apiKey 绝不能进 payload。

错误 5:API 无版本 ​

后续 breaking change 无法平滑迁移。用 /api/v1/ 前缀。

错误 6:401 与 403 混用 ​

  • 401 Unauthorized:未登录 / token 无效
  • 403 Forbidden:已认证但无权限

8. 实际应用 ​

Project 01 结构建议 ​

project-01/
├── cmd/api/main.go
├── internal/
│   ├── handler/      # HTTP handlers(net/http 或 Gin)
│   ├── middleware/   # auth, cors, logging
│   ├── model/        # Device, Scene struct
│   ├── service/      # 业务逻辑
│   └── store/        # 内存 / 后续 PostgreSQL
└── api/API.md        # 接口文档

数字孪生 API 认证 ​

  • 管理端(Vue admin):JWT 登录后 CRUD 设备
  • 大屏只读:可选 API Key 或公开 read-only 端点
  • WebSocket(Phase 06):连接时 query token 或首条 auth 消息

统一响应 Envelope ​

go
type Envelope struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    any    `json:"data,omitempty"`
}

Vue 响应拦截器统一处理 code !== 0 的情况。

REST 端点总览(Project 01) ​

MethodPath认证说明
POST/api/v1/login否登录获 token
GET/api/v1/profile是当前用户
GET/api/v1/devices是设备列表
POST/api/v1/devices是创建设备
GET/api/v1/devices/:id是设备详情

9. 深入理解 ​

9.1 Gin vs 标准库选择 ​

场景推荐
学习、小 demonet/http
Project 01、快速迭代Gin 或 chi
极简服务标准库

Gin 底层仍是 net/http,ListenAndServe 接收连接后交给 Gin 引擎路由。

9.2 JWT vs Session ​

JWTSession (Redis)
状态无状态服务端存 session
撤销难(需黑名单)易 delete session
扩展多实例无需共享需 Redis 共享

初学 JWT 足够;Phase 05 Redis 后可对比 Session 方案。

9.3 Refresh Token ​

Access token 短过期(15min)+ refresh token 长过期(7d),降低 token 泄露风险。Project 01 可先做单 token,后续扩展。

9.4 API 文档 ​

手写 API.md 或使用 Swagger(swaggo)。与 Vue 团队协作时文档是契约——字段名、状态码、错误格式必须一致。


10. 练习 ​

详细题目见 exercises/phase-03-web/04-gin-jwt-api-design.md。

Level 1 — 基础 ​

练习 1.1:用 Gin 实现 GET /api/v1/health。

练习 1.2:signToken 和 verifyToken roundtrip 测试。

练习 1.3:编写 API.md 描述 3 个 Device 端点。

Level 2 — 应用 ​

练习 2.1:Gin auth middleware:无 token 返回 401。

练习 2.2:POST /login 返回 JWT,GET /profile 需 token。

练习 2.3:统一 Envelope 响应 helper。

Level 3 — 综合 ​

练习 3.1:net/http 版 auth middleware + JWT,对比 Gin 版。

练习 3.2:完整 API 设计文档:devices CRUD + auth + 错误码表。

Level 4 — 项目实践 ​

练习 4.1:在 workspace/phase-03/gin-jwt-api-design/ 启动 Project 01 骨架:登录 + 受保护的 Device API(内存 store,Gin 或 net/http 二选一)。


11. 学习检查 ​

完成练习后,确认你能回答:

  1. Gin 与 net/http 是什么关系?
  2. JWT 三部分分别是什么?哪部分不能放敏感数据?
  3. 为什么生产环境 JWT secret 必须从环境变量读取?
  4. API 为什么要加 /api/v1 版本前缀?
  5. 401 和 403 分别表示什么?
  6. 何时选 Gin,何时坚持 net/http?
  7. Vue axios 拦截器如何配合 Go JWT 中间件?

12. 下一步 ​

已完成下一知识点关系
Gin, JWT, API designPhase 04 DatabaseAPI 接入 PostgreSQL
Project 01完整 REST API 项目

Phase 03 完成!开始 Project 01,并准备 PostgreSQL 持久化。


依赖说明 ​

Gin 和 golang-jwt 为第三方库。练习中若使用:

bash
go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5

学习阶段优先理解标准库实现;框架库作为加速工具。


学习导航 ​

上一篇:Middleware、Validation、CORS · 对应练习 · 下一篇:SQL 基础与表设计