Golang语言基于go module方式管理包(package)

简介: 这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。

                                              作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.Go Modules发展史

1.前言

一般编程语言都会提供依赖库管理工具,例如python的pip、node.js的npm,java的maven,rust的cargo,Go语言也有提供自己的依赖库管理工具。

Go语言在1.11提出了Go mod,每次版本或多或少都会对go.mod进行改进优化,go mod也越来越好,当前大多数公司都使用go mod来管理依赖库。

考虑新手很容易在"go module"使用上犯迷糊,本篇文章主要介绍"Go Modules"发展史及快速入门的必要知识点。

2.早期第三方包存储在GOPATH路径

起初Go语言在1.5之前没有依赖管理工具,若想引入依赖库,需要执行go get命令将代码拉取放入GOPATH/src目录下。

这样的依赖管理方式存在一个致命的缺陷,那就是不支持版本管理,同一个依赖包只能存在一个版本的代码。

可是我们本地的多个项目完全可能分别依赖同一个第三方包的不同版本。

3.vendor阶段

为了解决隔离项目的包依赖问题,Go1.5版本推出了vendor机制,环境变量中有一个GO15VENDOREXPERIMENT需要设置为1,该环境变量在Go1.6版本时变成默认开启,目前已经退出了历史舞台;

vendor其实就是将原来放在"GOPATH/src"的依赖包放到工程的vendor目录中进行管理,不同工程独立地管理自己的依赖包,相互之间互不影响,原来是包共享的模式,通过vendor这种机制进行隔离,在项目编译的时候会先去vendor目录查找依赖,如果没有找到才会再去GOPATH目录下查找。

vendor阶段的优劣势:
    优点:
        保证了功能项目的完整性,减少了下载依赖包,直接使用vendor就可以编译

    缺点:
        仍然没有解决版本控制问题,go get仍然是拉取最新版本代码。

4.社区管理工具层出不穷

很多优秀的开发者在这期间也都实现了不错的包依赖管理工具,例如:
  godep:
    https://github.com/tools/godep

  govendor:
    https://github.com/kardianos/govendor

  glide:
    https://github.com/Masterminds/glide

  dep:
    https://github.com/golang/dep

dep应该是其中最成功的,得到了Go语言官方的支持,该项目也被放到了https://github.com/golang/dep,但是为什么dep没有称为官宣的依赖工具呢?

其实因为随着"Russ Cox"与"Go团队"中的其他成员不断深入地讨论,发现dep的一些细节似乎越来越不适合Go,因此官方采取了另起"proposal(建议)"的方式来推进,其方案的结果一开始先是释出”vgo“,最终演变为我们现在所见到的”Go modules“;

5.go modules官宣官方管理工具

go modules是"Russ Cox"(Go语言核心开发团队人员之一)推出来的,发布于Go1.11。

go modules成长于Go1.12,丰富于Go1.13,正式于Go1.14推荐在生产上使用。

go modules几乎后续的每个版本都或多或少的有一些优化,比如在Go1.16引入go mod retract、在Go1.18引入go work工作区的概念。

参考连接:
    https://developer.aliyun.com/article/1604714

二.go module介绍

1.GO111MODULE环境变量

这个环境变量是"Go Modules"的开关,主要有以下参数:
    auto:
        只在项目包含了go.mod文件时启动go modules,在Go1.13版本中是默认值。
    on:
        无脑启动Go Modules,推荐设置,Go1.14版本以后的默认值。
    off:
        禁用Go Modules,一般没有使用go modules的工程使用。

我现在使用的Go版本是1.22.4,默认GO111MODULE=on。

2.GOPROXY

这个环境变量主要是用于设置Go模块代理"Go module proxy",其作用是用于使Go在后续拉取模块版本时能够脱离传统的VCS方式,直接通过镜像站点来快速拉取。

GOPROXY的默认值是"https://proxy.golang.org,direct",由于某些原因国内无法正常访问该地址,所以我们通常需要配置一个可访问的地址。目前社区使用比较多的有两个"https://goproxy.cn"和"https://goproxy.io",当然如果你的公司有提供GOPROXY地址那么就直接使用。

