catalogue
config - global configuration information
settings - set configuration information
Introduction to the use of redis redis
preface
In my cognitive world, the layered concept of MVC can't keep up with the pace of the times, in the era of separation of front and rear ends.
Premises
Tools used
vcsode
go module
CLD layering concept
1.controller -- control interface
2.local -- logic processing
3.dao -- database management
Gin web scaffolding
config - global configuration information
The file type is yaml
config.yaml
app: name: "bubble" mode: "dev" port: 8081 log: level: "debug" filename: "bubble.log" max_size: 200 max_age: 30 max_backups: 7 mysql: host: "127.0.0.1" port: "3306" user: "root" password: "xxxxxxxx" dbname: "sql_demo" max_open_conns: max_idle_conns: redis: host: "127.0.0.1" port: 6379 password: "" db: 0 pool_size: 100
Why does this folder exist?
Think about it. When we modify the configuration information, such as buying a new server, we need to modify a series of values of the host and port that need to be changed. We will modify the configuration in various files. The process of search and modification is time-consuming and laborious. At this time, the programmer's "laziness" comes into play, creating a new folder with configuration information. When looking, it's clear at a glance.
Note that on the Linux server, we can use the Docker container to host the code, which can be easily migrated to different servers
settings - set configuration information
Initialization of viper- Introduction and use of viper Library
func Init() (err error) { viper.SetConfigFile("config.yaml") // Specify profile viper.AddConfigPath("./config/") // Specify the path to find the configuration file err = viper.ReadInConfig() // Read configuration information if err != nil { // Failed to read configuration information return err } // Monitor profile changes viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("The configuration file has been modified") }) return }
The effect of using this library is to get twice the result with half the effort and obtain configuration information.
logger logging
Initialization of zap- zap receives the default log of gin
// InitLogger initialization Logger func Init() (err error) { writeSyncer := getLogWriter( viper.GetString("log.filename"), viper.GetInt("log.max_size"), viper.GetInt("log.max_backups"), viper.GetInt("log.max_age"), ) encoder := getEncoder() var l = new(zapcore.Level) err = l.UnmarshalText([]byte(viper.GetString("log.level"))) if err != nil { return } core := zapcore.NewCore(encoder, writeSyncer, l) lg := zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(lg) // Replace the global logger instance in the zap package. In other packages, you only need to use zap L () can be called return } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, } return zapcore.AddSync(lumberJackLogger) } // GinLogger receives the default log of the gin framework func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) zap.L().Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } // Ginrecovery recovers the possible Panics of the project and uses zap to record relevant logs func GinRecovery(stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { zap.L().Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() return } if stack { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } }
During initialization, the middleware should be rewritten to overwrite the default log of gin
dao database configuration
mysql-Use of slqx
var db *sqlx.DB func Init() (err error) { //fmt.Sprintf splicing function dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True", viper.GetString("mysql.user"), viper.GetString("mysql.password"), viper.GetString("mysql.host"), viper.GetInt("mysql.port"), viper.GetString("mysql.dbname"), ) // You can also use MustConnect to panic if the connection is unsuccessful db, err = sqlx.Connect("mysql", dsn) if err != nil { zap.L().Error("connect db err", zap.Error(err)) return } db.SetMaxOpenConns(viper.GetInt("mysql.max_open_conns")) db.SetMaxIdleConns(viper.GetInt("mysql.max_idle_conns")) return } func Close (){ _ = db.Close() }
Don't forget to import anonymously
_ "github.com/go-sql-driver/mysql"
The viper and zap libraries are used in the code
redis-Introduction to redis
// Declare a global rdb variable var rdb *redis.Client // Initialize connection func Init() (err error) { rdb = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", viper.GetString("redis.host"), viper.GetInt("redis.port"), ), Password: viper.GetString("redis.password"), // no password set DB: viper.GetInt("redis.db"), // use default DB PoolSize: viper.GetInt("reids.pool_size"), }) _, err = rdb.Ping().Result() return } func Close (){ _ = rdb.Close() }
The configuration information is in config yaml
Routes - routing
func Setup() *gin.Engine { r := gin.New() //Use of Middleware r.Use(logger.GinLogger(),logger.GinRecovery(true)) r.GET("/",func(c *gin.Context) { c.String(http.StatusOK,"ok") }) return r; }
controllers
According to the actual business scenario
logic
According to the actual business scenario
main.go
func main() { //load configuration if err := logger.Init(); err != nil { fmt.Printf("init settings failed,err:%v\n", err) return } //Initialize log if err := settings.Init(); err != nil { fmt.Printf("init settings failed,err:%v\n", err) return } defer zap.L().Sync() zap.L().Debug("logger init succ") //Initialize Mysql database if err := mysql.Init(); err != nil { fmt.Printf("init settings failed,err:%v\n", err) return } mysql.Close() //Initialize redis if err := redis.Init(); err != nil { fmt.Printf("init settings failed,err:%v\n", err) return } redis.Close() //Register routing r := routers.Setup() //Start service (graceful shutdown) srv := &http.Server{ Addr: fmt.Sprintf(":%d", viper.GetInt("app.port")), Handler: r, } go func() { // Start a goroutine startup service if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // Wait for the interrupt signal to gracefully shut down the server, and set a 5-second timeout for the server shutdown operation quit := make(chan os.Signal, 1) // Create a channel to receive signals // kill will send syscall by default SIGTERM signal // kill -2 send syscall SIGINT signal, the commonly used Ctrl+C is the SIGINT signal that triggers the system // kill -9 send syscall Sigkill signal, but it can't be captured, so it doesn't need to be added // signal.Notify sends the received syscall SIGINT or syscall SIGTERM signal forwarded to quit signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // There will be no blocking here <-quit // Blocking here, when the above two signals are received, it will be executed downward log.Println("Shutdown Server ...") // Create a 5-second timeout context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Close the service gracefully within 5 seconds (finish processing the unprocessed requests and then close the service). If it exceeds 5 seconds, it will timeout and exit if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown: ", err) } log.Println("Server exiting") }
summary
The basic scaffold has been completed and can meet the general needs. In view of the limited capacity, it will be continuously updated later.