Go Web项目结构 + 基础代码
Go Web工程
下面是项目的包图,可以通过包图来理清项目包的结构。
Go Web工程
下面是项目的包图,可以通过包图来理清项目包的结构。

因为我是从Java转过来的,其实这种包的结构与Java的类似。Java是Controller、Service、Respository。
Go就变成了 api、service、dao, 其实也差不多,因为Go设计思想跟Java的区别还是很大,但本质还是通过架构来解耦。
路由配置
建立 routes/router.go 文件。 此文件用来配置api的URI,配置路由、路由分组、添加中间件。
func InitRouter() http.Handler {
r := gin.New()
// 设置可信代理
r.SetTrustedProxies([]string{"*"})
// 设置静态文件目录
r.Use(middleware.Logger()) // 自定义的 zap 日志中间件
r.Use(middleware.ErrorRecovery(false)) // 自定义错误处理中间件
r.Use(middleware.Cors()) // 跨域中间件
// 需要鉴权的接口
auth := base.Group("") // "/admin"
// 需要鉴权的接口
auth.Use(middleware.JWTAuth()) // JWT 鉴权中间件
auth.Use(middleware.RBAC()) // casbin 权限中间件
auth.Use(middleware.ListenOnline()) // 监听在线用户
auth.Use(middleware.OperationLog()) // 记录操作日志
// 路由配置
page := auth.Group("/page")
{
page.GET("/list", pageAPI.GetList) // 页面列表
page.POST("", pageAPI.SaveOrUpdate) // 新增/编辑页面
page.DELETE("", pageAPI.Delete) // 删除页面
}
return r
}
数据库配置
func InitMySQLDB() *gorm.DB {
mysqlCfg := config.Cfg.Mysql
dns := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
mysqlCfg.Username,
mysqlCfg.Password,
mysqlCfg.Host,
mysqlCfg.Port,
mysqlCfg.Dbname,
)
db, err := gorm.Open(mysql.Open(dns), gormConfig())
if err != nil {
log.Fatal("MySQL 连接失败, 请检查参数")
}
log.Println("MySQL 连接成功")
// 迁移数据表,在没有数据表结构变更时候,建议注释不执行
// MakeMigrate(db)
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10) // 设置连接池中的最大闲置连接
sqlDB.SetMaxOpenConns(100) // 设置数据库的最大连接数量
sqlDB.SetConnMaxLifetime(10 * time.Second) // 设置连接的最大可复用时间
return db
}
// gorm 配置
func gormConfig() *gorm.Config {
return &gorm.Config{
// gorm 日志模式
Logger: logger.Default.LogMode(getLogMode(config.Cfg.Mysql.LogMode)),
// 禁用外键约束
DisableForeignKeyConstraintWhenMigrating: true,
// 禁用默认事务(提高运行速度)
SkipDefaultTransaction: true,
NamingStrategy: schema.NamingStrategy{
// 使用单数表名,启用该选项,此时,`User` 的表名应该是 `user`
SingularTable: true,
},
}
}
编码规范
这个主要是看人家怎么写的接口,怎么调用业务层。分析一下。
Api
routes/router.go
auth.GET("/home", blogInfoAPI.GetHomeInfo) // 后台首页信息
注:这里的API层非常的简单,只是对于 Service的一个调用
api/v1/home.go
func (*BlogInfo) GetHomeInfo(c *gin.Context) {
r.SuccessData(c, blogInfoService.GetHomeInfo())
}
RESTFul 返回操作
在utils\r\result.go 为 *gin.Context 实现了返回的方法,因为使用的是指针,所以不需要返回值,可以直接进行传递函数调用。
// 返回 JSON 数据
func ReturnJson(c *gin.Context, httpCode, code int, msg string, data any) {
// c.Header("", "") // 根据需要在头部添加其他信息
c.JSON(httpCode, Response{
Code: code,
Message: msg,
Data: data,
})
}
// 语法糖函数封装
// 自定义 httpCode, code, data
func Send(c *gin.Context, httpCode, code int, data any) {
ReturnJson(c, httpCode, code, GetMsg(code), data)
}
// 自动根据 code 获取 message, 且 data == nil
func SendCode(c *gin.Context, code int) {
Send(c, http.StatusOK, code, nil)
}
// 自动根据 code 获取 message, 且 data != nil
func SendData(c *gin.Context, code int, data any) {
Send(c, http.StatusOK, code, data)
}
func SuccessData(c *gin.Context, data any) {
Send(c, http.StatusOK, OK, data)
}
func Success(c *gin.Context) {
Send(c, http.StatusOK, OK, nil)
}
Service
因为在Api内调用的Service服务,返回的都是需要反序列化为Json的View Object,这个VO放在Model内,用于在持久层查询的Model也放在model里。感觉有点混乱,看可不可能把VO放在 service包里。
VO的目的就是把格式变成符合URI业务需求的一个结构体。把一些数据放到里面。
// 根据 [文章id] 获取 [文章详情]
func (*Article) GetInfo(id int) resp.ArticleDetailVO {
article := dao.GetOne(model.Article{}, "id", id)
category := dao.GetOne(model.Category{}, "id", article.CategoryId)
tagNames := tagDao.GetTagNamesByArtId(id)
articleVo := utils.CopyProperties[resp.ArticleDetailVO](article)
// 前端 category 为 '' 不显示 placeholder, 为 null 显示 placeholder
if category.ID != 0 {
articleVo.CategoryName = &category.Name
}
articleVo.TagNames = tagNames
return articleVo
}
Dao
Dao层的所有方法或函数
注:
- 不具有普适性的函数功能,应变成某个结构体的方法,变成方法后仅能其结构体初始化的变量调用
- 反之,则可以使用泛型
如何给 Dao包全局变量 dao.DB 赋值。
在dao包下,所有的使用到DB变量的go代码文件内,直接声明var DB *gorm.DB全局变量 。
在route初始函数里,对dao包的DB变量 也就是 dao.DB 进行赋值,赋的值,也就是关于初始化的gorm.DB。
routes/router.go 初始化全局变量
// 初始化全局变量
func InitGlobalVariable() {
// 初始化 Viper
utils.InitViper()
// 初始化 Logger
utils.InitLogger()
// 初始化数据库 DB
dao.DB = utils.InitMySQLDB() // 需要先导入 gvb.sql
// dao.DB = utils.InitSQLiteDB("gorm.db") // TODO: 默认无数据,暂时无法使用
// 初始化 Redis
utils.InitRedis()
// 初始化 Casbin
utils.InitCasbin(dao.DB)
}
基于泛型的普适型写法
这样,其使用Model进行创建的时候,就不需要指定类型了。非常方便用于简单的业务。如果很多业务的CURD都有共同特性,那么也可以如此。
func Create[T any](data *T) {
err := DB.Create(&data).Error
if err != nil {
panic(err)
}
}
针对特定结构体的写法
Views Object居然也被用于在Model进行查询,这个我感觉没必要。因为实体结构体与视力结构体应有所区分。
func (*Article) GetInfoById(id int) (res resp.FrontArticleDetailVO) {
DB.Table("article").
Preload("Category").
Preload("Tags").
Where("id = ? AND is_delete = 0 AND status = 1", id).
First(&res)
return
}
中间件
鉴权的原理就是,拦截一条线路,然后判断每次请求访问此条线路的权限。
根据我们开发时,我们的开发习惯是,基于一个原始URI为一组路由,这个原始URI就是我们鉴权的根据点。
例如 需要检查 /sys下所有的接口是否已登录,则 与/sys/** 匹配都会被进行鉴权。
就是钩子,一层一层的拦截

// 需要鉴权的接口
auth := base.Group("") // "/admin"
// !注意使用中间件的顺序
auth.Use(middleware.JWTAuth()) // JWT 鉴权中间件
auth.Use(middleware.RBAC()) // casbin 权限中间件
auth.Use(middleware.ListenOnline()) // 监听在线用户
auth.Use(middleware.OperationLog()) // 记录操作日志
Cors 跨域处理
这个就是使用别人写好的中间件,地址在: cors
// 跨域中间件
func Cors() gin.HandlerFunc {
return cors.New(cors.Config{
// 允许跨域请求网站
AllowOrigins: []string{"*"},
// 允许使用的请求方式
AllowMethods: []string{"PUT", "POST", "GET", "DELETE", "OPTIONS", "PATCH"},
// 允许使用的请求头
AllowHeaders: []string{"Origin", "Authorization", "Content-Type", "X-Requested-With"},
// 暴露的请求头
ExposeHeaders: []string{"Content-Type"},
// 凭证共享
AllowCredentials: true,
// 允许跨域的源网站
AllowOriginFunc: func(origin string) bool {
return true
},
// 超时时间设定
MaxAge: 24 * time.Hour,
})
}
Middleware 小结
Go语言里,函数也是一个值,可以直接转送匿名函数作为值。
中间件的写法就是
middleware/XXX.go
func XXX() gin.HandlerFunc {
return func(c *gin.Context) {
// anything else
}
}
将XXX挂在哪个基础的URI上。
routes/router.go
sys := router.Group("/sys")
sys.Use(middleware.XXX())
模板技术
渲染这是一个常用的技术。
// 指定导入模板的目录
router.LoadHTMLGlob("template/*")
测试模板渲染的代码
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是测试", "ce": "123456"})
})
模板代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{.title}}</title>
</head>
<body>
Number: <h1>{{.ce}}</h1>
</body>
</html>
头尾分离,模块化写法
注 : 需要提前定义好 public/header,public/footer
Gin HTML 渲染模板:https://www.topgoer.cn/docs/ginkuangjia/ginhtmlxuanran
{{ define "user/index.html" }}
{{template "public/header" .}}
Address: {{.address}}
{{template "public/footer" .}}
{{ end }}
因为我是从Java转过来的,其实这种包的结构与Java的类似。Java是Controller、Service、Respository。
Go就变成了 api、service、dao, 其实也差不多,因为Go设计思想跟Java的区别还是很大,但本质还是通过架构来解耦。
路由配置
建立 routes/router.go 文件。 此文件用来配置api的URI,配置路由、路由分组、添加中间件。
func InitRouter() http.Handler {
r := gin.New()
// 设置可信代理
r.SetTrustedProxies([]string{"*"})
// 设置静态文件目录
r.Use(middleware.Logger()) // 自定义的 zap 日志中间件
r.Use(middleware.ErrorRecovery(false)) // 自定义错误处理中间件
r.Use(middleware.Cors()) // 跨域中间件
// 需要鉴权的接口
auth := base.Group("") // "/admin"
// 需要鉴权的接口
auth.Use(middleware.JWTAuth()) // JWT 鉴权中间件
auth.Use(middleware.RBAC()) // casbin 权限中间件
auth.Use(middleware.ListenOnline()) // 监听在线用户
auth.Use(middleware.OperationLog()) // 记录操作日志
// 路由配置
page := auth.Group("/page")
{
page.GET("/list", pageAPI.GetList) // 页面列表
page.POST("", pageAPI.SaveOrUpdate) // 新增/编辑页面
page.DELETE("", pageAPI.Delete) // 删除页面
}
return r
}
数据库配置
func InitMySQLDB() *gorm.DB {
mysqlCfg := config.Cfg.Mysql
dns := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
mysqlCfg.Username,
mysqlCfg.Password,
mysqlCfg.Host,
mysqlCfg.Port,
mysqlCfg.Dbname,
)
db, err := gorm.Open(mysql.Open(dns), gormConfig())
if err != nil {
log.Fatal("MySQL 连接失败, 请检查参数")
}
log.Println("MySQL 连接成功")
// 迁移数据表,在没有数据表结构变更时候,建议注释不执行
// MakeMigrate(db)
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10) // 设置连接池中的最大闲置连接
sqlDB.SetMaxOpenConns(100) // 设置数据库的最大连接数量
sqlDB.SetConnMaxLifetime(10 * time.Second) // 设置连接的最大可复用时间
return db
}
// gorm 配置
func gormConfig() *gorm.Config {
return &gorm.Config{
// gorm 日志模式
Logger: logger.Default.LogMode(getLogMode(config.Cfg.Mysql.LogMode)),
// 禁用外键约束
DisableForeignKeyConstraintWhenMigrating: true,
// 禁用默认事务(提高运行速度)
SkipDefaultTransaction: true,
NamingStrategy: schema.NamingStrategy{
// 使用单数表名,启用该选项,此时,`User` 的表名应该是 `user`
SingularTable: true,
},
}
}
编码规范
这个主要是看人家怎么写的接口,怎么调用业务层。分析一下。
Api
routes/router.go
auth.GET("/home", blogInfoAPI.GetHomeInfo) // 后台首页信息
注:这里的API层非常的简单,只是对于 Service的一个调用
api/v1/home.go
func (*BlogInfo) GetHomeInfo(c *gin.Context) {
r.SuccessData(c, blogInfoService.GetHomeInfo())
}
RESTFul 返回操作
在utils\r\result.go 为 *gin.Context 实现了返回的方法,因为使用的是指针,所以不需要返回值,可以直接进行传递函数调用。
// 返回 JSON 数据
func ReturnJson(c *gin.Context, httpCode, code int, msg string, data any) {
// c.Header("", "") // 根据需要在头部添加其他信息
c.JSON(httpCode, Response{
Code: code,
Message: msg,
Data: data,
})
}
// 语法糖函数封装
// 自定义 httpCode, code, data
func Send(c *gin.Context, httpCode, code int, data any) {
ReturnJson(c, httpCode, code, GetMsg(code), data)
}
// 自动根据 code 获取 message, 且 data == nil
func SendCode(c *gin.Context, code int) {
Send(c, http.StatusOK, code, nil)
}
// 自动根据 code 获取 message, 且 data != nil
func SendData(c *gin.Context, code int, data any) {
Send(c, http.StatusOK, code, data)
}
func SuccessData(c *gin.Context, data any) {
Send(c, http.StatusOK, OK, data)
}
func Success(c *gin.Context) {
Send(c, http.StatusOK, OK, nil)
}
Service
因为在Api内调用的Service服务,返回的都是需要反序列化为Json的View Object,这个VO放在Model内,用于在持久层查询的Model也放在model里。感觉有点混乱,看可不可能把VO放在 service包里。
VO的目的就是把格式变成符合URI业务需求的一个结构体。把一些数据放到里面。
// 根据 [文章id] 获取 [文章详情]
func (*Article) GetInfo(id int) resp.ArticleDetailVO {
article := dao.GetOne(model.Article{}, "id", id)
category := dao.GetOne(model.Category{}, "id", article.CategoryId)
tagNames := tagDao.GetTagNamesByArtId(id)
articleVo := utils.CopyProperties[resp.ArticleDetailVO](article)
// 前端 category 为 '' 不显示 placeholder, 为 null 显示 placeholder
if category.ID != 0 {
articleVo.CategoryName = &category.Name
}
articleVo.TagNames = tagNames
return articleVo
}
Dao
Dao层的所有方法或函数
注:
- 不具有普适性的函数功能,应变成某个结构体的方法,变成方法后仅能其结构体初始化的变量调用
- 反之,则可以使用泛型
如何给 Dao包全局变量 dao.DB 赋值。
在dao包下,所有的使用到DB变量的go代码文件内,直接声明var DB *gorm.DB全局变量 。
在route初始函数里,对dao包的DB变量 也就是 dao.DB 进行赋值,赋的值,也就是关于初始化的gorm.DB。
routes/router.go 初始化全局变量
// 初始化全局变量
func InitGlobalVariable() {
// 初始化 Viper
utils.InitViper()
// 初始化 Logger
utils.InitLogger()
// 初始化数据库 DB
dao.DB = utils.InitMySQLDB() // 需要先导入 gvb.sql
// dao.DB = utils.InitSQLiteDB("gorm.db") // TODO: 默认无数据,暂时无法使用
// 初始化 Redis
utils.InitRedis()
// 初始化 Casbin
utils.InitCasbin(dao.DB)
}
基于泛型的普适型写法
这样,其使用Model进行创建的时候,就不需要指定类型了。非常方便用于简单的业务。如果很多业务的CURD都有共同特性,那么也可以如此。
func Create[T any](data *T) {
err := DB.Create(&data).Error
if err != nil {
panic(err)
}
}
针对特定结构体的写法
Views Object居然也被用于在Model进行查询,这个我感觉没必要。因为实体结构体与视力结构体应有所区分。
func (*Article) GetInfoById(id int) (res resp.FrontArticleDetailVO) {
DB.Table("article").
Preload("Category").
Preload("Tags").
Where("id = ? AND is_delete = 0 AND status = 1", id).
First(&res)
return
}
中间件
鉴权的原理就是,拦截一条线路,然后判断每次请求访问此条线路的权限。
根据我们开发时,我们的开发习惯是,基于一个原始URI为一组路由,这个原始URI就是我们鉴权的根据点。
例如 需要检查 /sys下所有的接口是否已登录,则 与/sys/** 匹配都会被进行鉴权。
就是钩子,一层一层的拦截

// 需要鉴权的接口
auth := base.Group("") // "/admin"
// !注意使用中间件的顺序
auth.Use(middleware.JWTAuth()) // JWT 鉴权中间件
auth.Use(middleware.RBAC()) // casbin 权限中间件
auth.Use(middleware.ListenOnline()) // 监听在线用户
auth.Use(middleware.OperationLog()) // 记录操作日志
Cors 跨域处理
这个就是使用别人写好的中间件,地址在: cors
// 跨域中间件
func Cors() gin.HandlerFunc {
return cors.New(cors.Config{
// 允许跨域请求网站
AllowOrigins: []string{"*"},
// 允许使用的请求方式
AllowMethods: []string{"PUT", "POST", "GET", "DELETE", "OPTIONS", "PATCH"},
// 允许使用的请求头
AllowHeaders: []string{"Origin", "Authorization", "Content-Type", "X-Requested-With"},
// 暴露的请求头
ExposeHeaders: []string{"Content-Type"},
// 凭证共享
AllowCredentials: true,
// 允许跨域的源网站
AllowOriginFunc: func(origin string) bool {
return true
},
// 超时时间设定
MaxAge: 24 * time.Hour,
})
}
Middleware 小结
Go语言里,函数也是一个值,可以直接转送匿名函数作为值。
中间件的写法就是
middleware/XXX.go
func XXX() gin.HandlerFunc {
return func(c *gin.Context) {
// anything else
}
}
将XXX挂在哪个基础的URI上。
routes/router.go
sys := router.Group("/sys")
sys.Use(middleware.XXX())
模板技术
渲染这是一个常用的技术。
// 指定导入模板的目录
router.LoadHTMLGlob("template/*")
测试模板渲染的代码
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是测试", "ce": "123456"})
})
模板代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{.title}}</title>
</head>
<body>
Number: <h1>{{.ce}}</h1>
</body>
</html>
头尾分离,模块化写法
注 : 需要提前定义好 public/header,public/footer
Gin HTML 渲染模板:https://www.topgoer.cn/docs/ginkuangjia/ginhtmlxuanran
{{ define "user/index.html" }}
{{template "public/header" .}}
Address: {{.address}}
{{template "public/footer" .}}
{{ end }}
致谢:
https://github.com/szluyu99/gin-vue-blog
GIN
GORM
Go Web项目结构 + 基础代码的更多相关文章
- 使用maven构建基本的web项目结构
由于当前公司在组织进行项目基本结构的整理,将以前通过eclipse/ ant 方式构建的项目向maven上迁移,于是便进行maven项目方面的调研. 对于maven项目,基本的结构已经在标准文件中: ...
- Java Web项目结构
Java Web项目结构(一般) 1.Java src 2.JRE System Library 3.Java EE 6 Libraries 4.Web App Libraries 5.WebRoot ...
- 做web项目时对代码改动后浏览器端不生效的应对方法(持续更新)
做web项目时,常常会遇到改动了代码,但浏览器端没有生效,原因是多种多样的,我会依据我遇到的情况逐步更新解决的方法 1.执行的时候採用debug模式,普通情况下使用项目部署button右边那个butt ...
- 做web项目时对代码修改后浏览器端不生效的应对方法(持续更新)
做web项目时,经常会遇到修改了代码,但浏览器端没有生效,原因是多种多样的,我会根据我遇到的情况逐步更新解决办法 1.运行的时候采用debug模式,一般情况下使用项目部署按钮右边那个按钮下的tomca ...
- VS2015 ASP.NET5 Web项目结构浅析
前言 本文个人同步博客地址http://aehyok.com/Blog/Detail/76.html 个人网站地址:aehyok.com QQ 技术群号:206058845,验证码为:aehyok 本 ...
- vs2017更新后web项目部分后台代码类没有颜色,也没有自动提示输入功能
vs2017有的版本更新后默认.net framework框架是.net framework4.6.1,将项目的.net framework框架更改为4.6.1,颜色和自动提示出现
- IDEA Tomcat Web项目修改了代码,重新部署页面没改变
今天被IDEA坑的不浅直接说一下问题: 这是html页面不管我怎么修改重启服务器在浏览器中还是一点都不变化,甚至把一些内容都删了都没有变化,target可执行文件是最新的没问题,找了点资料发现是浏览器 ...
- node web项目结构
- java web 项目中基础技术
1. 选择版本控制器(git, svn) 2. 用户登录的时候, 你需要进行认证, 权限受理 可以使用 spring shiro 框架,进行上面的工作 3. 过滤器(filter),监听器(liste ...
- 主要介绍JavaEE中Maven Web 项目的结构及其它几个小问题
先说下本篇随笔的目录. 1.介绍windows中环境变量Path与ClassPath的区别. 2.可能导致命令行运行javac编译成功,但 java命令 + 所要执行的类的类名 无效的原因. 3.介绍 ...
随机推荐
- 2021-09-22:请你判断一个 9x9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9
2021-09-22:请你判断一个 9x9 的数独是否有效.只需要 根据以下规则 ,验证已经填入的数字是否有效即可.数字 1-9 在每一行只能出现一次.数字 1-9 在每一列只能出现一次.数字 1-9 ...
- node 代理访问服务器
1.背景 由于在开发的过程中,我们经常需要访问测试/正式环境,会出现线上正常,本地访问不同 2.方案 方法一: 我们可以通过node作为中间件,解决这一问题.示例如下: var express = r ...
- Linux(redhat)镜像
作为一个合格的程序猿,Linux那就是必须得会玩哟呵,搜集了一些镜像分享大家,望笑纳. 云盘地址https://pan.baidu.com/s/1cB-llYI5RdRm9xJDmjFoWg 提取码 ...
- drf——登录功能、认证、权限、频率组件(Django转换器、配置文件作用)
Django转换器.配置文件作用 # django转换器 2.x以后 为了取代re_path int path('books/<int:pk>')--->/books/1---> ...
- hvv蓝初面试常见漏洞问题(下)
hvv蓝初面试常见漏洞问题(上) 6.ssrf 服务端伪造请求 原理 服务端提供了向其他服务器应用获取数据的功能,而没有对目标地址做任何过滤和限制.攻击者进而利用其对内部资源进行攻击.(通俗来说:就是 ...
- 安全测试实践-万家APP越权逻辑漏洞挖掘
逻辑漏洞会导致业务面临着巨大的经济损失隐患与敏感数据泄露的风险,本文从安全测试的角度,以越权逻辑漏洞为例,介绍逻辑漏洞的挖掘方法和实践过程. 一.什么是越权逻辑漏洞 定义: 指由于系统的权限控制逻辑不 ...
- 无限分解流----Fork/Join框架
Fork译为拆分,Join译为合并Fork/Join框架的思路是把一个非常巨大的任务,拆分成若然的小任务,再由小任务继续拆解.直至达到一个相对合理的任务粒度.然后执行获得结果,然后将这些小任务的结果汇 ...
- springboot下拦截器的单例模式写法
最近在学习springboot的时候,要把用户登录的做一个拦截,又想到了不采用new对象方式,于是想到使用单例模式来进行构造拦截器对象,所以下面看代码. (不知道这个是不是要写成单例模式,也许是我最近 ...
- JS逆向实战19——通杀webpack逆向
声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 网站 aHR0cHM6Ly ...
- K8S | 容器和Pod组件
对比软件安装和运行: 一.场景 作为研发人员,通常自己电脑的系统环境都是非常复杂,在个人的习惯上,是按照下图的模块管理电脑的系统环境: 对于「基础设施」.「主机操作系统」.「系统软件」来说,通常只做配 ...