節省時間與精力,更高效地打造穩定可靠的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 run ./cmd/main.go
專案編譯打包執行
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"
"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 "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 "skeleton/app/event/entity" "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
檔案