Gin 框架以及项目使用

7次阅读
没有评论

gin 官网文档:https://gin-gonic.com/zh-cn/docs/

Gin 结合了 Express.js 式路由的简洁与 Go 的高性能,特别适用于:

  • 构建高吞吐量的 REST AP
  • 开发需要高并发的微服务
  • 创建响应速度极快的 Web 应用
  • 用极少的样板代码快速原型化 Web 服务

安装与设置

设置镜像地址拉取

# 设置几个镜像地址保险
go env -w GOPROXY=https://goproxy.cn,https://mirrors.aliyun.com/goproxy/,direct
# 阿里云地址
https://mirrors.aliyun.com/goproxy/
# 腾讯云地址
https://mirrors.cloud.tencent.com/go/
# 华为云地址
https://repo.huaweicloud.com/repository/goproxy/

# 私有云地址仓库
go env -w GOPRIVATE=git.mycompany.com,github.com/myorg/*

安装依赖

# 初始化 mod
go mod init < 项目名称 >
go get -u github.com/gin-gonic/gin
# 热更新下载,一定要使用 go install 而不是 go get:go install 会生成可执行文件,而 go get 主要用于管理项目依赖
go install github.com/pilu/fresh@latest
# 然后在 powershell 执行,不要在 cmd 执行会报 fresh 未定义全局变量
fresh

项目开发

简单示例

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

func main() {
	// 创建带默认中间件(日志与恢复)的 Gin 路由器
	r := gin.Default()
	// 定义简单的 GET 路由
	r.GET("/", func(c *gin.Context) {
		// 返回 JSON 响应
		c.JSON(http.StatusOK, "hello world")
	})

	r.GET("/json", func(c *gin.Context) {
		// 返回 JSON 响应
		c.JSON(http.StatusOK, map[string]interface{}{
			"code":    200,
			"message": "success",
			"data": map[string]interface{}{
				"name": "张三",
				"age":  18,
			},
		})
	})
	// 默认端口 8080 启动服务器
	// 监听 0.0.0.0:8080(Windows 下为 localhost:8080)r.Run(":8000")
}

gin.Default()默认使用了 Logger 和 Recovery 中间件,其中:

  • Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN MODE=release。
  • Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500 响应码

如果不想使用上面两个默认的中间件,可以使用 gin.New()新建一个没有任何默认中间件的路由。

动态路由

// get 路由获取参数
r.GET("/query", func(c *gin.Context) {username := c.Query("username")
	page := c.DefaultQuery("page", "1")
	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"page":     page,
	})
})
// 动态路由
r.GET("/query/:cid", func(c *gin.Context) {p := c.Param("cid")
})
// post 路由
r.POST("/postmethod", func(c *gin.Context) {name := c.PostForm("name")
	age := c.DefaultPostForm("age", "22")
	c.JSON(http.StatusOK, gin.H{
		"username": name,
		"age":      age,
	})
})

静态资源服务

root
|__static
|__main.go

// 配置静态文件服务:URL 路径 /static 映射到 ./static 目录
r.Static("/static", "./static")

其他支持方式

# 支持 xml 类似 html 的文件返回
r.GET("/xml",func(c *gin.context){xmlSliceData,:= c.GetRawData()
  err := xml.Unmanshal(xmlsliceData, &article)
  if err!=nil
  c.XML(http.statusok, gin.H{
    "success": true,
    "msg":"你好 gin 我是一个 xm1"
  })
})
# 支持 jsonp 支持跨越 有 xxs 攻击风险
r.GET("/jsonp",func(c *gin.context){
  c.JSONP(http.statusok, gin.H{
    "success": true,
    "msg":"你好 gin 我是一个 JSONP 数据"
  })
})
# 支持 HTML 模板编译 现在前后端分离基本不使用
// 一次性加载所有需要的模板文件 r.LoadHTMLFiles("template/*.html")
// 者多文件夹 r.LoadHTMLFiles("template/**/**/*.html")
r.LoadHTMLFiles("template/index.html", "template/next/mn.html")
r.GET("/index", func(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", gin.H{
               // 第三个数据是传入 html 模板的变量
		"title":   "HTML 模板 ----",
		"content": "这是一个 HTML 模板",
	})
})

r.GET("/index2", func(c *gin.Context) {
	c.HTML(http.StatusOK, "mn.html", gin.H{
		"title":   "HTML 模板",
		"content": "这是一个 HTML 模板",
	})
})
# 重名的名称要在 HTML 内另外设置  "next/index.html" 按照在路由定义的字符串来
<!-- 相当于给模板定义一个名字 define end 成对出现 -->
{{define "next/index.html"}}
<!DOCTYPE html>
.....
  </body>
</html>
{{end}}
# 然后访问相同文件名称 html
r.GET("/indexsame", func(c *gin.Context) {
	c.HTML(http.StatusOK, "next/index.html", gin.H{
		"title":   "HTML 模板",
		"content": "这是一个 HTML 模板",
	})
})

路由模块化

参考文档:https://gin-gonic.com/zh-cn/docs/examples/grouping-routes/

# main.go
import (
	routes "gin/routes/apiroutes"
	router "gin/routes/defautroute"
	"net/http"
	"github.com/gin-gonic/gin"
)
func main() {
	// 创建带默认中间件(日志与恢复)的 Gin 路由器
	r := gin.Default()
	// 使用 LoadHTMLGlob 加载所有 HTML 模板文件(支持通配符)r.LoadHTMLGlob("template/**/*.html")
	// ========== 页面路由组(页面模块)==========
	router.DefaultInit(r)
	// ========== API 路由组(API 模块)==========
	routes.ApiInit(r)
	// 默认端口 8080 启动服务器
	// 监听 0.0.0.0:8080(Windows 下为 localhost:8080)r.Run(":8000")
}
# /routes/apiroutes
package routes
import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func ApiInit(r *gin.Engine) {apiRouters := r.Group("/api")
	{
		// get 路由获取参数
		apiRouters.GET("/query", func(c *gin.Context) {username := c.Query("username")
			page := c.DefaultQuery("page", "1")
			c.JSON(http.StatusOK, gin.H{
				"username": username,
				"page":     page,
			})
		})
               # 其他路由
	}
}
# /routes/defautroute
package routes
import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func DefaultInit(r *gin.Engine) {defaultRouters := r.Group("/")
	{defaultRouters.GET("/index", func(c *gin.Context) {
			c.HTML(http.StatusOK, "index.html", gin.H{
				"title":   "HTML 模板 ----",
				"content": "变量数据",
			})
		})
                 #  其他路由
	}
}

