基於範型的 gin 開發腳手架
作者:songcser
https://juejin.cn/post/7221691217968332858
Gingo
Introduce
Gingo 是基於 gin 框架爲核心的腳手架,能夠快速創建 Restful 風格的 API 接口,並且能提供簡單的後臺管理功能,使用本項目可以快速完成業務邏輯開發。
Feature
-
gin 框架,簡單,高效,輕量
-
gorm 數據庫 ORM 框架,封裝 mapper,使用簡單
-
viper 配置管理
-
zap 日誌框架,輸出日誌更靈活
-
api 接口封裝,快速實現 CURD 操作,提供 Restful 風格接口
-
admin 後臺管理,實現了簡單的後臺管理,方便進行數據管理
-
使用範型,go 版本不能低於 1.18
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
}
-
MakeMapper 方法重寫新的 Mapper,可以自定義一些查詢條件。
-
MakeResponse 方法可以重寫返回的內容
在 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:應用類型"`
}
-
type: 字段類型,取值 input, textarea, select, radio。
-
name: 字段名稱
-
label: 字段展示名稱
-
enum: 枚舉數據,在 select,radio 類型時展示更加友好
需要添加 form 標籤,由於是使用 html 的 form 提交數據。
訪問鏈接
http://localhost:8080/admin/
首頁
點擊應用,查看應用列表
點擊 創建應用 按鈕
GITHUB
https://github.com/songcser/gingo
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/uPa2S4zSSoRBhtuLff_bNA