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

打印 上一主题 下一主题

主题 841|帖子 841|积分 2523

节省时间与精力,更高效地打造稳定可靠的Web项目:基于Go语言和Gin框架的完善Web项目骨架。无需从零开始,直接利用这个骨架,快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势,轻松处理高并发、大流量的请求。构建可扩展性强、易于维护的代码架构,保证项目的长期稳定运行。同时,通过集成常用功能模块和最佳实践,减少繁琐的开发工作,使您专注于业务逻辑的实现。
该骨架每个组件之间可单独使用,组件之间松耦合,高内聚,组件的实现基于其他三方依赖包的封装。
目前该骨架实现了大多数的组件,比如事件,中间件,日志,配置,参数验证,命令行,定时任务等功能,目前可以满足大多数开发需求,后续会持续维护更新功能。
github地址:https://github.com/czx-lab/skeleton
设置环境变量并下载项目依赖
  1. go env -w GO111MODULE=on
  2. go env -w GOPROXY=https://goproxy.cn,direct
  3. go mod download
复制代码
运行项目
  1. go run ./cmd/main.go
复制代码
项目编译打包运行
  1. go build ./cmd/main.go
  2. // 编译
  3. make build
  4. // 运行
  5. make run
  6. // 编译与运行
  7. make
  8. // 运行项目
  9. ./main
复制代码
项目目录结构说明
  1. ├─app
  2. │  ├─command ---> 命令行
  3. │  ├─controller
  4. │  │    └─base.go ---> BaseController,主要定义了request参数验证器validator
  5. │  ├─event
  6. │  │  ├─entity ---> 事件实体目录
  7. │  │  ├─listen ---> 事件监听执行脚本目录
  8. │  │  └─event.go ---> 事件注册代码
  9. │  │      
  10. │  ├─middleware ---> 中间件代码目录
  11. │  ├─request ---> 请求参数校验代码目录
  12. │  │   └─request.go ---> 参数验证器
  13. │  └─task ---> 定时任务代码目录
  14. │     └─task.go ---> 注册定时任务脚本
  15. ├─cmd ---> 项目入口目录
  16. │  └─cli ---> 项目命令行模式入口目录
  17. ├─config
  18. │  └─config.yaml ---> 配置文件
  19. ├─internal ---> 包含第三方包的封装
  20. ├─router ---> 路由目录
  21. │  └─router.go
  22. ├─storage ---> 日志、资源存储目录
  23. │  └─logs
  24. └─test ---> 单元测试目录
复制代码
基础功能

路由

该骨架的web框架是gin,所以路由定义可直接阅读Gin框架的文档。
在该骨架中定义注册路由需要在router文件夹下面的router.go文件中的func (*AppRouter) Add(server *gin.Engine)方法定义注册:
  1. server.GET("/foo", func(ctx *gin.Context) {
  2.     ctx.String(http.StatusOK, "hello word!")
  3. })
复制代码
也可以通过自己定义路由的定义注册,只需要实现github.com/czx-lab/skeleton/internal/server/router下面的Interface接口。如下示例:
在router目录下定义了一个CustomRouter结构体,该结构体实现了Interface接口
  1. package router
  2. import (
  3.     "net/http"
  4.    
  5.     "github.com/czx-lab/skeleton/internal/server"
  6.     "github.com/gin-gonic/gin"
  7. )
  8. type CustomRouter struct {
  9.     server server.HttpServer
  10. }
  11. func NewCustom(srv server.HttpServer) *CustomRouter {
  12.     return &CustomRouter{
  13.         srv,
  14.     }
  15. }
  16. func (*CustomRouter) Add(srv *gin.Engine) {
  17.     srv.GET("/custom", func(ctx *gin.Context) {
  18.         ctx.String(http.StatusOK, "custom router")
  19.     })
  20. }
复制代码
需要注意的是,如果是自定义路由注册,需要修改项目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:
  1. type Custom struct{}
  2. func (c *Custom) Handle() gin.HandlerFunc {
  3.     return func(ctx *gin.Context) {
  4.         fmt.Println("Custom middleware exec...")
  5.     }
  6. }
复制代码
然后在定义路由的地方使用server.SetMiddleware(&middleware.Custom{})注册中间件。
定义全局路由中间件可以参考router/router.go中的New方法。
如果是局部中间件,可以直接在具体的路由上注册,参考gin路由中间件的用法
日志

