基於範型的 gin 開發腳手架

作者:songcser

https://juejin.cn/post/7221691217968332858

Gingo

Introduce

Gingo 是基於 gin 框架爲核心的腳手架,能夠快速創建 Restful 風格的 API 接口,並且能提供簡單的後臺管理功能,使用本項目可以快速完成業務邏輯開發。

Feature

Catalogue

.
|——.gitignore
|——go.mod
|——go.sum
|——cmd          
   └──migrate
      └──main.go            // 註冊數據庫表
   └──main.go               // 項目入口main
|——README.md
|——config                   // 配置文件目錄
|  └──autoload              // 配置文件的結構體定義包
|     └──admin.go           // admin配置
|     └──db.go
|     └──jwt.go             // jwt配置
|     └──mysql.go           // mysql配置
|     └──zap.go             // zap日誌配置
|  └──config.yaml           // .yaml配置示例文件
|  └──config.go             // 配置初始化文件
|——initialize               // 數據初始化目錄
|  └──admin.go              // admin初始化
|  └──constants.go          // 常量數據
|  └──gorm.go               // 數據庫初始化
|  └──mysql.go              // mysql初始化
|  └──router.go             // gin初始化
|  └──swagger.go            
|  └──viper.go              // viper配置初始化
|  └──zap.go                // zap日誌初始化
|——internal                 // 該服務所有不對外暴露的代碼,通常的業務邏輯都在這下面,使用internal避免錯誤引用
|──middleware               // 中間件目錄
|  └──logger.go             // 日誌中間件,打印請求數據
|  └──recovery.go           // 自定義recovery, 輸出錯誤格式化
|──pkg                      // 內部服務包    
|  └──admin                 // admin實現邏輯
|     └──admin.go
|     └──init.go
|     └──model.go
|     └──service.go
|  └──api                   // API接口封裝
|     └──api.go
|  └──auth                  // 登陸授權接口封裝
|     └──model.go           
|     └──user.go            
|  └──model                 // 底層模型封裝
|     └──mapper.go           
|     └──model.go           
|     └──page.go           
|     └──wrapper.go           
|  └──response              // 響應數據模型封裝
|     └──page.go         
|     └──response.go         
|  └──router                // 路由模塊封裝
|     └──router.go          
|  └──service               // 服務模塊封裝
|     └──service.go
|──templates                // admin模版頁面
|  └──add.html              // 新建頁面
|  └──edit.html             // 編輯頁面
|  └──embed.go          
|  └──header.html           // 頭部頁面
|  └──home.html             // 首頁
|  └──index.html            // 主頁面
|  └──login.html            // 登陸頁面
|  └──register.html         // 註冊頁面頁面
|  └──sidebar.html          // 左邊欄頁面
|──utils                    // 一些工具方法
|  └──cache.go              // 緩存
|  └──error.go              // error檢查
|  └──hash.go               // hash加密解密
|  └──http.go               // http客戶端請求
|  └──json.go               // 
|  └──jwt.go                // JWT
|  └──path.go               // 文件路徑
|  └──time.go               // time相關方法
|  └──translator.go         // 中英文翻譯

Usage

internal 目錄是不對外暴露的代碼,在做 go get 時,此目錄不會被下載,所以通常業務邏輯放在這個下面。我們將在這個目錄下面加一些業務代碼,說明腳手架的使用。

在 internal 目錄下新增 app 包目錄

Model

// Package app model.go
package app

import "github.com/songcser/gingo/pkg/model"

type App struct {
   model.BaseModel
   Name        string `json:"name" gorm:"column:name;type:varchar(255);not null"`
   Description string `json:"description" gorm:"column:description;type:varchar(4096);not null"`
   Level       string `json:"level" gorm:"column:level;type:varchar(8);not null"`
   Type        string `json:"type" gorm:"column:type;type:varchar(16);not null"`
}

App 模型有 4 個自定義字段,gorm 標籤會對應到數據庫的字段。

package model

type Model interface {
   Get() int64
}

type BaseModel struct {
   ID        int64          `json:"id" gorm:"primarykey" admin:"disable"`                // 主鍵ID
   CreatedAt utils.JsonTime `json:"createdAt" gorm:"index;comment:創建時間" admin:"disable"` // 創建時間
   UpdatedAt utils.JsonTime `json:"updatedAt" gorm:"index;comment:更新時間" admin:"disable"` // 更新時間
}

