背景
CICD(持续集成、持续部署)是现如今的项目常见的需求,而Jenkins是最为热门的开源自动化服务,使用Jenkins可以轻易地实现CICD。
除了自动化执行构建任务外,通知相关人员构建的信息也是非常重要的一环。为了能及时接收消息,我们可以考虑使用钉钉。钉钉提供了webhook方式的API可用于自动化发送消息。在构建过程中,利用钉钉API发送构建的信息,可以准确及时的发现CICD错误,监听构建过程。
技术实现
有两种方式,可以将得到的结果,发送到钉钉通知群中,一种是采用groovy脚本。写一个接口调用的过程。还有一种是使用DingTalk 插件。(其实也是作者封装了一个groovy脚本而已。)
两种方法各有千秋。通过groovy脚本比较简单和清晰而已。
执行发送的前提,需要我们在钉钉群里面创建了一个自定义机器人。创建方式可以参考;open.dingtalk.com/document/ro…
钉钉机器人通知
钉钉提供了群聊机器人的功能,可以提供webhook在群聊中自动发布消息。在群聊的群设置中,有添加机器人的选项,选择自定义机器人。
选择要推送的钉钉群 -> 点击群设置按钮 -> 点击智能群助手 -> 点击添加机器人 -> 点击添加机器人+号按钮 -> 点击自定义->填写机器人名字,用于匹配推送消息请求体内容的的关键词。
然后,复制出Webhook
地址,供下文钉钉消息推送Shell
脚本中使用。
完成机器人名称等配置后,会获得一个带有token的链接,向这个链接发送消息就可以被机器人接收到。机器人还有三种安全策略:
-
自定义关键词(在发送消息中必须包含关键词)
-
加签(利用HmacSHA256算法计算签名)
-
限制IP地址
三种方案各有优劣,按照自身项目需求决定即可。
通过webhook可以发送多个格式的消息:纯文本,Markdown,图片,链接,卡片,语音,文件,视频
方案一、安装钉钉插件
从 可选插件中 搜索 DingTalk 插件,而后安装 重启 Jenkins
从系统配置统一设置钉钉的基本信息
把添加完钉钉机器人对应的webhook,access_tocke ,秘钥 等信息添加到以上Jenkins的钉钉配置中。
点击测试 钉钉群里边是否是否有消息提示
给对应的项目添加 钉钉机器人
配置钉钉通知自定义内容
# ${PROJECT_NAME}
**构建分支:** ${GIT_BRANCH}
**执行人:** ${BUILD_USER}
**版本号:** ${buildVersion}
**Version Code:** ${buildVersionNo}
**包名:** ${buildIdentifier}
**构建环境:** ${buildEnv}
**渠道环境:** ${BuildType}
**构建结果:** ${JOB_STATUS}
**更新内容:** ${buildUpdateDescription}
**构建时间:** ${JOB_DURATION}
**查看详情:** [项目地址](${appBuildURL})
**下载安装包二维码:** 
方案二、groovy脚本发送
- 通过Groovy脚本发送,那需要安装插件:
groovy postbuild
,点击add post-build actions,选择Groovy Postbuild
。
在打开的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"
,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+"×tamp="+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")
}
执行构建后,就会在输出日志中,打印钉钉的发送内容和钉钉的接口回调内容。
技术选型
由于钉钉插件只能实现简单的自定义功能,无法满足定制需求。
使用groovy脚本能实现完全自主可控需求,包括打包构建的各种内容的获取和透出,例如:Git分支,版本号,渠道,构建时间,执行人,更新内容,包的下载二维码,失败详情等内容。用红色突出打包失败结果,通过鲜明的颜色足够的对比度有助于增加可识别性,提示开发同学排查失败原因。
最后选择了使用groovy脚本能实现打包推送钉钉通知,打包消息同步到钉钉群效果。
总结
Jenkins自动化打包结果分发到蒲公英,打包结果下发到钉钉通知,达到【即拿即用】效果。同时把打包权利交于测试和运营同学,让他们拥有打包到下载安装一条龙。这样不仅仅方便了测试和开发,在版本流程上也会规范一下,后续APP包都可以在蒲公英平台上查找,还起到了版本归档的作用。