设置GOPAROXY的命令如下:
    # go env -w GOPROXY=https://goproxy.cn,direct

GOPROXY允许设置多个代理地址,多个地址之间需使用英文逗号","分隔。最后的"direct"是一个特殊指示符,用于指示Go回源到源地址去抓取(比如"GitHub"等)。

当配置有多个代理地址时,如果第一个代理地址返回"404"或"410"错误时,Go会自动尝试下一个代理地址,当遇见"direct"时触发回源,也就是回到源地址去抓取。

3.GOSUMDB

该环境变量的值是一个Go checksum database,用于保证Go在拉取模块版本时拉取到的模块版本数据未经篡改。

若发现不一致会中止,也可以将值设置为off即可以禁止Go在后续操作中校验模块版本;

什么是Go checksum database?

Go checksum database主要用于保护Go不会从任何拉到被篡改过的非法Go模块版本,详细算法机制可以看一下:
    https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md#proxying-a-checksum-database

GOSUMDB的默认值是sum.golang.org,默认值与自定义值的格式不一样,默认值在国内是无法访问,这个值我们一般不用动,因为我们一般已经设置好了GOPROXY,goproxy.cn支持代理sum.golang.org;

GOSUMDB的值自定义格式如下:
    - 格式1:
        <SUMDB_NAME>+<PUBLIC_KEY>。
    - 格式2:
        <SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>。

4.GONOPROXY/GONOSUMDB/GOPRIVATE

这三个环境变量放在一起说,一般在项目中不经常使用,这三个环境变量主要用于私有模块的拉取。

在GOPROXY、GOSUMDB中无法访问到模块的场景中,例如拉取git上的私有仓库。

GONOPROXY、GONOSUMDB的默认值是GOPRIVATE的值,所以我们一般直接使用GOPRIVATE即可,其值也是可以设置多个,以英文逗号进行分割。

"GOPRIVATE"用来告诉"go"命令哪些仓库属于私有仓库,不必通过代理服务器拉取和校验。

"GOPRIVATE"的值也可以设置多个,多个地址之间使用英文逗号","分隔。我们通常会把自己公司内部的代码仓库设置到"GOPRIVATE"中,例如:
    $ go env -w GOPRIVATE="git.yinzhengjie.com" 

这样在拉取以"git.yinzhengjie.com"为路径前缀的依赖包时就能正常拉取了。

此外,如果公司内部自建了"GOPROXY"服务,那么我们可以通过设置"GONOPROXY=none",允许从内部代理拉取私有仓库的包。

也可以使用通配符的方式进行设置,对域名设置通配符号,这样子域名就都不经过Go module proxy和Go checksum database;

5.go.mod文件

go.mod是启用Go modules的项目所必须且最重要的文件,其描述了当前项目的元信息,每个go.mod文件开头符合包含如下信息:
    module:
        用于定义当前项目的模块路径(突破$GOPATH路径)。

    go:
        当前项目Go版本,目前只是标识作用、

    require:
        用设置一个特定的模块版本,可以指定需要依赖的版本号。
        Go modules中建议使用语义化版本控制,其建议的版本号格式如下:
            主版本号:
                发布了不兼容的版本迭代时递增(breaking changes)。
            次版本号:
                发布了功能性更新时递增。
            修订号:
                发布了bug修复类更新时递增。

    exclude:
        用于从使用中排除一个特定的模块版本。

    replace:
        用于将一个模块版本替换为另外一个模块版本。 
        比如将一个无法访问的域名替换成国内的镜像站点或者本地路径均可。

    retract:
        用来声明该第三方模块的某些发行版本不能被其他模块使用,在Go1.16引入。
        如果某个发布的版本存在致命缺陷不再想让用户使用时,我们可以使用retract声明废弃的版本。

    indirect:
        行尾的indirect表示该依赖包为间接依赖,说明在当前程序中的所有"import"语句中没有发现引入这个包。

6.go.sum文件

使用go module下载了依赖后,项目目录下还会生成一个go.sum文件。

