songcser / gingo

基于 gin 框架为核心的脚手架,使用本项目可以快速完成业务逻辑开发。
163 stars 39 forks source link

Gingo

Introduce

Gingo是基于 gin 框架为核心的脚手架,使用本项目可以快速完成业务逻辑开发。

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时,此目录不会被下载,所以通常业务逻辑放在这个下面。 我们将在这个目录下面加一些业务代码,说明脚手架的使用。

新增 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

auth 会开启认证授权,需要登陆

接入 Admin 也比较简单

// 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/

首页

首页

点击应用,查看应用列表

列表

点击 创建应用 按钮

新建