一、前言

本文主要以下几方面介绍Gin相关基础知识:

  • 为什么要使用框架写项目
  • 常见的Go语言框架及对比
  • 前后端分离
  • 什么是JSON
  • HTTP请求类型介绍

二、为什么要使用框架写项目

在 Go 语言中,使用框架来开发项目有许多好处:

  • 提高开发效率: 框架提供了常见功能的现成实现,减少了重复编码的工作。这使开发者能够更快速地开发应用程序。
  • 可维护性: 框架通常遵循良好的代码结构和设计模式,有助于提高代码的可读性和维护性。
  • 性能优化: 框架通常会针对性能进行优化,包括路由、中间件处理等方面,可以提供更好的性能。
  • 安全性: 框架通常会提供一些安全性保护机制,如防止 SQL 注入、XSS 攻击等。
  • 可扩展性: 大多数框架支持插件和中间件,使开发者能够轻松地扩展应用程序功能。
  • 社区支持: 流行的框架通常有庞大的社区支持,可以获得问题解答、文档和扩展模块等。
  • 标准化: 使用框架有助于遵循一些行业标准和最佳实践,从而减少了项目出现问题的可能性。

三、常见的Go语言框架及对比

常见的Go语言框架及对比:

Gin:

  • 轻量级 Web 框架,性能出色,适用于快速开发。
  • 它的 API 简单,易于学习和使用。
  • 支持中间件,有丰富的社区插件。
  • 缺点:对于大规模应用可能不够灵活。

Echo:

  • 与 Gin 类似,轻量级且性能很好。
  • 设计简单,易于入门。
  • 支持中间件和路由组。
  • 缺点:相对较小的社区。

Beego:

  • 一个全栈 Web 框架,提供了路由、ORM、模板引擎等功能。
  • 框架功能较全面,适合构建大型应用。
  • 缺点:学习曲线较陡峭。

Iris:

  • 一个性能强大的 Web 框架,支持异步请求处理。
  • 提供了中间件、路由组、模板引擎等功能。
  • 缺点:相对较新,社区相对较小。

Buffalo:

  • 用于构建 Web、API 和后端服务的框架,提供生成器工具。
  • 框架包括 ORM、模板引擎、前端构建工具等。
  • 缺点:复杂性较高,适用于大型应用。

Revel:

  • 全栈框架,提供了路由、ORM、模板引擎等功能。
  • 自带开发工具,适合快速开发。
  • 缺点:相对较大,学习曲线陡峭。

Fiber:

  • 类似于 Express.js 的 Web 框架,性能出色。
  • 提供了路由、中间件等功能。
  • 简单易用,适用于构建高性能应用。
  • 缺点:相对较新,社区支持尚在发展中。

小结:

选择框架取决于项目的性质和需求。如果需要快速开发小型应用,可以选择轻量级框架如 Gin 或 Echo。对于大型应用,可能需要全栈框架如 Beego 或 Revel。建议根据项目需求、个人偏好和团队熟悉度来选择适当的框架。

四、前后端分离

4.1 什么是前后端分离

前后端分离是一种软件架构模式,其中前端和后端的开发是分离的,它们彼此独立工作,通过网络接口进行通信。在这种模式下,前端负责用户界面和用户体验,通常使用 HTML、CSS 和 JavaScript 进行开发,而后端负责处理数据、业务逻辑和数据库访问。前后端通过API(Application Programming Interface)进行通信,前端发送请求,后端提供响应数据。这种分离的方式有助于提高开发团队的协作效率,允许使用不同技术栈来开发前端和后端,并提供更好的可维护性和扩展性。

前后端分离架构-20231020

在前后端分离框架中,通过 GET 请求查询数据并渲染的完整流程如下:

Day012-项目开发实战-Gin基础-图1

  1. 用户触发数据请求: 用户在前端界面执行操作(如点击按钮、页面加载),触发前端的数据获取逻辑(例如 Vue/React 组件的 mounted 钩子或点击事件处理函数)。

  2. 前端发起 GET 请求: 前端通过 HTTP 客户端(如 axiosfetch)向后端 API 发送 GET 请求,携带必要参数(如分页 page=1、筛选 keyword=xxx 等,通过查询字符串 ? 附加在 URL 中)。示例请求:http

http GET /api/data?page=1&size=10 HTTP/1.1 Host: example.com

  1. 后端处理请求

  2. 后端 API 解析请求参数(如 Node.js 的 req.query、Django 的 request.GET)。

  3. 根据参数执行数据库查询(如 SQL 语句 SELECT * FROM table WHERE ... LIMIT ...),通过数据库驱动(如 mysql2psycopg2)与数据库交互。

  4. 数据库返回查询结果: 数据库执行查询后,将结果(如多行数据)返回给后端。

  5. 后端响应 JSON 数据: 后端将查询结果转换为 JSON 格式(如数组、对象),并返回 HTTP 200 状态码。示例响应:json

json { "code": 200, "data": [{"id": 1, "name": "xxx"}, ...], "total": 100 }

  1. 前端解析并更新状态: 前端接收响应后,解析 JSON 数据,更新组件状态(如 Vue 的 data、React 的 useState)。例如:javascript