控制器

在路由中可以将路由后面的函数参数封装,作为一个 controller 函数将业务代码在路由中抽离出来,在控制器中还可以定义 service 方法将在数据库查询的方法在控制器中抽离做到低耦合解耦,代码更加简洁。

# /controllers/news/index.go
package news
func NewList(cgin.context){c.string(200,"新闻列表")
}

import "gin/controllers/news"
apiRouters.GET("/list", news.NewList)

或者在 controller 控制器上定义一个结构体,然后由下面的方法继承这个结构体,在路由调用这个结构体的方法

中间件

官方文档:https://gin-gonic.com/zh-cn/docs/examples/custom-middleware/

先看下 gin 的生命周期,对 gin 的执行流程理解后查找中间件执行的时期和对整个项目我们跨越操作执行的方法和事件

Gin 框架以及项目使用
Gin 框架以及项目使用

注意:当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c*gin.context),必须使用其只读副本(c.Copy())

在 gin 执行路由的时候,请求路径后可以执行多个函数作为参数, 每一个参数函数都可以作为中间件和执行业务代码的逻辑。通常处理的方式为:请求拦截 ==> 业务处理 ==> 响应拦截

routers.GET("/index", func(c *gin.Context)gin.HandlerFunc {fmt.PrintIn("请求拦截处理")
                c.next()}, func(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", gin.H{
		"title":   "HTML 模板 ----",
		"content": "变量数据",
	})},func(c *gin.Context) gin.HandlerFunc{fmt.PrintIn("请求响应处理")
                c.next()},
)