在该骨架中的日志是直接对go.uber.org/zap的封装,使用时,直接通过全局变量variable.Log访问写入日志,可直接使用zap支持的所有方法。
  1. package demo
  2. import "github.com/czx-lab/skeleton/internal/variable"
  3. func Demo() {
  4.     variable.Log.Info("info message")
  5. }
复制代码
日志文件默认是以json格式写入到storage/logs/system.log里面
配置

配置项的定义直接在config/config.yaml文件中定义,并且配置的读取写入是通过封装github.com/spf13/viper实现,在该骨架中,只提供了如下一些获取配置的方法:
  1. type ConfigInterface interface {
  2.         Get(key string) any
  3.         GetString(key string) string
  4.         GetBool(key string) bool
  5.         GetInt(key string) int
  6.         GetInt32(key string) int32
  7.         GetInt64(key string) int64
  8.         GetFloat64(key string) float64
  9.         GetDuration(key string) time.Duration
  10.         GetStringSlice(key string) []string
  11. }
复制代码
需要注意的是,骨架中对配置项的获取做了缓存的处理,第一次加载是在文件中获取,后面每次回去都是在cache中获取,目前cache默认只支持memory,骨架中也支持自定义cache的方法,只需要实现config.CacheInterface接口就可以,比如需要使用redis作为配置缓存,可以通过下面的方式处理:
  1. type ConfigRedisCache struct {}
  2. var _ config.CacheInterface = (*ConfigRedisCache)(nil)
  3. func (c *ConfigRedisCache) Get(key string) any {
  4.     return nil
  5. }
  6. func (c *ConfigRedisCache) Set(key string, value any) bool {
  7.     return true
  8. }
  9. func (c *ConfigRedisCache) Has(key string) bool {
  10.     return true
  11. }
  12. func (c *ConfigRedisCache) FuzzyDelete(key string) {
  13.    
  14. }
复制代码
然后将ConfigRedisCache结构体配置到config.Options中,如下所示,修改internal/bootstrap/init.go初始化配置的方法:
  1. variable.Config, err := config.New(driver.New(), config.Options{
  2.         BasePath: './',
  3.     Cache: &ConfigRedisCache{}
  4. })
复制代码
config.yaml基础配置如下:
  1. # http配置
  2. HttpServer:
  3.   Port: ":8888"
  4.   
  5.   # 服务模式,和gin的gin.SetMode的值是一样的
  6.   Mode: "debug"
  7. # socket配置
  8. Websocket:
  9.   WriteReadBufferSize: 2048
  10.   HeartbeatFailMaxTimes: 4
  11.   PingPeriod: 20
  12.   ReadDeadline: 100
  13.   WriteDeadline: 35
  14.   PingMsg: "ping"
  15.   
  16. # 数据库配置
  17. Database:
  18.   # 可以查看GORM相关的配置选项
  19.   Mysql:
  20.     SlowThreshold: 5
  21.     LogLevel: 4
  22.     ConnMaxLifetime: 1
  23.     MaxIdleConn: 2
  24.     MaxOpenConn: 2
  25.     ConnMaxIdleTime: 12
  26.     Reade:
  27.       - "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"
  28.     Write: "root:root@tcp(192.168.1.4:3306)/test?charset=utf8mb4&loc=Local&parseTime=True"
  29.   # mongo数据库的基础配置
  30.   Mongo:
  31.     Enable: false
  32.     Uri:
  33.     MinPoolSize: 10
  34.     MaxPoolSize: 20
  35. Redis:
  36.   Disabled: false
  37.   Addr: "192.168.1.4:6379"
  38.   Pwd: ""
  39.   Db: 0
  40.   PoolSize: 20
  41.   MaxIdleConn: 30
  42.   MinIdleConn: 10
  43.   # 单位(秒)
  44.   MaxLifeTime: 60
  45.   # 单位(分)
  46.   MaxIdleTime: 30
  47. # 定时任务
  48. Crontab:
  49.   Enable: true
  50. # 消息队列,使用rocketmq
  51. MQ:
  52.   Enable: false
  53.   Servers:
  54.     - "127.0.0.1:9876"
  55.   ConsumptionSize: 1
  56.   Retries: 1