javascript axios.get('/api/data').then(res => { this.list = res.data.data; // 更新列表数据 });

  1. 前端渲染视图: 基于更新后的状态,前端框架(如 Vue 的 v-for、React 的 map)将数据渲染到 DOM 中,展示给用户(如列表渲染、表格填充)。

Day012-项目开发实战-Gin基础-图3

在前后端分离框架中,通过 POST 请求查询数据并渲染的完整流程如下:

  1. 用户触发操作 用户在前端界面执行复杂查询操作(如提交包含多条件筛选的表单、发起高级搜索),触发前端的数据查询逻辑(例如表单提交事件、按钮点击回调)。

  2. 前端收集请求参数(步骤 1) 前端从界面元素(如输入框、下拉菜单、复选框)中提取用户输入的查询参数(如 { "keyword": "手机", "priceRange": [1000, 5000], "page": 2 }),整理为键值对结构。

  3. 前端构建请求体(步骤 2) 将收集的参数转换为 JSON 格式的请求体,确保数据结构符合后端 API 预期(如嵌套对象、数组等)。示例请求体:

json

json { "filters": { "keyword": "手机", "price": { "min": 1000, "max": 5000 } }, "pagination": { "page": 2, "size": 10 }, "sort": { "field": "price", "order": "asc" } }

  1. 前端发送 POST 请求(步骤 3) 使用 HTTP 客户端(如 axios)发送 POST 请求,将 JSON 请求体放入request body,设置Content-Type: application/json。请求示例:

http

```http POST /api/products/advanced-search HTTP/1.1 Content-Type: application/json Host: example.com

{ / 上述请求体JSON / } ```

  1. 后端解析请求体(步骤 4) 后端框架(如 Node.js Express 的body-parser、Django 的JSONParser)解析请求体,提取 JSON 数据到内存对象(如req.body)。

  2. 后端参数校验与业务处理(步骤 5)

  3. 参数校验:验证数据格式(如价格范围是否合法、分页参数是否为正整数),使用校验库(如 Joi、Pydantic)确保输入符合规则。

  4. 业务处理:根据业务逻辑补充必要参数(如用户 ID、时间戳),或进行权限检查(如仅管理员可访问某些筛选条件)。

  5. 后端执行复杂查询(步骤 6) 根据请求参数构建数据库查询:

  6. 多表关联:如SELECT p.*, c.name AS category FROM products p JOIN categories c ON p.category_id = c.id

  7. 条件筛选WHERE p.price BETWEEN 1000 AND 5000 AND p.name LIKE '%手机%'
  8. 排序与分页ORDER BY p.price ASC LIMIT 10 OFFSET 10(假设第 2 页,每页 10 条)。 通过数据库驱动(如mysql2psycopg2)执行查询,获取原始数据(如 SQL 结果集)。

  9. 数据库返回原始数据(步骤 7) 数据库执行查询后,将结果(如多行记录)返回给后端。

  10. 后端数据加工与格式化(步骤 8)

  11. 数据转换:将原始数据转换为前端友好的格式(如驼峰命名、嵌套结构展开)。

  12. 添加元信息:计算总记录数(SELECT COUNT(*) FROM products ...),组装分页信息({ "total": 125, "page": 2, "size": 10 })。 最终数据结构示例:

json

json { "code": 200, "message": "查询成功", "data": [ { "id": 1, "name": "智能手机", "price": 2999, "category": "数码" }, // ... 其他数据 ], "pagination": { "total": 125, "page": 2, "size": 10 } }

  1. 后端返回 JSON 响应(步骤 9) 以 HTTP 200 状态码返回格式化后的 JSON 数据,完成后端处理流程。

  2. 前端解析响应数据(步骤 10) 前端接收响应后,解析 JSON 数据,分离data(业务数据)、pagination(分页信息)等字段,存储到临时变量或状态管理库(如 Vuex、Redux)。

  3. 前端更新状态管理(步骤 11) 将解析后的数据更新到组件状态(如 Vue 的data、React 的useState),或全局状态(如分页状态、筛选条件记忆)。例如:

    javascript

    javascript axios.post('/api/...').then(res => { this.products = res.data.data; this.pagination = res.data.pagination; });

  4. 前端动态渲染视图(步骤 12) 基于更新后的状态,使用前端框架指令(如 Vue 的v-for、React 的map)渲染数据:

    • 列表渲染:遍历products数组,生成产品卡片或表格行。
    • 分页控件:根据pagination信息渲染页码、上一页 / 下一页按钮。
    • 筛选反馈:展示当前筛选条件(如 “价格 1000-5000 元”),增强用户体验。

4.2 什么是JSON

JSON 是一种数据交换格式,通常用于前端和后端之间的数据交互。它采用文本格式,易于阅读和编写,同时也易于机器解析和生成。

JSON 数据是一种键-值对的结构,类似于字典或对象。每个键都是一个字符串,每个值可以是字符串、数字、布尔值、数组、对象等。

JSON 的一个重要特点是它是平台无关的,可以在各种编程语言中轻松解析和生成。

在前后端分离的架构中,前端通常通过发送包含 JSON 数据的 HTTP 请求,而后端将响应数据以 JSON 格式返回给前端,以实现数据交互。 JSON 已成为一种广泛采用的数据交互格式,用于传输配置信息、API 响应、日志记录等。

JSON示例:

{
  "name": "John Smith",
  "age": 25,
  "email": "john.smith@example.com",
  "address": {
    "street": "123 Main Street",
    "city": "Anytown",
    "state": "CA",
    "zip": "12345"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "555-555-1234"
    },
    {
      "type": "work",
      "number": "555-555-5678"
    }
  ],
  "active": true
}

4.3 HTTP请求

HTTP(Hypertext Transfer Protocol)定义了一组请求方法(也称为HTTP请求类型),它们用于指定客户端对服务器执行的操作。每个HTTP请求类型都有特定的用途和应用场景。以下是一些常见的HTTP请求类型以及它们的具体应用场景:

  • GET:用于从服务器获取数据。通常用于请求页面、图像、样式表和其他资源。它是幂等的,不应该对服务器状态产生影响。

  • POST:用于向服务器发送数据,通常用于提交表单数据、上传文件、创建资源等。它不是幂等的,因为多次相同的POST请求可能会产生不同的结果。

  • PUT:用于更新或创建指定资源。通常用于完全替代目标资源,因此需要客户端提供完整的更新数据。
  • PATCH:用于部分更新资源。通常用于更新资源的一部分,而不是整个资源。它允许客户端提供需要更新的字段。
  • DELETE:用于删除指定资源。通常用于删除资源,但要谨慎使用,因为它会永久删除资源。
  • HEAD:类似于GET,但只返回响应头部,而不包含响应主体。通常用于检查资源的元数据,如内容类型或内容长度。
  • OPTIONS:用于获取目标资源支持的通信选项。通常用于跨域资源共享(CORS)请求,以确定是否允许跨域请求。

五、如何使用Gin快速搭建一个web服务

参考链接:Gin Web Framework

1.本地自定义文件夹

本地上新建一个名为ginstudy的文件夹,使用VSCode打开。如果没有go.mod,需要执行以下命令在ginstudy目录下进行模块初始化,自动会在该目录下创建go.mod文件

$ go mod init ginstudy

2.安装gin

使用以下命令安装 gin

$ go get -u github.com/gin-gonic/gin

3.编写代码文件

定义main.go文件

package main

import (
  "net/http"

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

func main() {
  // 1首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
  r := gin.Default()
  // 2其次添加路由至路由引擎以及制定该路由的处理逻辑
  // 2.1 添加路由路径
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "message": "pong",
    })
  })
  r.GET("/", func(c *gin.Context) {
    // 状态码
    c.String(http.StatusOK, "你在访问的是首页")
  })
  // 3最后启动路由引擎
  r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

4.运行代码

$ go run .\main.go

5.访问测试

打开浏览器,输入127.0.0.1:8080/,提示输出"你在访问的是首页"字样

image-20231020211351357

打开浏览器,输入127.0.0.1:8080/ping,提示输出如下字样:

{
    "message": "pong"
}

image-20231020211542202

在前端测试的同时,访问日志也会在后台显现出来

image-20231020211731978

六、Gin数据交互JSON串内容规范

1.编写代码文件

基于上面内容重新定义main.go文件

package main

import (
    "net/http"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */

    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = "zq"
    userInfo["age"] = "18"
    userInfo["address"] = "北京"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.访问测试

打开浏览器,输入127.0.0.1:8080/api/user/info,提示输出如下内容信息

{
  "address": "北京",
  "age": "18",
  "name": "zq"
}

七、Gin使用结构体返回数据给前端

1.编写代码文件

基于上面内容重新定义main.go文件

package main

import (
    "net/http"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */

    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = "zq"
    userInfo["age"] = "18"
    userInfo["address"] = "北京"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.访问测试

打开浏览器,输入http://127.0.0.1:8080/api/user/info/WithStruct,提示输出如下内容信息

{
  "username": "zq",
  "age": 18,
  "address": "北京"
}

八、Gin配置POST类型的路由

1.编写代码文件

基于上面内容重新定义main.go文件

package main

import (
    "net/http"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */

    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = "zq"
    userInfo["age"] = "18"
    userInfo["address"] = "北京"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

func addUser(c *gin.Context) {
    c.String(http.StatusOK, "这是一个POST请求")

}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.访问测试

打开浏览器,输入http://127.0.0.1:8080/api/user/info/WithStruct,提示404报错信息,这是因为浏览器默认都是GET请求。

4.使用Postman工具进行POST请求测试

填写Uri内容:http://127.0.0.1:8080/api/user/add后,点击【Send】,观察到返回回显内容为"这是一个POST请求"

Postman下载链接:Postman: The World's Leading API Platform | Sign Up for Free

image-20250516082852888

5.再次编写代码文件

基于上面内容重新定义main.go文件,添加addArticle函数

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

    r.POST("/api/addArticle/add", addArticle)

完整代码配置文件如下

package main

import (
    "net/http"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */

    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = "zq"
    userInfo["age"] = "18"
    userInfo["address"] = "北京"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

func addUser(c *gin.Context) {
    c.String(http.StatusOK, "这是一个POST请求")
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/addArticle/add", addArticle)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

6.使用Postman工具进行POST请求测试

填写Uri内容:http://127.0.0.1:8080/api/addArticle/add后,点击【Send】,观察到返回回显内容如下

{
    "message": "创建成功",
    "status": 200
}

九、Gin获取GET请求参数

1.编写代码文件

基于上面内容重新定义main.go文件,添加GET请求参数

package main

import (
    "fmt"
    "net/http"
    "strconv"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    address := c.Query("address")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

func addUser(c *gin.Context) {
    c.String(http.StatusOK, "这是一个POST请求")
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/addArticle/add", addArticle)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.访问测试

打开浏览器,输入http://127.0.0.1:8080/api/user/info/WithStruct,提示404报错信息,这是因为浏览器默认都是GET请求。

4.使用Postman工具进行GET请求测试

填写Uri内容:http://127.0.0.1:8080/api/user后,在【Params】下面定义下面的Key和Value后,url会变为http://127.0.0.1:8080/api/user/info?username=zq&age=18&address=北京&gen=man&id=1

Key Value
username zq
age 18
address 北京
gen man
ID 1

image-20250516210909079

点击【Send】,返回回显内容如下:

{
    "address": "\"北京\"",
    "age": "18",
    "name": "zq"
}

控制台回显内容如下:

...
查询用户的ID是:  1
拿到了用户名:  zq
拿到了地址:  北京
拿到了年龄:  18
...

十、Gin获取POST请求参数

10.1 form-data类型

1.编写代码文件

基于上面内容重新定义main.go文件,添加form-data类型的POST请求参数

package main

import (
    "fmt"
    "net/http"
    "strconv"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2其次添加路由至路由引擎以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/addArticle/add", addArticle)
    // 3最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行POST请求测试

第一次测试(address使用默认值)

填写Uri内容:http://127.0.0.1:8080/api/user/add后,点击【Body】后,选择"form-data",定义下面的Key和Value后,点击【Send】

Key Value
username zq
age 18

image-20250516213523740

回显内容为:

{
    "data": {
        "username": "zq",
        "age": 18,
        "address": "三亚"
    },
    "message": "创建成功",
    "status": 200
}

第二次测试(address使用指定值)

填写Uri内容:http://127.0.0.1:8080/api/user/add后,点击【Body】后,选择"form-data",定义下面的Key和Value后,点击【Send】

Key Value
username zq
age 18
address 北京

image-20250516213759077

回显内容为:

{
    "data": {
        "username": "zq",
        "age": 18,
        "address": "北京"
    },
    "message": "创建成功",
    "status": 200
}

10.2 JSON类型

拿到json串之后,也有两种处理方式:

  1. 转成一个map
  2. 模型绑定:可以将请求的数据直接绑定到结构体

10.2.1 转成一个map

1.编写代码文件

基于上面内容重新定义main.go文件

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)
    c.JSON(http.StatusOK, m)
    // 2.赋值给结构体
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/addArticle/add", addArticle)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行POST请求测试

填写Uri内容:http://127.0.0.1:8080/api/user/addUserByJson后,点击【Body】后,选择"raw",填写下面内容后,点击【Send】

{"username": "zq","age": 18}

image-20250516215837621

回显内容

{
    "age": 18,
    "username": "zq"
}

10.2.2 模型绑定

1.编写代码文件

基于上面内容重新定义main.go文件,添加GET请求参数

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)

    c.JSON(http.StatusOK, m)
}

// 常用:模型绑定:可以将请求的数据直接绑定到结构体
func addUserBindStruct(c *gin.Context) {
    userInfo := UserInfo{}
    if err := c.ShouldBindJSON(&userInfo); err != nil {
        // 说明绑定失败
        fmt.Println("数据绑定失败:", err.Error())
        c.JSON(http.StatusOK, gin.H{
            "message": "参数格式不正确",
            "status":  500,
        })
    } else {
        c.JSON(http.StatusOK, userInfo)
    }
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })

    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/user/addUserBindStruct", addUserBindStruct)
    r.POST("/api/addArticle/add", addArticle)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行POST请求测试

第一次测试

填写Uri内容:http://127.0.0.1:8080/api/user/addUserBindStruct后,点击【Body】后,选择"raw",填写下面内容后,点击【Send】

{"username": "zq","age": 18}

image-20250516222218819

回显内容

{
    "username": "zq",
    "age": 18,
    "address": ""
}

第二次测试

填写Uri内容:http://127.0.0.1:8080/api/user/addUserBindStruct后,点击【Body】后,选择"raw",填写内容为空后,点击【Send】

image-20250516222432115

回显内容

{
    "message": "参数格式不正确",
    "status": 500
}

十一、Gin路由分组

1.编写代码文件

基于上面内容重新定义main.go文件

添加如下代码

    apiGroup := r.Group("/api")
    productGroup := apiGroup.Group("/product")
    productGroup.GET("/get", getUserInfo)
    productGroup.POST("/add", addUser)

完整代码:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)

    c.JSON(http.StatusOK, m)
}

// 常用:模型绑定:可以将请求的数据直接绑定到结构体
func addUserBindStruct(c *gin.Context) {
    userInfo := UserInfo{}
    if err := c.ShouldBindJSON(&userInfo); err != nil {
        // 说明绑定失败
        fmt.Println("数据绑定失败:", err.Error())
        c.JSON(http.StatusOK, gin.H{
            "message": "参数格式不正确",
            "status":  500,
        })
    } else {
        c.JSON(http.StatusOK, userInfo)
    }
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })
    // 使用独立的函数处理路由逻辑
    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/user/addUserBindStruct", addUserBindStruct)
    r.POST("/api/addArticle/add", addArticle)
    /*
        路由分组:
        /v1/api/user
                /add
                /delete
                /update
        /v1/api/product
                /add
                /delete
                /update
        /v1/api/order
                /add
                /delete
                /update
    */
    apiGroup := r.Group("/api")
    productGroup := apiGroup.Group("/product")
    productGroup.GET("/get", getUserInfo)
    productGroup.POST("/add", addUser)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行GET请求测试

填写Uri内容:http://127.0.0.1:8080/api/product/get后,点击【Send】

image-20250517080030164

Postman工具回显内容

{
    "address": "北京",
    "age": "",
    "name": ""
}

4.使用Postman工具进行POST请求测试

填写Uri内容:http://127.0.0.1:8080/api/product/add后,点击【Send】

image-20250517080242652

Postman工具回显内容

{
    "data": {
        "username": "",
        "age": 0,
        "address": "三亚"
    },
    "message": "创建成功",
    "status": 200
}

十二、Gin中间件

12.1 Gin中间件初体验

在接收请求时,需要对一些请求做一些额外的处理,比如有没有权限执行操作。这些处理叫做钩子函数,也叫中间件

12.1.1 全局中间件

1.编写代码文件

基于上面内容重新定义main.go文件

添加如下代码

// 定义中间件
func globalMiddleWare(c *gin.Context) {
    token := c.Query("token")
    if token == "" {
        // 没有带token,说明没有登录
        c.String(401, "用户未登录")
        c.Abort() //停止请求
    }
    fmt.Println("用户未登录")
}

    // 中间件1:添加一个全局的中间件
    // token
    apiGroup.Use(globalMiddleWare)

完整代码:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

// 定义中间件
func globalMiddleWare(c *gin.Context) {
    token := c.Query("token")
    if token == "" {
        // 没有带token,说明没有登录
        c.String(401, "用户未登录")
        c.Abort() //停止请求
    }
    fmt.Println("用户未登录")
}

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)

    c.JSON(http.StatusOK, m)
}