func (m BaseModel) Get() int64 {
   return m.ID
}

BaseModel 是基礎模型,有一些公共字段, 並且實現了 Model interface, 所有引用 BaseModel 的模型都實現了 Model interface。

創建數據庫表

# Package initialize gorm.go
package initialize
// RegisterTables 註冊數據庫表專用
func RegisterTables(db *gorm.DB) {
   err := db.Set("gorm:table_options""CHARSET=utf8mb4").AutoMigrate(
      // 系統模塊表
      auth.BaseUser{},
      app.App{}, // app表註冊
   )
   if err != nil {
      os.Exit(0)
   }
}

執行 migrate 的 main 方法會在數據庫創建對應的表。

Api

// Package app api.go
package app

import (
   "github.com/songcser/gingo/pkg/api"
   "github.com/songcser/gingo/pkg/service"
)

type Api struct {
   api.Api
}

func NewApi() Api {
   var app App
   baseApi := api.NewApi[App](service.NewBaseService(app))
   return Api{baseApi}
}

api.Api 接口

// Package api api.go
package api

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

type Api interface {
   Query(c *gin.Context)
   Get(c *gin.Context)
   Create(c *gin.Context)
   Update(c *gin.Context)
   Delete(c *gin.Context)
}

api.Api 接口定義了 CURD 方法,並且方法都是 gin.HandlerFunc 類型,可以直接綁定到 gin Router 上。BaseApi 實現了 CURD 的基本方法,app.Api 類型組合了 BaseApi 的方法。

Router

// Package app router.go
package app

import (
   "github.com/gin-gonic/gin"
   "github.com/songcser/gingo/pkg/router"
)

func InitRouter(g *gin.RouterGroup) {
   r := router.NewRouter(g.Group("app"))
   a := NewApi()
   r.BindApi("", a)
}

router 是對 gin.RouterGroup 做了簡單封裝,方便和 Api 類型做綁定。BindApi 方法將 Api 的 CURD 方法和 router 進行了綁定。

啓動服務之後,執行腳本或者使用 postman 請求服務

創建數據

curl --location 'http://localhost:8080/api/v1/app' \
--header 'Content-Type: application/json' \
--data '{
    "name": "測試應用",
    "description": "測試應用服務",
    "level": "S3",
    "type": "container"
}'

返回內容

{
    "code": 0,
    "data": true,
    "message""success"
}

查詢數據

curl --location 'http://localhost:8080/api/v1/app'

返回內容

{
    "code": 0,
    "data"{
        "total": 1,
        "size": 10,
        "current": 1,
        "results"[
            {
                "id": 1,
                "createdAt""2023-04-13 16:35:59",
                "updatedAt""2023-04-13 16:35:59",
                "name""測試應用",
                "description""測試應用服務",
                "level""S3",
                "type""container"
            }
        ]
    },
    "message""success"
}

查詢單個數據

curl --location 'http://localhost:8080/api/v1/app/1'

返回內容

{
    "code": 0,
    "data"{
        "id": 1,
        "createdAt""2023-04-13 16:56:09",
        "updatedAt""2023-04-13 16:58:29",
        "name""測試應用",
        "description""測試應用服務",
        "level""S3",
        "type""container"
    },
    "message""success"
}

更新數據

curl --location --request PUT 'http://localhost:8080/api/v1/app/1' \
--header 'Content-Type: application/json' \
--data '{
    "name": "測試應用",
    "description": "測試應用服務",
    "level": "S1",
    "type": "container"
}'

返回內容

{
    "code": 0,
    "data": true,
    "message""success"
}

刪除數據

curl --location --request DELETE 'http://localhost:8080/api/v1/app/1'

返回內容

{
    "code": 0,
    "data": true,
    "message""success"
}

自定義方法

api 添加新的方法

// Package app api.go
package app

func (a Api) Hello(c *gin.Context) {
   response.OkWithData("Hello World", c)
}

router 進行綁定

    r.BindGet("hello", a.Hello)

接口請求

curl --location 'http://localhost:8080/api/v1/app/hello'

返回內容

