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=https://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
,以下是一个包含该函数及其对应测试的示例:
go
// user_test.go
package module
import (
"fmt"
"testing"
)
func TestAddUser(t *testing.T) {
fmt.Println("测试用户:")
user:=&User{}
user.AddUser()
}
go
// 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
}
在上面的示例中,我们首先编写了一个用于计算两个整数之和的Add
函数。然后,在user_test.go
文件中,我们编写了一个名为TestAddUser
的测试函数。
接下来,我们需要执行测试。可以通过在终端中运行以下命令来执行所有的单元测试:
bash
go test
Go会自动找到所有以_test.go
结尾的测试文件,并执行其中的测试函数。测试结果将显示在终端上,如果所有测试通过,将会显示一个通过的消息。
除了基本的测试功能,Go语言还提供了一些辅助函数和工具,帮助我们编写更复杂、全面的单元测试。例如,我们可以使用testing
包中的Skip
函数来跳过特定的测试,还可以使用testing
包中的Table Driven Tests
功能来简化多个输入/输出测试的编写。
总结起来,Go语言的单元测试非常简单直观,而且内置的测试框架提供了丰富的功能和工具。通过编写有效的单元测试,我们可以确保代码的正确性,提高代码质量,并减少在后续开发和维护过程中可能出现的问题。希望这篇文章能够帮助您入门Go语言的单元测试。祝您编写出更健壮的代码!
Gin
Gin框架导包"github.com/gin-gonic/gin"
Gin的使用
自定义拦截器
go
// 自定义拦截器
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
javascript
//gin restful API
ginServer.PUT("htllo", func(context *gin.Context) {
context.JSON(200, gin.H{"msg": "hello,world"})
})
ginServer.DELETE("/htllo")
页面加载响应
go
//加载静态页面
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
go
//传参方式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
go
//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
go
//前端给后端传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表单
go
//接收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})
})
路由
go
//路由
ginServer.GET("test", func(context *gin.Context) {
context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})
自定义404页面
go
//404
ginServer.NoRoute(func(context *gin.Context) {
context.HTML(http.StatusNotFound, "404.html", nil)
})
设置路由组
go
//路由组
userGroup := ginServer.Group("/user")
{
userGroup.GET("/add")
userGroup.POST("/login")
userGroup.POST("/logout")
}
启动
ginServer.Run(":8080")
整合Gin
导包
"github.com/gin-gonic/gin"
创建一个router的包
go
func SetupRouter() *gin.Engine {
router := gin.Default()
return router
}
启动类下
css
router := router.SetupRouter()
// 启动服务器
router.Run(":8080")
实战
需求分析
- 支持发布帖子
- 生成ID不重复、唯一性
- 更新索引
1.创建启动类
erlang
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")
}
2. 创建router包和类
go
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文件
ini
mysql:
url: user:password@(localhost:port)/data?charset=utf8mb4&parseTime=True&loc=Local
4.在utils包下创建初始化类
go
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
go
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.索引初始化
go
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层
go
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": "发布成功"})
}
}