// 常用:模型绑定:可以将请求的数据直接绑定到结构体
func addUserBindStruct(c *gin.Context) {
    userInfo := UserInfo{}
    if err := c.ShouldBindJSON(&userInfo); err != nil {
        // 说明绑定失败
        fmt.Println("数据绑定失败:", err.Error())
        c.JSON(http.StatusOK, gin.H{
            "message": "参数格式不正确",
            "status":  500,
        })
    } else {
        c.JSON(http.StatusOK, userInfo)
    }
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })
    // 使用独立的函数处理路由逻辑
    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/user/addUserBindStruct", addUserBindStruct)
    r.POST("/api/addArticle/add", addArticle)
    /*
        路由分组:
        /v1/api/user
                /add
                /delete
                /update
        /v1/api/product
                /add
                /delete
                /update
        /v1/api/order
                /add
                /delete
                /update
    */
    /*
        gin中间件:在接收请求时,需要对一些请求做一些额外的处理,比如有没有权限执行操作。这些处理叫做钩子函数,也叫中间件
    */
    apiGroup := r.Group("/api")
    // 中间件1:添加一个全局的中间件
    // token
    apiGroup.Use(globalMiddleWare)
    productGroup := apiGroup.Group("/product")
    productGroup.GET("/get", getUserInfo)
    productGroup.POST("/add", addUser)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行GET请求测试