中间件分三个级别:全局中间件、路由组中间件、路由中间件

// MiddleInit 返回一个 Gin 中间件函数 必须是一个返回值
func GlobalInit() gin.HandlerFunc {return func(c *gin.Context) {
		if c.Request.URL.Path == "/favicon.ico" {c.Next() // 直接放行,不执行本中间件的后续逻辑
			return
		}
		// 请求处理前的逻辑
		fmt.Println("我是全局中间件 - 请求前")
                start := time.Now()
		// 继续处理请求
		c.Next()
                latency := time.Since(start)   
		// 请求处理后的逻辑
		fmt.Printf("我是全局中间件请求 %s 耗时 %v - 请求后", c.Request.URL.Path, latency)
	}
}

// MiddleInit 返回一个 Gin 中间件函数
func MiddleInit() gin.HandlerFunc {return func(c *gin.Context) {
		if c.Request.URL.Path == "/favicon.ico" {fmt.Println("我是中间件 - 浏览器不处理")
			c.Next() // 直接放行,不执行本中间件的后续逻辑
			return
		}
		// 请求处理前的逻辑
		fmt.Println("我是路由组中间件 - 请求前")

		// 继续处理请求
		c.Next()

		// 请求处理后的逻辑
		fmt.Println("我是路由中间件 - 请求后")
	}
}

func ResponsInit() gin.HandlerFunc {return func(c *gin.Context) {
		// 请求处理前的逻辑
		fmt.Println("我是响应中间件 - 请求前")
          
		// 继续处理请求
		c.Next()

		// 请求处理后的逻辑
		fmt.Println("我是响应中间件 - 请求后")
	}
}

# main.go
import "gin/middlewares"
import (routes "gin/routes/apiroutes")

r := gin.Default()
r.Use(middlewares.GlobalInit())
routes.ApiInit(r)

# 路由组
package routes
import (
	"gin/middlewares"
	"net/http"
	"github.com/gin-gonic/gin"
)
func ApiInit(r *gin.Engine) {apiRouters := r.Group("/api")
        # 路由组中间件
	apiRouters.Use(middlewares.MiddleInit())  
	{
		// 测试路由
		apiRouters.GET("/ping", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "pong",})
		},
		middlewares.ResponsInit(),// 路由中间件)
	}
}

# 执行顺序
我是全局中间件 - 请求前
我是路由组中间件 - 请求前
我是响应中间件 - 请求前
我是响应中间件 - 请求后
我是路由中间件 - 请求后
我是全局中间件 - 请求后
 // 添加其他自定义中间件
router.Use(CorsMiddleware())
router.Use(AuthMiddleware())

全局或者路由组中间件在 c.next()后执行业务代码,就可以在前后做逻辑的处理

日志中间件

router := gin.Default()调用了默认的中间件,无法手动调用自定义格式

// 自定义 Logger 中间件(生产环境可能需要 JSON 格式)router.Use(gin.LoggerWithConfig(gin.LoggerConfig{
    Output: os.Stdout,
    SkipPaths: []string{"/health"},  // 跳过健康检查日志
}))
// 或者自定义 Logger 格式
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
	// 自定义日志格式
	return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
		param.ClientIP,
		param.TimeStamp.Format(time.RFC1123),
		param.Method,
		param.Path,
		param.Request.Proto,
		param.StatusCode,
		param.Latency,
		param.Request.UserAgent(),
		param.ErrorMessage,
	)
}))

使用第三方库

package logMiddle

import (
    "github.com/sirupsen/logrus" // 一个流行的结构化日志库
    "log"
    "os"
)

// 创建一个全局的 logrus 实例,便于在路由函数中使用
var FileLogger = logrus.New()
var Log *log.Logger
var logFile *os.File

