较通用web脚手架模板搭建

较通用web脚手架模板搭建

从这里开始就接触到以后写项目的思维了。

做一个web开发,那就要层次分明,要有个实现的规划,这通常也是有一个较为通用的模板的。

总的来说:不同的层次有不同的模块,每个模块有必须实现的功能,我们要做的就是填充每个模块。也就是每个模块就相当于给你需求,然后你用代码去把这些需求给实现,这就是做项目的过程。

内容层次划分

main函数(入口)
//1.加载配置文件

//2.日志初始化//3.初始化mysql连接//4.初始化redis连接//5.注册路由//6.启动服务

从这个地方作为项目的出发去构建,以上面的内容为框架模板来开发。


接下来一个个模块的讲

1.加载配置文件

这个模块的实现:利用viper来管理我们的配置文件。
这里的话我们就可以建两个模块,一个模块叫settings,就是作为我们的viper的操作配置。另一个模块叫conf,专门用来存放配置文件。
代码实现:
**现在的目标就是要在settings中去使用viper去管理这个配置文件config。
**

那就建一个conf文件夹,我们通常都用文件夹来作为模块
这个模块就用于存放我的配置文件。
建一个文件夹settings用于做viper的相关配置。
请添加图片描述
关于配置文件,那就是里面放了我整个web应用里面用的的所有的配置项信息。
类似这样的:
请添加图片描述
这用的是yaml格式。当然json格式也是可以的。

关于viper管理配置文件:
我个人总结逻辑实现分为两部分:
1.将viper与配置文件进行绑定,然后将配置文件读入viper中。
2.配置viper监控配置文件(这样的好处是当我们修改了配置文件,那可以使得我们的程序可以与修改后的配置文件进行同步)

package settingsimport ("fmt""github.com/fsnotify/fsnotify""github.com/spf13/viper"
)func Init() (err error) {viper.SetConfigFile("./conf/config.yaml")//设置目标配置文件的路径err = viper.ReadInConfig()//配置文件读入到viperif err != nil {fmt.Printf("viper.ReadInConfig() failed,err:%v\n", err)return}viper.WatchConfig()//启动监控viper.OnConfigChange(func(in fsnotify.Event) {//这个相当于钩子函数。//当配置文件被修改的时候,这个函数就会被调用,在这里你就可以实现当配置文件被修改了,你可以进行一些处理。后面会进一步说明。fmt.Println("配置文件修改了...")})return
}

接下来就是main函数
这两个模块模块配置好了。那肯定要在main函数进行总体的启动:

// 1.加载配置文件if err := settings.Init(); err != nil {fmt.Printf("init settings failed,err:%v\n", err)return}

启动也很简单,就是直接调用这个模块就可以了。

2.日志初始化

接下来实现的就是日志模块
那就先建一个文件夹,logger,里面的函数实现logger的配置,这里我使用的是zap日志库,不用go原生的日志库。

package loggerimport ("net""net/http""net/http/httputil""os""runtime/debug""strings""time""github.com/spf13/viper""github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore"
)var lg *zap.Logger //这是一个全局的logger,也就是说你在外部可以通过这个在Logger模块下创建的logger,你就等于可以使用zap.Logger日志库了。
//但是我们这里并不是这样做,原因就是麻烦,因为你要用这个日志库那么就要这么调用logger.lg这样,如果在有些场景下你的文件夹有很多层,那你要一层一层的调。
//这里我们用zap.ReplaceGloabls会更加方便,将在下面的代码说到。// 初始化Logger
func Init() (err error) {//核心是下面的zap.New()就可以创建logger。//上面这些内容全是logger的参数配置项。//用于定义日志消息的输出目标和方式,定义日志的文件名称,日志的最大大小,日志的最大备份数量,日志的最大保存天数。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)//这个将配置项反序列化给l作为日志登记传入下面的coreerr = l.UnmarshalText([]byte(viper.GetString("log.level")))if err != nil {return}//这个是构造核心,需要上面定义的三个参数core := zapcore.NewCore(encoder, writeSyncer, l)//这段代码才是创建logger,参数要传core。lg = zap.New(core, zap.AddCaller())//这个函数实现了全局替换,这样可以实现在任意一个包下调logger的更方便的方式。只需要调用zap.L()就可以拿到这个全局的logger。zap.AddCaller()是显示日志在那行调用。zap.ReplaceGlobals(lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可return
}//这个看看源码就知道了,这就是个Encoder的配置项。
//这个的核心关键在于zapcore.NewJsonEncoder,用的json的编码方式。然后传的参数都是一些配置项。返回值是一个编码器,就是构建一个最基础的编码配置,然后传入构建。
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)
}//这个是负责将日志输出到一个文件,并且通过lumberjack库支持日志文件的自动轮转。
//函数返回一个zapcore.WriteSyncer接口,这是zap日志库用于抽象日志写入行为的一个接口。
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {//这个是实现自动轮转的日志写入器lumberJackLogger := &lumberjack.Logger{Filename:   filename,MaxSize:    maxSize,MaxBackups: maxBackup,MaxAge:     maxAge,}//这个相当于给它包了一下,最后返回一个zapcore.WriteSyncer类型。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()}
}