填写Uri内容:http://127.0.0.1:8080/api/product/get后,点击【Send】后,返回401并输出提示信息"用户未登录"

image-20250517082110259

12.1.2 局部中间件

1.编写代码文件

基于上面内容重新定义main.go文件

添加如下代码

// 定义局部中间件
func deleteMiddleWare(c *gin.Context) {
    user := c.Query("username")
    if user != "admin" {
        c.String(403, "用户没有权限执行该操作")
        c.Abort() //停止请求
    }
}

func deleteProduct(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "删除成功"
    c.JSON(http.StatusOK, returnData)
}

    // 中间件2:添加一个局部的中间件
    // 功能:当我们的username=admin的时候才能进行删除的操作
    productGroup.GET("/delete", deleteMiddleWare, deleteProduct)

完整代码:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

// 定义全局中间件
func globalMiddleWare(c *gin.Context) {
    token := c.Query("token")
    if token == "" {
        // 没有带token,说明没有登录
        c.String(401, "用户未登录")
        c.Abort() //停止请求
    }
    fmt.Println("用户未登录")
}

// 定义局部中间件
func deleteMiddleWare(c *gin.Context) {
    user := c.Query("username")
    if user != "admin" {
        c.String(403, "用户没有权限执行该操作")
        c.Abort() //停止请求
    }
}