// Init 初始化日志系统
func Init() error {
    var err error
    // 打开或创建日志文件
    logFile, err = os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {return err}

    // 配置标准库 log 的输出
    Log = log.New(logFile, "", log.LstdFlags|log.Lshortfile)

    // 配置 logrus 的结构化日志输出
    FileLogger.SetOutput(logFile)
    FileLogger.SetFormatter(&logrus.JSONFormatter{}) // 设置为 JSON 格式,便于机器解析
    FileLogger.SetLevel(logrus.InfoLevel)            // 设置日志级别

    return nil
}

// ==================== Logrus 自定义封装方法 ====================

// Info 记录信息级别日志
func Info(message string, fields ...map[string]interface{}) {if len(fields) > 0 {FileLogger.WithFields(logrus.Fields(fields[0])).Info(message)
    } else {FileLogger.Info(message)
    }
}

// Error 记录错误级别日志
func Error(message string, err error, fields ...map[string]interface{}) {entry := FileLogger.WithError(err)
    if len(fields) > 0 {entry = entry.WithFields(logrus.Fields(fields[0]))
    }
    entry.Error(message)
}

// Warn 记录警告级别日志
func Warn(message string, fields ...map[string]interface{}) {if len(fields) > 0 {FileLogger.WithFields(logrus.Fields(fields[0])).Warn(message)
    } else {FileLogger.Warn(message)
    }
}

// Debug 记录调试级别日志
func Debug(message string, fields ...map[string]interface{}) {if len(fields) > 0 {FileLogger.WithFields(logrus.Fields(fields[0])).Debug(message)
    } else {FileLogger.Debug(message)
    }
}

// Fatal 记录致命错误并退出程序
func Fatal(message string, err error, fields ...map[string]interface{}) {entry := FileLogger.WithError(err)
    if len(fields) > 0 {entry = entry.WithFields(logrus.Fields(fields[0]))
    }
    entry.Fatal(message)
}

// WithFields 创建带字段的日志条目
func WithFields(fields map[string]interface{}) *logrus.Entry {return FileLogger.WithFields(logrus.Fields(fields))
}

// SetLevel 设置日志级别
func SetLevel(level logrus.Level) {FileLogger.SetLevel(level)
}


import ("< 项目名称 >/middleware/logMiddle")

router.GET("/test", func(c *gin.Context) {
	// 示例:使用标准库 log 记录简单信息(方案 1)logMiddle.Log.Println("[ 标准库] 收到访问 /test 的请求,IP:", c.ClientIP())
	// 示例:记录信息级别日志
	logMiddle.Info("业务请求处理",map[string]interface{}{
		"path":   c.Request.URL.Path,
		"method": c.Request.Method,
		"ip":     c.ClientIP(),
		"status": c.Writer.Status(),})
	// 示例:WithFields 创建带字段的日志条目
	logMiddle.WithFields(map[string]interface{}{
		"path":   c.Request.URL.Path,
		"method": c.Request.Method,
		"ip":     c.ClientIP(),
		"status": c.Writer.Status(),}).Info("业务请求处理")
	// 模拟一些业务逻辑
	c.JSON(200, gin.H{"message": "success",})
})

文件上传

官方文档:https://gin-gonic.com/zh-cn/docs/examples/upload-file/multiple-file/

现在基本都不使用文件上传到服务器了,除了服务器访问量小、内网使用(带宽基本没限制网速上传快)。都是上传到第三方的对象储存,例如阿里云的 OSS、七牛云的 Kodo、腾讯云的 COS 等。

cookie|session

cookie 基本是在服务端将 html 文件传到浏览器前端的模式才会用到,设置 cookie 过期时间、域名限制、http 访问模式等,现在前后端分离很少用。

session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 session 保存在服务器上。当客户端浏览器第一次访问服务器并发送请求时,服务器端会创建一个 session 对象,生成一个类似于 key,value 的键值对,然后将 value 保存到服务器 将 key(cookie)返回到浏览器 (客户) 端。浏览器下次访问时会携带 key(cookie),找到对应的 session(value)。