上面的代码实现了啥:
创建zap日志实例,然后还实现了关于gin的两个中间件,这两个中间件就是gin里面的自定义中间件,因为gin里面的中间件是使用原生的日志库来实现的,这里我用外部的zap来实现相同的功能的中间件,那就必须自己重写一个。
很多东西我觉得要是看不懂那就说明语法关于zap的知识忘了,可以回去zap补一下知识点。

我直接解读一下前面的配置的地方。

然后在main函数中也给他启动一下

if err := logger.Init(); err != nil {fmt.Printf("init logger failed,err:%v\n", err)return}defer zap.L().Sync() //这个是确保程序退出之前安全地同步日志缓冲区,将所有挂起的日志输出操作完成。zap.L().Debug("logger init success...")

那么日志模块我们也写完了。

3.初始化mysql连接模块

一般关于数据库的模块我们一般用dao模块(CLD分层),然后再在里面细分mysql模块,最后去完成这个mysql模块的填充:
这个模块比较简单:
完成的内容:
1、连接上数据库。
2、对数据库进行相关的配置,比如配置数据库的最大连接数,最大空闲连接数等待。

package mysqlimport ("fmt""go.uber.org/zap"_ "github.com/go-sql-driver/mysql""github.com/jmoiron/sqlx""github.com/spf13/viper"
)// 定义一个全局对象db
var db *sqlx.DB// 定义一个初始化数据库的函数
func Init() (err error) {// DSN:Data Source Name//这里就是先构建一个数据源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"))// 不会校验账号密码是否正确// 注意!!!这里不要使用:=,我们是给全局变量赋值,然后在main函数中使用全局变量db//这里就是直接连接db, err = sqlx.Connect("mysql", dsn)if err != nil {zap.L().Info("connect DB failed,err :%v\n", zap.Error(err))return}//这里就是进行数据库连接相关的配置。db.SetMaxIdleConns(viper.GetInt("mysql.max_idle_conns"))db.SetMaxOpenConns(viper.GetInt("mysql.max_open_conns"))return
}func Close() {_ = db.Close()
}

然后main函数启动一下

//3.初始化mysql连接if err := mysql.Init(); err != nil {fmt.Printf("init mysql failed,err:%v\n", err)return}//这里我们想实现在这里关闭连接也很简单,直接在mysql里面写一个关闭内部全局db的函数,然后调用就关了。defer mysql.Close()

redis模块

就是负责连接redis
可以看出很简单:就是从配置文件把配置项拉出填入到redis客户端创建的配置中。

package redisimport ("context""fmt""github.com/go-redis/redis/v8""github.com/spf13/viper"
)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"), // 密码DB:       viper.GetInt("redis.db"),          // 数据库PoolSize: viper.GetInt("redis.pool_size"),   // 连接池大小})ctx := context.Background()_, err = rdb.Ping(ctx).Result()return}func Close() {_ = rdb.Close()
}

main函数中,redis模块启动一下:

if err := redis.Init(); err != nil {fmt.Printf("init redis failed,err:%v\n", err)return}//这里也是相同的思想。defer redis.Close()

注册路由

这部分也是一个模块,建一个routes模块,里面全部是路由的注册信息。
里面要实现哪些内容:
1.创建一个引擎(New),并且对引擎进行配置(中间件添加)。
2.所有关于路由的注册信息都在这里写.
3.返回值就是这个引擎,在main入口函数,我们只想得到这个引擎直接拿来用。

