【golang自学之路(三)】go-web快速开发脚手架搭建

llbnk 2024-06-17 14:33:01 阅读 65

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

脚手架介绍一、main二、viper三、zap四、mysql五、redis六、gin路由总结我的目标


脚手架介绍

在这里插入图片描述

golang做web开发搭建快速脚手架:

1.采用gin框架+gorm框架负责resful+mysql

2.缓存采用go-redis

3.日志选择zap

4.配置采用viper

脚手架仓库:golang web脚手架

项目采用CLD分层理念

Controller:服务的入口,负责处理路由、参数校验、请求转发。

Logic/Service:逻辑(服务)层,负责处理业务逻辑。

**DAO/Repository:**负责数据与存储相关功能。

在这里插入图片描述

区别于java框架常用的MVC框架,CLD更好的杜绝了Model层在处理业务逻辑时的臃肿。

在这里插入图片描述

CLD目录结构

在这里插入图片描述


一、main

在main函数中去初始化viper,zap,grom,gin,go-redis。

初始化如下代码:

main.go

package mainimport ("context""fmt""log""net/http""os""os/signal""syscall""time""web_demo/dao/mysql""web_demo/dao/redis""web_demo/logger""web_demo/routes""web_demo/settings""github.com/spf13/viper""go.uber.org/zap")//go web demofunc main() { var err error// 1.加载配置if err = settings.Init(); err != nil { fmt.Printf("init settings failed,error:%v\n", err)}//2.初始化日志//2.初始化日志if err = logger.Init(settings.Conf.LogConfig, settings.Conf.Mode); err != nil { fmt.Printf("init logger failed,error:%v\n", err)}defer zap.L().Sync()zap.L().Debug("logger init success")//3.初始化MySQL连接if err = mysql.Init(settings.Conf.MySQLConfig); err != nil { fmt.Printf("init mysql failed,error:%v\n", err)}defer mysql.Close()//4.初始化Redis连接if err = redis.Init(settings.Conf.RedisConfig); err != nil { fmt.Printf("init redis failed,error:%v\n", err)}defer redis.Close()//5,注册路由//5,注册路由r := routes.Setup(settings.Conf.Mode)// 6.启动服务(优雅关机)srv := http.Server{ Addr: fmt.Sprintf(":%d", viper.GetInt("app.port")),Handler: r,}go func() { // 开启一个goroutine启动服务if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err)}}()// 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时quit := make(chan os.Signal, 1) // 创建一个接收信号的通道// kill 默认会发送 syscall.SIGTERM 信号// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quitsignal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞<-quit // 阻塞在此,当接收到上述两种信号时才会往下执行zap.L().Info("Shutdown Server ...")// 创建一个5秒超时的contextctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()// 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出if err := srv.Shutdown(ctx); err != nil { zap.L().Error("Server Shutdown: ", zap.Error(err))}}


二、viper

在这里插入图片描述

viper是用于读取配置的优秀第三方库,具体可以参考李文周老师的博客进行学习连接:https://liwenzhou.com/posts/Go/viper/,此处就不在赘述。

config.yaml

name: "web_demo"mode: "dev"port: 8081log: level: "debug" filename: "web_demo.log" max_size: 200 max_age: 30 max_backups: 7mysql: host: "127.0.0.1" port: 3306 user: "root" password: "abc123" dbname: "db1" max_open_conns: 200 max_idle_conns: 50redis: host: "192.168.238.128" port: 6379# db: 0

settings.go