这个文件中详细记录了当前项目中引入的依赖包的信息及其hash 值。go.sum文件内容通常是以类似下面的格式出现。
    - <module> <version>/go.mod <hash>
    - <module> <version> <hash>
    - <module> <version>/go.mod <hash>

不同于其他语言提供的基于中心的包管理机制,例如“npm”和“pypi”等,Go并没有提供一个中央仓库来管理所有依赖包,而是采用分布式的方式来管理包。为了防止依赖包被非法篡改,Go module引入了go.sum机制来对依赖包进行校验。

7."go mod"依赖保存位置

"go mod download"会将依赖缓存到本地,Go module缓存的目录是"$GOPATH/pkg/mod/cache“、"GOPATH/pkg/sum"。

这些缓存依赖可以被多个项目使用,未来可能会迁移到$GOCACHE下面。

每个依赖包都会带有版本号进行区分,这样就允许在本地存在同一个包的多个不同版本。


温馨提示:
    如果想清除所有本地已缓存的依赖包数据,可以执行"go clean -modcache"命令。

三. go module常用命令

1."go mod"常用的子命令概述

命令 介绍
go mod init 初始化项目依赖,生成go.mod文件
go mod download 根据go.mod文件下载依赖
go mod tidy 比对项目文件中引入的依赖与go.mod进行比对
go mod graph 输出依赖关系图
go mod edit 编辑go.mod文件
go mod vendor 将项目的所有依赖导出至vendor目录
go mod verify 检验一个依赖包是否被篡改过
go mod why 解释为什么需要某个依赖
Go module 是 Go1.11 版本发布的依赖管理方案,从 Go1.14 版本开始推荐在生产环境使用,于Go1.16版本默认开启。

如上表所示,Go module 提供了以上命令供我们使用,我们可以使用“go help mod”查看可以使用的命令。

Go语言在"go module"的过渡阶段提供了"GO111MODULE"这个环境变量来作为是否启用"go module"功能的开关。

考虑到Go1.16之后 "go module"已经默认开启,所以本书不再介绍该配置,对于刚接触Go语言的读者而言完全没有必要了解这个历史包袱。

2.使用go module引入包第三方包

2.1 初始化项目

    1.初始化项目
yinzhengjie@bogon 02-crm % pwd
/Users/yinzhengjie/golang/gosubjects/src/gocode/devops/05-package/02-crm
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % ls -l
total 0
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % go mod init web-server
go: creating new go.mod: module web-server
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % ls -l                 
total 8
-rw-r--r--@ 1 yinzhengjie  staff  29  7 14 23:00 go.mod
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % cat go.mod 
module web-server

go 1.22.4
yinzhengjie@bogon 02-crm % 


    2.go.mod内容说明:
module web-server:
    定义当前项目的导入路径。

go 1.22.4:
    标识当前项目使用的go版本。


温馨提示:
    go.mod文件会记录项目使用的第三方依赖包信息,包括包名和版本,由于我们的"web-server"项目目前还没有使用到第三方依赖包,所以go.mod文件暂时还没有记录任何依赖包信息,只有当前项目的一些信息。

2.2 下载第三方程序包

    1.安装gin框架
yinzhengjie@bogon 02-crm % go get -u github.com/gin-gonic/gin


    2.查看本地
yinzhengjie@bogon 02-crm % ls -l
total 24
-rw-r--r--@ 1 yinzhengjie  staff  1376  7 14 23:17 go.mod  # 依赖文件
-rw-r--r--@ 1 yinzhengjie  staff  7028  7 14 23:17 go.sum  # 依赖包的校验文件
yinzhengjie@bogon 02-crm % 
yinzhengjie@bogon 02-crm % cat go.mod  # 关于go.mod的文件内容解析请参考上文中的介绍哟~
module web-server

go 1.22.4