package routesimport ("my_web/logger""net/http""github.com/gin-gonic/gin"
)func SetUp() *gin.Engine {r := gin.New()r.Use(logger.GinLogger(), logger.GinRecovery(true))r.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "ok")})return r}

服务启动

就是负责将这个项目启动和停止。

//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 {zap.L().Fatal("listen: %s\n", zap.Error(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                                               // 阻塞在此,当接收到上述两种信号时才会往下执行log.Println("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().Fatal("Server Shutdown: ", zap.Error(err))}

最基本的就是这些模块,初次之外还有一些模块,在项目中也重要:
controllers:服务的入口,负责处理路由,参数校验,请求转发
logic:逻辑服务层
models:放一些模型。
pkg:第三方库
请添加图片描述

总结:
这个模板比较适合个人维护整个项目的情况下。下一次介绍一下在企业环境下比较常用的模板。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2804590.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

常用!基础!吴恩达deeplearning.ai:Tensorflow中数据形式

由于一些历史遗留问题&#xff0c;Numpy和Tensorflow在数据处理方面存在一些不一致的地方&#xff0c;今天我们主要来了解一下其中一些约定俗成的形式&#xff0c;从而能够让你写出正确的代码(耍帅&#xff09;。 文章目录 Tensorflow是如何表示数据的特征向量关于Numpy矩阵激…

Flutter(一):安装和环境配置、创建Flutter项目

安装和环境配置、创建Flutter项目 Flutter 下载方式1方式2 Flutter 环境配置配置国内镜像站点解压 Flutter将 flutter 添加到系统环境变量中运行 flutter doctor来验证安装 Android Studio下载插件创建项目安装 Android SDK 工具在模拟器上运行 Flutter 下载 方式1 全版本&…

深度学习介绍与环境搭建

深度学习介绍与环境搭建 慕课大学人工智能学习笔记&#xff0c;自己学习记录用的。&#xff08;赋上连接&#xff09; https://www.icourse163.org/learn/ZUCC-1206146808?tid1471365447#/learn/content?typedetail&id1256424053&cid1289366515人工智能、机器学习与…

数据结构day4

实现创建单向循环链表、创建结点、判空、输出、头插、按位置插入、尾删、按位置删除 loop_list.c #include "loop_list.h" loop_p create_head() {loop_p L(loop_p)malloc(sizeof(loop_list));if(LNULL){printf("空间申请失败\n");return NULL;}L->le…

华为配置WLAN AC和AP之间VPN穿越示例

配置WLAN AC和AP之间VPN穿越示例 组网图形 图1 配置WLAN AC和AP之间VPN穿越示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 企业用户接入WLAN网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff0c;不影响…

CentOS 7.9.2009离线安装mysql 8.0客户端 (rpm包)

环境&#xff1a; #需求&#xff1a; 该服务器需要将csv文件入库到远端的mysql 服务器上。 CentOS Linux release 7.9.2009 (Core) 离线环境 &#xff0c;需安装mysql客户端 8.0.27#下载地址 https://downloads.mysql.com/archives/community/#按此顺序安装 rpm -ivh mysql…

alist修改密码(docker版)

rootarmbian:~# docker exec -it [docker名称] ./alist admin set abcd123456 INFO[2024-02-20 11:06:29] reading config file: data/config.json INFO[2024-02-20 11:06:29] load config from env with prefix: ALIST_ INFO[2024-02-20 11:06:29] init logrus..…

Kotlin filterIsInstance filterNotNull forEach