func deleteProduct(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "删除成功"
    c.JSON(http.StatusOK, returnData)
}

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)

    c.JSON(http.StatusOK, m)
}

// 常用:模型绑定:可以将请求的数据直接绑定到结构体
func addUserBindStruct(c *gin.Context) {
    userInfo := UserInfo{}
    if err := c.ShouldBindJSON(&userInfo); err != nil {
        // 说明绑定失败
        fmt.Println("数据绑定失败:", err.Error())
        c.JSON(http.StatusOK, gin.H{
            "message": "参数格式不正确",
            "status":  500,
        })
    } else {
        c.JSON(http.StatusOK, userInfo)
    }
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })
    // 使用独立的函数处理路由逻辑
    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/user/addUserBindStruct", addUserBindStruct)
    r.POST("/api/addArticle/add", addArticle)
    /*
        路由分组:
        /v1/api/user
                /add
                /delete
                /update
        /v1/api/product
                /add
                /delete
                /update
        /v1/api/order
                /add
                /delete
                /update
    */
    /*
        gin中间件:在接收请求时,需要对一些请求做一些额外的处理,比如有没有权限执行操作。这些处理叫做钩子函数,也叫中间件
    */
    apiGroup := r.Group("/api")
    // 中间件1:添加一个全局的中间件
    // token
    apiGroup.Use(globalMiddleWare)
    productGroup := apiGroup.Group("/product")
    productGroup.GET("/get", getUserInfo)
    productGroup.POST("/add", addUser)
    // 中间件2:添加一个局部的中间件
    // 功能:当我们的username=admin的时候才能进行删除的操作
    productGroup.GET("/delete", deleteMiddleWare, deleteProduct)
    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行GET请求测试

第一次测试(非admin用户)

填写Uri内容:http://127.0.0.1:8080/api/product/delete?token=xxx&username=zq后,点击【Send】,Postman工具返回"用户没有权限执行该操作"消息

Key Value
token xx
username zq

image-20250517084247308

第二次测试(admin用户)

填写Uri内容:http://127.0.0.1:8080/api/product/delete?token=xxx&username=admin后,点击【Send】,返回内容如下:

{
    "message": "删除成功",
    "status": 200
}
Key Value
token xx
username admin

image-20250517084439051

12.1.3 多个中间件

1.编写代码文件

基于上面内容重新定义main.go文件

添加如下代码

// 定义多个中间件middleware1, middleware2, middleware3
func middleware1(c *gin.Context) {
    fmt.Println("middleware1")
}

