Jenkins 自动打包推送钉钉通知

1,042 阅读6分钟

背景

CICD(持续集成、持续部署)是现如今的项目常见的需求,而Jenkins是最为热门的开源自动化服务,使用Jenkins可以轻易地实现CICD。

除了自动化执行构建任务外,通知相关人员构建的信息也是非常重要的一环。为了能及时接收消息,我们可以考虑使用钉钉。钉钉提供了webhook方式的API可用于自动化发送消息。在构建过程中,利用钉钉API发送构建的信息,可以准确及时的发现CICD错误,监听构建过程。

技术实现

有两种方式,可以将得到的结果,发送到钉钉通知群中,一种是采用groovy脚本。写一个接口调用的过程。还有一种是使用DingTalk 插件。(其实也是作者封装了一个groovy脚本而已。)

两种方法各有千秋。通过groovy脚本比较简单和清晰而已。

执行发送的前提,需要我们在钉钉群里面创建了一个自定义机器人。创建方式可以参考;open.dingtalk.com/document/ro… 

钉钉机器人通知

钉钉提供了群聊机器人的功能,可以提供webhook在群聊中自动发布消息。在群聊的群设置中,有添加机器人的选项,选择自定义机器人。

image.png 选择要推送的钉钉群 -> 点击群设置按钮 -> 点击智能群助手 -> 点击添加机器人 -> 点击添加机器人+号按钮 -> 点击自定义->填写机器人名字,用于匹配推送消息请求体内容的的关键词。

image.png

然后,复制出Webhook地址,供下文钉钉消息推送Shell脚本中使用。

image.png

完成机器人名称等配置后,会获得一个带有token的链接,向这个链接发送消息就可以被机器人接收到。机器人还有三种安全策略:

  1. 自定义关键词(在发送消息中必须包含关键词)

  2. 加签(利用HmacSHA256算法计算签名)

  3. 限制IP地址

三种方案各有优劣,按照自身项目需求决定即可。

通过webhook可以发送多个格式的消息:纯文本,Markdown,图片,链接,卡片,语音,文件,视频

方案一、安装钉钉插件

从 可选插件中 搜索 DingTalk 插件,而后安装 重启 Jenkins

image.png

image.png

从系统配置统一设置钉钉的基本信息

image.png

把添加完钉钉机器人对应的webhook,access_tocke ,秘钥 等信息添加到以上Jenkins的钉钉配置中。

image.png

点击测试 钉钉群里边是否是否有消息提示

image.png

给对应的项目添加 钉钉机器人

image.png

image.png

配置钉钉通知自定义内容

image.png

# ${PROJECT_NAME}

**构建分支:** ${GIT_BRANCH}

**执行人:** ${BUILD_USER}

**版本号:** ${buildVersion}

**Version Code:** ${buildVersionNo}

**包名:** ${buildIdentifier}

**构建环境:** ${buildEnv}

**渠道环境:** ${BuildType}

**构建结果:** ${JOB_STATUS}

**更新内容:** ${buildUpdateDescription}

**构建时间:** ${JOB_DURATION}

**查看详情:** [项目地址](${appBuildURL})

**下载安装包二维码:** ![扫码下载](${buildQRCodeURL})

方案二、groovy脚本发送

  1. 通过Groovy脚本发送,那需要安装插件:groovy postbuild,点击add post-build actions,选择Groovy Postbuild

image.png

在打开的Groovy Script面板中输入:【注意替换钉钉WebHook链接】

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
//构建结果
def buildResult = manager.getResult()
//构建用户
def buildUser= manager.getEnvVariable("BUILD_USER")
//项目名称
def jobName= manager.getEnvVariable("JOB_NAME")
//构建job页面
def jobBuildUrl= manager.getEnvVariable("BUILD_URL").replace("http://0.0.0.0:8080","http://xxx.com")
//GIT分支
def gitBranch = manager.getEnvVariable("GIT_BRANCH")
//渠道
def productFlavors=manager.getEnvVariable("productFlavors")
//构建时间 BUILD_TIMESTAMP
def buildTime=manager.getEnvVariable("BUILD_TIMESTAMP")

//环境(debug,preRelease,release)
def buildEnv="测试"
if(manager.getEnvVariable("buildEnv")=="release"){
    buildEnv="生产"
}else if(manager.getEnvVariable("buildEnv")=="preRelease"){
    buildEnv="预发"
}else{
    buildEnv="测试"
}

def DingTalkWebHook="xxx"
def DingTalkSecret="xxx"

