基于go语言gin框架的web项目骨架

该骨架每个组件之间可单独使用,组件之间松耦合,高内聚,组件的实现基于其他三方依赖包的封装。

目前该骨架实现了大多数的组件,比如事件,中间件,日志,配置,参数验证,命令行,定时任务等功能,目前可以满足大多数开发需求,后续会持续维护更新功能。

github地址:https://github.com/czx-lab/skeleton

设置环境变量并下载项目依赖

shell 复制代码
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go mod download

运行项目

shell 复制代码
go run ./cmd/main.go

项目编译打包运行

shell 复制代码
go build ./cmd/main.go

// 编译
make build

// 运行
make run

// 编译与运行
make

// 运行项目
./main

项目目录结构说明

text 复制代码
├─app
│  ├─command ---> 命令行
│  ├─controller
│  │    └─base.go ---> BaseController,主要定义了request参数验证器validator
│  ├─event
│  │  ├─entity ---> 事件实体目录
│  │  ├─listen ---> 事件监听执行脚本目录
│  │  └─event.go ---> 事件注册代码
│  │       
│  ├─middleware ---> 中间件代码目录
│  ├─request ---> 请求参数校验代码目录
│  │   └─request.go ---> 参数验证器
│  └─task ---> 定时任务代码目录
│     └─task.go ---> 注册定时任务脚本
├─cmd ---> 项目入口目录
│  └─cli ---> 项目命令行模式入口目录
├─config
│  └─config.yaml ---> 配置文件
├─internal ---> 包含第三方包的封装
├─router ---> 路由目录
│  └─router.go
├─storage ---> 日志、资源存储目录
│  └─logs
└─test ---> 单元测试目录

基础功能


路由

该骨架的web框架是gin,所以路由定义可直接阅读Gin框架的文档。

在该骨架中定义注册路由需要在router文件夹下面的router.go文件中的func (*AppRouter) Add(server *gin.Engine)方法定义注册:

go 复制代码
server.GET("/foo", func(ctx *gin.Context) {
    ctx.String(http.StatusOK, "hello word!")
})

也可以通过自己定义路由的定义注册,只需要实现github.com/czx-lab/skeleton/internal/server/router下面的Interface接口。如下示例:

在router目录下定义了一个CustomRouter结构体,该结构体实现了Interface接口

go 复制代码
package router

import (
    "net/http"
    
    "github.com/czx-lab/skeleton/internal/server"
    "github.com/gin-gonic/gin"
)

type CustomRouter struct {
    server server.HttpServer
}

func NewCustom(srv server.HttpServer) *CustomRouter {
    return &CustomRouter{
        srv,
    }
}

func (*CustomRouter) Add(srv *gin.Engine) {
    srv.GET("/custom", func(ctx *gin.Context) {
        ctx.String(http.StatusOK, "custom router")
    })
}

需要注意的是,如果是自定义路由注册,需要修改项目cmd文件夹下面的main.go入口文件,通过http.SetRouters(router.NewCustom(http))注册给gin

中间件

定义中间件与gin框架一样,该估计默认实现了panic异常的中间件,可以查看internal/server/middleware文件夹中的exception.go文件。

如果需要定义其他的中间件并加载注册,可以将定义好的中间件通过server.HttpServer接口的SetMiddleware(middlewares ...middleware.Interface)方法注册加载,

比如我们实现如下自定义全局中间件middleware/custom.go

go 复制代码
type Custom struct{}

func (c *Custom) Handle() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        fmt.Println("Custom middleware exec...")
    }
}

然后在定义路由的地方使用server.SetMiddleware(&middleware.Custom{})注册中间件。

定义全局路由中间件可以参考router/router.go中的New方法。

如果是局部中间件,可以直接在具体的路由上注册,参考gin路由中间件的用法

日志

在该骨架中的日志是直接对go.uber.org/zap的封装,使用时,直接通过全局变量variable.Log访问写入日志,可直接使用zap支持的所有方法。

go 复制代码
package demo
import "github.com/czx-lab/skeleton/internal/variable"
func Demo() {
    variable.Log.Info("info message")
}

日志文件默认是以json格式写入到storage/logs/system.log里面

配置

配置项的定义直接在config/config.yaml文件中定义,并且配置的读取写入是通过封装github.com/spf13/viper实现,在该骨架中,只提供了如下一些获取配置的方法:

go 复制代码
type ConfigInterface interface {
	Get(key string) any
	GetString(key string) string
	GetBool(key string) bool
	GetInt(key string) int
	GetInt32(key string) int32
	GetInt64(key string) int64
	GetFloat64(key string) float64
	GetDuration(key string) time.Duration
	GetStringSlice(key string) []string
}

需要注意的是,骨架中对配置项的获取做了缓存的处理,第一次加载是在文件中获取,后面每次回去都是在cache中获取,目前cache默认只支持memory,骨架中也支持自定义cache的方法,只需要实现config.CacheInterface接口就可以,比如需要使用redis作为配置缓存,可以通过下面的方式处理:

go 复制代码
type ConfigRedisCache struct {}

var _ config.CacheInterface = (*ConfigRedisCache)(nil)

func (c *ConfigRedisCache) Get(key string) any {
    return nil
}

func (c *ConfigRedisCache) Set(key string, value any) bool {
    return true
}

func (c *ConfigRedisCache) Has(key string) bool {
    return true
}

func (c *ConfigRedisCache) FuzzyDelete(key string) {
    
}

然后将ConfigRedisCache结构体配置到config.Options中,如下所示,修改internal/bootstrap/init.go初始化配置的方法:

go 复制代码
variable.Config, err := config.New(driver.New(), config.Options{
	BasePath: './',
    Cache: &ConfigRedisCache{}
})

config.yaml基础配置如下:

yaml 复制代码
# http配置
HttpServer:
  Port: ":8888"
  
  # 服务模式,和gin的gin.SetMode的值是一样的
  Mode: "debug"
# socket配置
Websocket:
  WriteReadBufferSize: 2048
  HeartbeatFailMaxTimes: 4
  PingPeriod: 20
  ReadDeadline: 100
  WriteDeadline: 35
  PingMsg: "ping"
  
# 数据库配置
Database:
  # 可以查看GORM相关的配置选项
  Mysql:
    SlowThreshold: 5
    LogLevel: 4
    ConnMaxLifetime: 1
    MaxIdleConn: 2
    MaxOpenConn: 2
    ConnMaxIdleTime: 12
    Reade:
      - "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"
    Write: "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"
  # mongo数据库的基础配置
  Mongo:
    Enable: false
    Uri:
    MinPoolSize: 10
    MaxPoolSize: 20


Redis:
  Disabled: false
  Addr: "192.168.1.4:6379"
  Pwd: ""
  Db: 0
  PoolSize: 20
  MaxIdleConn: 30
  MinIdleConn: 10
  # 单位(秒)
  MaxLifeTime: 60
  # 单位(分)
  MaxIdleTime: 30

# 定时任务
Crontab:
  Enable: true

# 消息队列,使用rocketmq
MQ:
  Enable: false
  Servers:
    - "127.0.0.1:9876"
  ConsumptionSize: 1
  Retries: 1

事件机制

  • 定义事件实体

    app/event/entity目录下定义一个事件实体,该实体实现了event.EventInterface接口:

    go 复制代码
    package entity
    
    type DemoEvent struct {}
    
    func (d *DemoEvent) EventName() string {
        return "demo-event"
    }
    
    func (d *DemoEvent) GetData() any {
        return "demo param"
    }
  • 定义事件监听

    app/event/listen目录中定义一个DemoEventListen事件监听,并且该DemoEventListen结构体必须要实现event.Interface接口:

    复制代码
    package listen
    
    import (
    	"fmt"
    	event2 "github.com/czx-lab/skeleton/app/event/entity"
    	"github.com/czx-lab/skeleton/internal/event"
    )
    
    type DemoEventListen struct {
    }
    
    func (*DemoEventListen) Listen() event.EventInterface {
    	return &event2.DemoEvent{}
    }
    
    func (*DemoEventListen) Process(data any) (any, error) {
    	return fmt.Sprintf("%v --> %s", data, "exec DemoEventListen.Process"), nil
    }
  • 最后需要将事件进行注册,在app/event/event.go文件中的Init方法内执行:

    go 复制代码
    variable.Event.Register(&listen.DemoEventListen{})
  • 调用事件执行

    go 复制代码
    variable.Event.Dispatch(&entity.DemoEvent{})

