Jenkins 插件打包Java软件版本号的改进-自动化版本号

问题

软件打包为什么需要版本呢?
我个人觉得,这个软件包的版本对于快速迭代的中小型公司是一个非常好的东西。
常见的场景是,测试会问当前跑的是哪个版本?作为一个协作团体,多个环境的情况下,如果要知道版本需要问前端,后端,运维一圈本身就是在浪费时间。
尽管软件带上版本号不能解决所有的问题,在一定程度上还是可以解决研发过程中可能存在的版本差异问题。

因此软件自身能够以某种形式暴露出版本号,对于定位问题代码起了很大的作用。

何为版本号

先看例子

origin/master#250616-154759-sha1:617bcf6
或者
origin/master#250616-154759-sha1:617bcf6_build108

一般来说,一个版本是分支+git commit id 组成。当然自己可以加入其他的信息。
大公司也有这种,atlassion 公司的jira产品会在页面最下方显示它的版本

Atlassian Jira Project Management Software (v8.5.0#805000-sha1:facbf8b)

根据我实际的经验,我建议jira的版本号是比较好一个组合。

  • v8.5.0 迭代版本 (通常是Git 分支)
  • 805000 多少次打包
  • sha1:facbf8b 实际的Git commit id

对于Git Commit Id 作用等同于每一次提交。非常方便回查对应的源码。
分布式版本控制系统,每个 commit 都有一个唯一的 commit ID
这么说吧,没有Git commit id,则无法准确回溯到源码是什么。
所以说,缺少了Git commit id 的版本号的意义就不大了。

版本号总结

版本号是一串自定义的编码,它包括迭代版本号(虚拟)和源码记录号(真实,可追溯性)。
它的用途是呈现给用户,测试,开发,管理等多个岗位一个统一的当前运行软件是什么的。

在哪里呈现版本

以移动端和Web技术为主的软件可以简单分为前端和后端两个部分。如果有一些服务本身不产生响应,比如:中间件服务,也可以通过心跳等消息来向外暴露其版本号。
总结一下:

  • 前端 - 页面下部位置,或者关于的地方
  • 后端REST - header 比较合适
  • 中间件 - 心跳消息中比较合适

版本号以什么形式存在

以配置文件的形式是常态,如果是容器打包,也可以直接作为环境变量导入容器中
不同的存储形式,在使用的时候,采用不同方式读取。
这个可以根据需求和便利做选择。

在什么阶段取得

通常是在打包阶段打入。如果是java则打包时候有plugin支持获取当前的Commint Id
如果是jenkins,则在环境变量中可以使用
在这里插入图片描述

在这里插入图片描述

需求和实现的例子

公司要求某Java开发系统每个REST需要返回版本号,以header 方式返回。
这样的考虑是因为:后端的系统 REST 非显式的反应即可。 从加载在 request body 还是 header ,更具有性价比。

整体流程

根据上述需求,我们可以简单设计为

  1. 软件编译时候打入版本号
  2. 软件运行时,响应REST服务的时候,将版本号附加到header

1-编译打包时候打入版本号

由于是java 平台,我们可以很简单找到一个maven build plugin - git-commit-id. 利用这个就很容易在打包阶段得到一个文本里面包括版本号。

    <plugin>
                <groupId>pl.project13.maven</groupId>
                <artifactId>git-commit-id-plugin</artifactId>
                <version>2.2.3-withMavenProperties-SNAPSHOT</version>
                <executions>
                    <execution>
                        <id>get-the-git-infos</id>
                        <goals>
                            <goal>revision</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>validate-the-git-infos</id>
                        <goals>
                            <goal>validateRevision</goal>
                        </goals>
                        <!-- *NOTE*: The default phase of validateRevision is verify, but in case you want to change it, you can do so by adding the phase here -->
                        <phase>package</phase>
                    </execution>
                </executions>

                <configuration>
                    <dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
                    <prefix>git</prefix>
                    <!--<dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>-->
                    <dateFormat>yyMMdd-HHmmss</dateFormat>
                    <dateFormatTimeZone>${user.timezone}</dateFormatTimeZone>
                    <!-- false is default here, it prints some more information during the build -->
                    <verbose>true</verbose>
                    <generateGitPropertiesFile>true</generateGitPropertiesFile>
                    <generateGitPropertiesFilename>${project.build.outputDirectory}/resources/version/latest.json
                    </generateGitPropertiesFilename>
                    <!-- <format>properties</format> -->
                    <format>json</format>
                    <skipPoms>true</skipPoms>
                    <injectAllReactorProjects>false</injectAllReactorProjects>
                    <failOnNoGitDirectory>true</failOnNoGitDirectory>
                    <failOnUnableToExtractRepoInfo>true</failOnUnableToExtractRepoInfo>
                    <skip>false</skip>
                    <runOnlyOnce>false</runOnlyOnce>
                    <includeOnlyProperties>
                        <includeOnlyProperty>^git.version.fullName$</includeOnlyProperty>
                        <includeOnlyProperty>^git.build.host$</includeOnlyProperty>
                        <includeOnlyProperty>^git.build.time$</includeOnlyProperty>
                        <includeOnlyProperty>^git.build.version$</includeOnlyProperty>
                        <includeOnlyProperty>^git.commit.id.abbrev$</includeOnlyProperty>
                        <includeOnlyProperty>^git.sprintName$</includeOnlyProperty>
                        <includeOnlyProperty>^git.jenkins$</includeOnlyProperty>
                        <includeOnlyProperty>^BUILD_TAG$</includeOnlyProperty>
                    </includeOnlyProperties>
                    <useNativeGit>false</useNativeGit>
                    <abbrevLength>7</abbrevLength>
                    <commitIdGenerationMode>flat</commitIdGenerationMode>
                    <gitDescribe>
                        <skip>false</skip>
                        <always>false</always>
                        <abbrev>7</abbrev>
                        <dirty>-dirty</dirty>
                        <match>*</match>
                        <tags>false</tags>
                        <forceLongFormat>false</forceLongFormat>
                    </gitDescribe>
                    <validationProperties>
                        <!--不能通过 x.x.-SNAPSHOT -->
                        <!-- <validationProperty>
                            <name>validating project version</name>
                            <value>${project.version}</value>
                            <shouldMatchTo><![CDATA[^.*(?<!-SNAPSHOT)$]]></shouldMatchTo>
                        </validationProperty> -->
                    </validationProperties>
                    <validationShouldFailIfNoMatch>true</validationShouldFailIfNoMatch>
                </configuration>
            </plugin>

上面是一个配置例子。

2 - 响应REST服务的时候,将版本号附加到header

如果是使用Java spring boot 2.x

@Component
@WebFilter(urlPatterns = "/*", filterName = "ApplicationHeaderFilter")
@Slf4j
public class ApplicationHeaderFilter implements Filter {

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;


        JsonVersion jsonVersion = JsonVersion.instance();
        if (jsonVersion == null) {
            log.warn("jsonVersion not set!");
        } else {
            response.setHeader("x-your-app-version", jsonVersion.getBuildVersion());
            response.setHeader("x-your-app-build", jsonVersion.getJenkins());
        }
        response.setHeader("x-frame-options", "SAMEORIGIN");
        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

}

上述的 JsonVersion 是在 ApplicationVersionContextInitializer implements ApplicationContextInitializer 中进行初始化注入的。

Resource resource = applicationContext.getResource(VERSION_PATH);
        if (resource.exists()) {
            log.info("注入应用程序版本-{}", VERSION_PATH);
            String jsonString = openJsonAsString(resource.getInputStream());

            //JavaPropsMapper mapper = new JavaPropsMapper();
            ObjectMapper mapper = new ObjectMapper();
            try {
                JsonVersion jsonVersion = mapper.readValue(jsonString, JsonVersion.class);
                JsonVersion.setInstance(jsonVersion);
                log.warn("GIT_TAG=[{}]", jsonVersion.getBuildVersion().toString());
                log.warn("BUILD_TAG=[{}]", jsonVersion.getJenkins().toString());
                applicationContext.getEnvironment().getSystemProperties().put("GIT_TAG", jsonVersion.getBuildVersion().toString());

            } catch (IOException e) {
                log.error("无法获取应用的版本信息");
                e.printStackTrace();
            }


        } else {
            log.warn("没有发现应用程序版本信息-{}", VERSION_PATH);
        }


        log.info(this.getClass().getName() + " 初始化完成 ... ");

效果

在这里插入图片描述

整个过程,总体上说还是比较简单。希望对从事总体设计和开发的朋友有所帮助。

也欢迎评价和讨论!


请收藏问题合集,方便随时查找
jenkins的全解-工欲善其事,必先利其器


最近夏天跳闸断电,莫名几个虚拟机数据丢了。。哎,

在这里插入图片描述某东 USB4 接口 的硬盘盒-一个字 快! USB 4 接口就是快,就是不用等那种快!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山外山与楼外楼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值