本章節主要的內容是對go-admin中的一些有趣編碼進行分析,為自己以後提供一些借鑑
使用cli方式啟動專案
使用cobra[眼鏡蛇]完成強壯cli的工具,確保穩定。
使用cli的方式啟動專案的好處顯而易見,可以在進行配置的自定義化,而不是固定的使用某個配置檔案中的資訊。在一些需要頻繁更換命令引數的場景下尤為有效。
cobra的使用有一個預設的規定,即新建一個cmd
資料夾,基於這個資料夾定義自己的命令結構
1、小型專案
cmd 資料夾
-- root.go 根命令
-- version.go 版本命令【子命令】
2、中/大型專案
cmd 資料夾
--version 資料夾
--server.go
--config 資料夾
--server.go
-- root.go 入口指令
不同的專案選用不同的方式進行命令的定義。
監聽中斷訊號
// 等待中斷訊號以優雅地關閉伺服器(設定 5 秒的超時時間)
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
fmt.Printf("%s Shutdown Server ... \r\n", tools.GetCurrentTimeStr())
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Info("Server exiting")
訊號監聽,當遇到ctrl+c的時候當前服務結束,列印結束日誌
配置檔案Viper的使用
viper的使用,配置檔案路徑,獲取viper根類
//資料庫配置
cfgDatabase = viper.Sub("settings.database")
DatabaseConfig = InitDatabase(cfgDatabase)
//應用程式配置
cfgApplication = viper.Sub("settings.application")
ApplicationConfig = InitApplication(cfgApplication)
其他的內容與此方式類似
viper.Sub獲取對應分類的內容,然後使用自定義的Init***函式初使化類
/tools/config資料夾中定義實體類,對應config.yml中的分類
舉例:application.go對應圖二的application分類內容,其他的也是同樣意思
此目錄下的內容,package包名為config。config下的配置資訊使用大寫表示【對外暴露】。如果其他地方要呼叫,引用包後使用config.分類名稱.引數
即可得到配置檔案的內容
初始化資料庫
配置檔案 dirver:mysql,定義不同的資料庫字串,程式啟動時case 連線字串,對不同的資料庫型別做不同的配置
資料庫日誌開關作為單獨配置區分,如果開啟,資料庫日誌單獨啟用
現在程式的資料庫.go都需要實現介面如下
type Database interface {
Setup()
Open(conn string, cfg *gorm.Config) (db *gorm.DB, err error)
GetConnect() string
GetDriver() string
}
interface定義介面,其他的go檔案實現介面-使用不同的開源資料庫驅動
介面訪問控制
casbin
輕量級開源訪問控制框架,採用了元模型的設計思想,支援多種經典的訪問控制方案,如基於角色的訪問控制 RBAC、基於屬性的訪問控制 ABAC 等
策略檔案
// Initialize the model from a string.
var text = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*")
`
使用 github.com/casbin/gorm-adapter/v3
作為casbin的資料庫介面卡即可
資料庫上下文整合gin
gin一次請求共用一個資料庫例項
r.Use()是gin的中介軟體擴充套件方法,WithContextDb方法說明的是,如果每次請求到來會開啟一次資料庫連結,獲得資料庫連結例項,將例項返回後作為引數傳遞給方法。如果有此例項,c.Set()方法執行,將此例項儲存到gin執行上下文中。
全域性異常處理
使用一個異常捕獲方法recover()補獲未知曉的異常
如果異常訊息符合定義的規則,列印後返回給前臺。
如果無異常,正常執行下一個定義的中介軟體
r.Use(CustomError)
func CustomError(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
if c.IsAborted() {
c.Status(200)
}
switch errStr := err.(type) {
case string:
p := strings.Split(errStr, "#")
if len(p) == 3 && p[0] == "CustomError" {
statusCode, e := strconv.Atoi(p[1])
if e != nil {
break
}
c.Status(statusCode)
fmt.Println(
time.Now().Format("2006-01-02 15:04:05"),
"[ERROR]",
c.Request.Method,
c.Request.URL,
statusCode,
c.Request.RequestURI,
c.ClientIP(),
p[2],
)
c.JSON(http.StatusOK, gin.H{
"code": statusCode,
"msg": p[2],
})
}
default:
panic(err)
}
}
}()
c.Next()
}
web服務執行區分ssl和正常
從配置檔案中獲取內容,定義addr
得到對應的web執行引擎,當前使用gin
啟動一個協程,判斷如果ssl,使用TlS方法。否則使用正常模式
srv := &http.Server{
Addr: config.ApplicationConfig.Host + ":" + config.ApplicationConfig.Port,
Handler: global.Cfg.GetEngine(),
}
go func() {
// 服務連線
if config.SslConfig.Enable {
if err := srv.ListenAndServeTLS(config.SslConfig.Pem, config.SslConfig.KeyStr); err != nil && err != http.ErrServerClosed {
log.Fatal("listen: ", err)
}
} else {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("listen: ", err)
}
}
}()