func middleware2(c *gin.Context) {
    fmt.Println("middleware2")
}

func middleware3(c *gin.Context) {
    fmt.Println("middleware3")
}

    // 中间件3:配置多个中间件时,执行顺序采用先进先出的原则
    productGroup.GET("/list", middleware1, middleware2, middleware3, getUserInfo)

完整代码文件:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

// 定义全局中间件
func globalMiddleWare(c *gin.Context) {
    token := c.Query("token")
    if token == "" {
        // 没有带token,说明没有登录
        c.String(401, "用户未登录")
        c.Abort() //停止请求
    }
    fmt.Println("用户未登录")
}

// 定义局部中间件
func deleteMiddleWare(c *gin.Context) {
    user := c.Query("username")
    if user != "admin" {
        c.String(403, "用户没有权限执行该操作")
        c.Abort() //停止请求
    }
}

func deleteProduct(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "删除成功"
    c.JSON(http.StatusOK, returnData)
}

// 定义多个中间件middleware1, middleware2, middleware3
func middleware1(c *gin.Context) {
    fmt.Println("middleware1")
}

func middleware2(c *gin.Context) {
    fmt.Println("middleware2")
}

func middleware3(c *gin.Context) {
    fmt.Println("middleware3")
}

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)

    c.JSON(http.StatusOK, m)
}

// 常用:模型绑定:可以将请求的数据直接绑定到结构体
func addUserBindStruct(c *gin.Context) {
    userInfo := UserInfo{}
    if err := c.ShouldBindJSON(&userInfo); err != nil {
        // 说明绑定失败
        fmt.Println("数据绑定失败:", err.Error())
        c.JSON(http.StatusOK, gin.H{
            "message": "参数格式不正确",
            "status":  500,
        })
    } else {
        c.JSON(http.StatusOK, userInfo)
    }
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })
    // 使用独立的函数处理路由逻辑
    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/user/addUserBindStruct", addUserBindStruct)
    r.POST("/api/addArticle/add", addArticle)
    /*
        路由分组:
        /v1/api/user
                /add
                /delete
                /update
        /v1/api/product
                /add
                /delete
                /update
        /v1/api/order
                /add
                /delete
                /update
    */
    /*
        gin中间件:在接收请求时,需要对一些请求做一些额外的处理,比如有没有权限执行操作。这些处理叫做钩子函数,也叫中间件
    */
    apiGroup := r.Group("/api")
    // 中间件1:添加一个全局的中间件
    // token
    apiGroup.Use(globalMiddleWare)
    productGroup := apiGroup.Group("/product")
    productGroup.GET("/get", getUserInfo)
    productGroup.POST("/add", addUser)
    // 中间件2:添加一个局部的中间件
    // 功能:当我们的username=admin的时候才能进行删除的操作
    productGroup.GET("/delete", deleteMiddleWare, deleteProduct)
    // 中间件3:配置多个中间件时,执行顺序采用先进先出的原则
    productGroup.GET("/list", middleware1, middleware2, middleware3, getUserInfo)

    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行GET请求测试

填写Uri内容:http://127.0.0.1:8080/api/product/list?token=xxx后,点击【Send】,Postman工具返回内容如下:

{
    "address": "北京",
    "age": "",
    "name": "admin"
}
Key Value
token xx

image-20250517085611505

命令行界面回显内容如下,证实了如果存在多个中间件,遵循"先进先出"的规则

...
middleware1
middleware2
middleware3
...

12.2 Gin中间件Next和Abort方法

12.2.1 Next方法

你可以把Next方法想象成接力比赛中的 “交接棒” 动作。当一个中间件调用Next时,就相当于把处理请求的 “接力棒” 交给后续的中间件或者路由处理函数。后续的中间件和处理函数执行完毕后,还会回到当前的中间件,继续执行Next之后的代码。

1.编写代码文件

基于上面内容重新定义main.go文件

修改如下代码

func middleware1(c *gin.Context) {
    fmt.Println("middleware1")
    c.Next()
    fmt.Println("middleware1 next")
}

func middleware2(c *gin.Context) {
    fmt.Println("middleware2")
    c.Next()
    fmt.Println("middleware2 next")
}

完整代码文件:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

// 定义全局中间件
func globalMiddleWare(c *gin.Context) {
    token := c.Query("token")
    if token == "" {
        // 没有带token,说明没有登录
        c.String(401, "用户未登录")
        c.Abort() //停止请求
    }
    fmt.Println("用户未登录")
}

// 定义局部中间件
func deleteMiddleWare(c *gin.Context) {
    user := c.Query("username")
    if user != "admin" {
        c.String(403, "用户没有权限执行该操作")
        c.Abort() //停止请求
    }
}

func deleteProduct(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "删除成功"
    c.JSON(http.StatusOK, returnData)
}

// 定义多个中间件middleware1, middleware2, middleware3
func middleware1(c *gin.Context) {
    fmt.Println("middleware1")
    c.Next()
    fmt.Println("middleware1 next")
}

func middleware2(c *gin.Context) {
    fmt.Println("middleware2")
    c.Next()
    fmt.Println("middleware2 next")
}

func middleware3(c *gin.Context) {
    fmt.Println("middleware3")
}

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)

    c.JSON(http.StatusOK, m)
}