session 使用第三方插件库:https://github.com/gin-contrib/sessions

数据库连接

GORM 官网:https://gorm.io/zh_CN/docs/

Postgresql 连接

MySQL 连接池

package model

import (
	"database/sql"
	"fmt"
	"sync"
	"time"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var (
	DB   *gorm.DB
	once sync.Once
)

// PoolConfig 连接池配置
type PoolConfig struct {
	MaxOpenConns    int           // 最大打开连接数
	MaxIdleConns    int           // 最大空闲连接数
	ConnMaxLifetime time.Duration // 连接最大生命周期
	ConnMaxIdleTime time.Duration // 连接最大空闲时间
}

// DefaultPoolConfig 返回默认连接池配置
func DefaultPoolConfig() *PoolConfig {
	return &PoolConfig{
		MaxOpenConns:    20,               // 最大打开连接数
		MaxIdleConns:    10,               // 最大空闲连接数
		ConnMaxLifetime: time.Hour,        // 连接最大生命周期 1 小时
		ConnMaxIdleTime: 10 * time.Minute, // 连接最大空闲时间 10 分钟
	}
}

// InitDB 初始化数据库连接
func InitDB() error {return InitDBWithConfig(nil)
}

// InitDBWithConfig 使用自定义配置初始化数据库连接  once 只初始化执行一次
func InitDBWithConfig(config *PoolConfig) error {
	var err error
	once.Do(func() {
		// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
		// 注意:如果密码中包含特殊字符(如 @),需要进行 URL 编码
		// @ 应该编码为 %40
		dsn := "mn:xxxxxx@tcp(192.168.1.68:3306)/nest?charset=utf8mb4&parseTime=True&loc=Local"
		DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
		if err != nil {fmt.Printf("数据库连接失败: %v\n", err)
			fmt.Println("\n 解决方案:")
			fmt.Println("1. 检查 MySQL 服务器是否允许当前主机连接")
			fmt.Println("2. 在 MySQL 服务器上执行以下命令授权:")
			fmt.Println("GRANT ALL PRIVILEGES ON nest.* TO'root'@'WEDO'IDENTIFIED BY'20251126@Mysql.com';")
			fmt.Println("或者授权所有主机:")
			fmt.Println("GRANT ALL PRIVILEGES ON nest.* TO'root'@'%'IDENTIFIED BY'20251126@Mysql.com';")
			fmt.Println("FLUSH PRIVILEGES;")
			fmt.Println("3. 检查防火墙设置,确保 3306 端口可访问")
			return
		}

		// 配置连接池
		if config == nil {config = DefaultPoolConfig()
		}

		// 获取底层的 sql.DB 以配置连接池
		sqlDB, err := DB.DB()
		if err != nil {fmt.Printf("获取数据库连接失败: %v\n", err)
			return
		}

		// 设置连接池参数
		sqlDB.SetMaxOpenConns(config.MaxOpenConns)       // 设置最大打开连接数
		sqlDB.SetMaxIdleConns(config.MaxIdleConns)       // 设置最大空闲连接数
		sqlDB.SetConnMaxLifetime(config.ConnMaxLifetime) // 设置连接最大生命周期
		sqlDB.SetConnMaxIdleTime(config.ConnMaxIdleTime) // 设置连接最大空闲时间

		fmt.Printf("连接数据库成功,连接池配置: MaxOpen=%d, MaxIdle=%d, MaxLifetime=%v, MaxIdleTime=%v\n",
			config.MaxOpenConns, config.MaxIdleConns, config.ConnMaxLifetime, config.ConnMaxIdleTime)
	})
	return err
}

// GetDB 获取数据库连接实例,如果未初始化则先初始化
func GetDB() *gorm.DB {
	if DB == nil {if err := InitDB(); err != nil {panic(fmt.Sprintf("数据库未初始化: %v", err))
		}
	}
	return DB
}

// GetPoolStats 获取连接池统计信息
func GetPoolStats() (*sql.DBStats, error) {
	if DB == nil {return nil, fmt.Errorf("数据库未初始化")
	}
	sqlDB, err := DB.DB()
	if err != nil {return nil, fmt.Errorf("获取数据库连接失败: %v", err)
	}
	stats := sqlDB.Stats()
	return &stats, nil
}

// CloseDB 关闭数据库连接
func CloseDB() error {
	if DB == nil {return nil}
	sqlDB, err := DB.DB()
	if err != nil {return err}
	return sqlDB.Close()}

# 外部调用
import "< 项目名>/model"
db := model.GetDB()

数据库映射

使用数据库映射后,服务定义的数据库格式会映射修改数据库表的结构和属性

func main() {
	// 初始化数据库连接
	if err := model.InitDB(); err != nil {fmt.Printf("警告: 数据库连接失败,程序将继续运行但数据库功能可能不可用 \n")
		fmt.Printf("错误详情: %v\n", err)
	} else {
		// 初始化表结构(仅在启动时执行一次)if err := model.InitUserTable(); err != nil {fmt.Printf("警告: 用户表初始化失败: %v\n", err)
		}
		// 初始化产品表结构
		if err := model.InitProductTable(); err != nil {fmt.Printf("警告: 产品表初始化失败: %v\n", err)
		}
	}
	r := gin.Default()

	// 直接读取全局配置变量
	r.Run(":" + common.ServerPort)
}

