gin 框架
gin 框架介绍
一、Gin 是什么?
Gin 是 Go 语言的一个 Web 框架,主要用于开发:
-
Web 网站
-
RESTful API
-
后端服务
-
微服务 HTTP 接口
-
前后端分离项目的后端
-
网关/API Gateway
Go 原生其实就可以写 Web 服务:
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "pong")
})
http.ListenAndServe(":8080", nil)
但是项目一复杂,就会遇到:
-
路由管理
-
参数解析
-
JSON 返回
-
中间件
-
请求上下文
-
错误处理
-
参数校验
-
日志
-
JWT 鉴权
所以 Gin 在 Go 原生 net/http 的基础上,提供了一套更加方便的开发方式。
可以粗略理解:
Go
│
├── net/http ← Go 原生 HTTP 能力
│
└── Gin ← 在 net/http 基础上的 Web 框架
二、Gin 最核心的东西:路由
你之前看到的代码:
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
这其实就是 Gin 最核心的使用方式。
拆开:
r.GET(
"/ping",
func(c *gin.Context) {
...
},
)
意思是:
当客户端使用 GET 请求访问
/ping时,就执行这个函数。
所以:
浏览器
│
│ GET /ping
↓
Gin
│
│ 匹配 /ping
↓
func(c *gin.Context) {
...
}
│
↓
返回 JSON
这里你之前困惑的:
为什么传函数,而不是执行函数?
就是因为:
func(c *gin.Context) {
...
}
是一个函数值。
你是在告诉 Gin:
“以后有人访问
/ping,你帮我调用这个函数。”
而不是现在就调用。
如果写成:
r.GET("/ping", handler())
那就是现在立刻执行 handler(),这和 Gin 需要的逻辑完全不同。
三、Gin 的 Context 非常重要
Gin 中你会大量看到:
func(c *gin.Context) {
}
这个 c 就是:
*gin.Context
可以把它理解成:
一次 HTTP 请求的“上下文对象”。
它里面包含了大量这次请求相关的信息。
例如:
1. 获取 URL 参数
请求:
GET /user/123
路由:
r.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id")
fmt.Println(id)
})
得到:
123
2. 获取 Query 参数
请求:
GET /user?id=123
代码:
id := c.Query("id")
得到:
123
3. 获取 POST JSON
客户端:
{
"username": "张三",
"age": 20
}
Go:
type User struct {
Username string `json:"username"`
Age int `json:"age"`
}
var user User
err := c.ShouldBindJSON(&user)
然后:
user.Username user.Age
就可以拿到数据。
四、Gin 返回 JSON 非常方便
你之前看到:
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
返回:
{
"message": "pong"
}
其中:
gin.H
本质上就是:
map[string]interface{}
所以:
gin.H{
"message": "pong",
}
可以理解成:
map[string]interface{}{
"message": "pong",
}
五、Gin 的 Middleware(中间件)
这是 Gin 非常重要的一个概念。
例如你有:
客户端
↓
日志
↓
JWT鉴权
↓
权限检查
↓
业务 Handler
↓
数据库
这些“中间环节”就是 Middleware。
例如:
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("请求进入")
c.Next()
fmt.Println("请求结束")
}
}
然后:
r.Use(Logger())
那么请求:
GET /user
就可能变成:
Logger
↓
User Handler
↓
Logger
这和你前面学习的 **gRPC Interceptor(拦截器)**其实非常像。
你可以建立这样的对应关系:
Gin
Middleware
↓
拦截 HTTP 请求
↓
执行 Handler
↓
返回响应
而 gRPC:
Interceptor
↓
拦截 RPC 请求
↓
执行 RPC 方法
↓
返回响应
所以现在学 Gin,实际上也会帮助理解中间件、请求生命周期、拦截器这些后端核心概念。
六、Gin 的路由分组
实际项目不会把所有接口都写在一起。
比如:
api := r.Group("/api")
{
api.GET("/user", getUser)
api.POST("/user", createUser)
}
那么:
GET /api/user
POST /api/user
都属于 /api。
还可以继续分:
api := r.Group("/api")
v1 := api.Group("/v1")
v1.GET("/user", getUser)
v1.POST("/user", createUser)
最终:
/api/v1/user
这在大型项目里非常常见。
七、Gin 一个完整的小例子
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
})
})
r.Run(":8080")
}
启动:
服务器监听 8080
访问:
GET /ping
得到:
{
"message": "pong"
}
访问:
GET /user/123
得到:
{
"id": "123"
}
八、Gin 项目一般怎么组织?
实际项目一般不会把所有东西塞进 main.go。
例如:
mall/
│
├── main.go
│
├── router/
│ └── router.go
│
├── handler/
│ ├── user.go
│ └── product.go
│
├── service/
│ ├── user.go
│ └── product.go
│
├── model/
│ ├── user.go
│ └── product.go
│
├── middleware/
│ ├── auth.go
│ └── logger.go
│
└── config/
└── config.go
大概形成:
HTTP请求 ↓ Gin Router ↓ Middleware ↓ Handler ↓ Service ↓ Repository / DAO ↓ Database
这就是你以后写真正业务系统时非常重要的一条链路。
九、Gin 和 gRPC 是什么关系?
这个特别值得你现在理解。
Gin 和 gRPC 并不是竞争关系。
它们解决的问题不同:
后端系统
│
┌───────────┴───────────┐
↓ ↓
Gin gRPC
│ │
HTTP/REST RPC通信
│ │
前端/浏览器 微服务之间
比如你的电商系统:
Vue │ │ HTTP ↓ Gin API │ │ gRPC ↓ 商品服务 │ ├── 商品数据库 │ └── Redis
所以完全可以:
Vue ↓ Gin ↓ gRPC ↓ User Service Product Service Order Service
这其实就是非常典型的:
Gin + gRPC + Redis + MySQL + Kubernetes
的微服务技术栈。
十、现在最应该掌握 Gin 的哪些东西?
如果正在从 Go 基础往后端开发走,建议按照这个顺序:
① HTTP 基础
↓
② Gin 路由
↓
③ gin.Context
↓
④ GET / POST 参数
↓
⑤ JSON 请求与响应
↓
⑥ 路由分组
↓
⑦ Middleware
↓
⑧ 参数校验
↓
⑨ JWT 鉴权
↓
⑩ 文件上传
↓
⑪ 全局错误处理
↓
⑫ Gin + MySQL
↓
⑬ Gin + Redis
↓
⑭ Gin + gRPC
↓
⑮ Gin + Kubernetes
你现在尤其应该把 Handler → Middleware → Context → Router 这几个概念吃透。
因为你前面刚好在问:
“Gin 为什么传匿名函数?”
其实一旦把 “注册路由 = 把函数交给框架,等请求到来后由框架调用” 这个思想真正理解了,Gin 后面的很多东西都会突然顺起来。
gin 的入门
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func pong(c *gin.Context){
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
}
func main() {
//实例化一个gin的server对象
r := gin.Default()
r.GET("/ping", pong)
r.Run(":8083") // listen and serve on 0.0.0.0:8080
}
启动
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.pong (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
[GIN] 2026/08/28 - 13:01:30 | 200 | 0s | 127.0.0.1 | GET "/ping"
gin 的路由分组
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func goodsList(c *gin.Context){
c.JSON(http.StatusOK,gin.H{
"name":"goodsList",
})
}
func goodsDetail(c *gin.Context){}
func createGoods(c *gin.Context){}
func main() {
router := gin.Default()
goodsGroup:=router.Group("/goods")//路由分组
//路由分组;{}这对括号可有可无
{
goodsGroup.GET("/list",goodsList)
goodsGroup.GET("/1",goodsDetail)
goodsGroup.POST("/add",createGoods)
}
// router.GET("/goods/list",goodsList)
// router.GET("/goods/1",goodsDetail)
// router.POST("/goods/add",createGoods)
// v1:= router.Group("/v1")
// {
// v1.POST("/login",loginEndpoint)
// v1.POST("/submit",submitEndpoint)
// v1.POST("/read",readEndpoint)
// }
// v2:= router.Group("/v2")
// {
// v2.POST("/login",loginEndpoint)
// v2.POST("/submit",submitEndpoint)
// v2.POST("/read",readEndpoint)
// }
router.Run(":8081")//执行端口;如果不指定默认是8080
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch01\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /goods/list --> main.goodsList (3 handlers)
[GIN-debug] GET /goods/1 --> main.goodsDetail (3 handlers)
[GIN-debug] POST /goods/add --> main.createGoods (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8081
[GIN] 2026/08/28 - 14:13:23 | 404 | 0s | 127.0.0.1 | GET "/list"
[GIN] 2026/08/28 - 14:13:24 | 404 | 0s | 127.0.0.1 | GET "/favicon.ico"
[GIN] 2026/08/28 - 14:13:54 | 200 | 0s | 127.0.0.1 | GET "/goods/list"
gin 处理带参数的url变量
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func goodsList(c *gin.Context){
c.JSON(http.StatusOK,gin.H{
"name":"goodsList",
})
}
func goodsDetail(c *gin.Context){
id:=c.Param("id")//获取变量的值
c.JSON(http.StatusOK,gin.H{
"name":id,
})
}
func createGoods(c *gin.Context){}
func main() {
router := gin.Default()
goodsGroup:=router.Group("/goods")//路由分组
//路由分组;{}这对括号可有可无
{
goodsGroup.GET("/list",goodsList)
goodsGroup.GET("/:id",goodsDetail)//获取变量匹配
goodsGroup.POST("/add",createGoods)
}
// router.GET("/goods/list",goodsList)
// router.GET("/goods/1",goodsDetail)
// router.POST("/goods/add",createGoods)
// v1:= router.Group("/v1")
// {
// v1.POST("/login",loginEndpoint)
// v1.POST("/submit",submitEndpoint)
// v1.POST("/read",readEndpoint)
// }
// v2:= router.Group("/v2")
// {
// v2.POST("/login",loginEndpoint)
// v2.POST("/submit",submitEndpoint)
// v2.POST("/read",readEndpoint)
// }
router.Run(":8081")//执行端口;如果不指定默认是8080
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch01\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /goods/list --> main.goodsList (3 handlers)
[GIN-debug] GET /goods/:id --> main.goodsDetail (3 handlers)
[GIN-debug] POST /goods/add --> main.createGoods (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8081
[GIN] 2026/08/28 - 15:49:04 | 200 | 0s | 127.0.0.1 | GET "/goods/6"
[GIN] 2026/08/28 - 15:49:14 | 200 | 0s | 127.0.0.1 | GET "/goods/a"
[GIN] 2026/08/28 - 15:49:22 | 200 | 0s | 127.0.0.1 | GET "/goods/list"
[GIN] 2026/08/28 - 15:49:29 | 200 | 0s | 127.0.0.1 | GET "/goods/a"
获取url 多个变量
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func goodsList(c *gin.Context){
c.JSON(http.StatusOK,gin.H{
"name":"goodsList",
})
}
func goodsDetail(c *gin.Context){
id:=c.Param("id")
action:=c.Param("action")//多个变量
c.JSON(http.StatusOK,gin.H{
"name":id,
"action":action,
})
}
func createGoods(c *gin.Context){}
func main() {
router := gin.Default()
goodsGroup:=router.Group("/goods")//路由分组
//路由分组;{}这对括号可有可无
{
goodsGroup.GET("/list",goodsList)
goodsGroup.GET("/:id/:action",goodsDetail) //多个变量
goodsGroup.POST("/add",createGoods)
}
// router.GET("/goods/list",goodsList)
// router.GET("/goods/1",goodsDetail)
// router.POST("/goods/add",createGoods)
// v1:= router.Group("/v1")
// {
// v1.POST("/login",loginEndpoint)
// v1.POST("/submit",submitEndpoint)
// v1.POST("/read",readEndpoint)
// }
// v2:= router.Group("/v2")
// {
// v2.POST("/login",loginEndpoint)
// v2.POST("/submit",submitEndpoint)
// v2.POST("/read",readEndpoint)
// }
router.Run(":8081")//执行端口;如果不指定默认是8080
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch01\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /goods/list --> main.goodsList (3 handlers)
[GIN-debug] GET /goods/:id/:action --> main.goodsDetail (3 handlers)
[GIN-debug] POST /goods/add --> main.createGoods (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8081
[GIN] 2026/08/28 - 16:01:32 | 200 | 0s | 127.0.0.1 | GET "/goods/a/d"
url 里的* 使用(前后匹配所有)
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func goodsList(c *gin.Context){
c.JSON(http.StatusOK,gin.H{
"name":"goodsList",
})
}
func goodsDetail(c *gin.Context){
id:=c.Param("id")
action:=c.Param("action")
c.JSON(http.StatusOK,gin.H{
"name":id,
"action":action,
})
}
func createGoods(c *gin.Context){}
func main() {
router := gin.Default()
goodsGroup:=router.Group("/goods")//路由分组
//路由分组;{}这对括号可有可无
{
goodsGroup.GET("/list",goodsList)
goodsGroup.GET("/:id/*action",goodsDetail)
goodsGroup.POST("/add",createGoods)
}
router.Run(":8081")//执行端口;如果不指定默认是8080
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch01\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /goods/list --> main.goodsList (3 handlers)
[GIN-debug] GET /goods/:id/*action --> main.goodsDetail (3 handlers)
[GIN-debug] POST /goods/add --> main.createGoods (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8081
[GIN] 2026/08/28 - 16:14:24 | 200 | 0s | 127.0.0.1 | GET "/goods/a/d/u"
[GIN] 2026/08/28 - 16:14:48 | 200 | 0s | 127.0.0.1 | GET "/goods/a/d/u"
浏览器效果

强约定数字
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Person struct{
ID string `uri:"id" binding:"required,uuid"`//强约定uuid
Name string `uri:"name" binding:"required"`
}
func main() {
router := gin.Default()
router.GET("/:name/:id",func(c *gin.Context) {
var person Person
if err:= c.ShouldBindUri(&person);err!=nil{
c.Status(404)
return
}
c.JSON(http.StatusOK,gin.H{
"name":person.Name,
"id":person.ID,
})
})
router.Run(":8015")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch03\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /:name/:id --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8015
[GIN] 2026/08/28 - 17:22:11 | 404 | 0s | 127.0.0.1 | GET "/goods/6"
[GIN] 2026/08/28 - 17:24:13 | 200 | 0s | 127.0.0.1 | GET "/goods/ccce7edd-329b-4f12-bee0-92566b51d833"
[GIN] 2026/08/28 - 17:24:13 | 404 | 0s | 127.0.0.1 | GET "/favicon.ico"
id 那边改成int 类型数字
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Person struct{
ID int `uri:"id" binding:"required"`
Name string `uri:"name" binding:"required"`
}
func main() {
router := gin.Default()
router.GET("/:name/:id",func(c *gin.Context) {
var person Person
if err:= c.ShouldBindUri(&person);err!=nil{
c.Status(404)
return
}
c.JSON(http.StatusOK,gin.H{
"name":person.Name,
"id":person.ID,
})
})
router.Run(":8015")
}
启动测试
PS D:\golang\goproject\src\lnhgo> go run gin\ch03\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /:name/:id --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8015
[GIN] 2026/08/28 - 17:32:06 | 200 | 551.7µs | 127.0.0.1 | GET "/goods/6"
获取get参数
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//匹配的url格式:/welcome?firstname=Jane&lastname=Doe
router.GET("/welcome",func(c *gin.Context) {
firstname := c.DefaultQuery("firstname","Guest")
lastname := c.Query("lastname")//是c.Request.URL.Query().Get("lastname")的
c.String(http.StatusOK,"Hello %s %s",firstname,lastname)
})
router.Run(":8084")
}
获取post
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//匹配的url格式:/welcome?firstname=Jane&lastname=Doe
router.GET("/welcome",func(c *gin.Context) {
firstname := c.DefaultQuery("firstname","Guest")
//lastname := c.Query("lastname")//是c.Request.URL.Query().Get("lastname")
lastname:=c.DefaultQuery("lastname","imooc")
// c.String(http.StatusOK,"Hello %s %s",firstname,lastname)
c.JSON(http.StatusOK,gin.H{
"first_name":firstname,
"last_name":lastname,
})
})
router.Run(":8084")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch04\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /welcome --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8084
[GIN] 2026/08/30 - 17:10:52 | 200 | 507.1µs | 127.0.0.1 | GET "/welcome"
[GIN] 2026/08/30 - 17:10:53 | 404 | 0s | 127.0.0.1 | GET "/favicon.ico"
[GIN] 2026/08/30 - 17:12:08 | 200 | 0s | 127.0.0.1 | GET "/welcome?firstname=%E6%85%95%E8%AF%BE%E7%BD%91&last_name=bool"
[GIN] 2026/08/30 - 17:12:40 | 200 | 0s | 127.0.0.1 | GET "/welcome?firstname=%E6%85%95%E8%AF%BE%E7%BD%91&lastname=bool"
测试

http://127.0.0.1:8084/welcome?firstname=cx&lastname=bool

post 请求参数
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//匹配的url格式:/welcome?firstname=Jane&lastname=Doe
router.GET("/welcome",func(c *gin.Context) {
firstname := c.DefaultQuery("firstname","Guest")
//lastname := c.Query("lastname")//是c.Request.URL.Query().Get("lastname")
lastname:=c.DefaultQuery("lastname","imooc")
// c.String(http.StatusOK,"Hello %s %s",firstname,lastname)
c.JSON(http.StatusOK,gin.H{
"first_name":firstname,
"last_name":lastname,
})
})
router.POST("/form_post",func(c *gin.Context) {
message:=c.PostForm("message")
nick:= c.DefaultPostForm("nike","anonymous")
c.JSON(http.StatusOK,gin.H{
"message":message,
"nick":nick,
})
})
router.Run(":8084")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch04\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /welcome --> main.main.func1 (3 handlers)
[GIN-debug] POST /form_post --> main.main.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8084
[GIN] 2026/08/30 - 17:35:57 | 404 | 0s | 127.0.0.1 | POST "/"
[GIN] 2026/08/30 - 17:38:03 | 404 | 0s | 127.0.0.1 | POST "/from_post?message=%E4%BD%A0%E5%A5%BD&nick=bobby"
[GIN] 2026/08/30 - 17:38:12 | 404 | 0s | 127.0.0.1 | POST "/from_post?message=%E4%BD%A0%E5%A5%BD&nick=bobby"
[GIN] 2026/08/30 - 17:38:13 | 404 | 0s | 127.0.0.1 | POST "/from_post?message=%E4%BD%A0%E5%A5%BD&nick=bobby"
[GIN] 2026/08/30 - 17:38:53 | 200 | 0s | 127.0.0.1 | POST "/form_post?message=%E4%BD%A0%E5%A5%BD&nick=bobby"
[GIN] 2026/08/30 - 17:38:58 | 200 | 0s | 127.0.0.1 | POST "/form_post?message=%E4%BD%A0%E5%A5%BD&nick=bobby"
[GIN] 2026/08/30 - 17:40:30 | 200 | 0s | 127.0.0.1 | POST "/form_post"
工具postman测试

混合 post 与get
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//匹配的url格式:/welcome?firstname=Jane&lastname=Doe
router.GET("/welcome",func(c *gin.Context) {
firstname := c.DefaultQuery("firstname","Guest")
//lastname := c.Query("lastname")//是c.Request.URL.Query().Get("lastname")
lastname:=c.DefaultQuery("lastname","imooc")
// c.String(http.StatusOK,"Hello %s %s",firstname,lastname)
c.JSON(http.StatusOK,gin.H{
"first_name":firstname,
"last_name":lastname,
})
})
router.POST("/form_post",func(c *gin.Context) {
message:=c.PostForm("message")
nick:= c.DefaultPostForm("nike","anonymous")
c.JSON(http.StatusOK,gin.H{
"message":message,
"nick":nick,
})
})
router.POST("/post",func(c *gin.Context) {
id := c.Query("id")//获取url参数
page:=c.DefaultQuery("page","0")
name:=c.PostForm("name")//获取表单里内容
message:=c.DefaultPostForm("massage","信息")
c.JSON(http.StatusOK,gin.H{
"id":id,
"page":page,
"name":name,
"message":message,
})
})
router.Run(":8084")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch04\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /welcome --> main.main.func1 (3 handlers)
[GIN-debug] POST /form_post --> main.main.func2 (3 handlers)
[GIN-debug] POST /post --> main.main.func3 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8084
[GIN] 2026/08/30 - 17:56:55 | 200 | 505.2µs | 127.0.0.1 | POST "/post?id=1&page=2"
工具postman测试1 url 参数

pastman 2

json、protobuf 渲染
syntax = "proto3";
option go_package = ".;proto";
message Teacher {
string name =1;
repeated string course =2;
}
生成文件
protoc --go_out=. --go-grpc_out=. user.proto
gin 代码
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"lnhgo/gin/ch06/proto"
)
func main() {
router := gin.Default()
//匹配的url格式:/welcome?firstname=Jane&lastname=Doe
router.GET("/moreJSON", moreJSON)
router.GET("/someProtoBuf",returnProto)
router.Run(":8015")
}
func moreJSON(c *gin.Context){
var msg struct{
Name string `json:"user"`
Messages string
Number int
}
msg.Name="boobby"
msg.Messages="这是一个测试"
msg.Number= 20
c.JSON(http.StatusOK,msg)
}
func returnProto(c *gin.Context){
coures:=[]string{"python","go","微服务"}
user:=&proto.Teacher{
Name: "bobby",
Course: coures,
}
c.ProtoBuf(http.StatusOK,user)
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch06\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /moreJSON --> main.moreJSON (3 handlers)
[GIN-debug] GET /someProtoBuf --> main.returnProto (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8015
[GIN] 2026/08/31 - 15:23:51 | 200 | 530.5µs | 127.0.0.1 | GET "/someProtoBuf"
[GIN] 2026/08/31 - 15:25:51 | 404 | 0s | 127.0.0.1 | GET "/.well-known/appspecific/com.chrome.devtools.json"
[GIN] 2026/08/31 - 15:26:10 | 200 | 0s | 127.0.0.1 | GET "/moreJSON"
[GIN] 2026/08/31 - 15:26:10 | 404 | 0s | 127.0.0.1 | GET "/.well-known/appspecific/com.chrome.devtools.json"
[GIN] 2026/08/31 - 15:26:49 | 404 | 0s | 127.0.0.1 | GET "/.well-known/appspecific/com.chrome.devtools.json"
[GIN] 2026/08/31 - 15:26:54 | 200 | 0s | 127.0.0.1 | GET "/someProtoBuf"
PS D:\golang\goproject\src\lnhgo> go get github.com/go-playground/validator/v10 go: downloading github.com/go-playground/validator v9.31.0+incompatible go: downloading github.com/go-playground/validator/v10 v10.30.3 go: downloading github.com/gabriel-vasile/mimetype v1.4.13 go: upgraded github.com/gabriel-vasile/mimetype v1.4.12 => v1.4.13 go: upgraded github.com/go-playground/validator/v10 v10.30.1 => v10.30.3
若要将请求主体绑定到结构体中,请使用模型绑定,目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定。
需要在绑定的字段上设置tag,比如,绑定格式为json,需要这样设置 json:"fieldname" 。
此外,Gin还提供了两套绑定方法:
- Must bind
- Methods -
Bind,BindJSON,BindXML,BindQuery,BindYAML - Behavior - 这些方法底层使用
MustBindWith,如果存在绑定错误,请求将被以下指令中止c.AbortWithError(400, err).SetType(ErrorTypeBind),响应状态代码会被设置为400,请求头Content-Type被设置为text/plain; charset=utf-8。注意,如果你试图在此之后设置响应代码,将会发出一个警告[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422,如果你希望更好地控制行为,请使用ShouldBind相关的方法
- Should bind
- Methods -
ShouldBind,ShouldBindJSON,ShouldBindXML,ShouldBindQuery,ShouldBindYAML - Behavior - 这些方法底层使用
ShouldBindWith,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。
当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用MustBindWith或者BindingWith。
表单验证
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"` //前端传递的模式;各种类型数据
Password string `json:"password" binding:"required"`
}
func main() {
routes := gin.Default()
routes.POST("/loginJSON",func(c *gin.Context) {
var loginFor LoginForm
if err:=c.ShouldBind(&loginFor);err!=nil{
fmt.Println(err)
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"mag":"登录成功",
})
})
routes.Run(":8083")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch07\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /loginJSON --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
Key: 'LoginForm.User' Error:Field validation for 'User' failed on the 'required' tag
Key: 'LoginForm.Password' Error:Field validation for 'Password' failed on the 'required' tag
[GIN] 2026/08/31 - 16:42:13 | 400 | 531.7µs | 127.0.0.1 | POST "/loginJSON"
Key: 'LoginForm.User' Error:Field validation for 'User' failed on the 'required' tag
Key: 'LoginForm.Password' Error:Field validation for 'Password' failed on the 'required' tag
[GIN] 2026/08/31 - 16:42:19 | 400 | 0s | 127.0.0.1 | POST "/loginJSON"
登录测试
用户名失败不符合tge标准

测试成功的

注册表单
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"` //前端传递的模式;各种类型数据
Password string `json:"password" binding:"required"`
}
type SignUpForm struct{
Age uint8 `json:"age" binding:"required,gte=1,lte=130"`//表示大于1;或者小于130
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`//强制满足email 格式
Password string `json:"password" binding:"required"`
RePassword string `json:"repassword" binding:"required,eqfield=Password"`//跨字段验证
}
func main() {
routes := gin.Default()
routes.POST("/loginJSON",func(c *gin.Context) {
var loginFor LoginForm
if err:=c.ShouldBind(&loginFor);err!=nil{
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"mag":"登录成功",
})
})
routes.POST("/signup",func(c *gin.Context) {
var SignUpForm SignUpForm
if err := c.ShouldBind(&SignUpForm);err !=nil{
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"msg":"登录成功",
})
})
routes.Run(":8083")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch07\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /loginJSON --> main.main.func1 (3 handlers)
[GIN-debug] POST /signup --> main.main.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
验证两次密码输入不一致

测试成功查看返回

错误提示验证信息翻译成中文
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)
var trans ut.Translator
type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"` //前端传递的模式;各种类型数据
Password string `json:"password" binding:"required"`
}
type SignUpForm struct{
Age uint8 `json:"age" binding:"required,gte=1,lte=130"`//表示大于1;或者小于130
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"repassword" binding:"required,eqfield=Password"`//跨字段验证
}
func InintTrans(locale string)(err error){
//修改gin框架中的validator引擎属性,实现定制
if v,ok :=binding.Validator.Engine().(*validator.Validate);ok{
zhT:= zh.New()//中文
enT:=en.New()//英文翻译
//第一个参数备用语言环境,后续参数应该支持的语言环境
uni:=ut.New(enT,zhT,enT)
if trans,ok =uni.GetTranslator(locale);!ok{
return fmt.Errorf("GetTranslator(%s)",locale)
}else{
switch locale{
case "en":
enTranslations.RegisterDefaultTranslations(v,trans)
case "zh":
zhTranslations.RegisterDefaultTranslations(v,trans)
default:
enTranslations.RegisterDefaultTranslations(v,trans)
}
return
}
//return
}
return
}
func main() {
if err := InintTrans("zh");err!=nil{
fmt.Println("初始化获取翻译器错误")
return
}
routes := gin.Default()
routes.POST("/loginJSON",func(c *gin.Context) {
var loginFor LoginForm
if err:=c.ShouldBind(&loginFor);err!=nil{
errs,ok:=err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK,gin.H{
"mgs":err.Error(),
})
}
c.JSON(http.StatusOK,gin.H{
"error":errs.Translate(trans),
})
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"mag":"登录成功",
})
})
routes.POST("/signup",func(c *gin.Context) {
var SignUpForm SignUpForm
if err := c.ShouldBind(&SignUpForm);err !=nil{
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"msg":"登录成功",
})
})
routes.Run(":8083")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch07\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /loginJSON --> main.main.func1 (3 handlers)
[GIN-debug] POST /signup --> main.main.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
[GIN] 2026/08/31 - 19:18:00 | 200 | 0s | 127.0.0.1 | POST "/loginJSON"
Key: 'LoginForm.User' Error:Field validation for 'User' failed on the 'min' tag
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 400
[GIN] 2026/08/31 - 19:18:17 | 200 | 11.53ms | 127.0.0.1 | POST "/loginJSON"
测试

表单中文翻译的json格式化细节
package main
import (
"fmt"
"net/http"
"reflect"
"strings"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)
var trans ut.Translator
type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"` //前端传递的模式;各种类型数据
Password string `json:"password" binding:"required"`
}
type SignUpForm struct{
Age uint8 `json:"age" binding:"required,gte=1,lte=130"`//表示大于1;或者小于130
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"repassword" binding:"required,eqfield=Password"`//跨字段验证
}
func InintTrans(locale string)(err error){
//修改gin框架中的validator引擎属性,实现定制
if v,ok :=binding.Validator.Engine().(*validator.Validate);ok{
//注册一个获取json的tag的自定义来
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name:= strings.SplitN(fld.Tag.Get("json"),",",2)[0]
if name=="-"{
return ""
}
return name
})
zhT:= zh.New()//中文
enT:=en.New()//英文翻译
//第一个参数备用语言环境,后续参数应该支持的语言环境
uni:=ut.New(enT,zhT,enT)
if trans,ok =uni.GetTranslator(locale);!ok{
return fmt.Errorf("GetTranslator(%s)",locale)
}else{
switch locale{
case "en":
enTranslations.RegisterDefaultTranslations(v,trans)
case "zh":
zhTranslations.RegisterDefaultTranslations(v,trans)
default:
enTranslations.RegisterDefaultTranslations(v,trans)
}
return
}
//return
}
return
}
func main() {
if err := InintTrans("zh");err!=nil{
fmt.Println("初始化获取翻译器错误")
return
}
routes := gin.Default()
routes.POST("/loginJSON",func(c *gin.Context) {
var loginFor LoginForm
if err:=c.ShouldBind(&loginFor);err!=nil{
errs,ok:=err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK,gin.H{
"mgs":err.Error(),
})
}
c.JSON(http.StatusOK,gin.H{
"error":errs.Translate(trans),
})
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"mag":"登录成功",
})
})
routes.POST("/signup",func(c *gin.Context) {
var SignUpForm SignUpForm
if err := c.ShouldBind(&SignUpForm);err !=nil{
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"msg":"登录成功",
})
})
routes.Run(":8083")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch07\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /loginJSON --> main.main.func1 (3 handlers)
[GIN-debug] POST /signup --> main.main.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
Key: 'LoginForm.user' Error:Field validation for 'user' failed on the 'min' tag
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 400
[GIN] 2026/09/01 - 14:34:25 | 200 | 578µs | 127.0.0.1 | POST "/loginJSON"
Key: 'LoginForm.user' Error:Field validation for 'user' failed on the 'min' tag
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 400
[GIN] 2026/09/01 - 14:35:35 | 200 | 788.1µs | 127.0.0.1 | POST "/loginJSON"
[GIN] 2026/09/01 - 14:35:52 | 200 | 0s | 127.0.0.1 | POST "/loginJSON"
测试结果

格式化
package main
import (
"fmt"
"net/http"
"reflect"
"strings"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)
var trans ut.Translator
type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"` //前端传递的模式;各种类型数据
Password string `json:"password" binding:"required"`
}
type SignUpForm struct{
Age uint8 `json:"age" binding:"required,gte=1,lte=130"`//表示大于1;或者小于130
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"repassword" binding:"required,eqfield=Password"`//跨字段验证
}
func removeTopStruct(fileds map[string]string) map[string]string{
rsp :=map[string]string{}
for field,err:=range fileds{
rsp[field[strings.Index(field,".")+1:]]=err
}
return rsp
}
func InintTrans(locale string)(err error){
//修改gin框架中的validator引擎属性,实现定制
if v,ok :=binding.Validator.Engine().(*validator.Validate);ok{
//注册一个获取json的tag的自定义来
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name:= strings.SplitN(fld.Tag.Get("json"),",",2)[0]
if name=="-"{
return ""
}
return name
})
zhT:= zh.New()//中文
enT:=en.New()//英文翻译
//第一个参数备用语言环境,后续参数应该支持的语言环境
uni:=ut.New(enT,zhT,enT)
if trans,ok =uni.GetTranslator(locale);!ok{
return fmt.Errorf("GetTranslator(%s)",locale)
}else{
switch locale{
case "en":
enTranslations.RegisterDefaultTranslations(v,trans)
case "zh":
zhTranslations.RegisterDefaultTranslations(v,trans)
default:
enTranslations.RegisterDefaultTranslations(v,trans)
}
return
}
//return
}
return
}
func main() {
if err := InintTrans("zh");err!=nil{
fmt.Println("初始化获取翻译器错误")
return
}
routes := gin.Default()
routes.POST("/loginJSON",func(c *gin.Context) {
var loginFor LoginForm
if err:=c.ShouldBind(&loginFor);err!=nil{
errs,ok:=err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK,gin.H{
"mgs":err.Error(),
})
}
c.JSON(http.StatusOK,gin.H{
"error":removeTopStruct(errs.Translate(trans)),
})
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"mag":"登录成功",
})
})
routes.POST("/signup",func(c *gin.Context) {
var SignUpForm SignUpForm
if err := c.ShouldBind(&SignUpForm);err !=nil{
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest,gin.H{
"error":err.Error(),
})
return
}
c.JSON(http.StatusOK,gin.H{
"msg":"登录成功",
})
})
routes.Run(":8083")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch07\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /loginJSON --> main.main.func1 (3 handlers)
[GIN-debug] POST /signup --> main.main.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
Key: 'LoginForm.user' Error:Field validation for 'user' failed on the 'min' tag
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 400
[GIN] 2026/09/01 - 15:20:37 | 200 | 1.2ms | 127.0.0.1 | POST "/loginJSON"
[GIN] 2026/09/01 - 15:20:55 | 200 | 392.8µs | 127.0.0.1 | POST "/loginJSON"
Key: 'LoginForm.user' Error:Field validation for 'user' failed on the 'min' tag
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 400
[GIN] 2026/09/01 - 15:21:10 | 200 | 682.1µs | 127.0.0.1 | POST "/loginJSON"
invalid character '}' looking for beginning of value
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 400
[GIN] 2026/09/01 - 15:21:18 | 200 | 0s | 127.0.0.1 | POST "/loginJSON"
Key: 'LoginForm.user' Error:Field validation for 'user' failed on the 'min' tag
Key: 'LoginForm.password' Error:Field validation for 'password' failed on the 'required' tag
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 400
[GIN] 2026/09/01 - 15:21:29 | 200 | 0s | 127.0.0.1 | POST "/loginJSON"
测试

gin 中间件
c.Next() 的意义不是“让后续流程自动执行”,而是让当前中间件“主动进入后续 Handler”,然后再回来继续执行当前中间件后面的代码。它就像一个分界线
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func MyLogger() gin.HandlerFunc{
return func(c *gin.Context) {
t:=time.Now()
c.Set("example","123456")
c.Next()//让原来该执行的逻辑继续执行
end:=time.Since(t)
fmt.Printf("耗时%v\n",end)
status :=c.Writer.Status()
fmt.Println("状态",status)
}
}
func main() {
router := gin.Default()
router.Use(MyLogger())
router.GET("/ping",func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{
"message":"pong",
})
})
router.Run(":8083")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch08\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (4 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
耗时0s
状态 200
[GIN] 2026/09/01 - 19:00:12 | 200 | 0s | 127.0.0.1 | GET "/ping"
耗时0s
状态 404
[GIN] 2026/09/01 - 19:00:12 | 404 | 0s | 127.0.0.1 | GET "/favicon.ico"
中间件终止后续流程abort
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func MyLogger() gin.HandlerFunc{
return func(c *gin.Context) {
t:=time.Now()
c.Set("example","123456")
//return
c.Next()//让原来该执行的逻辑继续执行
end:=time.Since(t)
fmt.Printf("耗时%v\n",end)
status :=c.Writer.Status()
fmt.Println("状态",status)
}
}
func TokenRequired() gin.HandlerFunc{
return func(c *gin.Context) {
var token string
for k,v:=range c.Request.Header{
//if k == "x-token"{注意这里一定要首字母大写
if k=="X-Token"{
token=v[0]
}else{
fmt.Println(k,v)
}
}
if token != "bobby"{
c.JSON(http.StatusUnauthorized,gin.H{
"msg":"未登录",
})
//return在这里无法终止后续流程
c.Abort()//必须如此才能终止后续流程
}
c.Next()
}
}
func main() {
router := gin.Default()
router.Use(TokenRequired())//框架调用
router.GET("/ping",func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{
"message":"pong",
})
})
router.Run(":8083")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch08\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (4 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
postman 测试中间件阶段终止

postman 测试完整中间件通过流程

gin 中间件原理
可以把 c.Next() 理解成 Gin 中间件里的一个**“分界线 / 控制点”**。
你的描述稍微精确一下就是:
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// ① Next 上面的代码
fmt.Println("A")
c.Next()
// ② Next 下面的代码
fmt.Println("B")
}
}
假设后面还有:
func Handler(c *gin.Context) {
fmt.Println("C")
}
那么执行顺序就是:
进入 Middleware
↓
执行 A
↓
遇到 c.Next()
↓
进入 Gin 后面的 Handler Chain
↓
执行 C
↓
后面的 Handler Chain 执行完
↓
返回 Middleware
↓
继续执行 c.Next() 后面的 B
↓
Middleware 结束
最终:
A C B
所以可以把它记成:
┌──────────── Middleware ────────────┐ │ │ │ Next() 前面的代码 │ │ ↓ │ │ c.Next() │ ← 分界线 │ ↓ │ │ 后续 Gin Handler │ │ ↓ │ │ 执行完返回 │ │ ↓ │ │ Next() 后面的代码 │ │ │ └────────────────────────────────────┘
而且这里有一个特别重要的理解:
c.Next()不是“调用下一个函数然后就不回来了”,而是“进入后续 Handler Chain,等后续 Chain 执行完成,再回来继续当前中间件”。
所以以后看到这种代码:
start := time.Now() c.Next() cost := time.Since(start)
你马上就应该想到:
记录开始时间
↓
进入 Controller
↓
Controller 执行完
↓
回来
↓
计算耗时
这也是为什么日志、耗时统计、权限、事务、recover 等很多功能都特别适合用 Gin 中间件实现。
你现在c.Next() 的理解已经到位了。
为什么return在这里会出现无法终止后续流程
因为 Gin 中间件里的 return 只能终止“当前这个中间件函数”,不能自动终止整个 Gin 的 c.Next() 执行链。
这是 Gin 中间件最容易搞混的地方。
先看一个典型例子
func MyMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始")
if true {
return
}
fmt.Println("中间件结束")
}
}
这里 return 确实会执行:
中间件开始 ↓ return ↓ 当前中间件函数结束
但是如果你的代码结构是:
func MyMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if true {
return
}
c.Next()
}
}
return 后面的 c.Next() 不会执行。
但这不代表整个请求链一定停止。
关键在于:Gin 的中间件是怎么调用的
假设:
r.Use(Middleware1())
r.GET("/test", func(c *gin.Context) {
fmt.Println("业务处理")
})
Gin 内部可以简单理解成:
Middleware1
↓
Middleware2
↓
Handler
而 c.Next() 的作用就是:
继续执行当前中间件后面的 Handler 链。
例如:
func Middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("M1 前")
c.Next()
fmt.Println("M1 后")
}
}
执行:
M1 前
↓
c.Next()
↓
后面的 Middleware / Handler
↓
M1 后
为什么 return 经常让人觉得“没终止”?
因为你可能写的是这种:
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if token == "" {
c.JSON(401, gin.H{
"message": "未登录",
})
return
}
c.Next()
}
}
这个实际上是可以阻止后续 Handler 的。
执行:
AuthMiddleware
↓
token == ""
↓
c.JSON(...)
↓
return
↓
AuthMiddleware 结束
因为 c.Next() 根本没有执行。
所以:
如果你是在中间件里直接
return,并且没有提前调用c.Next(),正常情况下后续 Handler 不会执行。
但是有一种情况非常容易踩坑
比如:
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if somethingWrong {
return
}
}
}
这里:
c.Next()
已经把后面的 Handler 执行完了。
所以:
c.Next()
return
相当于:
先执行后续所有 Handler
↓
回来
↓
return
这时候你再 return,当然阻止不了后面的执行。
真正终止 Gin 后续链的方法
如果你想表达:
“从这里开始,后面的 Handler 一个都不要执行。”
最常见的是:
c.Abort()
例如:
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if token == "" {
c.JSON(401, gin.H{
"message": "未登录",
})
c.Abort()
return
}
c.Next()
}
}
这里:
c.Abort()
return
两个东西作用不同:
c.Abort()
告诉 Gin:
这个请求的 Handler Chain 到此为止。
return
告诉 Go:
当前这个函数到此为止。
所以通常一起写:
c.Abort()
return
非常清晰。
你可以把它记成一句话
return 是退出函数,c.Abort() 是终止 Gin 的执行链。
| 操作 | 作用 |
|---|---|
return |
退出当前 Go 函数 |
c.Next() |
继续执行后面的 Handler |
c.Abort() |
终止 Gin 后续 Handler 链 |
c.Abort(); return |
终止 Gin 链 + 退出当前中间件 |
最重要的一个理解
如果你看到:
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
if !check() {
c.JSON(403, gin.H{
"error": "forbidden",
})
c.Abort()
return
}
c.Next()
}
}
你应该脑子里形成这个模型:
请求
↓
Middleware
↓
检查
├── 失败 → c.Abort() → return → 结束
│
└── 成功 → c.Next()
↓
下一个中间件
↓
Controller
另外一个很关键的点:c.Abort() 本身也不会让 Go 函数立即退出。
所以很多 Gin 代码才会写成:
c.Abort() return
这两个不是重复,而是分别控制 Gin 的执行链 和 Go 当前函数。
gin 是函数链
这个理解方向是对的,但“队列”这个词可以再精确一点。
Gin 更准确地说是一个 Handler 链(Handler Chain),可以把它理解成一个按顺序排列的函数链:
Middleware1 → Middleware2 → Middleware3 → Handler
你的 return:
return
只会让当前正在执行的匿名函数退出:
Middleware1
↓
return
↓
Middleware1 结束
↓
Gin 的外层执行机制
↓
后续 Handler 仍可能继续
所以你说的:
“return 的只是当前返回的匿名函数,不影响下一个函数执行”
这个核心理解是对的。
但有一个非常重要的补充:
c.Next() 是“推进 Handler 链”的关键
可以简单理解成:
┌──────── Middleware ────────┐
│ │
│ 前置逻辑 │
│ ↓ │
│ c.Next() ─────────────┐ │
│ ↓ │
└────────────────────────────┘
↓
后续 Handler
↓
执行完成
↓
返回当前中间件
↓
后置逻辑
而:
c.Abort()
相当于告诉 Gin:
Handler Chain
↓
❌ 后面的不要再执行
所以你可以最终记成一句非常核心的话:
return 控制的是 Go 当前函数;c.Next() 控制的是进入后续 Handler;c.Abort() 控制的是 Gin Handler 链是否继续。
这三个概念分清楚,Gin 中间件的原理就基本吃透了。
gin 模版返回html
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//LoadHTMLFiles 将指定文件目录加载好,但是是执行目录的相对目录
router.LoadHTMLFiles("gin/ch09/templates/index.tmpl")
router.GET("/index",func(c *gin.Context) {
c.HTML(http.StatusOK,"index.tmpl",gin.H{
"title":"慕课网",
})
})
router.Run(":8082")
}
index.tmpl 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
{{ .title }}
</h1>
</body>
</html>
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch09\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /index --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8082
[GIN] 2026/09/01 - 21:20:28 | 200 | 692.4µs | 127.0.0.1 | GET "/index"
浏览器访问http://127.0.0.1:8082/index

加载多个html 文件
测试2

加载多级目录
package main
import (
//"fmt"
"net/http"
//"os"
//"path/filepath"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//dir,_ := filepath.Abs(filepath.Dir(os.Args[0]))
//fmt.Println(dir)
//LoadHTMLFiles 将指定文件目录加载好,但是目录是相对目录
//router.LoadHTMLGlob("gin/ch09/templates/*")//加载指定目录下所有文件
router.LoadHTMLGlob("gin/ch09/templates/**/*")//加载所有二级目录下的所有文件
//router.LoadHTMLFiles("gin/ch09/templates/index.tmpl","gin/ch09/templates/goods.html")//加载指定的两个文件
router.GET("/index",func(c *gin.Context) {
c.HTML(http.StatusOK,"index.tmpl",gin.H{
"title":"慕课网",
})
})
router.GET("/goods/list",func(c *gin.Context) {
c.HTML(http.StatusOK,"goods/list.html",gin.H{ // goods/list.html 在html文件里填写
"title":"慕课网",
})
})
router.GET("/user/list",func(c *gin.Context) {
c.HTML(http.StatusOK,"user/list.html",gin.H{//user/list.html 在html 文件里填写
"title":"慕课网",
})
})
router.GET("/goods",func(c *gin.Context) {
c.HTML(http.StatusOK,"goods.html",gin.H{
"name":"bobby",
})
})
router.Run(":8082")
}
启动
PS D:\golang\goproject\src\lnhgo> go run gin\ch09\main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] Loaded HTML Templates (4):
-
- list.html
- goods/list.html
- user/list.html
[GIN-debug] GET /index --> main.main.func1 (3 handlers)
[GIN-debug] GET /goods/list --> main.main.func2 (3 handlers)
[GIN-debug] GET /user/list --> main.main.func3 (3 handlers)
[GIN-debug] GET /goods --> main.main.func4 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8082
[GIN] 2026/09/02 - 13:32:10 | 200 | 1.85ms | 127.0.0.1 | GET "/goods/list"
[GIN] 2026/09/02 - 13:32:12 | 200 | 783.9µs | 127.0.0.1 | GET "/goods/list"
[GIN] 2026/09/02 - 13:32:35 | 200 | 1.05ms | 127.0.0.1 | GET "/user/list"
html 页面
{{define "goods/list.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>商品列表页</h1>
</body>
</html>
{{end}}
user/list.html
{{define "user/list.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
用户列表页
</h1>
</body>
</html>
{{end}}
目录结构

浏览器访问http://127.0.0.1:8082/goods/list

浏览器访问http://127.0.0.1:8082/user/list

如何有优雅退出程序
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
//"sync"
"syscall"
"github.com/gin-gonic/gin"
)
func main() {
routes := gin.Default()
routes.GET("/",func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{
"msg":"pong",
})
})
go func () {
routes.Run(":8083")
}()
//如果想要接收kill-9强杀命令;不会处理后续逻辑的
quit := make(chan os.Signal,1)
signal.Notify(quit,syscall.SIGINT,syscall.SIGTERM)
<-quit
fmt.Println("关闭中")
fmt.Println("注销操作")
}
启动测试
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
关闭中
注销操作

浙公网安备 33010602011771号