// 常用:模型绑定:可以将请求的数据直接绑定到结构体
func addUserBindStruct(c *gin.Context) {
    userInfo := UserInfo{}
    if err := c.ShouldBindJSON(&userInfo); err != nil {
        // 说明绑定失败
        fmt.Println("数据绑定失败:", err.Error())
        c.JSON(http.StatusOK, gin.H{
            "message": "参数格式不正确",
            "status":  500,
        })
    } else {
        c.JSON(http.StatusOK, userInfo)
    }
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })
    // 使用独立的函数处理路由逻辑
    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/user/addUserBindStruct", addUserBindStruct)
    r.POST("/api/addArticle/add", addArticle)
    /*
        路由分组:
        /v1/api/user
                /add
                /delete
                /update
        /v1/api/product
                /add
                /delete
                /update
        /v1/api/order
                /add
                /delete
                /update
    */
    /*
        gin中间件:在接收请求时,需要对一些请求做一些额外的处理,比如有没有权限执行操作。这些处理叫做钩子函数,也叫中间件
    */
    apiGroup := r.Group("/api")
    // 中间件1:添加一个全局的中间件
    // token
    apiGroup.Use(globalMiddleWare)
    productGroup := apiGroup.Group("/product")
    productGroup.GET("/get", getUserInfo)
    productGroup.POST("/add", addUser)
    // 中间件2:添加一个局部的中间件
    // 功能:当我们的username=admin的时候才能进行删除的操作
    productGroup.GET("/delete", deleteMiddleWare, deleteProduct)
    // 中间件3:配置多个中间件时,执行顺序采用先进先出的原则
    productGroup.GET("/list", middleware1, middleware2, middleware3, getUserInfo)

    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行GET请求测试

填写Uri内容:http://127.0.0.1:8080/api/product/list?token=xxx后,点击【Send】,Postman工具返回内容如下:

{
    "address": "北京",
    "age": "",
    "name": "admin"
}
Key Value
token xx

image-20250517085611505

命令行界面回显内容如下,观察到先打印middleware2 next再打印middleware1 next,遵循"先进后出"的规则

...
middleware2 next
middleware1 next
...

12.2.2 Abort方法

Abort方法的作用类似于 “紧急刹车”。一旦在中间件里调用了Abort,Gin 就会马上停止执行后续的中间件和路由处理函数,请求的处理流程会就此终止。不过,已经执行过的中间件中Next之后的代码还是会正常执行。

1.编写代码文件

基于上面内容重新定义main.go文件

修改如下代码

func middleware1(c *gin.Context) {
    fmt.Println("middleware1")
    // c.Next()
    c.Abort()
    fmt.Println("middleware1 next")
}

func middleware2(c *gin.Context) {
    fmt.Println("middleware2")
    // c.Next()
    fmt.Println("middleware2 next")
}

完整代码文件:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

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

// 定义全局中间件
func globalMiddleWare(c *gin.Context) {
    token := c.Query("token")
    if token == "" {
        // 没有带token,说明没有登录
        c.String(401, "用户未登录")
        c.Abort() //停止请求
    }
    fmt.Println("用户未登录")
}

// 定义局部中间件
func deleteMiddleWare(c *gin.Context) {
    user := c.Query("username")
    if user != "admin" {
        c.String(403, "用户没有权限执行该操作")
        c.Abort() //停止请求
    }
}

func deleteProduct(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "删除成功"
    c.JSON(http.StatusOK, returnData)
}

// 定义多个中间件middleware1, middleware2, middleware3
func middleware1(c *gin.Context) {
    fmt.Println("middleware1")
    // c.Next()
    c.Abort()
    fmt.Println("middleware1 next")
}

func middleware2(c *gin.Context) {
    fmt.Println("middleware2")
    // c.Next()
    fmt.Println("middleware2 next")
}

func middleware3(c *gin.Context) {
    fmt.Println("middleware3")
}

func getUserInfo(c *gin.Context) {
    /*
        {
            "status": 200 //请求我已经收到了,处理也是正常的
            "message": "获取成功", //msg
            "data": {
                "name": "zq",
                "age": "18",
                "address": "北京"
            }
        }
    */
    // 接收请求参数
    u := c.Query("username")
    // address := c.Query("address")
    // 配置参数的默认值
    address := c.DefaultQuery("address", "北京")
    a := c.Query("age")
    id := c.Query("id")
    idNumber, _ := strconv.Atoi(id)
    fmt.Println("查询用户的ID是: ", idNumber)
    // 去数据库中去查询用户信息
    fmt.Println("拿到了用户名: ", u)
    fmt.Println("拿到了地址: ", address)
    fmt.Println("拿到了年龄: ", a)
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "查询成功"
    userInfo := make(map[string]string)
    userInfo["name"] = u
    userInfo["age"] = a
    userInfo["address"] = address
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, userInfo)

}

/*
1.用户发来请求,查询用户信息username="xxx"
2.我们的后端是不是要根据用户发来请求,获取到他要查询哪个用户的信息
3.后端从数据库查询到xxx用户详细信息
4.查询数据绑定到结构体,gorm,orm
*/

type UserInfo struct {
    // json:后面无空格
    Username string `json:"username"`
    Age      int    `json:"age"`
    Address  string `json:"address"`
}

// returnData
type ReturnData struct {
    Status  int
    Message string
    Data    UserInfo `json:"data"`
}

// POST数据提交格式有两种:
// JSON()
// form-data

func addUser(c *gin.Context) {
    // form-data使用
    u := c.PostForm("username")
    age, _ := strconv.Atoi(c.PostForm("age"))
    // 配置POST请求的默认值
    address := c.DefaultPostForm("address", "三亚")
    userInfo := UserInfo{}
    userInfo.Address = address
    userInfo.Age = age
    userInfo.Username = u
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    returnData["data"] = userInfo
    c.JSON(http.StatusOK, returnData)
}

func addUserByJson(c *gin.Context) {
    jsonData, _ := c.GetRawData()
    // 拿到json串之后,也有两种处理
    // 1.转成一个map
    var m map[string]interface{}
    json.Unmarshal(jsonData, &m)

    c.JSON(http.StatusOK, m)
}