复制代码
事件机制


  • 定义事件实体
    在app/event/entity目录下定义一个事件实体,该实体实现了event.EventInterface接口:
    1. package entity
    2. type DemoEvent struct {}
    3. func (d *DemoEvent) EventName() string {
    4.     return "demo-event"
    5. }
    6. func (d *DemoEvent) GetData() any {
    7.     return "demo param"
    8. }
    复制代码
  • 定义事件监听
    在app/event/listen目录中定义一个DemoEventListen事件监听,并且该DemoEventListen结构体必须要实现event.Interface接口:
    1. package listen
    2. import (
    3.         "fmt"
    4.         event2 "github.com/czx-lab/skeleton/app/event/entity"
    5.         "github.com/czx-lab/skeleton/internal/event"
    6. )
    7. type DemoEventListen struct {
    8. }
    9. func (*DemoEventListen) Listen() event.EventInterface {
    10.         return &event2.DemoEvent{}
    11. }
    12. func (*DemoEventListen) Process(data any) (any, error) {
    13.         return fmt.Sprintf("%v --> %s", data, "exec DemoEventListen.Process"), nil
    14. }
    复制代码
  • 最后需要将事件进行注册,在app/event/event.go文件中的Init方法内执行:
    1. variable.Event.Register(&listen.DemoEventListen{})
    复制代码
  • 调用事件执行
    1. variable.Event.Dispatch(&entity.DemoEvent{})
    复制代码
验证器

gin框架本身内置了validator校验,骨架里面只是对其参数的校验做了统一的校验入口。
通过如下方式获取进行参数的校验,并设置中文错误提示:
  1. type Param struct {
  2.     Name  int    `binding:"required" form:"name" query:"name" json:"name"`
  3. }
  4. appRequest, err := AppRequest.New("zh")
  5. if err != nil {
  6.     return
  7. }
  8. var data Param
  9. errMap := appRequest.Validator(ctx, &data)
  10. fmt.Println(errMap)
复制代码
骨架里面已经实现了默认的参数校验,可以在app/request/request.go文件中查看。并且在controller目录中base.go有一个Validate(ctx *gin.Context, param any)方法,在其他controller中要进行参数校验的时候,只需要继承base结构体,然后调用Validate方法。
  1. package controller
  2. import "github.com/gin-gonic/gin"
  3. type DemoController struct {
  4.     base
  5. }
  6. type DemoRequest struct {
  7.     Id int `binding:"required" form:"id" query:"id" json:"id"`
  8. }
  9. func (d *DemoController) Index(ctx *gin.Context) {
  10.     var param DemoRequest
  11.     if err := d.base.Validate(ctx, &param); err == nil {
  12.                 ctx.JSON(http.StatusOK, gin.H{"data": param})
  13.         } else {
  14.                 ctx.JSON(http.StatusBadRequest, gin.H{"message": err})
  15.         }
  16. }
复制代码
验证规格参考github.com/go-playground/validator官方文档
命令行

基于github.com/spf13/cobra封装

  • 定义命令
    在app/command目录中定义自己的命令,比如自定义一个输出success ok的命令
    1. package command
    2. import (
    3.     "fmt"
    4.     "github.com/spf13/cobra"
    5. )
    6. type FooCommand struct {}
    7. func (f *FooCommand) Command() *cobra.Command {
    8.     return &cobra.Command{
    9.                 Use:   "foo",
    10.                 Short: "命令使用简介.",
    11.                 Long: `命令介绍.`,
    12.                 Run: func(cmd *cobra.Command, args []string) {
    13.                         str, _ := cmd.Flags().GetString("name")
    14.              fmt.Printf("success, %s", str)
    15.                 },
    16.         }
    17. }
    18. func (f *FooCommand) Flags(root *cobra.Command) {
    19.         root.PersistentFlags().String("name", "", "命令参数")
    20. }
    复制代码
  • 注册命令
    需要在cmd/cli/cli.go中的main方法内注册自定义命令。
  • 执行命令
    1. go run cmd/cli/cli.go foo --name ok
    复制代码
  • 查看命令信息
    1. go run cmd/cli/cli.go help
    2. // 或者
    3. go run cmd/cli/cli.go foo --help
    复制代码
定时任务

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

  • 定义定时任务方法
    在app/task目录下定义执行方法,比如每一分钟打印success字符
    1. package task
    2. import "fmt"
    3. type SuccessTask struct {
    4. }
    5. // 时间规则
    6. func (s *SuccessTask) Rule() string {
    7.         return "* * * * *"
    8. }
    9. func (s *SuccessTask) Execute() func() {
    10.         return func() {
    11.                 fmt.Println("success")
    12.         }
    13. }
    复制代码
  • 加载定时任务
    需要在app/task/task.go文件中的Tasks方法内,加载自定义的任务,参考task目录下的task.go文件

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

冬雨财经

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表