节省时间与精力,更高效地打造稳定可靠的Web项目:基于Go语言和Gin框架的完善Web项目骨架。无需从零开始,直接利用这个骨架,快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势,轻松处理高并发、大流量的请求。构建可扩展性强、易于维护的代码架构,保证项目的长期稳定运行。同时,通过集成常用功能模块和最佳实践,减少繁琐的开发工作,使您专注于业务逻辑的实现。
该骨架每个组件之间可单独使用,组件之间松耦合,高内聚,组件的实现基于其他三方依赖包的封装。
目前该骨架实现了大多数的组件,比如事件,中间件,日志,配置,参数验证,命令行,定时任务等功能,目前可以满足大多数开发需求,后续会持续维护更新功能。
github地址:https://github.com/czx-lab/skeleton
设置环境变量并下载项目依赖
- go env -w GO111MODULE=on
- go env -w GOPROXY=https://goproxy.cn,direct
- go mod download
复制代码 运行项目
项目编译打包运行
- go build ./cmd/main.go
- // 编译
- make build
- // 运行
- make run
- // 编译与运行
- make
- // 运行项目
- ./main
复制代码 项目目录结构说明
- ├─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)方法定义注册:- 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接口- 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:- 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支持的所有方法。- 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实现,在该骨架中,只提供了如下一些获取配置的方法:- 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作为配置缓存,可以通过下面的方式处理:- 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初始化配置的方法:- variable.Config, err := config.New(driver.New(), config.Options{
- BasePath: './',
- Cache: &ConfigRedisCache{}
- })
复制代码 config.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接口:- 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方法内执行:
- variable.Event.Register(&listen.DemoEventListen{})
复制代码 - 调用事件执行
- variable.Event.Dispatch(&entity.DemoEvent{})
复制代码 验证器
gin框架本身内置了validator校验,骨架里面只是对其参数的校验做了统一的校验入口。
通过如下方式获取进行参数的校验,并设置中文错误提示:- 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方法。- 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, ¶m); 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的命令- 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 run cmd/cli/cli.go foo --name ok
复制代码 - 查看命令信息
- go run cmd/cli/cli.go help
- // 或者
- go run cmd/cli/cli.go foo --help
复制代码 定时任务
定时是通过封装github.com/robfig/cron/v3实现
- 定义定时任务方法
在app/task目录下定义执行方法,比如每一分钟打印success字符- 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文件
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |