08 | 实现版本号打印功能

server/2025/3/16 22:35:09/

提示:

  • 所有体系课见专栏:Go 项目开发极速入门实战课;
  • 欢迎加入 云原生 AI 实战 星球,12+ 高质量体系课、20+ 高质量实战项目助你在 AI 时代建立技术竞争力(聚焦于 Go、云原生、AI Infra);
  • 本节课最终源码位于 fastgo 项目的 feature/s05 分支;
  • 更详细的课程版本见:Go 项目开发中级实战课:14 | 应用构建(3):如何给应用添加版本号打印功能?

在 Go 项目开发中,为了方便排障等,需要知道某个线上应用的具体版本。另外,在你开发命令行工具的时候,也需要支持 -v/--version之类的命令行参数。这时候,就需要给应用添加版本号打印功能。

本节课,就来看下,如何给应用添加版本号打印功能。

如何添加版本号?

在实际开发中,当完成一个应用特性开发后,会编译应用源码并发布到生产环境。为了定位问题或出于安全考虑(确认发布的是正确的版本),开发者通常需要了解当前应用的版本信息以及一些编译时的详细信息,例如编译时使用的 Go 版本、Git 目录是否干净,以及基于哪个 Git 提交 ID 进行的编译。在一个编译好的可执行程序中,通常可以通过类似。/appname -v 的方式来获取版本信息。

我们可以将这些信息写入版本号配置文件中,程序运行时从版本号配置文件中读取并显示。然而,在程序部署时,除了二进制文件外还需要额外的版本号配置文件,这种方式既不方便,又面临版本号配置文件被篡改的风险。另一种方式是将这些信息直接写入代码中,这样无需额外的版本号配置文件,但每次编译时都需要修改代码以更新版本号,这种实现方式同样不够优雅。

Go 官方提供了一种更优的方式:通过编译时指定 -ldflags -X importpath.name=value 参数,来为程序自动注入版本信息。

提示:在实际开发中,绝大多数情况是使用 Git 进行源码版本管理,因此 fastgo 的版本功能也基于 Git 实现。

给 fg-apisearver 组件添加版本号功能

可以通过以下步骤为 fg-apiserver 添加版本功能:

  1. 创建一个 version 包用于保存版本信息;
  2. 将版本信息注入到 version 包中;
  3. fg-apiserver 应用添加 --version 命令行选项。

创建一个 version 包

创建一个 pkg/version/version.go 文件,代码如下所示:

package versionimport ("encoding/json""fmt""runtime""github.com/gosuri/uitable"
)var (// semantic version, derived by build scripts (see// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/release/versioning.md// for a detailed discussion of this field)//// TODO: This field is still called "gitVersion" for legacy// reasons. For prerelease versions, the build metadata on the// semantic version is a git hash, but the version itself is no// longer the direct output of "git describe", but a slight// translation to be semver compliant.// NOTE: The $Format strings are replaced during 'git archive' thanks to the// companion .gitattributes file containing 'export-subst' in this same// directory.  See also https://git-scm.com/docs/gitattributesgitVersion   = "v0.0.0-master+$Format:%H$"gitCommit    = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD)gitTreeState = ""            // state of git tree, either "clean" or "dirty"buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)// Info contains versioning information.
type Info struct {GitVersion   string `json:"gitVersion"`GitCommit    string `json:"gitCommit"`GitTreeState string `json:"gitTreeState"`BuildDate    string `json:"buildDate"`GoVersion    string `json:"goVersion"`Compiler     string `json:"compiler"`Platform     string `json:"platform"`
}// String returns info as a human-friendly version string.
func (info Info) String() string {return info.GitVersion
}// ToJSON returns the JSON string of version information.
func (info Info) ToJSON() string {s, _ := json.Marshal(info)return string(s)
}// Text encodes the version information into UTF-8-encoded text and
// returns the result.
func (info Info) Text() string {table := uitable.New()table.RightAlign(0)table.MaxColWidth = 80table.Separator = " "table.AddRow("gitVersion:", info.GitVersion)table.AddRow("gitCommit:", info.GitCommit)table.AddRow("gitTreeState:", info.GitTreeState)table.AddRow("buildDate:", info.BuildDate)table.AddRow("goVersion:", info.GoVersion)table.AddRow("compiler:", info.Compiler)table.AddRow("platform:", info.Platform)return table.String()
}// Get returns the overall codebase version. It's for detecting
// what code a binary was built from.
func Get() Info {// These variables typically come from -ldflags settings and in// their absence fallback to the settings in pkg/version/base.goreturn Info{GitVersion:   gitVersion,GitCommit:    gitCommit,GitTreeState: gitTreeState,BuildDate:    buildDate,GoVersion:    runtime.Version(),Compiler:     runtime.Compiler,Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),}
}

version 包用于记录版本号信息,而版本号功能几乎是所有 Go 应用都会用到的通用功能。因此,需要考虑将 version 包提供给其他外部应用程序使用。根据目录规范,应将 version 包放在 pkg/ 目录下,以便其他项目可以导入并使用 version 包。由于 version 包需要是面向第三方应用的包,因此需确保 version 包的功能稳定、完善,并能够独立对外提供预期的功能。

上述代码, 定义了一个 Info 结构体,用于统一保存版本信息。Info 结构体记录了较为详细的构建信息,包括 Git 版本号、Git 提交 ID、Git 仓库状态、应用构建时间、Go 版本、用到的编译器和构建平台。此外,Info 结构体还实现了以下方法,用于展示不同格式的版本信息:

  • Get 方法:返回详尽的代码库版本信息;
  • String 方法:以更友好、可读的格式展示构建信息;
  • ToJSON 方法:以 JSON 格式输出版本信息;
  • Text 方法:展示格式化的版本信息。

将版本信息注入到 version 包中

接下来,可以通过 -ldflags -X "importpath.name=value" 构建参数将版本信息注入到 version 包中。

由于需要解析当前 Git 仓库的状态、Commit ID、Tag 等信息,为了方便在编译时将版本号信息注入到 version 包中,这里我们将这些注入操作统一封装到 build.sh 脚本中。这样,在编译 fg-apiserver 组件时,只需要执行 build.sh 脚本即可。

build.sh 脚本内容如下:

#!/bin/bash# 获取脚本所在目录作为项目根目录
PROJ_ROOT_DIR=$(dirname "${BASH_SOURCE[0]}")# 定义构建产物的输出目录为项目根目录下的_output文件夹
OUTPUT_DIR=${PROJ_ROOT_DIR}/_output# 指定版本信息包的路径,后续会通过-ldflags参数将版本信息注入到这个包的变量中
VERSION_PACKAGE=github.com/onexstack/fastgo/pkg/version# 确定VERSION值:如果环境变量中没有设置VERSION,则使用git标签作为版本号
# git describe --tags --always --match='v*'命令会获取最近的v开头的标签,如果没有则使用当前commit的短哈希
if [[ -z "${VERSION}" ]];thenVERSION=$(git describe --tags --always --match='v*')
fi# 检查代码仓库状态:判断工作目录是否干净
# 默认状态设为"dirty"(有未提交更改)
GIT_TREE_STATE="dirty"
# 使用git status检查是否有未提交的更改
is_clean=$(git status --porcelain 2>/dev/null)
# 如果is_clean为空,说明没有未提交的更改,状态设为"clean"
if [[ -z ${is_clean} ]];thenGIT_TREE_STATE="clean"
fi# 获取当前git commit的完整哈希值
GIT_COMMIT=$(git rev-parse HEAD)# 构造链接器标志(ldflags)
# 通过-X选项向VERSION_PACKAGE包中注入以下变量的值:
# - gitVersion: 版本号
# - gitCommit: 构建时的commit哈希
# - gitTreeState: 代码仓库状态(clean或dirty)
# - buildDate: 构建日期和时间(UTC格式)
GO_LDFLAGS="-X ${VERSION_PACKAGE}.gitVersion=${VERSION} \-X ${VERSION_PACKAGE}.gitCommit=${GIT_COMMIT} \-X ${VERSION_PACKAGE}.gitTreeState=${GIT_TREE_STATE} \-X ${VERSION_PACKAGE}.buildDate=$(date -u +'%Y-%m-%dT%H:%M:%SZ')"# 执行Go构建命令
# -v: 显示详细编译信息
# -ldflags: 传入上面定义的链接器标志
# -o: 指定输出文件路径和名称
# 最后参数是入口文件路径
go build -v -ldflags "${GO_LDFLAGS}" -o ${OUTPUT_DIR}/fg-apiserver -v cmd/fg-apiserver/main.go

脚本中已有详尽的注释,这里不再多做说明。

fg-apiserver 添加 --version 命令行选项

通过前面的步骤,在编译 fg-apiserver 之后,所需的版本信息已成功注入 version 包中。接下来,还需要在 fg-apiserver 主程序中调用 version 包打印版本信息。

编辑 cmd/fg-apiserver/app/server.go 文件,在 run 函数中添加以下代码:

package appimport (..."github.com/onexstack/fastgo/pkg/version"
)
...
// NewFastGOCommand 创建一个 *cobra.Command 对象,用于启动应用程序.
func NewFastGOCommand() *cobra.Command {...// 添加 --version 标志version.AddFlags(cmd.PersistentFlags())return cmd
}// run 是主运行逻辑,负责初始化日志、解析配置、校验选项并启动服务器。
func run(opts *options.ServerOptions) error {// 如果传入 --version,则打印版本信息并退出version.PrintAndExitIfRequested()...
}   

version.AddFlags(cmd.PersistentFlags()) 用来给 fg-apiserver 命令添加 -v/--version 命令行选项。

version.PrintAndExitIfRequested() 用来指定当 fg-apiserver 命令执行并传入 -v/--version 命令行选项时,应用会打印版本号信息并退出。

测试 fg-apiserver 版本号打印功能

开发完成后,执行以下命令来编译 fg-apiserver 组件源码,并打印版本号信息:

$ git tag -a v0.0.1 -m "release v0.0.1" # 给当前 Commit 打上标签
$ ./build.sh # 编译 fg-apiserver 源码
$ _output/fg-apiserver --version # 打印版本号信息
v0.0.1
$ _output/fg-apiserver --version=raw # 打印更详细的编译信息,包括版本号gitVersion: v0.0.1                                  gitCommit: b9d5b4425c8296b2c7642a35589ae83b6f7a8721
gitTreeState: clean                                   buildDate: 2025-03-03T04:52:53Z                    goVersion: go1.24.0                                compiler: gc                                      platform: linux/amd64

可以看到,fg-apiserver 程序根据 --version 的值输出了不同格式且内容详尽的版本信息。通过这些版本信息,可以精确定位当前应用所使用的代码及编译环境,为日后的故障排查奠定了坚实的基础。


http://www.ppmy.cn/server/175543.html

相关文章

颠覆语言认知的革命!神经概率语言模型如何突破人类思维边界?

颠覆语言认知的革命!神经概率语言模型如何突破人类思维边界? 一、传统模型的世纪困境:当n-gram遇上"月光族难题" 令人震惊的案例:2012年Google语音识别系统将 用户说:“我要还信用卡” 系统识别&#xff…

JavaScript相关面试题

以下是150道JavaScript相关面试题及详细答案: JavaScript基础 1.JavaScript是什么? JavaScript是一种直译式脚本语言,主要用于网页开发,也可用于服务器端开发(如Node.js)。它是一种动态类型、弱类型、基于原…

FPGA初级项目9——基于SPI的ADC芯片进行模数转换

FPGA初级项目9——基于SPI的ADC芯片进行模数转换 ADC芯片介绍 ADC(Analog-to-Digital Converter)芯片是一种将连续变化的模拟信号转换为离散数字信号的电子器件,广泛应用于电子系统中,是连接现实世界与数字世界的桥梁。可将电压、…

骑士74CMS_v3.34.0SE版uniapp全开源小程序怎么编译admin和member流程一篇文章说清楚

有粉丝一直问我骑士系统怎么编译后台和小程序目前骑士人才系统74CMS分标准版,创业板,专业版,其除功能不同外其配置方法完全一致有喜欢系统的也可以私信我或者找我获取 一.安装打包环境[Nodejs]这个就不用我说了吧,用不小于V20的版…

hadoop集群配置-scp的使用

1.推送。在hadoop100机器上,把文件推送到hadoop101机器上 在101和102中新建文件夹 在100中输入命令将jdk拷贝到101中 scp -r jdk1.8.0_212/ roothadoop101:/opt/module 用同样的方法拷贝hadoop scp -r hadoop-3.1.3/ roothadoop101:/opt/module 2.拉取。在hadoop1…

零信任架构实战手册-企业安全升级

🔐 开篇痛点暴击: “又被黑客钓鱼了?VPN漏洞补到心累?😫” 传统边界安全像纸糊的墙,内鬼渗透、APT攻击防不胜防! 别慌!零信任架构(Zero Trust)用「永不信任,持续验证」原则,让安全等级飙升10倍! 🚦 零信任3大核心武器(附实操步骤): 1. 🌟 身份即边界!抛…

玩转python:通俗易懂掌握高级数据结构-collections模块之ChainMap

引言 ChainMap是Python中collections模块提供的一个强大工具,用于将多个字典或映射合并为一个逻辑视图。它允许我们在不修改原始字典的情况下,对多个字典进行统一的查找和操作。ChainMap非常适合用于配置管理、多层作用域模拟等场景。本文将详细介绍Cha…

vue3实现跨页面缓存

避免频繁向后端发送请求,vue3中,可以用缓存机制,为了实现跨页面缓存,可以把缓存放到localsotrage里面 关键代码: const globalCache JSON.parse(localStorage.getItem(globalCache)) || {}; 然后加一个forceRefresh关键字, const fetchData async (forceRefresh false) …