swagger

官网:https://pkg.go.dev/github.com/swaggo/gin-swagger@v1.6.1

# 安装依赖  不考虑 1.17 之前版本安装方式
go install github.com/swaggo/swag/cmd/swag@latest
# main.go
package main
import (
	"fmt"
	"saas/common"
	"saas/model"
	"saas/router"

	_ "saas/docs" // 重要:导入 swag init 刚生成的 docs 包

	"github.com/gin-gonic/gin"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
)

// @title           Go Demo API
// @version         1.0
// @description     这是一个 Go Demo 项目的 API 文档
// @host            localhost:8090
// @BasePath        /
func main() {// 加载配置优先级:系统环境变量> .env 文件 > 代码默认值
	common.LoadConfig()
	// 初始化数据库连接
	// 直接读取全局配置变量
	mode := common.ServerMode
	if mode == "" {mode = gin.ReleaseMode}
	gin.SetMode(mode)
	r := gin.Default()

	// 注册中间件
	router.RegisterMiddleware(r)
	// 根据配置动态注册 Swagger 路由(生产环境默认禁用)if common.EnableSwagger {r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
		fmt.Println("Swagger 地址: http://localhost:" + common.ServerPort + "/swagger/index.html")
	} else {
		// 生产环境禁用 Swagger,返回 404
		r.GET("/swagger/*any", func(c *gin.Context) {c.JSON(404, gin.H{"error": "Not Found"})
		})
	}

	router.RegisterRouter(r)
	// 直接读取全局配置变量
	r.Run(":" + common.ServerPort)
}
# 路由使用
package router
import (
	"net/http"
	"saas/common"
	"saas/model"
	"saas/router/dto"
	"saas/service"
	"strconv"
	"github.com/gin-gonic/gin"
)

// GetUser 获取用户信息
// @Summary      获取用户信息
// @Description  根据 ID 获取用户信息
// @Tags         用户管理
// @Accept       json
// @Produce      json
// @Param        id    path      int     true  "用户 ID"
// @Success      200   {object}  common.Response{data=model.BasUser}  "成功"
// @Failure      400   {object}  common.ErrorResponse  "请求参数错误"
// @Failure      404   {object}  common.NotFoundResponse  "资源不存在"
// @Failure      500   {object}  common.ServerErrorResponse  "服务器错误"
// @Router       /api/users/{id} [get]
func GetUser(c *gin.Context) {...}

