一、了解gin
Gin 是一个用 Golang编写的 高性能的web 框架, 由于http路由的优化,速度提高了近 40 倍。 Gin的特
点就是封装优雅、API友好。
特性类别 | 具体说明 |
---|---|
性能表现 | 基于 Radix 树实现路由,匹配速度极快,性能接近原生 Go HTTP 服务器 |
API 设计 | 接口简洁直观,学习成本低,如 gin.Default() 快速创建实例,GET() / POST() 注册路由 |
中间件支持 | 内置日志、恢复 panic、静态文件服务等中间件,支持自定义中间件实现扩展功能 |
路由管理 | 支持路由分组(Group),便于按模块 / 版本 / 权限组织路由(如 /v1/* 、/admin/* ) |
参数处理 | 提供便捷的请求参数绑定(ShouldBind),支持 JSON / 表单 / Query 等格式,可配合标签验证 |
数据响应 | 内置高效 JSON 处理,c.JSON() 快速返回 JSON 响应,性能优于标准库 |
错误处理 | 简化异常处理流程,c.AbortWithError() 等方法可快速返回结构化错误响应 |
模板渲染 | 集成 HTML 模板引擎,支持模板继承、自定义函数,适合传统 Web 应用开发 |
扩展性 | 设计灵活,易于与 ORM、缓存、认证等工具集成,生态丰富 |
轻量特性 | 核心代码简洁,依赖少,编译后二进制体积小,适合微服务或轻量应用 |
下载gin包
go get -u github.com/gin-gonic/gin
测试一下
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(200, map[string]interface{}{
"name": "彪子",
"age": 15,
})
})
router.Run(":8080") // 不写默认,8080,监听并在 0.0.0.0:8080 上启动服务
}
①返回字符串
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
// 类似printf的写法
router.GET("/", func(c *gin.Context) {
c.String(200, "我的值为:%v", "啊啊啊")
})
router.Run(":8080") // 不写默认,8080,监听并在 0.0.0.0:8080 上启动服务
}
②返回json
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(200, map[string]interface{}{
"name": "彪子",
"age": 15,
})
})
router.Run(":8080") // 不写默认,8080,监听并在 0.0.0.0:8080 上启动服务
}
或者可以将map类型用gin自带的,和我们写的一样
router.GET("/", func(c *gin.Context) {
c.JSON(200,gin.H{
"name": "彪子",
"age": 15,
})
})
也可以使用结构体
package main
import (
"github.com/gin-gonic/gin"
)
type Ariticle struct {
Title string
Content string
}
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*") //配置模板文件
router.GET("/", func(c *gin.Context) {
article := &Ariticle{
Title: "这是标题",
Content: "这是内容",
}
c.JSON(200, article)
})
router.Run(":8080") // 不写默认,8080,监听并在 0.0.0.0:8080 上启动服务
}
二、html渲染
除了上诉的返回字符串和json数据,我们也可以渲染html。
如果模板目录下存在多个目录,每个目录下含有不同的html文件,那么我们这样操作
在html定义值
{{ $title := .Title }}
<h1>{{ $title }}</h1>
{{ $sum := 10 + 20 }}
<p>总和:{{ $sum }}</p>
注释
{{/* 这是模板注释,浏览器中看不到 */}}
条件判断
{{ if .IsLogin }}
<p>欢迎回来,{{ .Username }}</p>
{{ else }}
<a href="/login">请登录</a>
{{ end }}
<!-- 否定判断 -->
{{ if not .IsEmpty }}
<p>内容不为空</p>
{{ end }}
循环遍历
<!-- 遍历切片/数组 -->
<ul>
{{ range .Items }}
<li>{{ . }}</li> <!-- 内部的 . 代表当前元素 -->
{{ end }}
</ul>
<!-- 带索引的遍历 -->
<!-- 使用 {{ . }} 时注意上下文变化(如在 range 内部,. 代表当前元素) -->
{{ range $index, $item := .Items }}
<p>{{ $index }}: {{ $item.Name }}</p>
{{ end }}
<!-- 遍历映射 -->
{{ range $key, $value := .UserInfo }}
<p>{{ $key }}: {{ $value }}</p>
{{ end }}
<!-- 空集合处理 -->
{{ range .Items }}
<li>{{ . }}</li>
{{ else }}
<p>没有数据</p> <!-- 当集合为空时执行 -->
{{ end }}
原始html输出
<!-- 假设 .Content 包含 <strong>文本</strong> -->
{{ .Content }} <!-- 输出:<strong>文本</strong> -->
{{ .Content | safeHTML }} <!-- 输出:<strong>文本</strong> -->
三、gin中get/post获取值
get请求
name:=c.Query("name")
age:=c.DefaultQuery("age","18")
post请求
name := c.PostForm("name")
age := c.DefaultPostForm("age", "18") // 默认值为string类型
将get/post请求的值绑定都按结构体
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// 定义接收参数的结构体
type UserQuery struct {
// form标签指定对应的查询参数名
Name string `form:"name"`
Page int `form:"page" binding:"default=1"` // 默认值1
}
func main() {
r := gin.Default()
// 注册路由
r.GET("/user", func(c *gin.Context) {
var query UserQuery
// 绑定GET请求参数到结构体
if err := c.ShouldBindQuery(&query); err == nil {
// 绑定成功,使用参数
c.JSON(http.StatusOK, query)
return
}
})
// 启动服务
r.Run(":8080")
}
动态路由传值
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 注册路由
r.GET("/:uid", func(c *gin.Context) {
uid:=c.Param("uid")
// 绑定GET请求参数到结构体
c.String(http.StatusOK, "uid: %s", uid)
})
// 启动服务
r.Run(":8080")
}
四、路由分组
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 1. 基础路由分组 - 添加路径前缀
userGroup := r.Group("/user")
{
// 实际路径: /user
userGroup.GET("", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "用户列表"})
})
// 实际路径: /user/:id
userGroup.GET("/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"message": "查看用户", "id": id})
})
// 实际路径: /user/create
userGroup.POST("/create", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "创建用户"})
})
}
// 2. 带中间件的路由分组
// 创建需要身份验证的路由分组
authGroup := r.Group("/admin")
// 为该分组添加中间件(如身份验证)
authGroup.Use(AuthMiddleware())
{
// 实际路径: /admin/dashboard,访问前会经过AuthMiddleware验证
authGroup.GET("/dashboard", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "管理员面板"})
})
// 实际路径: /admin/settings
authGroup.GET("/settings", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "系统设置"})
})
}
// 3. 嵌套路由分组
v1 := r.Group("/api/v1")
{
// 嵌套用户分组,实际路径: /api/v1/users
users := v1.Group("/users")
{
users.GET("", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "API v1 用户列表"})
})
}
// 嵌套文章分组,实际路径: /api/v1/articles
articles := v1.Group("/articles")
{
articles.GET("", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "API v1 文章列表"})
})
}
}
r.Run(":8080")
}
// 定义一个身份验证中间件示例
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 实际应用中这里会验证token或session
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未授权访问"})
c.Abort() // 终止请求处理链
return
}
// 验证通过,继续处理请求
c.Next()
}
}
如果把不同路由放到不同文件
五、中间件
c.Next() 的核心作用
流程控制:在中间件中调用 c.Next() 会暂停当前中间件的执行,先执行后续的中间件或路由处理器。
前后置操作: c.Next() 调用前的代码可以视为 “前置操作”(如权限验证、日志记录开始),调用后的代码可以视为 “后置操作”(如日志记录结束、响应处理)。
全局中间件:可以使后续的所有请求都走此中间件
func main() {
r := gin.Default()
r.Use(mid1, mid2)
// 实际路径: /user
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "成功"})
})
r.Run(":8080")
}
在分组路由后配置中间件的两种方式
// 方式1
a:=r.Group("admin",mid1, mid2)
// 方式2
a:=r.Group("admin")
a.Use(mid1, mid2)
在不同中间件传递值
gin中间件中使用grountine
六、文件上传
单个文件上传
package main
import (
"net/http"
"os"
"path/filepath"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 单个文件上传
r.POST("/upload", func(c *gin.Context) {
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "获取文件失败: " + err.Error(),
})
return
}
// 定义保存路径(当前目录下的uploads文件夹)
savePath := "./uploads"
// 确保保存目录存在
if err := ensureDir(savePath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "创建保存目录失败: " + err.Error(),
})
return
}
// 构建完整的保存路径(目录+文件名)
dst := filepath.Join(savePath, file.Filename)
// 使用SaveUploadedFile保存文件
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "保存文件失败: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "文件上传成功",
"filename": file.Filename,
"savePath": dst,
})
})
r.Run(":8080")
}
// 确保目录存在,不存在则创建
func ensureDir(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return os.MkdirAll(dir, 0755)
}
return nil
}
多文件上传
package main
import (
"net/http"
"os"
"path/filepath"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 多个文件上传
r.POST("/upload-multiple", func(c *gin.Context) {
// 解析多部分表单
form, err := c.MultipartForm()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "解析表单失败: " + err.Error(),
})
return
}
// 获取所有名为"file"的文件
files := form.File["file"]
if len(files) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "未找到上传的文件",
})
return
}
// 定义保存路径
savePath := "./uploads"
if err := ensureDir(savePath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "创建保存目录失败: " + err.Error(),
})
return
}
// 保存所有文件
uploadedFiles := make([]string, 0, len(files))
for _, file := range files {
dst := filepath.Join(savePath, file.Filename)
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "保存文件 " + file.Filename + " 失败: " + err.Error(),
})
return
}
uploadedFiles = append(uploadedFiles, file.Filename)
}
c.JSON(http.StatusOK, gin.H{
"message": "所有文件上传成功",
"fileCount": len(uploadedFiles),
"filenames": uploadedFiles,
})
})
r.Run(":8080")
}
// 确保目录存在,不存在则创建
func ensureDir(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return os.MkdirAll(dir, 0755)
}
return nil
}
七、gin中的cookie
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 设置Cookie
r.GET("/set_cookie", func(c *gin.Context) {
// 设置一个名为"username",值为"JohnDoe",1小时后过期的Cookie
c.SetCookie("username", "JohnDoe", 3600, "/", "example.com", false, true)
c.JSON(http.StatusOK, gin.H{"message": "Cookie set successfully"})
})
// 获取 Cookie
r.GET("/get_cookie", func(c *gin.Context) {
cookie, err := c.Cookie("username")
if err != nil {
c.JSON(http.StatusOK, gin.H{"message": "Cookie not found", "error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Cookie retrieved successfully", "username": cookie})
})
//删除 Cookie
r.GET("/delete_cookie", func(c *gin.Context) {
// 将过期时间设置为1小时前,以达到删除Cookie的效果
c.SetCookie("username", "", -3600, "/", "example.com", false, true)
c.JSON(http.StatusOK, gin.H{"message": "Cookie deleted successfully"})
})
// 遍历Cookie
r.GET("/list_cookies", func(c *gin.Context) {
cookies := c.Request.Cookies()
var cookieList []string
for _, cookie := range cookies {
cookieList = append(cookieList, cookie.Name+"="+cookie.Value)
}
c.JSON(http.StatusOK, gin.H{"message": "List of cookies", "cookies": strings.Join(cookieList, ", ")})
})
r.Run(":8080")
}