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层的所有方法或函数

注:

  1. 不具有普适性的函数功能,应变成某个结构体的方法,变成方法后仅能其结构体初始化的变量调用
  2. 反之,则可以使用泛型

如何给 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

cros.go

// 跨域中间件
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层的所有方法或函数

注:

  1. 不具有普适性的函数功能,应变成某个结构体的方法,变成方法后仅能其结构体初始化的变量调用
  2. 反之,则可以使用泛型

如何给 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

cros.go

// 跨域中间件
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项目结构 + 基础代码的更多相关文章

  1. 使用maven构建基本的web项目结构

    由于当前公司在组织进行项目基本结构的整理,将以前通过eclipse/ ant 方式构建的项目向maven上迁移,于是便进行maven项目方面的调研. 对于maven项目,基本的结构已经在标准文件中: ...

  2. Java Web项目结构

    Java Web项目结构(一般) 1.Java src 2.JRE System Library 3.Java EE 6 Libraries 4.Web App Libraries 5.WebRoot ...

  3. 做web项目时对代码改动后浏览器端不生效的应对方法(持续更新)

    做web项目时,常常会遇到改动了代码,但浏览器端没有生效,原因是多种多样的,我会依据我遇到的情况逐步更新解决的方法 1.执行的时候採用debug模式,普通情况下使用项目部署button右边那个butt ...

  4. 做web项目时对代码修改后浏览器端不生效的应对方法(持续更新)

    做web项目时,经常会遇到修改了代码,但浏览器端没有生效,原因是多种多样的,我会根据我遇到的情况逐步更新解决办法 1.运行的时候采用debug模式,一般情况下使用项目部署按钮右边那个按钮下的tomca ...

  5. VS2015 ASP.NET5 Web项目结构浅析

    前言 本文个人同步博客地址http://aehyok.com/Blog/Detail/76.html 个人网站地址:aehyok.com QQ 技术群号:206058845,验证码为:aehyok 本 ...

  6. vs2017更新后web项目部分后台代码类没有颜色,也没有自动提示输入功能

    vs2017有的版本更新后默认.net framework框架是.net framework4.6.1,将项目的.net framework框架更改为4.6.1,颜色和自动提示出现

  7. IDEA Tomcat Web项目修改了代码,重新部署页面没改变

    今天被IDEA坑的不浅直接说一下问题: 这是html页面不管我怎么修改重启服务器在浏览器中还是一点都不变化,甚至把一些内容都删了都没有变化,target可执行文件是最新的没问题,找了点资料发现是浏览器 ...

  8. node web项目结构

  9. java web 项目中基础技术

    1. 选择版本控制器(git, svn) 2. 用户登录的时候, 你需要进行认证, 权限受理 可以使用 spring shiro 框架,进行上面的工作 3. 过滤器(filter),监听器(liste ...

  10. 主要介绍JavaEE中Maven Web 项目的结构及其它几个小问题

    先说下本篇随笔的目录. 1.介绍windows中环境变量Path与ClassPath的区别. 2.可能导致命令行运行javac编译成功,但 java命令 + 所要执行的类的类名 无效的原因. 3.介绍 ...

随机推荐

  1. 2021-09-10:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案

    2021-09-10:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,并返回它们的数组下标.你可以假设每种输入只会对应一个答案, ...

  2. Django4全栈进阶之路21 项目实战(三种方式开发部门管理):方式二:CBV+Django内置类(ListView, CreateView, UpdateView, DeleteView, DetailView)

    在 Django 中,视图(View)是处理请求并返回响应的主要机制.Django 中有许多视图类可用于处理常见的 CRUD(Create.Read.Update.Delete)操作以及其他类型的请求 ...

  3. 什么是DOM和BOM?

    DOM:文档对象模型,描述了处理网页内容的方法和接口.最根本对象是document 由于DOM的操作对象是文档,所以DOM和浏览器没有直接关系 BOM:浏览器对象模型,描述了与浏览器进行交互的方法和接 ...

  4. nginx 反向代理proxy_pass 后加斜杠和不加斜杆的区别

    今日准备使用nginx 将上次使用docker 部署的一个vue项目进行地址代理,让他看起来高达尚一点,原本docker打包的镜像只是向外暴露了一个8191的端口,访问的时候就只能是 http://w ...

  5. vue对vue-giant-tree进行节点操作

    vue 项目中使用到了vue-giant-tree这个使用ztree封装的树形插件,在对其节点进行操作时遇到了无法向传统的jquery那样获取到ztreeObj:而导致了无法控制节点dom:浪费了许多 ...

  6. 人工智能导论——机器人自动走迷宫&强化学习

    一.问题重述 强化学习是机器学习中重要的学习方法之一,与监督学习和非监督学习不同,强化学习并不依赖于数据,并不是数据驱动的学习方法,其旨在与发挥智能体(Agent)的主观能动性,在当前的状态(stat ...

  7. R数据分析:多项式回归与响应面分析的理解与实操

    今天给大家分享一个新的统计方法,叫做响应面分析,响应面分析是用来探究变量一致性假设的(Congruence hypotheses).本身是一个工程学方法,目前在组织行为学,管理,市场营销等等领域中使用 ...

  8. 《数据结构》之栈和堆结构及JVM简析

    导言: 在数据结构中,我们第一了解到了栈或堆栈,它的结构特点是什么呢?先进后出,它的特点有什么用呢?我们在哪里可以使用到栈结构,栈结构那么简单,使用这么久了为什么不用其它结构替代? 一.程序在内存中的 ...

  9. Galaxy Release (v 21.05),众多核心技术栈变更

    2021年6月初,Galaxy Project 正式发布了 release 21.05 版本:随后6月中旬,发布该版本的 announcement 文档.这里总结一下该版本一些主要的更新内容,为关注和 ...

  10. C# 集合类 入门

    什么是集合类? 集合类的位置在System.Collections.Generic命名空间中. 在我看来,集合类和大学里<数据结构>中所学的各种结构很像.集合类中包含Queue<T& ...