if(buildResult == "SUCCESS"){
            //将蒲公英上传得到的数据进行封装成mk文件,让钉钉进行发送
            apkDownloadUrl = "https://www.pgyer.com/"+ manager.getEnvVariable("buildShortcutUrl") //apk下载地址
            apkQrCode =  manager.getEnvVariable("buildQRCodeURL")//apk二维码
            apkbuildIdentifier = manager.getEnvVariable("buildIdentifier")//apk应用程序包
            apkbuildVersion = manager.getEnvVariable("buildVersion")//apk 版本号
            apkbuildVersionNo= manager.getEnvVariable("buildVersionNo")
            //构建日期
            apkbuildCreateTime = manager.getEnvVariable("buildCreated")
            //更新日志
            apkbuildUpdateDescription = manager.getEnvVariable("buildUpdateDescription")
            //构建结果页面
            def buildUrl= manager.getEnvVariable("appBuildURL")

            //应用类型(1:iOS; 2:Android)
            def buildType="Android"
            if(manager.getEnvVariable("buildType")==1){
                buildType="iOS"
            }else{
                buildType="Android"
            }
    
            dingdingTask("标题","## 【"+jobName+"】构建成功" +
                    "\n\n **GIT分支:** "+gitBranch+
                    "\n\n **应用版本号:** "+apkbuildVersion+
                    "\n\n **VersionCode:** "+apkbuildVersionNo+
                    "\n\n **打包渠道:** "+productFlavors+
                    "\n\n **打包环境:** "+buildEnv+
                    "\n\n **构建时间:** " + apkbuildCreateTime+
                    "\n\n **执行人:** "+buildUser+
                    "\n\n **应用类型:** "+buildType+
                    "\n\n **更新内容:** "+apkbuildUpdateDescription +
                    "\n\n **构建任务:** [任务地址]("+jobBuildUrl+")" +
                    "\n\n **查看详情:** [项目地址]("+buildUrl+")" +
                    "\n\n **下载二维码:** "+ 
                    "\n\n![扫码下载]("+apkQrCode+")"
                    ,DingTalkWebHook,DingTalkSecret)

        }else{
            dingdingTask("标题","## <font color=red>【"+jobName+"】构建失败</font>" +
                         "\n\n **GIT分支:** " + gitBranch +
                         "\n\n **打包渠道:** "+productFlavors+
                         "\n\n **打包环境:** "+buildEnv+
                         "\n\n **构建时间:** " + buildTime+
                         "\n\n **执行人:** "+buildUser+
                        "\n\n **失败详情:** [构建任务]("+jobBuildUrl+")" 
                         ,DingTalkWebHook,DingTalkSecret)
}


//发送钉钉任务 ,第一个为钉钉消息,第二个参数为 消息内容主体
    def dingdingTask(mk_title,mk_test,webhook,secret){
        def json = new groovy.json.JsonBuilder()
        json{
            msgtype "markdown"
            markdown {
                title mk_title
                text mk_test
            }
            at {
                atMobiles([])
                isAtAll false
            }
        }
        //消息准备完毕,执行发送请求
        manager.listener.logger.println("钉钉内容:"+json)
        def timestamp =System.currentTimeMillis() //得到系统时间
        def sign = getSign(timestamp,secret)
        def url =webhook+"&sign="+sign+"&timestamp="+timestamp
        manager.listener.logger.println("钉钉请求参数:"+url )
        def connection = new URL(url).openConnection()
        connection.setRequestMethod('POST')
        connection.doOutput = true
        connection.setRequestProperty('Content-Type', 'application/json')
        def writer = new OutputStreamWriter(connection.outputStream)
        writer.write(json.toString())
        writer.flush()
        writer.close()
        connection.connect()
        def respText = connection.content.text
        manager.listener.logger.println("钉钉服务器返回结果:"+respText )
    }


    def getSign(timestamp,secret){
        def stringToSign = timestamp+"\n"+secret
        Mac mac = Mac.getInstance("HmacSHA256")
        mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"))
        byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"))
        return URLEncoder.encode(new String(Base64.encoder.encode(signData)),"UTF-8")
    }

执行构建后,就会在输出日志中,打印钉钉的发送内容和钉钉的接口回调内容。

image.png

技术选型

由于钉钉插件只能实现简单的自定义功能,无法满足定制需求。

使用groovy脚本能实现完全自主可控需求,包括打包构建的各种内容的获取和透出,例如:Git分支,版本号,渠道,构建时间,执行人,更新内容,包的下载二维码,失败详情等内容。用红色突出打包失败结果,通过鲜明的颜色足够的对比度有助于增加可识别性,提示开发同学排查失败原因。

最后选择了使用groovy脚本能实现打包推送钉钉通知,打包消息同步到钉钉群效果。

总结

Jenkins自动化打包结果分发到蒲公英,打包结果下发到钉钉通知,达到【即拿即用】效果。同时把打包权利交于测试和运营同学,让他们拥有打包到下载安装一条龙。这样不仅仅方便了测试和开发,在版本流程上也会规范一下,后续APP包都可以在蒲公英平台上查找,还起到了版本归档的作用。

其他

蒲公英文档中心-API 2.0 接口说明

 jenkins-DingTalk插件官方使用说明

 钉钉开放平台-自定义机器人接口

钉钉开放平台-自定义机器人安全设置