验证器

gin框架本身内置了validator校验,骨架里面只是对其参数的校验做了统一的校验入口。

通过如下方式获取进行参数的校验,并设置中文错误提示:

go 复制代码
type Param struct {
    Name  int    `binding:"required" form:"name" query:"name" json:"name"`
}
appRequest, err := AppRequest.New("zh")
if err != nil {
    return
}
var data Param
errMap := appRequest.Validator(ctx, &data)
fmt.Println(errMap)

骨架里面已经实现了默认的参数校验,可以在app/request/request.go文件中查看。并且在controller目录中base.go有一个Validate(ctx *gin.Context, param any)方法,在其他controller中要进行参数校验的时候,只需要继承base结构体,然后调用Validate方法。

go 复制代码
package controller

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

type DemoController struct {
    base
}

type DemoRequest struct {
    Id int `binding:"required" form:"id" query:"id" json:"id"`
}

func (d *DemoController) Index(ctx *gin.Context) {
    var param DemoRequest
    if err := d.base.Validate(ctx, &param); err == nil {
		ctx.JSON(http.StatusOK, gin.H{"data": param})
	} else {
		ctx.JSON(http.StatusBadRequest, gin.H{"message": err})
	}
}

验证规格参考github.com/go-playground/validator官方文档

命令行

基于github.com/spf13/cobra封装

  • 定义命令

    app/command目录中定义自己的命令,比如自定义一个输出success ok的命令

    go 复制代码
    package command
    
    import (
        "fmt"
        "github.com/spf13/cobra"
    )
    
    type FooCommand struct {}
    
    func (f *FooCommand) Command() *cobra.Command {
        return &cobra.Command{
    		Use:   "foo",
    		Short: "命令使用简介.",
    		Long: `命令介绍.`,
    		Run: func(cmd *cobra.Command, args []string) {
    			str, _ := cmd.Flags().GetString("name")
                 fmt.Printf("success, %s", str)
    		},
    	}
    }
    
    func (f *FooCommand) Flags(root *cobra.Command) {
    	root.PersistentFlags().String("name", "", "命令参数")
    }
  • 注册命令

    需要在cmd/cli/cli.go中的main方法内注册自定义命令。

  • 执行命令

    go 复制代码
    go run cmd/cli/cli.go foo --name ok
  • 查看命令信息

    go 复制代码
    go run cmd/cli/cli.go help
    
    // 或者
    go run cmd/cli/cli.go foo --help

定时任务

定时是通过封装github.com/robfig/cron/v3实现

  • 定义定时任务方法

    app/task目录下定义执行方法,比如每一分钟打印success字符

    go 复制代码
    package task
    
    import "fmt"
    
    type SuccessTask struct {
    }
    
    // 时间规则
    func (s *SuccessTask) Rule() string {
    	return "* * * * *"
    }
    
    func (s *SuccessTask) Execute() func() {
    	return func() {
    		fmt.Println("success")
    	}
    }
  • 加载定时任务

    需要在app/task/task.go文件中的Tasks方法内,加载自定义的任务,参考task目录下的task.go文件

相关推荐
互联网中的一颗神经元5 小时前
10. Large 对象分配:直接路径
开发语言·golang
microrain8 小时前
从关系库硬扛到专业时序引擎:SagooIoT时序数据存储的架构取舍
物联网·golang·开源·sagooiot
Vect__10 小时前
channel 底层剖析
golang
李燚13 小时前
数据库迁移不翻车:golang-migrate 实战,143 个 DDL 有序执行(第97篇-E83)
golang·agent·multiagent·migrate·eino·subagent·deepflux
念何架构之路14 小时前
Gin内置中间件详解
中间件·gin
运维开发笔记15 小时前
7.1 Go Map 基础
golang
运维开发笔记15 小时前
7.2 Go Map 进阶
golang
西瓜很甜哟1 天前
golang每日十题第2期
golang
程序员小八7771 天前
Go 语言特性
开发语言·后端·golang
X1A0RAN1 天前
pytest 动态报告:数据可视化篇 —— 用 Golang + Echo 打造自动化测试任务可视化平台
信息可视化·golang·pytest