// CreateUser 创建用户
// @Summary      创建用户
// @Description  创建新用户
// @Tags         用户管理
// @Accept       json
// @Produce      json
// @Param        user  body      dto.CreateUserRequest  true  "用户信息"
// @Success      201   {object}  common.Response{data=model.BasUser}  "创建成功"
// @Failure      400   {object}  common.ErrorResponse  "请求参数错误"
// @Failure      500   {object}  common.ServerErrorResponse  "服务器错误"
// @Router       /api/users [post]
func CreateUser(c *gin.Context) {
       var req dto.CreateUserRequest
	...
}

// DefaultAPI 注册默认 API 路由
func DefaultAPI(r *gin.Engine) {apiGroup := r.Group("/api")
	{usersGroup := apiGroup.Group("/users")
		{usersGroup.GET("/:id", GetUser)
			usersGroup.POST("", CreateUser)
			usersGroup.PUT("/:id", UpdateUser)
			usersGroup.DELETE("/:id", DeleteUser)
		}
	}
}
# 结构体 /router/dto
package dto

// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
	StoreID   int    `json:"store_id" binding:"required"`   // 定义请求必填
	Staff     string `json:"staff" binding:"required"`
	Username  string `json:"username" binding:"required,min=3,max=100"` // 定义参数
	Password  string `json:"password" binding:"required,min=6"`
	Nickname  string `json:"nickname"`
	Realname  string `json:"realname"`
	AdminName string `json:"admin_name"`
	Level     string `json:"level"`
	Avatar    string `json:"avatar"`
	Status    int8   `json:"status"`
	LastIP    string `json:"last_ip"`
	Token     string `json:"token"`
	Remark    string `json:"remark"`
}

package common

import (
	"net/http"

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

// Response 统一响应结构
// @Description 统一 API 响应结构
type Response struct {
	Code    int         `json:"code" example:"200"`     // 状态码
	Message string      `json:"message" example:"操作成功"` // 消息
	Data    interface{} `json:"data"`                   // 数据}

// ErrorResponse 错误响应结构(用于 Swagger 文档,显示具体的错误值)// @Description 错误响应结构
type ErrorResponse struct {
	Code    int         `json:"code" example:"400"`       // 状态码
	Message string      `json:"message" example:"请求参数错误"` // 错误消息
	Data    interface{} `json:"data"`                     // 数据(错误时通常为 null)}

// NotFoundResponse 404 错误响应结构(用于 Swagger 文档)// @Description 资源不存在错误响应
type NotFoundResponse struct {
	Code    int         `json:"code" example:"404"`      // 状态码
	Message string      `json:"message" example:"资源不存在"` // 错误消息
	Data    interface{} `json:"data"`                    // 数据(错误时通常为 null)}

// ServerErrorResponse 500 错误响应结构(用于 Swagger 文档)// @Description 服务器错误响应
type ServerErrorResponse struct {
	Code    int         `json:"code" example:"500"`      // 状态码
	Message string      `json:"message" example:"服务器错误"` // 错误消息
	Data    interface{} `json:"data"`                    // 数据(错误时通常为 null)}


// Success 成功响应
func Success(c *gin.Context, data interface{}, message ...string) {
	msg := "操作成功"
	if len(message) > 0 && message[0] != "" {msg = message[0]
	}
	c.JSON(http.StatusOK, Response{
		Code:    200,
		Message: msg,
		Data:    data,
	})
}

// Error 错误响应
func Error(c *gin.Context, code int, message string) {
	c.JSON(code, Response{
		Code:    code,
		Message: message,
		Data:    nil,
	})
}

// HandleError 统一错误处理
func HandleError(c *gin.Context, err error) {
	if err == nil {return}

	// 处理 GORM 错误
	if err == gorm.ErrRecordNotFound {Error(c, http.StatusNotFound, "资源不存在")
		return
	}

	// 默认服务器错误
	Error(c, http.StatusInternalServerError, "服务器内部错误:"+err.Error())
}

# 生成文件 运行项目后就可以访问 swagger 了
swag init

正文完
 0
评论(没有评论)
验证码