package settingsimport ("fmt""github.com/fsnotify/fsnotify""github.com/spf13/viper")var Conf = new(AppConfig)type AppConfig struct { Name string `mapstructure:"name"`Mode string `mapstructure:"mode"`Version string `mapstructure:"version"`Port int `mapstructure:"port"`*LogConfig `mapstructure:"log"`*MySQLConfig `mapstructure:"mysql"`*RedisConfig `mapstructure:"redis"`}type MySQLConfig struct { Host string `mapstructure:"host"`User string `mapstructure:"user"`Password string `mapstructure:"password"`DB string `mapstructure:"dbname"`Port int `mapstructure:"port"`MaxOpenConns int `mapstructure:"max_open_conns"`MaxIdleConns int `mapstructure:"max_idle_conns"`}type RedisConfig struct { Host string `mapstructure:"host"`Password string `mapstructure:"password"`Port int `mapstructure:"port"`DB int `mapstructure:"db"`PoolSize int `mapstructure:"pool_size"`MinIdleConns int `mapstructure:"min_idle_conns"`}type LogConfig struct { Level string `mapstructure:"level"`Filename string `mapstructure:"filename"`MaxSize int `mapstructure:"max_size"`MaxAge int `mapstructure:"max_age"`MaxBackups int `mapstructure:"max_backups"`}func Init() (err error) { viper.SetConfigFile("./config/config.yaml")err = viper.ReadInConfig() // 读取配置信息if err != nil { // 读取配置信息失败fmt.Printf("viper.ReadInConfig failed, err:%v\n", err)return}// 把读取到的配置信息反序列化到 Conf 变量中if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err)}viper.WatchConfig()viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("配置文件修改了...")if err := viper.Unmarshal(Conf); err != nil { fmt.Printf("viper.Unmarshal failed, err:%v\n", err)}})return}


三、zap

在这里插入图片描述

zap日志库是非常快的、结构化的,分日志级别的Go日志库。具体zap库教程可看李文周老师连接:https://liwenzhou.com/posts/Go/zap/

package loggerimport ("net""net/http""net/http/httputil""os""runtime/debug""strings""time""web_demo/settings""github.com/spf13/viper""github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore")// InitLogger 初始化Loggerfunc Init(cfg *settings.LogConfig, mode string) (err error) { writeSyncer := getLogWriter(cfg.Filename,cfg.MaxSize,cfg.MaxBackups,cfg.MaxAge,)encoder := getEncoder()var l = new(zapcore.Level)err = l.UnmarshalText([]byte(viper.GetString("log.level")))if err != nil { return}var core zapcore.Coreif mode == "dev" { // 进入开发模式,日志输出到终端consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())core = zapcore.NewTee(zapcore.NewCore(encoder, writeSyncer, l),zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel),)} else { core = zapcore.NewCore(encoder, writeSyncer, l)}lg := zap.New(core, zap.AddCaller())zap.ReplaceGlobals(lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可return}func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.TimeKey = "time"encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderencoderConfig.EncodeDuration = zapcore.SecondsDurationEncoderencoderConfig.EncodeCaller = zapcore.ShortCallerEncoderreturn 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 接收gin框架默认的日志func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.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 recover掉项目可能出现的panic,并使用zap记录相关日志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 boolif 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: errcheckc.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()}}


四、mysql

在这里插入图片描述

使用gorm连接mysql,具体gorm教程可看李文周老师连接:https://liwenzhou.com/posts/Go/gorm/

mysql.go

package mysqlimport ("fmt""web_demo/settings""go.uber.org/zap""github.com/jinzhu/gorm"_ "github.com/jinzhu/gorm/dialects/mysql")var db *gorm.DBfunc Init(cfg *settings.MySQLConfig) (err error) { var dbConfig stringdbConfig = getMysqlConfig(cfg)db, err = gorm.Open("mysql", dbConfig)if err != nil { zap.L().Error("grom init failed %v\n", zap.Error(err))}//GORM定义mysql最大连接数sqlDB := db.DB() //设置数据库连接池参数sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) //设置数据库连接池最大连接数sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) //连接池最大允许的空闲连接数,如果没有sql任务需要执行的连接数大于20,超过的连接会被连接池关闭。return}func getMysqlConfig(cfg *settings.MySQLConfig) string { return fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s?charset=utf8mb4&parseTime=True&loc=Local",cfg.User,cfg.Password,cfg.DB)}func Close() (err error) { err = db.Close()if err != nil { //fmt.Printf("grom close failed error:%v\n", err)zap.L().Error("grom close failed error:%v\n", zap.Error(err))}return}


五、redis

在这里插入图片描述

使用go-redis连接redis,具体go-redis教程可看李文周老师连接:https://liwenzhou.com/posts/Go/redis/

redis.go

package redisimport ("fmt""github.com/go-redis/redis""web_demo/settings")// 声明一个全局的rdb变量var Rdb *redis.Client// 初始化连接func Init(cfg *settings.RedisConfig) (err error) { Rdb = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d",cfg.Host,cfg.Port),Password: "", // no password setDB: cfg.DB, // use default DBPoolSize: cfg.PoolSize,})_, err = Rdb.Ping().Result()if err != nil { return err}return}func Close() { _ = Rdb.Close()}


六、gin路由

在这里插入图片描述

gin框架注册路由。

routes.go

package routesimport ("github.com/gin-gonic/gin""net/http""web_demo/logger")func Setup(mode string) *gin.Engine { if mode == gin.ReleaseMode { gin.SetMode(gin.ReleaseMode) // gin设置成发布模式}r := gin.New()r.Use(logger.GinLogger(), logger.GinLogger())r.GET("/", func(context *gin.Context) { context.JSON(http.StatusOK, "ok")})return r}


总结

学习go开发必须需要通用web脚手架,经过搭建成功启动。

,


我的目标

希望在年底学习一下内容:

java学习内容:

1.tomcat源码

2.dubbo源码

3.zookeeper源码

4.netty源码

go学习内容:

1.gin框架学习

2.简单go项目

3.go基础知识进阶(gmp,gc,channel,map,slice源码等)

中间件学习内容:

1.kafka使用及源码

框架学习内容:

1.从零开始学架构

算法学习内容:

1.复习leetcode中top 100

我平时喜欢没事还打游戏,因为有宝宝所以希望在平时时间能尽量完成上述学习内容(希望能戒掉游戏哈哈哈)。

也希望有和我一样的一起学习的小伙伴共同学习进步,我建一个qq后端交流群:279868576,希望小伙伴们加入共同督促进步。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。