本章節主要的內容是對go-admin中的一些有趣編碼進行分析,爲本身之後提供一些借鑑mysql
使用cobra[眼鏡蛇]完成強壯cli的工具,確保穩定。git
使用cli的方式啓動項目的好處顯而易見,能夠在進行配置的自定義化,而不是固定的使用某個配置文件中的信息。在一些須要頻繁更換命令參數的場景下尤其有效。github
cobra的使用有一個默認的規定,即新建一個cmd
文件夾,基於這個文件夾定義本身的命令結構web
一、小型項目sql
cmd 文件夾數據庫
-- root.go 根命令服務器
-- version.go 版本命令【子命令】app
二、中/大型項目框架
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根類
//數據庫配置 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一次請求共用一個數據庫實例
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() }
從配置文件中獲取內容,定義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) } } }()