Kotlin filterIsInstance filterNotNull forEach fun main(args: Array<String>) {val i1 MyItem(1, 1)val i2: MyItem? nullval i3: Int 3val i4 "4"val i5 nullval i6 MyItem(6, 6)val list mutableListOf<Any?>(i1, i2, i3, i4, i5, i6)lis…

【算法与数据结构】链表、哈希表、栈和队列、二叉树(笔记二)

文章目录 四、链表理论五、哈希表理论五、栈和队列理论5.1 单调栈 六、二叉树理论6.1 树的定义6.2 二叉树的存储方式6.3 二叉树的遍历方式6.4 高度和深度 最近博主学习了算法与数据结构的一些视频&#xff0c;在这个文章做一些笔记和心得&#xff0c;本篇文章就写了一些基础算法…

【初始RabbitMQ】高级发布确认的实现

在生产环境中由于一些不明原因&#xff0c;导致 rabbitmq 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c; 导致消息丢失&#xff0c;需要手动处理和恢复。于是&#xff0c;我们开始思考&#xff0c;如何才能进行 RabbitMQ 的消息可靠投递呢&#xff1f; …

浅谈集群的分类

本文主要介绍集群部署相关的知识&#xff0c;介绍集群部署的基础&#xff0c;集群的分类、集群的负载均衡技术&#xff0c;集群的可用性以及集群的容错机制。随后介绍Redis-Cluster以及Mysql的架构以及主从复制原理。 集群介绍 单台服务器本身会受到带宽、内存、处理器等多方面…

万界星空科技电子机电行业MES系统,2000元/年起

电子行业在生产管理上具有典型的离散制造特点&#xff0c;采用多品种、多批量或单件的生产组织方式。产品升级换代迅速&#xff0c;生命周期短&#xff0c;变更频繁&#xff0c;版本控制复杂。 同时产品的种类较多&#xff0c;非标准产品多&#xff0c;加工工序复杂&#xff0…

使用MongoDB数据库和Mongoose库在Node.js中进行数据存储

在Node.js中使用MongoDB数据库和Mongoose库进行数据存储是前端开发中常用的技术之一。MongoDB是一种非关系型数据库&#xff0c;具有高性能、易扩展等优点&#xff1b;而Mongoose是在Node.js中对MongoDB进行操作的框架&#xff0c;简化了数据库操作&#xff0c;并提供了丰富的功…

Leetcode日记 2583. 二叉树中的第 K 大层和

Leetcode日记 2583. 二叉树中的第 K 大层和 题目&#xff1a;解题思路&#xff1a;代码实现制作不易&#xff0c;感谢三连&#xff0c;谢谢啦 题目&#xff1a; 给你一棵二叉树的根节点 root 和一个正整数 k 。 树中的 层和 是指 同一层 上节点值的总和。 返回树中第 k 大的层和…

QT常用类

五、常用类 QString 字符串类&#xff08;掌握&#xff09; QString是Qt的字符串类&#xff0c;与C的std::string相比&#xff0c; 不再使用ASCII编码。QString使用的是Unicode编码。 QString中每个字符都是一个16位的QChar&#xff0c;而不是8位的char。 QString完全支持中文&…

动态预测波动率:ARCH模型和Heston模型

制定符合需要的资产组合需要了解每支的波动率&#xff0c;波动率高的资产意味着价格波动大&#xff0c;风险高&#xff0c;为了降低资产组合的风险&#xff0c;通常会在波动率较低的资产中分配更多的资金。同时波动率也和市场参与者的情绪有关&#xff0c;波动率大&#xff0c;…

【算法与数据结构】684、685、LeetCode冗余连接I II

文章目录 一、684、冗余连接 I二、685、冗余连接 II三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、684、冗余连接 I 思路分析&#xff1a;题目给出一个无向有环图&#xff0c;要求去掉一个边以后构成一个树&#xf…

如何在VSCode中带有参数的Debug(name、program、$file、args、pickArgs、指定虚拟环境)

0. 省流 {"version": "0.2.0","configurations": [{"name": "调试train.py文件","type": "debugpy","request": "launch","program": "train.py","cons…

如何改变.net托管的入口main函数

有小伙伴问: .NET托管入口Main函数可以修改成别的函数&#xff0c;用来作为程序的入口吗&#xff1f; 答案&#xff1a;当然是可以的。这也算是.NET里面非常简单的骚操了。本篇来用最新的.NET8演示下&#xff0c;如何修改Main入口。 1.简单控制台例子&#xff1a; namespace…

美国硅谷大带宽服务器|大带宽服务器租赁贵吗?

在数字化时代&#xff0c;服务器成为了支撑各种在线业务和应用程序的重要基石。尤其对于那些需要处理大量数据、保证快速响应和稳定连接的企业或个人来说&#xff0c;大带宽服务器成为了不可或缺的选择。而美国硅谷&#xff0c;作为全球科技创新的摇篮&#xff0c;其服务器租赁…