require (
        github.com/bytedance/sonic v1.11.9 // indirect
        github.com/bytedance/sonic/loader v0.1.1 // indirect
        github.com/cloudwego/base64x v0.1.4 // indirect
        github.com/cloudwego/iasm v0.2.0 // indirect
        github.com/gabriel-vasile/mimetype v1.4.4 // indirect
        github.com/gin-contrib/sse v0.1.0 // indirect
        github.com/gin-gonic/gin v1.10.0 // indirect
        github.com/go-playground/locales v0.14.1 // indirect
        github.com/go-playground/universal-translator v0.18.1 // indirect
        github.com/go-playground/validator/v10 v10.22.0 // indirect
        github.com/goccy/go-json v0.10.3 // indirect
        github.com/json-iterator/go v1.1.12 // indirect
        github.com/klauspost/cpuid/v2 v2.2.8 // indirect
        github.com/leodido/go-urn v1.4.0 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
        github.com/modern-go/reflect2 v1.0.2 // indirect
        github.com/pelletier/go-toml/v2 v2.2.2 // indirect
        github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
        github.com/ugorji/go/codec v1.2.12 // indirect
        golang.org/x/arch v0.8.0 // indirect
        golang.org/x/crypto v0.25.0 // indirect
        golang.org/x/net v0.27.0 // indirect
        golang.org/x/sys v0.22.0 // indirect
        golang.org/x/text v0.16.0 // indirect
        google.golang.org/protobuf v1.34.2 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
)
yinzhengjie@bogon 02-crm %  



温馨提示:
    1.在执行go get命令下载一个新的依赖包时一般会额外添加"-u"参数,强制更新现有依赖;
    2.行尾的indirect表示该依赖包为间接依赖,说明在当前程序中的所有"import"语句中没有发现引入这个包;
    3.下载软件包的名称和版本使用"@"区分,若不指定版本则默认会下载最新的发布版本;

3.代码测试

3.1 项目组织结构

如上图所示,是我随意找的目录,做的目录组织结构,其中包括引入第三方包和本项目中自定义包引入。

3.2 blog.go源代码

package blog

import (
    // 导入第三方仓库代码
    "github.com/gin-gonic/gin"
)

func StartWebServer() {
    // 创建一个默认的路由引擎
    r := gin.Default()

    // GET:请求方式;/hello:请求的路径
    // 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
    r.GET("/hello", func(c *gin.Context) {
        // c.JSON:返回JSON格式的数据
        c.JSON(200, gin.H{
            "name": "尹正杰",
            "blog": "https://www.cnblogs.com/yinzhengjie",
        })
    })

    // 启动HTTP服务,默认在0.0.0.0:8080启动服务
    r.Run()
}

3.3 main.go源代码

package main

import (
    // 注意,此处的"web-server"是"go.mod文件中记录的"module web-server"对应的名称哟~
    // 而"web-server"下对应的"blog"是我们自定义的包名哟~
    "web-server/blog"
)

func main() {
    blog.StartWebServer()
}

3.4 运行项目并访问WebUI

如上图所示,我们可以正常访问咱们自定义的项目啦~
目录
相关文章
|
5天前
|
存储 Go 容器
深入探究Go语言中的数据结构
深入探究Go语言中的数据结构
19 3
|
15天前
|
Go
Go 语言循环语句
在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。
25 1
|
4天前
|
Go
GO语言时间转换
GO语言时间转换
15 0
|
15天前
|
Go 开发者
探索Go语言的并发之美
在Go语言的世界里,"并发"不仅仅是一个特性,它是一种哲学。本文将带你领略Go语言中goroutine和channel的魔力,揭示如何通过Go的并发机制来构建高效、可靠的系统。我们将通过一个简单的示例,展示如何利用Go的并发特性来解决实际问题,让你的程序像Go一样,轻盈而强大。
|
16天前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
16天前
|
Go
go语言创建字典
go语言创建字典
|
2天前
|
Rust Go C语言
Python通过C动态链接库调用Go语言函数
Python通过C动态链接库调用Go语言函数
|
2天前
|
存储 Kubernetes Go
Go语言项目组织架构
Go语言项目组织架构
|
2天前
|
算法 安全 Go
Python与Go语言中的哈希算法实现及对比分析
Python与Go语言中的哈希算法实现及对比分析
12 0
|
15天前
|
Go
Go 语言接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。 接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
10 0