原文章:https://zhuanlan.zhihu.com/p/605869965
如有侵权请联系我
代码结构
constants # 定义常量目录
state.go
db # 数据库连接初始化
core.go
handler # 控制器
......
middleware # 中间件
......
models # 模型
......
routers # 路由
router.go
service # 服务层,业务逻辑处理,与models交互操作数据库
......
types # 状态码,状态信息
code.go
util # 工具包
encrypt.go
result.go
.gitignore
gin-blog
go.mod
main.go
README.md
一. 项目初始化
➜ cd /Users/cc/goproject # go项目目录
➜ mkdir gin-blog # 创建项目
➜ go mod init gin-blog
➜ go get -u github.com/gin-gonic/gin
➜ go get -u gorm.io/gorm
➜ go get -u gorm.io/driver/mysql
1. vim main.go测试ping
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "success...",
})
})
r.Run(":8080")
}
运行 go run main.go
启动项目
运行 go build
编译项目
2. 配置热重载
fresh
# 项目目录gin-blog下执行
➜ go get github.com/pilu/fresh
➜ go install github.com/pilu/fresh // fresh命令无法识别执行
➜ fresh
3. 设计状态码 && 提示信息
vim types/code.go
package types
type Codes struct {
SUCCESS uint
FAILED uint
GENERATETOKEN uint
NOAUTH uint
AUTHFAILED uint
AUTHFORMATERROR uint
INVALIDTOKEN uint
NOSUCHID uint
CREATEUSERFAILED uint
LCAKPARAMETERS uint
CONVERTFAILED uint
NOSUCHNAME uint
EXISTSNAME uint
Message map[uint]string
}
var ApiCode = &Codes{
SUCCESS: 200,
FAILED: 0,
AUTHFAILED: 4001,
GENERATETOKEN: 4002,
NOAUTH: 4003,
AUTHFORMATERROR: 4004,
INVALIDTOKEN: 4005,
NOSUCHID: 1001,
CREATEUSERFAILED: 2001,
LCAKPARAMETERS: 3001,
CONVERTFAILED: 3002,
NOSUCHNAME: 5001,
EXISTSNAME: 5002,
}
func init() {
ApiCode.Message = map[uint]string{
ApiCode.SUCCESS: "成功",
ApiCode.FAILED: "失败",
ApiCode.GENERATETOKEN: "生成Token失败",
ApiCode.AUTHFAILED: "鉴权失败",
ApiCode.NOAUTH: "请求头中auth为空",
ApiCode.AUTHFORMATERROR: "请求头中auth格式有误",
ApiCode.INVALIDTOKEN:"无效的Token",
ApiCode.NOSUCHID: "id不存在",
ApiCode.CREATEUSERFAILED: "用户创建失败",
ApiCode.LCAKPARAMETERS: "缺少参数",
ApiCode.CONVERTFAILED: "参数类型转换报错",
ApiCode.NOSUCHNAME: "根据名称查不到数据",
ApiCode.EXISTSNAME: "名称重复",
}
}
func (c *Codes) GetMessage(code uint) string {
message, ok := c.Message[code]
if !ok {
return ""
}
return message
}
4. 封装统一APT返回格式
vim util/result.go
package util
import (
"gin-blog/types"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
//返回的结果:
type Result struct {
Time time.Time `json:"time"`
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
//成功
func Success(c *gin.Context, data interface{}) {
if (data == nil) {
data = gin.H{}
}
res := Result{}
res.Time = time.Now()
res.Code = int(types.ApiCode.SUCCESS)
res.Msg = types.ApiCode.GetMessage(types.ApiCode.SUCCESS)
res.Data = data
c.JSON(http.StatusOK,res)
}
//出错
func Error(c *gin.Context, code int,msg string) {
res := Result{}
res.Time = time.Now()
res.Code = code
res.Msg = msg
res.Data = gin.H{}
c.JSON(http.StatusOK,res)
}
5. 封装md5加密
vim util/encrypt.go
package util
import (
"crypto/md5"
"encoding/hex"
)
//EncryMd5
func EncryMd5(s string) string {
ctx := md5.New()
ctx.Write([]byte(s))
return hex.EncodeToString(ctx.Sum(nil))
}
6. 封装连接mysql
vim db/core.go
package core
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
// 定义db全局变量
var Db *gorm.DB
func init() {
var err error
dsn := "root:root@tcp(127.0.0.1:3306)/gin-blog-api?charset=utf8mb4&parseTime=True&loc=Local"
Db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: false,
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 禁用表名加s
},
Logger: logger.Default.LogMode(logger.Info),// 打印sql语句
DisableAutomaticPing: false,
DisableForeignKeyConstraintWhenMigrating: true, // 禁用创建外键约束
})
if err != nil {
panic("Connecting database failed: " + err.Error())
}
}
//GetDB
func GetDB() *gorm.DB {
return Db
}
7. 路由设计示例
vim routers/router.go
package routers
import (
"gin-blog/handler"
"gin-blog/middleware"
"github.com/gin-gonic/gin"
)
func InitRouter() {
r := gin.Default()
// 管理后台
// 管理员登陆
r.POST("/login", handler.AuthLogin)
// 管理员路由组
userGroup := r.Group("/user/v1")
userGroup.Use(middleware.AuthMiddleware())
{
userGroup.POST("/create-user", handler.CreateUser)
userGroup.GET("/get-users", handler.GetUsers)
userGroup.POST("/del-user-by-id", handler.DeleteUserById)
userGroup.GET("/get-user-by-id", handler.GetUserById)
userGroup.POST("/update-user-by-id", handler.UpdateUserById)
userGroup.POST("/disable-user-by-id", handler.DisableUserById)
userGroup.POST("/enable-user-by-id", handler.EnableUserById)
}
// 标签路由组
tagsGroup := r.Group("/tags/v1")
tagsGroup.Use(middleware.AuthMiddleware())
{
tagsGroup.POST("/create-tags", handler.CreateTags)
tagsGroup.GET("/get-tags-list", handler.GetTagsList)
tagsGroup.POST("/update-Tags-by-id", handler.UpdateTagsById)
tagsGroup.POST("/del-tags-by-id", handler.DeleteTagsById)
}
// 分类路由组
cateGroup := r.Group("/cate/v1")
cateGroup.Use(middleware.AuthMiddleware())
{
cateGroup.POST("/create-cate", handler.CreateCate)
cateGroup.GET("/get-cate-list", handler.GetCateList)
cateGroup.POST("/update-cate-by-id", handler.UpdateCateById)
cateGroup.POST("/del-cate-by-id", handler.DeleteCateById)
}
// 文章路由组
postsGroup := r.Group("/posts/v1")
postsGroup.Use(middleware.AuthMiddleware())
{
postsGroup.POST("/create-post", handler.CreatePost)
postsGroup.GET("/get-posts-list", handler.GetPostsList)
postsGroup.POST("/update-post-by-id", handler.UpdatePostById)
postsGroup.POST("/del-post-by-id", handler.DeletePostById)
}
// 文章评论路由组
commentGroup := r.Group("/comment/v1")
{
commentGroup.POST("/create-comment", handler.CreateComment)
commentGroup.GET("/get-comment-list", handler.GetCommentList)
commentGroup.POST("/del-comment-by-id", handler.DelCommentById)
}
// 友联路由组
linkGroup := r.Group("/link/v1")
{
linkGroup.POST("/create-link", handler.CreateLink)
linkGroup.GET("/get-link-list", handler.GetLinkList)
linkGroup.POST("/update-link-by-id", handler.UpdateLinkById)
linkGroup.POST("/del-link-by-id", handler.DeleteLinkById)
}
r.Run(":8080")
}
二. api文档
1. 安装
go get -u github.com/swaggo/swag/cmd/swag
go install -u github.com/swaggo/swag/cmd/swag // swag命令无法识别执行
go get -u -v github.com/swaggo/gin-swagger
go get -u -v github.com/swaggo/files
2. go-swapper注解规范说明
注解 | 描述 |
---|---|
@Summary | 摘要 |
@Description | 接口描述 |
@Tags | 接口的标签,用来给 API 分组的 |
@Accept | 接口接收入参的类型,支持mpfd(表单),json 等 |
@Produce | 接口返回的出参类型,支持mpfd(表单),json 等 |
@Param | 参数格式,从左到右分别为:参数名、入参类型、数据类型、是否必填、注释 |
@Success | 响应成功,从左到右分别为:状态码、参数类型、数据类型、注释 |
@Failure | 响应失败,从左到右分别为:状态码、参数类型、数据类型、注释 |
@Router | 路由,从左到右分别为:路由地址,HTTP 方法 |
示例demo:
// @Summary 测试接口
// @Produce json
// @Param token header string true "token"
// @Param page query int true "page"
// @Param size query int true "size"
// @Success 200 {object} []modle.Role "成功"
// @Failure 400 {object} string "请求错误"
// @Router /api/test [get]
func test(ctx *gin.Context) {
ctx.JSON(http.StatusOK, "Ok")
}
3. 生成接口文档数据
swag fmt // 格式化swag注解
swag init // 在项目根目录执行以下命令,使用swag工具生成接口文档数据。
注:使用热部署时,执行命令后需要再保存一次,触发第二次热部署后doc.json才会更新
执行完毕生成一下文件
docs
--dosc.go
--swagger.json
--swager.yaml
4. 引入gin-swagger渲染文档数据
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
_ "github/mwqnice/swag/docs" // 千万不要忘了导入把你上一步生成的docs
)
//添加swagger访问路由
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
启动项目,在浏览器中输入地址:http://127.0.0.1:[项目端口]/swagger/index.html