外观
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")| 特性 | Gin | net/http |
|---|---|---|
| 路由 | r.GET, Group | ServeMux |
| JSON | c.JSON(200, obj) | 手动 Encode |
| 参数 | c.Param("id"), c.Query("page") | PathValue, URL.Query |
| Binding | c.ShouldBindJSON(&req) | json.Decoder |
| 中间件 | r.Use(mw) | 手动 wrap |
建议:练习项目先用 net/http;Project 01 可选用 Gin 提高效率。
3.2 JWT 结构
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature
─── Header ─── ───── Payload ───── ── Sig ──| 部分 | 内容 |
|---|---|
| Header | 算法(如 HS256) |
| Payload | claims(sub, exp, iat 等) |
| Signature | HMAC(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 到 context3.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 Gin | Vue / Express |
|---|---|---|
| 框架 | Gin(可选) | Express / 无(axios 直调) |
| JWT 库 | golang-jwt | jsonwebtoken |
| 存 token | 不涉及(服务端签发) | localStorage / cookie |
| 路由 | gin.RouterGroup | vue-router(前端) |
| 拦截器 | auth middleware | axios 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)关键差异:
- Go 负责 sign 和 verify;前端只存和发送
- JWT payload 可解码,只放 user id、role 等非敏感标识
- 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)
| Method | Path | 认证 | 说明 |
|---|---|---|---|
| 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 标准库选择
| 场景 | 推荐 |
|---|---|
| 学习、小 demo | net/http |
| Project 01、快速迭代 | Gin 或 chi |
| 极简服务 | 标准库 |
Gin 底层仍是 net/http,ListenAndServe 接收连接后交给 Gin 引擎路由。
9.2 JWT vs Session
| JWT | Session (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. 学习检查
完成练习后,确认你能回答:
- Gin 与 net/http 是什么关系?
- JWT 三部分分别是什么?哪部分不能放敏感数据?
- 为什么生产环境 JWT secret 必须从环境变量读取?
- API 为什么要加
/api/v1版本前缀? - 401 和 403 分别表示什么?
- 何时选 Gin,何时坚持 net/http?
- Vue axios 拦截器如何配合 Go JWT 中间件?
12. 下一步
| 已完成 | 下一知识点 | 关系 |
|---|---|---|
| Gin, JWT, API design | Phase 04 Database | API 接入 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学习阶段优先理解标准库实现;框架库作为加速工具。