GO语言工程实践课后作业|青训营
GO语言是一门简洁、高效、并发性强的编程语言,它在工程实践中有一些需要注意的内容,以确保代码的可维护性、可扩展性和性能。以下是GO语言工程实践需要注意的一些内容。
目录结构:良好的目录结构可以帮助组织代码,提高代码的可读性和可维护性。一般来说,可以按照功能或模块将代码分组,并将相关的文件放在同一个目录下。可以参考传统的MVC(Model-View-Controller)模式来组织代码。
包管理:GO语言使用go mod来管理包依赖关系。在工程实践中,需要注意选择合适的包版本,并指定版本号,以避免意外的依赖问题。可以使用go mod tidy命令来自动清理不需要的依赖。
错误处理:GO语言使用错误值(error)来处理错误。在工程实践中,需要注意正确处理错误,避免忽略错误或者错误处理不当导致的程序崩溃。可以使用多个返回值来返回错误信息,同时可以使用defer关键字来确保资源的正确释放。
并发性:GO语言天生支持并发编程,可以使用goroutine和channel来实现并发操作。在工程实践中,需要注意正确使用goroutine和channel,避免竞态条件和死锁问题。可以使用互斥锁(mutex)和条件变量(cond)来保护共享资源和实现同步。
性能优化:GO语言具有优秀的性能,但在工程实践中,仍然需要注意一些性能优化的问题。例如,避免过度使用内存分配,使用sync.Pool来重用对象,使用性能分析工具来定位性能瓶颈等。
测试:测试是保证代码质量的重要手段。在工程实践中,需要编写单元测试和集成测试来验证代码的正确性和稳定性。可以使用go test命令来运行测试,并使用覆盖率工具来评估测试覆盖率。
日志和监控:在工程实践中,需要合理使用日志和监控来帮助排查问题和监控系统运行状态。可以使用标准库中的log包,也可以选择成熟的第三方日志库。同时,可以使用监控工具来收集系统的运行指标,例如Prometheus和Grafana。
代码规范:良好的代码规范可以提高代码的可读性和可维护性。在工程实践中,需要遵循GO语言的官方代码规范,并可以根据团队的实际情况进行适当的调整。可以使用静态代码分析工具来检查代码规范的合理性。
文档:良好的文档可以帮助团队成员理解代码的设计和实现,并提高代码的可维护性。在工程实践中,需要编写清晰、简洁的文档,并将文档与代码保持同步。可以使用godoc工具生成代码文档。
总之,GO语言工程实践需要注意的内容包括目录结构、包管理、错误处理、并发性、性能优化、测试、日志和监控、代码规范、文档等。通过遵循这些工程实践,可以提高代码的质量和可维护性,提高团队的开发效率。
依赖管理
Go依赖管理演进
早期版本的Go语言并没有官方的依赖管理工具。开发者通常将项目的依赖包放在项目目录下的vendor文件夹中,这样每个项目都会复制一份自己的依赖包。这种方式存在一些问题,例如依赖包更新困难、冲突处理复杂等。
为了解决这些问题,社区推出了一些第三方的依赖管理工具,例如Godep和glide。这些工具通过管理项目的vendor文件夹,实现对依赖包版本的记录和管理。开发者可以使用类似于Godep save或glide install的命令,将当前项目所需的所有依赖包复制到vendor文件夹中,并生成一个描述依赖关系的文件。
然而,这些工具仍然存在一些问题。首先,它们无法解决全局依赖冲突的问题,即当多个项目依赖相同的包但版本不同时可能导致的冲突。其次,它们的配置文件格式各不相同,缺乏统一性和标准化。
为了解决这些问题,Go官方在Go 1.11版本中引入了Go Modules作为官方的依赖管理解决方案。Go Modules通过在项目目录中添加一个go.mod文件来管理项目的依赖关系。在go.mod文件中,开发者可以明确指定所需的依赖包及其版本要求。
使用Go Modules的好处是,它能够自动解决全局依赖冲突问题,避免了手动处理依赖包版本的繁琐工作。此外,Go Modules还支持从代理服务器下载依赖包,提高了依赖包的下载速度。
随着时间的推移,Go Modules不断发展和改进。它增加了对私有仓库、替代模块和版本补丁的支持,提供了更多灵活且强大的功能。同时,一些第三方工具如dep和vgo也逐渐被集成到Go Modules中。
总结而言,Go语言的依赖管理在过去几年里经历了一次重要的演进。从早期的手动复制依赖包到第三方工具的出现再到官方的Go Modules,Go的依赖管理水平得到了显著提高。Go Modules提供了一个统一、标准化的依赖管理解决方案,并不断更新和改进,为Go开发者提供了更好的依赖管理体验。
Go Module实践
Go Module 是 Go 语言中用于管理包依赖关系的工具。它自 Go 1.11 版本开始成为标准的包管理方式,取代了原有的 GOPATH 和 vendor 目录的方式。Go Module 的出现,极大地简化了 Go 项目的包管理和版本控制,并提供了更好的依赖管理和构建可重复性的功能。
Go Module 的基本概念是将每个项目作为一个模块(module),并有独立的模块路径。每个模块都可以包含多个包(package),并且每个模块都有一个 go.mod 文件来记录包依赖关系。
使用 Go Module 需要满足以下条件:
Go 1.11 及以上版本。
需要将环境变量 GO111MODULE 设置为 on,这样才能启用 Go Module 功能。set GO111MODULE =on set GOPROXY=goproxy.io(win环境下的国内代理)
在一个项目中启用 Go Module 后,可以通过以下步骤进行包依赖管理:
在项目根目录下执行 go mod init 模块路径 命令来初始化一个新的模块。例如:go mod init myapp 将会创建一个名为 myapp 的模块,并生成一个默认的 go.mod 文件。
执行 go mod tidy 命令来自动分析代码中的依赖关系,并更新当前模块的 go.mod 文件。这个命令会根据代码中的实际引用,自动添加和删除模块的依赖关系。
执行 go mod vendor 命令来将所有依赖项复制到项目的 vendor 目录中。这样可以确保项目的构建过程中使用的是 vendor 目录中的依赖项,而不是全局的 GOPATH 下的依赖项。
在使用 Go Module 进行包依赖管理时,还有一些其他常用的命令和功能:
go mod init: 初始化一个新的模块。
go get: 下载指定模块的源代码,并添加到当前模块的依赖关系。
go list: 列出当前模块的所有依赖项。
go build / go run: 构建或运行包含 Go Module 的项目。
go mod edit: 编辑 go.mod 文件,手动添加、删除或更新依赖项。
go mod graph: 显示模块之间的依赖关系图。
go mod verify: 验证模块的依赖项已经完整下载并且没有被篡改。
Go Module 提供了更灵活和可靠的包管理方式,可以帮助开发者更好地管理项目的依赖关系,并且能够确保每个项目在不同环境中的构建结果一致。它的出现使得 Go 语言的包管理更加现代化和便捷化,也受到了开发者的广泛欢迎和推崇。
测试
单元测试
Go语言是一种强大且受欢迎的编程语言,它支持测试驱动开发(TDD)的开发方法。在Go中,编写单元测试是非常简单直观的。在本文中,我将向您介绍如何使用Go语言编写高质量的单元测试。
首先,让我们了解一下Go语言的测试工具。Go语言内置了一个名为testing的测试框架,它提供了一些函数和工具来编写和执行测试。测试文件的名称应以_test.go结尾,这样Go编译器才会将其识别为测试文件。
接下来,让我们来看一个简单的示例。假设我们要编写一个用于向数据库插入用户函数AddUser,以下是一个包含该函数及其对应测试的示例:
// user_test.go package module import ( "fmt" "testing" ) func TestAddUser(t *testing.T) { fmt.Println("测试用户:") user:=&User{} user.AddUser() } // user.go package module import ( "fmt" "project/web/utils" ) type User struct { id int username int password string } func(user *User) AddUser() error{ sqlStr:="insert into table(user,password) values(?,?) " //执行 _,err:=utils.Db.Exec(sqlStr,2,"ddddd") if(err!=nil) { fmt.Println("执行异常:",err) return err } return nil }
在上面的示例中,我们首先编写了一个向数据库插入用户的AddUser函数。然后,在user_test.go文件中,我们编写了一个名为TestAddUser的测试函数。
接下来,我们需要执行测试。可以通过在终端中运行以下命令来执行所有的单元测试:
go test
Go会自动找到所有以_test.go结尾的测试文件,并执行其中的测试函数。测试结果将显示在终端上,如果所有测试通过,将会显示一个通过的消息。
除了基本的测试功能,Go语言还提供了一些辅助函数和工具,帮助我们编写更复杂、全面的单元测试。例如,我们可以使用testing包中的Skip函数来跳过特定的测试,还可以使用testing包中的Table Driven Tests功能来简化多个输入/输出测试的编写。
总结起来,Go语言的单元测试非常简单直观,而且内置的测试框架提供了丰富的功能和工具。通过编写有效的单元测试,我们可以确保代码的正确性,提高代码质量,并减少在后续开发和维护过程中可能出现的问题。希望这篇文章能够帮助您入门Go语言的单元测试。祝您编写出更健壮的代码!
Gin
Gin框架导包"github.com/gin-gonic/gin"
Gin的使用
自定义拦截器
// 自定义拦截器 func myHandler() gin.HandlerFunc { return func(context *gin.Context) { context.Set("usersession", "userid-1") context.Next() context.Abort() } } //传参方式url?userid=xxx&password=xxxxx 加了中间件 //myhandler()没有指定则全部一起用 ginServer.GET("/user/info", myHandler(), func(context *gin.Context) { //取出中间件值 usersession := context.MustGet("usersession").(string) log.Println("--------------", usersession) userid := context.Query("userid") password := context.Query("password") context.JSON(http.StatusOK, gin.H{"userid": userid, "password": password}) })
设置图标
导包"github.com/thinkerou/favicon"
代码ginServer.Use(favicon.New("./u=G.jpg"))
Restful Api
//gin restful API ginServer.PUT("htllo", func(context *gin.Context) { context.JSON(200, gin.H{"msg": "hello,world"}) }) ginServer.DELETE("/htllo")
页面加载响应
//加载静态页面 ginServer.LoadHTMLGlob("templates/*") //响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { context.HTML(http.StatusOK, "index.html", gin.H{"msg": "hello,world"}) })
传参方式url?userid=xxx&password=xxxxx
//传参方式url?userid=xxx&password=xxxxx ginServer.GET("/user/info", func(context *gin.Context) { userid := context.Query("userid") password := context.Query("password") context.JSON(http.StatusOK, gin.H{"userid": userid, "password": password}) })
传参方式user/info/1/kuangshen
//user/info/1/kuangshen ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) { userid := context.Param("userid") username := context.Param("username") context.JSON(http.StatusOK, gin.H{"userid": userid, "username": username}) })
前端给后端传json
//前端给后端传json ginServer.POST("/json", func(context *gin.Context) { data, _ := context.GetRawData() var m map[string]interface{} _ = json.Unmarshal(data, &m) context.JSON(http.StatusOK, m) })
接收form表单
//接收form表单 ginServer.POST("/sdsd", func(context *gin.Context) { username := context.PostForm("username") password := context.PostForm("password") context.JSON(http.StatusOK, gin.H{"username": username, "password": password}) })
路由
//路由 ginServer.GET("test", func(context *gin.Context) { context.Redirect(http.StatusMovedPermanently, "www.baidu.com") })
自定义404页面
//404 ginServer.NoRoute(func(context *gin.Context) { context.HTML(http.StatusNotFound, "404.html", nil) })
设置路由组
//路由组 userGroup := ginServer.Group("/user") { userGroup.GET("/add") userGroup.POST("/login") userGroup.POST("/logout") }
启动
ginServer.Run(":8080")
整合Gin
导包
"github.com/gin-gonic/gin"
创建一个router的包
func SetupRouter() *gin.Engine { router := gin.Default() return router }
启动类下
router := router.SetupRouter() // 启动服务器 router.Run(":8080")
实战
需求分析
支持发布帖子
生成ID不重复、唯一性
更新索引
1.创建启动类
package main import ( _ "github.com/Moonlight-Zhao/go-project-example/cotroller" "github.com/Moonlight-Zhao/go-project-example/repository" "github.com/Moonlight-Zhao/go-project-example/router" "github.com/Moonlight-Zhao/go-project-example/utils" ) func main() { utils.InitConfig() utils.InitMysql() repository.Init() router := router.SetupRouter() // 启动服务器 router.Run(":8080") }
- 创建router包和类
package router import ( "github.com/Moonlight-Zhao/go-project-example/service" "github.com/gin-gonic/gin" ) func SetupRouter() *gin.Engine { router := gin.Default() // 定义路由和处理函数 router.POST("/topic", service.Insert) return router }
3.在config包下创建yml文件
mysql: url: user:password@(localhost:port)/data?charset=utf8mb4&parseTime=True&loc=Local
4.在utils包下创建初始化类
package utils import ( "fmt" "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "log" "os" "time" ) func InitConfig() { //设置配置文件的名称为 "app" viper.SetConfigName("app") //添加配置文件的路径为 "config" viper.AddConfigPath("config") //读取配置文件并将其加载到 viper 中。 err := viper.ReadInConfig() if err != nil { fmt.Println(err) } } var DB *gorm.DB func InitMysql() { //自定义日志模板打印sql语句 newLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Second, LogLevel: logger.Info, Colorful: true, }, ) DB, _ = gorm.Open(mysql.Open(viper.GetString("mysql.url")), &gorm.Config{Logger: newLogger}) }
5.repository
package repository import ( "github.com/Moonlight-Zhao/go-project-example/utils" "log" "sync" ) type Topic struct { Id int64 json:"id" Title string json:"title" Content string json:"content" CreateTime int64 json:"create_time" } type TopicDao struct { } var ( topicDao *TopicDao topicOnce sync.Once ) func (Topic) TableName() string { return "topic" } func NewTopicDaoInstance() *TopicDao { topicOnce.Do( func() { topicDao = &TopicDao{} }) return topicDao } func (*TopicDao) QueryTopicById(id int64) *Topic { return topicIndexMap[id] } func (*TopicDao) Insert(topic Topic) error { result := utils.DB.Create(&topic) if result.Error != nil { // 插入失败,处理错误情况 log.Println("插入失败:", result.Error) return result.Error } Init() return nil } func (*TopicDao) findAllTopics() ([]Topic, error) { var topics []Topic result := utils.DB.Find(&topics) if result.Error != nil { return nil, result.Error } return topics, nil }
6.索引初始化
package repository var ( topicIndexMap map[int64]*Topic postIndexMap map[int64][]*Post ) func Init() error { if err := initTopicIndexNewMap(); err != nil { return err } return nil } // 从数据库中读取表来初始化索引 func initTopicIndexNewMap() error { rows, err := topicDao.findAllTopics() if err != nil { return err } topicTmpMap := make(map[int64]*Topic) for _, topic := range rows { topicTmpMap[topic.Id] = &topic } // 将结果存储到全局变量 topicIndexMap 中 topicIndexMap = topicTmpMap return nil }
7.service层
package service import ( "github.com/Moonlight-Zhao/go-project-example/repository" "github.com/gin-gonic/gin" "net/http" "time" ) func Insert(context *gin.Context) { // 接收form表单数据 title := context.PostForm("title") content := context.PostForm("content") currentTime := time.Now() createTime := currentTime.Unix() u1 := repository.Topic{ Id: 0, Title: title, Content: content, CreateTime: createTime, } topicDao := repository.NewTopicDaoInstance() err := topicDao.Insert(u1) // repository. if err != nil { repository.Init() context.JSON(http.StatusBadRequest, gin.H{"msg": "发布失败"}) } else { repository.Init() context.JSON(http.StatusOK, gin.H{"msg": "发布成功"}) } }