
什么是GPT-4?完整指南
| ├── v1 # v1版本接口服務
| ├── system # 系統級服務
| └── enter.go # 統一入口
├── config # 配置相關
├── core # 核心模塊
├── dao # dao層
├── global # 全局變量
├── initialize # 配置啟動初始化
├── middleware # 中間件
├── model # 數據庫結構體
├── router # 路由
├── service # 業務層
├── utils # 工具函數
├── config.yaml # 配置文件
├── go.mod # 包管理
├── main.go # 項目啟動文件
└── README.md # 項目README
使用以下命令初始化:
mkdir ewa_admin_server
cd ewa_admin_server
# init go.mod
go mod init ewa_admin_server
就會在根目錄下生成一個包管理配置文件 go.mod
。
一般來說,基本的后端服務都需要包括配置解析、日志、數據庫連接等流程,新建入口文件?main.go
:
package main
import "fmt"
func main() {
fmt.Println("hello world")
// TODO:1.配置初始化
// TODO:2.日志
// TODO:3.數據庫連接
// TODO:4.其他初始化
// TODO:5.啟動服務
}
安裝依賴
go get -u github.com/gin-gonic/gin
接著,我們可以試試用gin開啟一個服務:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// TODO:1.配置初始化
// TODO:2.日志
// TODO:3.數據庫連接
// TODO:4.其他初始化
// TODO:5.啟動服務
r := gin.Default()
// 測試路由
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
// 啟動服務器
r.Run(":8080")
}
啟動服務:
go run main.go
然后在瀏覽器中輸入?http://127.0.0.1:8080/ping
,就會在頁面返回一個?pong
。
配置文件是每個項目必不可少的部分,用來保存應用基本數據、數據庫配置等信息,避免要修改一個配置項需要到處找的尷尬。這里我使用?viper[1]?作為配置管理方案。
Viper
是 Go
語言中一個非常流行的配置管理庫,它可以幫助程序員在應用程序中加載和解析各種配置格式,如 JSON、YAML、TOML、INI
等。Viper
庫提供了一個簡單的接口,允許開發人員通過各種方法來訪問和管理配置。
下面是 Viper
庫的一些主要特點:
JSON
、TOML
、YAML
、HCL
、envfile
和 Java properties
格式的配置文件讀取配置信息etcd或Consul
)讀取并監控配置變化先安裝依賴:
go get -u github.com/spf13/viper
然后在項目根目錄下的?config.yaml
?文件中添加基本的配置信息:
app: # 基本配置信息
env: local # 環境
port: 8889 # 服務監聽端口
app_name: ewa_admin_server # 應用名稱
app_url: http://localhost # 應用域名
db_type: mysql # 使用的數據庫
在項目根目錄下新建文件夾?config
,用于存放所有配置對應的結構體,新建?config.go
?文件,定義?Configuration
?結構體,其?App
?屬性對應?config.yaml
?中的?app
:
package config
type Configuration struct {
App App mapstructure:"app" json:"app" yaml:"app"
}
新建?app.go
?文件,定義 App 結構體,其所有屬性分別對應?config.yaml
?中 app 下的所有配置
package config
type App struct {
Env string mapstructure:"env" json:"env" yaml:"env"
Port int mapstructure:"port" json:"port" yaml:"port"
AppName string mapstructure:"app_name" json:"app_name" yaml:"app_name"
AppUrl string mapstructure:"app_url" json:"app_url" yaml:"app_url"
DbType string mapstructure:"db_type" json:"db_type" yaml:"db_type"
}
注意:配置結構體中
mapstructure
標簽需對應config.ymal
中的配置名稱,viper
會根據標簽value
值把config.yaml
的數據賦予給結構體
為什么要將配置信息放入全局變量中?
在Go語言中,將配置信息存儲在全局變量中是一種常見的做法,這主要是因為全局變量的值可以在整個程序中訪問和共享,因此在某些情況下可以方便地進行配置管理和使用。
下面是一些常見的場景,可能會使用全局變量來存儲配置信息:
不過,使用全局變量也可能會帶來一些潛在的問題,比如:
因此,在使用全局變量存儲配置信息時,應該仔細考慮其對程序的影響,并確保采取適當的措施來減少潛在的問題。例如,可以使用只讀全局變量或使用鎖來保護全局變量的訪問。此外,也可以考慮使用依賴注入等技術來管理程序中的配置信息。
下面我們在?global
?中創建一個?global.go
?文件來集中存放全局變量:
package global
import (
"ewa_admin_server/config"
"github.com/spf13/viper"
)
var (
EWA_CONFIG config.Configuration
EWA_VIPER *viper.Viper
)
考慮實際工作中多環境開發、測試的場景,我們需要針對不同的環境使用不同的配置,在core
中加入一個internal
文件,添加一個constants.go
,寫入:
考慮實際工作中多環境開發、測試的場景,我們需要針對不同的環境使用不同的配置,在core中加入一個internal文件,添加一個constants.go,寫入:
然后在?core
?中新建?viper.go
,編寫配置初始化方法:
package core
import (
"ewa_admin_server/core/internal"
"ewa_admin_server/global"
"flag"
"fmt"
"os"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
// InitializeViper 優先級: 命令行 > 環境變量 > 默認值
func InitializeViper(path ...string) *viper.Viper {
var config string
if len(path) == 0 {
// 定義命令行flag參數,格式:flag.TypeVar(Type指針, flag名, 默認值, 幫助信息)
flag.StringVar(&config, "c", "", "choose config file.")
// 定義好命令行flag參數后,需要通過調用flag.Parse()來對命令行參數進行解析。
flag.Parse()
// 判斷命令行參數是否為空
if config == "" {
/*
判斷 internal.ConfigEnv 常量存儲的環境變量是否為空
比如我們啟動項目的時候,執行:GVA_CONFIG=config.yaml go run main.go
這時候 os.Getenv(internal.ConfigEnv) 得到的就是 config.yaml
當然,也可以通過 os.Setenv(internal.ConfigEnv, "config.yaml") 在初始化之前設置
*/
if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" {
switch gin.Mode() {
case gin.DebugMode:
config = internal.ConfigDefaultFile
fmt.Printf("您正在使用gin模式的%s環境名稱,config的路徑為%s\n", gin.EnvGinMode, internal.ConfigDefaultFile)
case gin.ReleaseMode:
config = internal.ConfigReleaseFile
fmt.Printf("您正在使用gin模式的%s環境名稱,config的路徑為%s\n", gin.EnvGinMode, internal.ConfigReleaseFile)
case gin.TestMode:
config = internal.ConfigTestFile
fmt.Printf("您正在使用gin模式的%s環境名稱,config的路徑為%s\n", gin.EnvGinMode, internal.ConfigTestFile)
}
} else {
// internal.ConfigEnv 常量存儲的環境變量不為空 將值賦值于config
config = configEnv
fmt.Printf("您正在使用%s環境變量,config的路徑為%s\n", internal.ConfigEnv, config)
}
} else {
// 命令行參數不為空 將值賦值于config
fmt.Printf("您正在使用命令行的-c參數傳遞的值,config的路徑為%s\n", config)
}
} else {
// 函數傳遞的可變參數的第一個值賦值于config
config = path[0]
fmt.Printf("您正在使用func Viper()傳遞的值,config的路徑為%s\n", config)
}
vip := viper.New()
vip.SetConfigFile(config)
vip.SetConfigType("yaml")
err := vip.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
vip.WatchConfig()
vip.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file changed:", e.Name)
if err = vip.Unmarshal(&global.EWA_CONFIG); err != nil {
fmt.Println(err)
}
})
if err = vip.Unmarshal(&global.EWA_CONFIG); err != nil {
fmt.Println(err)
}
fmt.Println("====1-viper====: viper init config success")
return vip
}
重新啟動項目,就會在控制臺打印:
====1-viper====: viper init config success
====app_name====: ewa_admin_server
這里面涉及到幾個知識點:
flag
Go 提供了一個?flag
?包,支持基本的命令行標志解析,請看下面的示例:
package main
import (
"flag"
"fmt"
)
func main() {
wordPtr := flag.String("word", "foo", "a string")
numbPtr := flag.Int("numb", 42, "an int")
forkPtr := flag.Bool("fork", false, "a bool")
var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")
flag.Parse()
fmt.Println("word:", *wordPtr)
fmt.Println("numb:", *numbPtr)
fmt.Println("fork:", *forkPtr)
fmt.Println("svar:", svar)
fmt.Println("tail:", flag.Args())
}
使用:
$ go build command-line-flags.go
$ ./command-line-flags -word=opt -numb=7 -fork -svar=flag
word: opt
numb: 7
fork: true
svar: flag
tail: []
$ ./command-line-flags -word=opt
word: opt
numb: 42
fork: false
svar: bar
tail: []
$ ./command-line-flags -word=opt a1 a2 a3
word: opt
...
tail: [a1 a2 a3]
$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
word: opt
numb: 42
fork: false
svar: bar
tail: [a1 a2 a3 -numb=7]
$ ./command-line-flags -h
Usage of ./command-line-flags:
-fork=false: a bool
-numb=42: an int
-svar="bar": a string var
-word="foo": a string
$ ./command-line-flags -wat
flag provided but not defined: -wat
Usage of ./command-line-flags:
...
2.3.2?os.Setenv()
?&?os.Getenv()
package main
import (
"fmt"
"os"
"strings"
)
func main() {
os.Setenv("FOO", "1")
fmt.Println("FOO:", os.Getenv("FOO"))
fmt.Println("BAR:", os.Getenv("BAR"))
fmt.Println()
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
fmt.Println(pair[0])
}
}
使用:
$ go run environment-variables.go
FOO: 1
BAR:
TERM_PROGRAM
PATH
SHELL
$ BAR=2 go run environment-variables.go
FOO: 1
BAR: 2
...
gin.Mode()
在初始化本路由的時候使用,從源碼可看出,通過給變量ginMode
賦值的方式提供了三種模式:
DebugMode
ReleaseMode
TestMode
DebugMode
比ReleaseMode
多了一些額外的錯誤信息,生產環境不需要這些信息。而TestMode
是gin
用于自己的單元測試,用來快速開關DebugMode
。對其它開發者沒什么意義。可以通過gin.SetMode(AppMode)
來設置mode。
需要注意的是:
SetMode()
應該聲明在gin.New()
前,否則配置無法更新:
關于viper的使用,最好是看官方文檔
現在我們已經將配置解析到了全局變量中,就可以將其使用到服務啟動邏輯中了,在?core
?中新建?server.go
?文件,然后將服務啟動的方法寫在這里:
package core
import (
"ewa_admin_server/global"
"fmt"
"net/http"
"time"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
type server interface {
ListenAndServe() error
}
func RunServer() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
address := fmt.Sprintf(":%d", global.EWA_CONFIG.App.Port)
s := initServer(address, r)
// 保證文本順序輸出
time.Sleep(10 * time.Microsecond)
fmt.Println(address
, address)
s.ListenAndServe()
}
func initServer(address string, router *gin.Engine) server {
// 使用endless庫創建一個HTTP服務器,其中address是服務器的監聽地址(如:8080),router是HTTP請求路由器。
s := endless.NewServer(address, router)
// 設置HTTP請求頭的讀取超時時間為20秒,如果在20秒內未讀取到請求頭,則會返回一個超時錯誤。
s.ReadHeaderTimeout = 20 * time.Second
// 設置HTTP響應體的寫入超時時間為20秒,如果在20秒內未將響應體寫入完成,則會返回一個超時錯誤。
s.WriteTimeout = 20 * time.Second
// 設置HTTP請求頭的最大字節數為1MB。如果請求頭超過1MB,則會返回一個錯誤。
s.MaxHeaderBytes = 1 << 20
return s
}
使用 endless
的作用是實現無縫重載和優雅關閉 HTTP
服務器。自帶優雅地重啟或停止你的Web服務器,我們可以使用fvbock/endless[2]來替換默認的ListenAndServe
,有關詳細信息,請參閱問題#296[3]。
endless
是一個可以用于重新加載和優雅關閉HTTP服務器的庫。它可以在運行時更新服務器代碼而無需停止正在運行的HTTP服務器。這使得服務器能夠在生產環境下無縫地進行更新和維護,同時不影響當前正在運行的請求和連接。
使用 endless
,可以在代碼修改后,通過發送信號量通知服務器進行重載,新的代碼會被加載并運行,舊的連接會繼續服務,新的連接將使用新的代碼進行處理。當需要關閉服務器時,endless
會等待所有當前處理的請求完成后再關閉服務器,這樣可以確保所有請求都能得到處理,避免數據丟失和用戶體驗下降。
在?Gin
?中使用?endless
?可以提高服務器的可靠性和穩定性,同時也能提高開發效率,減少服務器維護和更新的停機時間。
這些配置可以幫助我們優化HTTP服務器的性能和安全性。通過設置超時時間和最大字節數等參數,可以防止一些潛在的安全問題和性能問題。
例如,設置超時時間可以防止客戶端故意保持連接而導致的資源浪費,設置最大字節數可以防止客戶端發送過大的請求頭而導致的資源浪費和安全問題。
重啟項目,訪問?http://127.0.0.1:8889/ping
,依然能在頁面看到?pong
,就說明我們的初始化配置成功了。
為什么需要記錄日志?
在Go語言中記錄日志是一個非常常見的行為。以下是幾個原因:
值得注意的是,日志記錄是一個非常重要的行為,可以幫助我們快速排查問題、優化性能、監控安全性以及進行后續分析和審計。因此,在Go語言中記錄日志是一個必要的步驟。
這里將使用 zap[4] 作為日志庫,一般來說,日志都是需要寫入到文件保存的,這也是 zap 唯一缺少的部分,所以我將結合 lumberjack[5] 來使用,實現日志切割歸檔的功能。使用Zap作為日志記錄器,有以下幾個原因:
而Lumberjack是一個用于高性能、輪轉和滾動日志文件的庫。在Zap中使用Lumberjack有以下幾個好處:
在Zap中使用Lumberjack可以提高日志的性能、穩定性和可靠性,并且方便管理和備份歷史日志。
安裝zap:
go get -u go.uber.org/zap
go get -u gopkg.in/natefinch/lumberjack.v2
在?config.yarml
?中新增日志配置:
# ...
zap: # 日志配置
level: info # 日志級別
prefix: '[east_white_common_admin/server]' # 日志前綴
format: console # 輸出
director: log # 日志存放的文件
encode_level: LowercaseColorLevelEncoder # 編碼級別
stacktrace_key: stacktrace # 棧名
max_age: 0 # 日志留存時間
show_line: true # 顯示行
log_in_console: true # 輸出控制臺
然后新建?config/zap.go
,在文件中增加對應的結構體和日志級別轉換方法:
package config
import (
"strings"
"go.uber.org/zap/zapcore"
)
type Zap struct {
Level string mapstructure:"level" json:"level" yaml:"level"
// 級別
Prefix string mapstructure:"prefix" json:"prefix" yaml:"prefix"
// 日志前綴
Format string mapstructure:"format" json:"format" yaml:"format"
// 輸出
Director string mapstructure:"director" json:"director" yaml:"director"
// 日志文件夾
EncodeLevel string mapstructure:"encode_level" json:"encode_level" yaml:"encode_level"
// 編碼級
StacktraceKey string mapstructure:"stacktrace_key" json:"stacktrace_key" yaml:"stacktrace_key"
// 棧名
MaxAge int mapstructure:"max_age" json:"max_age" yaml:"max_age"
// 日志留存時間
ShowLine bool mapstructure:"show_line" json:"show_line" yaml:"show_line"
// 顯示行
LogInConsole bool mapstructure:"log_in_console" json:"log_in_console" yaml:"log_in_console"
// 輸出控制臺
}
// ZapEncodeLevel 根據 EncodeLevel 返回 zapcore.LevelEncoder
func (z *Zap) ZapEncodeLevel() zapcore.LevelEncoder {
switch {
case z.EncodeLevel == "LowercaseLevelEncoder": // 小寫編碼器(默認)
return zapcore.LowercaseLevelEncoder
case z.EncodeLevel == "LowercaseColorLevelEncoder": // 小寫編碼器帶顏色
return zapcore.LowercaseColorLevelEncoder
case z.EncodeLevel == "CapitalLevelEncoder": // 大寫編碼器
return zapcore.CapitalLevelEncoder
case z.EncodeLevel == "CapitalColorLevelEncoder": // 大寫編碼器帶顏色
return zapcore.CapitalColorLevelEncoder
default:
return zapcore.LowercaseLevelEncoder
}
}
// TransportLevel 根據字符串轉化為 zapcore.Level
func (z *Zap) TransportLevel() zapcore.Level {
z.Level = strings.ToLower(z.Level)
switch z.Level {
case "debug":
return zapcore.DebugLevel
case "info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.WarnLevel
case "dpanic":
return zapcore.DPanicLevel
case "panic":
return zapcore.PanicLevel
case "fatal":
return zapcore.FatalLevel
default:
return zapcore.DebugLevel
}
}
別忘了在?config.go
?中加入?zap
:
package config
type Configuration struct {
App App mapstructure:"app" json:"app" yaml:"app"
Zap Zap mapstructure:"zap" json:"zap" yaml:"zap"
}
接下來就是寫日志初始化方法了,在?core
?中新建?zap.go
:
package core
import (
"ewa_admin_server/core/internal"
"ewa_admin_server/global"
"ewa_admin_server/utils"
"fmt"
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// InitializeZap Zap 獲取 zap.Logger
func InitializeZap() (logger *zap.Logger) {
if ok, _ := utils.PathExists(global.EWA_CONFIG.Zap.Director); !ok { // 判斷是否有Director文件夾
fmt.Printf("create %v directory\n", global.EWA_CONFIG.Zap.Director)
_ = os.Mkdir(global.EWA_CONFIG.Zap.Director, os.ModePerm)
}
cores := internal.Zap.GetZapCores()
logger = zap.New(zapcore.NewTee(cores...))
if global.EWA_CONFIG.Zap.ShowLine {
logger = logger.WithOptions(zap.AddCaller())
}
fmt.Println("====2-zap====: zap log init success")
return logger
}
在?core/internal
?中新建?zap.go
:
package internal
import (
"ewa_admin_server/global"
"fmt"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var Zap = new(_zap)
type _zap struct{}
// GetEncoder 獲取 zapcore.Encoder
func (z *_zap) GetEncoder() zapcore.Encoder {
if global.EWA_CONFIG.Zap.Format == "json" {
return zapcore.NewJSONEncoder(z.GetEncoderConfig())
}
return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
}
// GetEncoderConfig 獲取zapcore.EncoderConfig
func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
TimeKey: "time",
NameKey: "logger",
CallerKey: "caller",
StacktraceKey: global.EWA_CONFIG.Zap.StacktraceKey,
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: global.EWA_CONFIG.Zap.ZapEncodeLevel(),
EncodeTime: z.CustomTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
}
}
// GetEncoderCore 獲取Encoder的 zapcore.Core
func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
writer, err := FileRotateLogs.GetWriteSyncer(l.String()) // 使用file-rotatelogs進行日志分割
if err != nil {
fmt.Printf("Get Write Syncer Failed err:%v", err.Error())
return nil
}
return zapcore.NewCore(z.GetEncoder(), writer, level)
}
// CustomTimeEncoder 自定義日志輸出時間格式
func (z *_zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(global.EWA_CONFIG.Zap.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
}
// GetZapCores 根據配置文件的Level獲取 []zapcore.Core
func (z *_zap) GetZapCores() []zapcore.Core {
cores := make([]zapcore.Core, 0, 7)
for level := global.EWA_CONFIG.Zap.TransportLevel(); level <= zapcore.FatalLevel; level++ {
cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
}
return cores
}
// GetLevelPriority 根據 zapcore.Level 獲取 zap.LevelEnablerFunc
func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
switch level {
case zapcore.DebugLevel:
return func(level zapcore.Level) bool { // 調試級別
return level == zap.DebugLevel
}
case zapcore.InfoLevel:
return func(level zapcore.Level) bool { // 日志級別
return level == zap.InfoLevel
}
case zapcore.WarnLevel:
return func(level zapcore.Level) bool { // 警告級別
return level == zap.WarnLevel
}
case zapcore.ErrorLevel:
return func(level zapcore.Level) bool { // 錯誤級別
return level == zap.ErrorLevel
}
case zapcore.DPanicLevel:
return func(level zapcore.Level) bool { // dpanic級別
return level == zap.DPanicLevel
}
case zapcore.PanicLevel:
return func(level zapcore.Level) bool { // panic級別
return level == zap.PanicLevel
}
case zapcore.FatalLevel:
return func(level zapcore.Level) bool { // 終止級別
return level == zap.FatalLevel
}
default:
return func(level zapcore.Level) bool { // 調試級別
return level == zap.DebugLevel
}
}
}
以及?file_rotate_logs.go
:
package internal
import (
"ewa_admin_server/global"
"os"
"path"
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap/zapcore"
)
var FileRotateLogs = new(fileRotateLogs)
type fileRotateLogs struct{}
// GetWriteSyncer 獲取 zapcore.WriteSyncer
func (r *fileRotateLogs) GetWriteSyncer(level string) (zapcore.WriteSyncer, error) {
fileWriter, err := rotatelogs.New(
path.Join(global.EWA_CONFIG.Zap.Director, "%Y-%m-%d", level+".log"),
rotatelogs.WithClock(rotatelogs.Local),
rotatelogs.WithMaxAge(time.Duration(global.EWA_CONFIG.Zap.MaxAge)*24*time.Hour), // 日志留存時間
rotatelogs.WithRotationTime(time.Hour*24),
)
if global.EWA_CONFIG.Zap.LogInConsole {
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err
}
return zapcore.AddSync(fileWriter), err
}
新建?utils/directory.go
?文件,編寫?PathExists
?函數,用于判斷路徑是否存在:
package utils
import (
"errors"
"os"
)
//@function: PathExists
//@description: 文件目錄是否存在
//@param: path string
//@return: bool, error
func PathExists(path string) (bool, error) {
fi, err := os.Stat(path)
if err == nil {
if fi.IsDir() {
return true, nil
}
return false, errors.New("存在同名文件")
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
在?global/global.go
?中,添加?Log
?成員屬性
package global
import (
"ewa_admin_server/config"
"go.uber.org/zap"
"github.com/spf13/viper"
)
var (
EWA_CONFIG config.Configuration
EWA_VIPER *viper.Viper
EWA_LOG *zap.Logger
)
在?main.go
?中調用日志初始化函數,并嘗試寫入日志:
package main
import (
"ewa_admin_server/core"
"ewa_admin_server/global"
"go.uber.org/zap"
"github.com/gin-gonic/gin"
)
const AppMode = "debug" // 運行環境,主要有三種:debug、test、release
func main() {
gin.SetMode(AppMode)
// TODO:1.配置初始化
global.EWA_VIPER = core.InitializeViper()
// TODO:2.日志
global.EWA_LOG = core.InitializeZap()
zap.ReplaceGlobals(global.EWA_LOG)
global.EWA_LOG.Info("server run success on ", zap.String("zap_log", "zap_log"))
// TODO:3.數據庫連接
// TODO:4.其他初始化
// TODO:5.啟動服務
core.RunServer()
}
重啟項目,就會發現在根目錄下生成了一個?log
?文件夾,作為我們日后開發用的日志記錄文件。
本文章轉載微信公眾號@Go開發大全