写在前面:由于项目,第一次使用 golang,因此记录一下一些 bug,都比较基础
bugs
1. vscode 打开项目有很多红色波浪线
参考文章:link
gopls
是Go 语言的语言服务器,提供了诸如代码补全、定义跳转、重构等功能。自从Go 1.11引入模块后,Go项目可以在GOPATH之外的任何地方创建和构建,在模块化之前,所有项目都是在GOPATH内部,工具可以自然地知道如何查找依赖。但是当你的项目位于GOPATH之外时,gopls
需要明确知道你正在哪些模块上工作。
解决方案
- 将工作区根目录设置为Go模块的根目录,即包含
go.mod
文件的目录。这样gopls
就能找到并理解你的模块。 - 如果你在一个工作区中工作于多个模块,你可以使用
go.work
文件来指定这些模块。go.work
文件使你能够在一个单独的工作区中组合多个Go模块,gopls
将使用这个文件来了解哪些模块被包括在当前的工作环境中。
比如现在的项目,整个项目文件是由不同模块的,有些模块是 go 语言的子项目,有些模块不是,因此使用第二种方更方便:
// 项目跟目录创建 go.work 文件
go 1.22
use (
./application/server
./chaincode
)
2. package net/netip is not in GOROOT
问题
在 dockerfile执行语句:
RUN CGO_ENABLED=0 go build -v -o "app"
出现以下错误:
34.60 /go/pkg/mod/github.com/libp2p/go-libp2p@v0.26.3/core/peer/peer.go:12:2: found packages multicodec (code.go) and main (gen.go) in /go/pkg/mod/github.com/multiformats/go-multicodec@v0.9.0
34.60 /go/pkg/mod/golang.org/x/net@v0.16.0/http2/transport.go:19:2: package io/fs is not in GOROOT (/usr/local/go/src/io/fs)
34.60 /go/pkg/mod/github.com/jackc/pgx/v5@v5.4.3/pgtype/builtin_wrappers.go:9:2: package net/netip is not in GOROOT (/usr/local/go/src/net/netip)
解决
net/netip
包是在Go 1.18中引入的,这个错误表明Go环境版本低于1.18,需要用高于1.18版本的go环境来进行编译,编译
⚠️ 注意:go mod 指定的 go 版本是最低的 go 版本,实际编译项目时的版本取决于系统安装的 go 版本或者使用的 go 的 docker 景象的版本
3. 同一个包出现在两个不同的模块路径中
参考文章:link
问题
error while importing github.com/gin-gonic/gin: ambiguous import: found package github.com/ugorji/go/codec in multiple modules:
github.com/ugorji/go v1.1.4 (/home/puyijun/go/pkg/mod/github.com/ugorji/go@v1.1.4/codec)
github.com/ugorji/go/codec v1.2.11 (/home/puyijun/go/pkg/mod/github.com/ugorji/go/codec@v1.2.11)
// 或者
packages.Load error: err: exit status 1: stderr: go: github.com/ugorji/go/codec@v1.2.11 used for two different module paths (github.com/ugorji/go and github.com/ugorji/go/codec)
分析
在github.com/ugorji/go
的v1.1.1时,github.com/ugorji/go/codec
还不是一个独立的模块。因此,即使你只需要codec
包,你也是通过引用整个github.com/ugorji/go
仓库来获取它。
github.com/ugorji/go
的v1.1.2后,在codec
目录下添加go.mod
文件后,codec
成为意味着,理论上,github.com/ugorji/go
和github.com/ugorji/go/codec
可以被视为两个独立的模块,有自己的版本管理。
github.com/ugorji/go
的v1.1.4的时候,作者又把codec下的go.mod删除了,而在根目录下新增了go.mod,这样这之后github.com/ugorji/go
又是一个独立的模块了
当codec
变成了独立模块之后,如果有项目同时依赖github.com/ugorji/go
(假设为旧的逻辑或代码)和github.com/ugorji/go/codec
(作为独立模块),Go模块系统需要解析这些依赖关系。因为依赖解析规则可能导致不一致的版本选择或者不清楚应该从哪个模块加载codec
包。
解决
ambiguous import 错误通常是因为Go模块系统在解析依赖时发现了不明确的引入,即相同的包在不同的模块路径下被找到
查看具体的依赖路径:
go mod graph | grep ugorji
看到viper和gin分别依赖了github.com/ugorji/go
和github.com/ugorji/go/codec
。go把这两个path当成不同的模块引入导致的冲突
具体解决方法是:
在go.mod中,将 github.com/ugorji/go
和 github.com/ugorji/go/codec
两个包的统一为1.1.7版本的codec包:
replace github.com/ugorji/go => github.com/ugorji/go/codec v1.1.7
4. 通过sdk调用链码时出现 socket hang up
问题
项目代码执行到链码调用的位置时,具体为执行以下函数时
func ChannelExecute(fcn string, args [][]byte) (channel.Response, error) {
// 创建客户端,表明在通道的身份
ctx := sdk.ChannelContext(channelName, fabsdk.WithUser(user))
cli, err := channel.New(ctx)
if err != nil {
return channel.Response{}, err
}
// 对区块链账本的写操作(调用了链码的invoke)
resp, err := cli.Execute(channel.Request{
ChaincodeID: chainCodeName,
Fcn: fcn,
Args: args,
}, channel.WithTargetEndpoints(endpoints...))
if err != nil {
return channel.Response{}, err
}
//返回链码执行后的结果
return resp, nil
}
运行后端的容器会直接崩溃并消失,在apifox软件内发送会调用ChannelExecute函数的请求时,会出现socket hang up
的错误
查看容器日志,显示:
panic: interface conversion: credentials.AuthInfo is insecure.info, not credentials.TLSInfo
goroutine 107 [running]:
github.com/hyperledger/fabric-sdk-go/pkg/fab/comm.NewStreamConnection({0x1094ac0?, 0x4000209040?}, {0x1086340?, 0x400034d500}, 0x40004bfd58, {0x40000409f0, 0x16}, {0x40003b3e40?, 0x40004bfd68?, 0x6cf134?})
/go/pkg/mod/github.com/hyperledger/fabric-sdk-go@v1.0.0/pkg/fab/comm/streamconnection.go:60 +0x40c
github.com/hyperledger/fabric-sdk-go/pkg/fab/events/deliverclient/connection.New({0x1094ac0, 0x4000209040}, {0x1086340, 0x400034d500}, 0xf2bd20, {0x40000409f0, 0x16}, {0x40003b3e40, 0x6, 0x8})
……
分析
错误 “panic: interface conversion: credentials.AuthInfo is insecure.info, not credentials.TLSInfo” 可能是由于 SDK 在建立与 Peer 节点的连接时使用了不安全的身份验证信息(credentials.AuthInfo),而不是预期的 TLS 证书信息(credentials.TLSInfo)。配置文件 config.yaml
中,peer 节点的 grpcOptions
中指定了 allow-insecure: true
,这表示 SDK 允许不安全的连接。但是,从错误信息来看,SDK 尝试将不安全的身份验证信息转换为 TLS 证书信息时出现了问题。
检查config.yaml文件中关于gRPC的配置,因为TLS相关的选项在gRPC连接中设置的:
peers:
peer0.certman.com:
url: peer0.certman.com:7051
grpcOptions:
ssl-target-name-override: peer0.certman.com
keep-alive-time: 0s
keep-alive-timeout: 20s
keep-alive-permit: false
fail-fast: false
allow-insecure: true # chatgpt提醒:此处可能需要修改为 false
将grpc选项中的allow-insecure改为false后出现了其他错误(忘记记录),并且这个项目是基于原来的一个商品交易的项目源码更改的,原项目是可以顺利执行链码的
原本的项目与目前项目代码的区别:
- network区块链网络部分,原项目使用 fabric1.x的peer和orderer节点镜像以及fabric二进制工具,本项目改成了fabric2.5.4版本,因此
- configtx文件的形式有改动
- 链码部署方法有改动
- 链码方面,原项目和本项目都使用的go 1.14版本写的,由于将链码安装到peer节点上直接安装时会出现依赖包的问题,可能是因为peer节点从原来的1.x升级为了2.x版本,go环境不同。因此链码依赖包使用vender打包,然后安装
- ⚠️ 后端代码方面,相比于原项目添加了很多关于证书管理的后端业务逻辑,引入了数据库、ipfs、加解密等模块,加了很多其他依赖
错误的原因是TLS证书信息的类型问题,因此判断应该是由于后端加入了其他的依赖包,使用的grpc包的版本出现了变动,在对于TLS或者是其他身份验证信息的处理方面有更改
解决
- 回到原项目,查看原项目的grpc包的版本
grpc包并没有出现在go.mod文件中,但是go.sum中有。在go.sum
文件中看到多个版本的gRPC包并不能直接告诉我们项目实际使用的是哪一个版本。go.sum
文件包含了项目依赖的所有包的版本和它们的哈希值,用于确保这些依赖的一致性和安全性。
-
go list -m
命令来列出项目中使用的所有模块及其版本,包括gRPC
-
go mod why
命令可以帮助了解为什么项目依赖于特定的包
-
go mod graph
命令显示项目依赖项的图,以及它们依赖的版本。可以手动检查这些包来确定哪个版本的gRPC被实际使用
go mod graph
命令的结果有很多,可以只看grpc包出现在后面的行,这些行是grpc作为被依赖的包,可以看到grpc的版本
但最直观的方法还是使用go list -m命令查看最终使用的模块和版本,可以看到原项目使用的是v1.29.1- 查看本项目grpc包的版本
用相同的方法,查看了本项目使用的grpc包的版本,发现是1.50.x(没有截图),与原项目不相同
- 强制本项目的grpc版本为原项目的版本
go.mod中使用
replace
指令替换模块路径或指定模块的版本go list -m google.golang.org/grpc # 查看google.golang.org/grpc模块的实际使用版本 go clean -modcache # 清理Go模块的缓存 rm go.sum # 删除现有的go.sum go mod tidy # 重新生成依赖项
成功解决,可以调用链码
总结
1. go mod 的版本与系统安装 go 的版本
go.mod
文件中指定的Go版本:
go 1.14
这行表示项目是基于Go 1.14版本的语言特性编写的,这里只是由开发者指定的项目的最低兼容 go 版本。这不会限制使用更高版本的Go来编译项目,但它意味着您的代码应该能够在Go 1.14环境下编译和运行,不依赖于任何Go 1.14之后引入的特性。
- Dockerfile中使用的Go镜像版本:
FROM golang:1.22 AS app
表示Docker容器是基于Go 1.22的官方镜像构建的,这意味着实际的编译过程是在Go 1.22环境中进行的。
- 兼容性考虑
使用高于go.mod
文件中指定版本的Go编译器通常是安全的,因为Go保证了向后兼容性。这意味着在Go 1.14中可以运行的代码,应该也能在Go 1.22中编译和运行,除非依赖了后续版本中被废弃的特性。
- 结论
尽管go.mod
中声明了go 1.14
,实际编译时使用的是Dockerfile中指定的Go 1.22版本。在多数情况下,这种做法是没有问题的,并且能够利用新版本的Go提供的改进和特性。然而,确保代码在go.mod
声明的最低Go版本上兼容仍然是一个好习惯
2. GOROOT 与 GOPATH
GOROOT
和 GOPATH
是两个环境变量,用于管理 Go 语言的工作环境
- GOROOT:
GOROOT
是指定 Go 语言安装目录的环境变量。- 这个目录包含了 Go 标准库和其他基本工具。
- 在安装 Go 时会自动设置
GOROOT
。 - 通常情况下,
GOROOT
在安装时被设置为 Go 语言的安装目录,例如/usr/local/go
。 - 在大多数情况下,你不需要手动设置或更改
GOROOT
,我们只需要确保GOROOT
在系统的环境变量PATH
中。这样做的目的是让系统能够找到 Go 的可执行文件(比如go
、go build
、go run
等)和标准库。而对于你自己的项目和依赖包,你会使用GOPATH
来管理。
- GOPATH:
GOPATH
是指定 Go 语言工作目录的环境变量。- 这个目录包含了你的 Go 项目、源代码以及相关的依赖包。
- 当你使用
go get
命令下载第三方包时,它们会被放在GOPATH
下。 - 你可以在
GOPATH
下创建自己的项目或者导入其他人的项目。 - 你可以设置多个
GOPATH
,用冒号(Linux/macOS)或分号(Windows)分隔。 - 如果未设置
GOPATH
,Go 会使用默认的工作目录~/go
(Linux/macOS)或%USERPROFILE%\go
(Windows)。
3. go mod、go sum
go.mod 文件
- 模块(module):
go.mod
文件在其所在的目录中定义了一个Go模块。这个文件标识了模块的根,所有在此目录及其子目录中的Go代码都是该模块的一部分。 - require 语句:
go.mod
文件中的require
部分指定了该模块依赖的其他模块及其版本。这里的“模块”是指其他独立发布和版本控制的代码库。每个被依赖的模块都会有一个唯一的路径(如github.com/gin-gonic/gin
)和一个指定的版本号。
.go 文件
- 包(Package):在
.go
文件中,通过import
语句导入的是具体的包。包是Go代码的基本组织单元,每个包由一个或多个在同一目录下的.go
文件组成。包的路径通常是模块路径加上包在模块中的相对路径。例如,如果你的模块依赖github.com/gin-gonic/gin
模块,你可能会导入它的子包github.com/gin-gonic/gin
(实际上这里子包的路径与模块路径相同,因为这是根包)。 - 导入:当你在
.go
文件中使用import
声明时,你实际上是在引用模块中的特定包。这使得你可以使用该包中定义的公共接口、类型、函数等。
go.sum文件:
go.sum
文件包含了项目所依赖的每个模块的加密哈希。这些哈希用于确保所下载的模块版本的完整性和未被篡改。
其他:
- 存储位置:在Go模块模式下,当你使用
go get
命令下载模块依赖时,这些依赖被存储在$GOPATH/pkg/mod
目录下。这里的$GOPATH
是你的Go工作目录的路径。 - 版本分隔存储:每个下载的模块依赖都会按照其版本号在
$GOPATH/pkg/mod
目录下分别存储。这意味着,如果你的项目依赖多个版本的同一个模块,每个版本都会在$GOPATH/pkg/mod
目录下有自己的独立子目录。例如,如果你依赖github.com/gin-gonic/gin
的v1.6.3
和v1.7.0
,这两个版本将分别存储在不同的子目录下。 - 下载行为:当你构建项目时(如运行
go build
或go test
),Go会检查go.mod
文件中列出的直接依赖,并解析这些依赖所需的具体版本。如果本地$GOPATH/pkg/mod
目录下不存在这些版本,Go工具链会下载它们。go.sum
文件确保下载的模块未被篡改。 - 多个版本的下载:只有当你的项目(或者项目的依赖)显式地要求不同版本的同一个模块时,这些不同版本的模块才会被下载到本地。简单地说,
go.sum
文件中存在多个版本的记录,并不意味着所有这些版本都会被下载;只有项目实际需要用到的版本才会被下载。
4. 查看和指定依赖包的版本、查看依赖包的依赖情况
- 初始化模块:如果你的项目还没有初始化为一个模块,可以通过
go mod init <module name>
命令来初始化。这里的<module name>
是你的模块名,通常是项目的GitHub路径。 - 添加依赖:当你导入一个包并运行
go build
或go test
命令时,Go会自动添加依赖到你的go.mod
文件中,并选择最新的版本。你也可以通过运行go get <package>@<version>
来手动添加一个依赖包的特定版本,例如go get github.com/gin-gonic/gin@v1.6.3
。 - 更加一般的添加依赖方法:在项目的.go文件import对应的包,然后命令行go mod tidy,会自动将依赖添加到go.mod文件中,但这种方法好像没法指定依赖的版本,因此使用5.的方法指定依赖版本(replace行,因为直接改动go.mod中的require中的内容后再次go mod tidy会变回去)
- 查看依赖版本:可以通过查看
go.mod
文件来看到项目依赖的具体版本。此外,go.mod
文件声明了项目直接依赖的包及其版本,而go.sum
文件则包含了每个依赖包(包括间接依赖)的预期加密校验和,用于确保这些依赖的一致性和安全性。go list -m all
命令可以列出当前模块所有依赖使用的实际版本。 - 指定依赖版本:你可以直接编辑
go.mod
文件来手动修改依赖的版本,或者使用go get <package>@<version>
命令来更新依赖的版本。也可以使用replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
,这样的命令在go.mod中指定版本。 - 查看特定依赖的依赖情况:可以使用
go list -m -json <package>
命令来查看一个特定依赖包的依赖情况,这将以JSON格式输出依赖包及其依赖的详细信息。 - 查看项目依赖图:
go mod graph
命令显示项目依赖的图,列出项目中每个模块依赖的所有模块。这有助于理解依赖之间的关系。 - 查看为何需要依赖:如果你想了解为什么项目需要某个依赖,可以使用
go mod why -m <package>
命令。这个命令将会告诉你为什么这个包被引入项目中。
5. 模块路径、包导入路径
模块路径
- 定义:模块路径是
go.mod
文件中定义的模块的唯一标识符。它通常是项目的仓库位置,如github.com/example/modA
。这个路径用于Go包的引用和版本控制。 - 作用:模块路径不仅标识了代码仓库的位置,也是依赖管理和版本控制的基础。它允许Go工具链从远程仓库获取正确的模块版本。
包导入路径
- 定义:包导入路径是在Go文件中通过
import
语句使用的路径,用于引用模块中的特定包。例如,github.com/example/modA/util
是一个完整的包导入路径,指向modA
模块中的util
包。 - 作用:包导入路径确定了项目中如何引用和使用模块内部的具体包,它基于模块路径,并附加上模块内部的目录结构来定位具体的包。
模块和包的关系
-
每个包属于一个模块:这是正确的。在Go模块系统中,包是构建模块的基本单元。一个模块由一个根目录(其中包含
go.mod
文件)以及该目录下所有子目录中的Go文件组成。这些子目录可以是包,而模块则提供了一种版本控制和包共享的机制。 -
包的引用路径:确实,你需要模块路径加上包的相对路径来唯一确定一个包。通常情况下,包的完整引用路径会包含它所属的模块的路径。例如,如果你有一个模块
github.com/example/modA
,并且这个模块里有一个包在/util
目录,那么这个包的完整引用路径就是github.com/example/modA/util
。
replace 的用法
- 定义:
replace
指令在go.mod
文件中用于替换模块的导入路径或指定使用模块的特定版本或位置。 - 应用场景:
- 开发中的本地替换:如果你正在开发一个模块,并且需要针对本地的改动进行测试,你可以使用
replace
将远程模块路径替换为本地文件系统中的路径。 - 修正依赖问题:如果一个项目依赖的模块有bug或者不再维护,你可以使用
replace
将其替换为修正版本的fork。 - 解决版本冲突:当项目依赖的两个模块间接依赖了同一个模块的不同版本时,可以使用
replace
来统一版本。
- 开发中的本地替换:如果你正在开发一个模块,并且需要针对本地的改动进行测试,你可以使用