{
    "code": 0,
    "data""Hello World",
    "message""success"
}

Service

在 NewApi 時使用的是 BaseService,可以實現自定義的 Service,重寫一些方法。

package app

import (
   "github.com/gin-gonic/gin"
   "github.com/songcser/gingo/pkg/model"
   "github.com/songcser/gingo/pkg/service"
   "github.com/songcser/gingo/utils"
)

type Service struct {
   service.Service[App]
}

func NewService(a App) Service {
   return Service{service.NewBaseService[App](a)}
}

func (s Service) MakeMapper(c *gin.Context) model.Mapper[App] {
   var r Request
   err := c.ShouldBindQuery(&r)
   utils.CheckError(err)
   w := model.NewWrapper()
   w.Like("name", r.Name)
   w.Eq("level", r.Level)
   m := model.NewMapper[App](App{}, w)
   return m
}

func (s Service) MakeResponse(val model.Model) any {
   a := val.(App)
   res := Response{
      Name:        a.Name,
      Description: fmt.Sprintf("名稱:%s, 等級: %s, 類型: %s", a.Name, a.Level, a.Type),
      Level:       a.Level,
      Type:        a.Type,
   }
   return res
}

在 Api 中替換新的 Service

// Package app api.go

func NewApi() Api {
   var app App
   s := NewService(app)  //使用新的Service
   baseApi := api.NewApi[App](s)
   return Api{Api: baseApi}
}

查詢數據

curl --location 'http://localhost:8080/api/v1/app?name=測試&level=S3'

返回內容

{
    "code": 0,
    "data"{
        "total": 1,
        "size": 10,
        "current": 1,
        "results"[
            {
                "name""測試應用",
                "description""名稱:測試應用, 等級: S3, 類型: container",
                "level""S3",
                "type""container"
            }
        ]
    },
    "message""success"
}

如果要在 Service 增加新的方法,需要在 Api 模型中重寫 Service

// Package app service.go
func (s Service) Hello() string {
   return "Hello World"
}

Api 實現

// Package app api.go
package app

type Api struct {
   api.Api
   Service Service  // 重寫Service
}

func NewApi() Api {
   var app App
   s := NewService(app)
   baseApi := api.NewApi[App](s)
   return Api{Api: baseApi, Service: s}
}

func (a Api) Hello(c *gin.Context) {
   str := a.Service.Hello() // 調用Service方法
   response.OkWithData(str, c)
}

Admin

Admin 提供了簡單的後臺管理服務,可以很方便的對數據進行管理。

首先需要在配置文件中開啓 Admin

admin:
  enable: true
  auth: true
// Package app admin.go
package app

import (
   "github.com/songcser/gingo/pkg/admin"
)

func Admin() {
   var a App
   admin.New(a, "app""應用")
}

在 initialize 中引入 admin

// Package initialize admin.go
package initialize

func Admin(r *gin.Engine) {
   if !config.GVA_CONFIG.Admin.Enable {
      return
   }
   admin.Init(r, nil)
   app.Admin()
}

在管理頁面可以進行簡單的查詢,創建,修改,刪除操作。

Model 字段中可以配置 admin 標籤,管理頁面可以根據類型展示。

// Package app model.go
package app

import "github.com/songcser/gingo/pkg/model"

type App struct {
   model.BaseModel
   Name        string `json:"name" form:"name" gorm:"column:name;type:varchar(255);not null" admin:"type:input;name:name;label:應用名"`
   Description string `json:"description" form:"description" gorm:"column:description;type:varchar(4096);not null" admin:"type:textarea;name:description;label:描述"`
   Level       string `json:"level" form:"level" gorm:"column:level;type:varchar(8);not null" admin:"type:radio;enum:S1,S2,S3,S4,S5;label:級別"`
   Type        string `json:"type" form:"type" gorm:"column:type;type:varchar(16);not null" admin:"type:select;enum:container=容器應用,web=前端應用,mini=小程序應用;label:應用類型"`
}

需要添加 form 標籤,由於是使用 html 的 form 提交數據。

訪問鏈接

http://localhost:8080/admin/

首頁

點擊應用,查看應用列表

點擊 創建應用 按鈕

GITHUB

https://github.com/songcser/gingo

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/uPa2S4zSSoRBhtuLff_bNA