// 常用:模型绑定:可以将请求的数据直接绑定到结构体
func addUserBindStruct(c *gin.Context) {
    userInfo := UserInfo{}
    if err := c.ShouldBindJSON(&userInfo); err != nil {
        // 说明绑定失败
        fmt.Println("数据绑定失败:", err.Error())
        c.JSON(http.StatusOK, gin.H{
            "message": "参数格式不正确",
            "status":  500,
        })
    } else {
        c.JSON(http.StatusOK, userInfo)
    }
}

func addArticle(c *gin.Context) {
    returnData := make(map[string]interface{})
    returnData["status"] = 200
    returnData["message"] = "创建成功"
    c.JSON(http.StatusOK, returnData)
}

// 使用结构体返回数据给前端
func returnDataWithStruct(c *gin.Context) {
    userInfo := UserInfo{
        Username: "zq",
        Age:      18,
        Address:  "北京",
    }
    c.JSON(http.StatusOK, userInfo)
}

func main() {
    // 1、首先创建一个路由引擎(方便之后可以在路由引擎中添加一些路由和路由的处理逻辑)
    r := gin.Default()
    // 2、其次添加路由至路由引擎,以及制定该路由的处理逻辑
    // 2.1 添加路由路径
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.GET("/", func(c *gin.Context) {
        // 状态码
        c.String(http.StatusOK, "你在访问的是首页")
    })
    // 使用独立的函数处理路由逻辑
    r.GET("/api/user/info", getUserInfo)
    r.GET("/api/user/info/WithStruct", returnDataWithStruct)
    r.POST("/api/user/add", addUser)
    r.POST("/api/user/addUserByJson", addUserByJson)
    r.POST("/api/user/addUserBindStruct", addUserBindStruct)
    r.POST("/api/addArticle/add", addArticle)
    /*
        路由分组:
        /v1/api/user
                /add
                /delete
                /update
        /v1/api/product
                /add
                /delete
                /update
        /v1/api/order
                /add
                /delete
                /update
    */
    /*
        gin中间件:在接收请求时,需要对一些请求做一些额外的处理,比如有没有权限执行操作。这些处理叫做钩子函数,也叫中间件
    */
    apiGroup := r.Group("/api")
    // 中间件1:添加一个全局的中间件
    // token
    apiGroup.Use(globalMiddleWare)
    productGroup := apiGroup.Group("/product")
    productGroup.GET("/get", getUserInfo)
    productGroup.POST("/add", addUser)
    // 中间件2:添加一个局部的中间件
    // 功能:当我们的username=admin的时候才能进行删除的操作
    productGroup.GET("/delete", deleteMiddleWare, deleteProduct)
    // 中间件3:配置多个中间件时,执行顺序采用先进先出的原则
    productGroup.GET("/list", middleware1, middleware2, middleware3, getUserInfo)

    // 3、最后启动路由引擎
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

2.运行代码

$ go run .\main.go

3.使用Postman工具进行GET请求测试

填写Uri内容:http://127.0.0.1:8080/api/product/list?token=xxx后,点击【Send】,Postman工具返回内容如下:

{
    "address": "北京",
    "age": "",
    "name": "admin"
}
Key Value
token xx

image-20250517085611505

命令行界面回显内容如下,观察到打印完middleware1和middleware1 next

后面的内容不再进行打印

...
middleware1
middleware1 next
...

十一、项目工程化

11.1 什么是项目工程化

项目工程化就是将代码根据不同的功能和环节,进行规范化、标准化、系统化及模块化,从而提高开发效率、降低开发成本、提高软件质量和可维护性。

11.2 如何实现项目工程化

实现项目工程化步骤:

  1. 导入必须的依赖包
  2. 加载程序配置
  3. 配置程序的路由规则
  4. 实现路由的处理逻辑
  5. 配置路由的中间件
  6. 数据的增删改查操作
  7. 工具类封装

项目工程化规范:

  1. config:配置层
  2. routers:路由层
  3. controllers:控制器层
  4. middlewares:中间件层
  5. models:模型层
  6. utils:工具类层

11.3 项目工程化实践

1、新建一个名为"project-demo"的文件夹并通过VScode软件打开

2、在project-demo文件夹下面执行go mod init project-demo初始化,生成对应的go.mod文件

$ go mod init project-demo

3、在project-demo文件夹下面新建下面文件夹,并在对应的文件夹下面新建同名的go文件

  • config
  • config.go
  • routers
  • routers.go
  • controllers
  • controllers.go
  • middlewares
  • middlewares.go
  • models
  • models.go
  • utils
  • utils.go

4、简单定义上述中的go文件

config.go文件

// 存放程序的配置信息
package config

import "fmt"

func init() {
    fmt.Println("开始加载程序配置")

}

routers.go文件

// 路由层 配置程序的路由规则
package routers

controllers.go文件

// 控制器层
package controllers

middlewares.go文件

// 实现路由的处理逻辑
package middlewares

models.go文件

// 模型层
package models

utils.go文件

// 工具层
package utils

5、执行命令安装gin

$ go get github.com/gin-gonic/gin

6、在project-demo文件夹下面新建main.go文件并定义如下内容信息

// 项目的总入口
package main

import (
    _ "project-demo/config"

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

func main() {
    // 1.加载程序的配置
    // 2.配置gin
    r := gin.Default()
    r.Run()
}

7、新建README.md文件,设置一些内容用来描述项目的信息

## xxx

8、运行代码

$ go run .\main.go

观察回显内容,观察到已经成功打印出"开始加载程序配置"信息

...
开始加载程序配置
...