本文将介绍Android通过gradle进行打包,上传,以及发送通知到钉钉群聊中的实现流程,闲暇之下摸索摸索,涨涨姿势
Gradle
- 首先大家要了解下什么是gradle,以及gradle的生命周期和Groovy的基本语法
- 了解gradle:docs.gradle.org/current/use…
- Groovy: www.jianshu.com/p/1e95d0306…
打包
打包相信大家都很熟悉了,在平时的开发过程中有很多时候都在打包,调试等,而打包的这套流程Android已经给咱们提供了,就在我们平时运行的时候,build列表中的一个个task,而咱们只需执行assembleDebug/assembleRelease
命令就可以打包了,这两个task在AS的gradle栏中也可以看到,在这里双击也可以进行打包,这里显示的可能与你的不一样,是根据自己项目中配置的buildTypes
和productFlavors
决定的。
打包后的apk文件就保存在项目中的app/build/outputs/apk/ 目录下
上传蒲公英(摘自蒲公英文档说明:www.pgyer.com/doc/view/ap…)
新版本的蒲公英分为三步:
-
获取上传的 token, api地址(www.pgyer.com/apiv2/app/g…)通过该接口,可以获取预上传 url 和相关的签名参数
POST参数
参数 类型 说明 _api_key String (必填) API Key 点击获取_api_key buildType String (必填) 需要上传的应用类型,如果是iOS类型请传 ios
或ipa
,如果是Android类型请传android
或apk
oversea Integer (选填) 是否使用海外加速上传,值为:1 使用海外加速上传,2 国内加速上传;留空根据 IP 自动判断海外加速或国内加速 buildInstallType Integer (选填)应用安装方式,值为(1,2,3,默认为1 公开安装)。1:公开安装,2:密码安装,3:邀请安装 buildPassword String (选填) 设置App安装密码,密码为空时默认公开安装 buildUpdateDescription String (选填) 版本更新描述,请传空字符串,或不传。 buildInstallDate Integer (选填)是否设置安装有效期,值为:1 设置有效时间, 2 长期有效,如果不填写不修改上一次的设置 buildInstallStartDate String (选填)安装有效期开始时间,字符串型,如:2018-01-01 buildInstallEndDate String (选填)安装有效期结束时间,字符串型,如:2018-12-31 buildChannelShortcut String (选填)所需更新的指定渠道的下载短链接,只可指定一个渠道,字符串型,如:abcd 返回数据
参数 | 类型 | 说明 | | -------- | ------ | ---------------------------------------------- | | key | String | key 上传文件存储标识唯一 key | | endpoint | String | 上传文件的 URL | | params | Object | 上传文件需要的参数,包含signature、x-cos-security-token、key
-
上传文件到第一步获取的 URL(字段名:
endpoint
),在这一步中上传 App 成功后,App 会自动进入服务器后台队列继续后续的发布流程。所以,在这一步中 App 上传完成后,并不代表能及时的获取到成功的响应。一般来说,一般1分钟以内就能完成发布。所以要多次进行检查是否发布完成,直到收到发布成功的返回,检查接口调用下一步中的 API。POST参数
key | String | (必填) 从上一步响应中得到 | | -------------------- | ------ | ------------------------------- | | signature | String | (必填) 从上一步响应中得到 | | x-cos-security-token | String | (必填) 从上一步响应中得到 | | x-cos-meta-file-name | String | (选填) 上传的原始文件名,如 app-release.apk | | file | File | (必填) App 文件的本地路径
返回数据
如果上传成功:返回 http 状态码为 204 No Content; 如果上传失败:返回相应错误信息说明
-
检测应用是否发布完成,并获取发布应用的信息 api地址(www.pgyer.com/apiv2/app/b…),如果返回 code = 1246 ,可间隔 3s ~ 5s 重新调用 URL 进行检测,直到返回成功或失败。
参数
参数 类型 说明 _api_key String (必填) API Key 点击获取_api_key buildKey String (必填) 第一步中返回的key 返回的值分为以下 3 种情况:
发布成功返回数据
参数 类型 说明 buildKey String Build Key是唯一标识应用的索引ID buildType Integer 应用类型(1:iOS; 2:Android) buildIsFirst Integer 是否是第一个App(1:是; 2:否) buildIsLastest Integer 是否是最新版(1:是; 2:否) buildFileSize Integer App 文件大小 buildName String 应用名称 buildVersion String 版本号, 默认为1.0 (是应用向用户宣传时候用到的标识,例如:1.1、8.2.1等。) buildVersionNo String 上传包的版本编号,默认为1 (即编译的版本号,一般来说,编译一次会变动一次这个版本号, 在 Android 上叫 Version Code。对于 iOS 来说,是字符串类型;对于 Android 来说是一个整数。例如:1001,28等。) buildBuildVersion Integer 蒲公英生成的用于区分历史版本的build号 buildIdentifier String 应用程序包名,iOS为BundleId,Android为包名 buildIcon String 应用的Icon图标key,访问地址为 www.pgyer.com/image/view/…] buildDescription String 应用介绍 buildUpdateDescription String 应用更新说明 buildScreenShots String 应用截图的key,获取地址为 www.pgyer.com/image/view/…] buildShortcutUrl String 应用短链接 buildQRCodeURL String 应用二维码地址 buildCreated String 应用上传时间 buildUpdated String 应用更新时间 发布成功失败返回数据
参数 类型 说明 code Integer 错误码,1216 应用发布失败 message String 信息提示 正在发布返回数据
参数 类型 说明 code Integer 错误码,1247 应用正在发布中 message String 信息提示 如果返回 code = 1246 ,可间隔 3s ~ 5s 重新调用 URL 进行检测,直到返回成功或失败。
实现代码
获取上传Token
/**
* 获取上传的 token
* key String key 上传文件存储标识唯一 key
* endpoint String 上传文件的 URL
* params Object 上传文件需要的参数,包含signature、x-cos-security-token、key
*/
private fun getToken(
apiKey: String,
updateDesc: String,
buildInstallType: String,
httpCallback: HttpCallback
) {
val multipartBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("_api_key", apiKey)
.addFormDataPart("buildUpdateDescription", updateDesc)
.addFormDataPart(
"buildInstallType",
buildInstallType
)//(必填)应用安装方式,默认为1 公开安装 1:公开安装,2:密码安装,3:邀请安装
.addFormDataPart("buildPassword", "111111")//安装密码 buildInstallType为2时需要此参数
.addFormDataPart("buildType", "android")//平台类型
.build()
val okHttpClient = OkHttpClient()
val request = Request.Builder()
.url(GET_TOKEN_URL)
.post(multipartBody)
.build()
val response = okHttpClient.newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("上传失败,http code is ${response.code}")
}
val responseBody = response.body?.string()
val result = Gson().fromJson(responseBody, JsonObject::class.java)
println("请求token结果为:$result")
val code = result.get("code").asInt
if (code != SUCCESS_CODE) {
println(responseBody)
val errorMsg = result.get("message").asString
httpCallback.onError(code, errorMsg)
throw IOException("上传失败,$errorMsg")
}
val paramsObject = result["data"].asJsonObject["params"].asJsonObject
val key = result["data"].asJsonObject["key"].asString
val endpoint = result["data"].asJsonObject["endpoint"].asString
httpCallback.onSuccess(code, paramsObject, key, endpoint)
}
Token获取成功后上传apk
/**
* 上传文件到蒲公英
*/
private fun uploadFileToServer(
uploadBody: MultipartBody,
url: String,
uploadFileToServiceCallback: UploadFileToServiceCallback
) {
val request = Request.Builder()
.url(url)
.post(uploadBody)
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful) {
uploadFileToServiceCallback.onUploadError(response.code, "上传失败!")
throw IOException("上传失败,http code is ${response.code}")
}
//如果上传成功:返回 http 状态码为 204 No Content; 如果上传失败:返回相应错误信息说明
if (response.code == FILE_UPLOAD_SUCCESSFUL) {
uploadFileToServiceCallback.onUploadBack(FILE_UPLOAD_SUCCESSFUL, "文件上传成功等待同步数据");
} else {
uploadFileToServiceCallback.onUploadError(response.code, "上传失败!");
}
}
检查上传结果
/**
* 检查
*/
private fun checkUploadResult(checkUploadResultUrl: String) {
println("请耐心等待一会~")
Sleep().doSleep(3000)
val request = Request.Builder()
.url(checkUploadResultUrl)
.get()
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("检查失败,http code is ${response.code}")
}
val responseBody = response.body?.string()
val result = Gson().fromJson(responseBody, JsonObject::class.java)
val code = result["code"].asInt
val message = result["message"].asString
when (code) {
//需要过会再次请求,给蒲公英一个同步的时间
CHECK_UPLOAD_ERROR_CODE_1027, CHECK_UPLOAD_ERROR_CODE_1026 -> {
println(message)
checkUploadResult(checkUploadResultUrl)
}
//获取结果成功
SUCCESS_CODE -> {
println("上传结果:$result")
prepareNotify(result)
}
else -> {
println("上传结果:$result")
}
}
}
说明:代码的存放位置是在buildSrc中,调用方式通过task来调用。
发送通知到钉钉
发送通知到钉钉这一步就是在我们上传apk到蒲公英成功后进行,而发送通知自然也是需要调用钉钉的接口,接口就是咱们在群里创建的钉钉机器人生成的webHook
。
1、添加机器人:
2、自定义关键词:我这里使用的是自定义关键词(在使用自定义关键词时,调用接口发送通知的时候也需将关键词带上,不然接口会报错)
3、复制生成的webHook:
创建好之后就是接入,其实就是调用咱们上边生成的webHook,这里可以看下钉钉的文档,里边有详细介绍: open.dingtalk.com/document/gr…
发送消息类型(msgType)支持:
文本 (text)
链接 (link)
markdown(markdown)
ActionCard
FeedCard
发送通知实现具体代码:(我这里发送通知的格式是markdown格式的消息)
/**
* 发送钉钉消息
* https://open.dingtalk.com/document/group/custom-robot-access 推送消息钉钉文档
*/
private fun sendDingDingMessage(
title: String,//通知标题
message: String,//通知内容
dingDingRobotUrl: String,//webHook
buildQRCodeURL:String//上传apk到蒲公英后生成的二维码地址
) {
val contentJob = JsonObject()
contentJob.addProperty("title","## $title")
contentJob.addProperty("text", "$message 111 \n@123,@321")
contentJob.addProperty("messageUrl","https://www.pgyer.com/xxx")
contentJob.addProperty("picUrl", buildQRCodeURL)
val mobiles = JsonArray()
mobiles.add(123)
mobiles.add(321)
val atJob = JsonObject()
atJob.addProperty("isAtAll",false) //是否@所有人 如果不是则需要传具体@人的手机号
atJob.add("atMobiles",mobiles)//at人的手机号 (在发送内容中也需要有艾特人的手机号)
val body = JsonObject()
.apply {
addProperty("msgtype", "markdown") //发送消息的类型
add("markdown",contentJob)
add("at",atJob)
}.toString()
val request = Request
.Builder()
.url(dingDingRobotUrl)// 机器人地址
.post(body.toRequestBody("application/json".toMediaType()))
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("发送消息失败,http code:${response.code}")
}
val resultText = response.body!!.string()
val result = Gson().fromJson(resultText, JsonObject::class.java)
//println("sendDingDingMessage: $result")
require(result.get("errcode").asInt == 0) { "发送消息失败,${resultText}" }
println("发送钉钉通知成功")
}
这里附上效果图来进行比较:
补充
我这里将调用上传和发送通知的代码封装在了一个类中(PygerTask
),这个类存放的目录是在buildSrc中,如果有不清楚buildSrc的小伙伴,可以查下buildSrc是什么(传送道),代码中的upload-pgyer-desc.txt,是一个更新说明填写文件,每次打包上传前将更新的内容填进去,然后代码里边会读取出来,进行上传,此文件的存放目录大家可以自定义,最好是根目录,比较好找,我这里是放在了app模块得根目录下,上传和发送通知我这里用的是okhttp
。
PygerTask全部代码:
/**获取上传token*/
internal const val GET_TOKEN_URL = "https://www.pgyer.com/apiv2/app/getCOSToken"
/**上传成功*/
internal const val FILE_UPLOAD_SUCCESSFUL = 204
/**检查上传结果*/
internal const val CHECK_UPLOAD_RESULT = "https://www.pgyer.com/apiv2/app/buildInfo"
/**检查上传结果code*/
internal const val CHECK_UPLOAD_ERROR_CODE_1027 = 1247
internal const val CHECK_UPLOAD_ERROR_CODE_1026 = 1246
internal const val SUCCESS_CODE = 0
open class PygerTask : DefaultTask() {
/** Pgyer的上传apiKey */
@Input
var apiKey: String = ""
/** 上传的apk路径,相对project的路径*/
@Input
var apkPath: String = ""
/** 钉钉机器人通知地址 */
@Input
var dingDingRobotUrl: String = ""
/** 更新描述标题 */
@Input
var updateTitle: String = ""
/** 更新描述后缀 */
@Input
var updateDescSuffix: String = ""
/** 更新描述文件路径,相对project的路径 */
@Input
var updateDescPath: String = "upload-pgyer-desc.txt"
/**安装方式*/
private val buildInstallType = 1 //公开安装 1:公开安装,2:密码安装,3:邀请安装
init {
doFirst {
require(apiKey.isNotEmpty()) { "apiKey must not null or empty" }
require(apkPath.isNotEmpty()) { "apkPath must not null or empty" }
require(dingDingRobotUrl.isNotEmpty()) { "dingDingRobotUrl must not null or empty" }
require(updateTitle.isNotEmpty()) { "updateTitle must not null or empty" }
}
doLast {
performUpload()
}
}
final override fun doFirst(action: Action<in Task>): Task {
return super.doFirst(action)
}
final override fun doFirst(action: Closure<*>): Task {
return super.doFirst(action)
}
final override fun doFirst(actionName: String, action: Action<in Task>): Task {
return super.doFirst(actionName, action)
}
final override fun doLast(action: Action<in Task>): Task {
return super.doLast(action)
}
final override fun doLast(action: Closure<*>): Task {
return super.doLast(action)
}
final override fun doLast(actionName: String, action: Action<in Task>): Task {
return super.doLast(actionName, action)
}
private fun performUpload() {
println("上传apk到pgyer")
//todo 先注释git检查
//requireGitUpToDate()
// 检查更新描述文件
val descFile = project.file(updateDescPath)
val updateDesc = descFile.readText(Charset.forName("utf-8"))
println(updateDesc)
if (updateDesc.isEmpty()) {
throw RuntimeException("请填写更新描述到[$descFile]中")
}
//获取上传的apk文件路径
val files = project.file("build/outputs/apk/$apkPath").listFiles()
var currentApkPath = ""
for (i in 0..files.size) {
val file = files[i]
if (file.name.contains(".apk")) {
println(file.name)
currentApkPath = file.absolutePath
break
}
}
//拿到上传的apk file
val apkFile = project.file(currentApkPath)
//获取上传token及所需参数
getToken(apiKey, updateDesc, buildInstallType.toString(), object : HttpCallback {
override fun onSuccess(
code: Int,
params: JsonObject?,
key: String?,
endpoint: String?
) {
if (code == SUCCESS_CODE && params != null) {
//成功后拿到上传所需参数开始上传apk
uploadFile(endpoint ?: "", key ?: "", params, apkFile)
} else {//获取失败
throw IOException("上传失败: code = $code")
}
}
override fun onError(code: Int, errorMsg: String?) {
throw IOException("上传失败: code = $code")
}
})
}
/**
* 获取上传的 token
* key String key 上传文件存储标识唯一 key
* endpoint String 上传文件的 URL
* params Object 上传文件需要的参数,包含signature、x-cos-security-token、key
*/
private fun getToken(
apiKey: String,
updateDesc: String,
buildInstallType: String,
httpCallback: HttpCallback
) {
val multipartBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("_api_key", apiKey)
.addFormDataPart("buildUpdateDescription", updateDesc)
.addFormDataPart(
"buildInstallType",
buildInstallType
)//(必填)应用安装方式,默认为1 公开安装 1:公开安装,2:密码安装,3:邀请安装
.addFormDataPart("buildPassword", "111111")//安装密码 buildInstallType为2时需要此参数
.addFormDataPart("buildType", "android")
.build()
val okHttpClient = OkHttpClient()
val request = Request.Builder()
.url(GET_TOKEN_URL)
.post(multipartBody)
.build()
val response = okHttpClient.newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("上传失败,http code is ${response.code}")
}
val responseBody = response.body?.string()
val result = Gson().fromJson(responseBody, JsonObject::class.java)
println("请求token结果为:$result")
val code = result.get("code").asInt
if (code != SUCCESS_CODE) {
println(responseBody)
val errorMsg = result.get("message").asString
httpCallback.onError(code, errorMsg)
throw IOException("上传失败,$errorMsg")
}
val paramsObject = result["data"].asJsonObject["params"].asJsonObject
val key = result["data"].asJsonObject["key"].asString
val endpoint = result["data"].asJsonObject["endpoint"].asString
httpCallback.onSuccess(code, paramsObject, key, endpoint)
}
/**
* 上传文件
* @param url 上传apk的url
*/
private fun uploadFile(
url: String,
key: String,
params: JsonObject,
apkFile: File
) {
if (url.isEmpty()) return
//创建组装上传所需参数
val uploadBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("key", key)
.addFormDataPart("signature", params["signature"].asString)
.addFormDataPart(
"x-cos-security-token",
params["x-cos-security-token"].asString
)
.addFormDataPart(
"file",
apkFile.name,
NewProgressesRequestBody(
apkFile.asRequestBody(),
NewConsoleProgressListener(
apkFile.name,
services[ProgressLoggerFactory::class.java]
)
)
).build()
uploadFileToServer(uploadBody, url, object : UploadFileToServiceCallback {
override fun onUploadBack(code: Int, msg: String?) {
if (code == FILE_UPLOAD_SUCCESSFUL) {
//蒲公英官方文档上注明了数据同步需要时间,所以检查接口可能会多次调用
println(msg)
val checkUploadResultUrl =
"$CHECK_UPLOAD_RESULT?_api_key=$apiKey&buildKey=$key"
checkUploadResult(checkUploadResultUrl)
}
}
override fun onPackageSizeComputed(param1Long: Long) {
}
override fun onProgressChanged(param1Long: Float) {
}
override fun onUploadError(code: Int, error: String?) {
}
})
}
/**
* 上传文件到蒲公英
*/
private fun uploadFileToServer(
uploadBody: MultipartBody,
url: String,
uploadFileToServiceCallback: UploadFileToServiceCallback
) {
val request = Request.Builder()
.url(url)
.post(uploadBody)
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful) {
uploadFileToServiceCallback.onUploadError(response.code, "上传失败!")
throw IOException("上传失败,http code is ${response.code}")
}
//如果上传成功:返回 http 状态码为 204 No Content; 如果上传失败:返回相应错误信息说明
if (response.code == FILE_UPLOAD_SUCCESSFUL) {
uploadFileToServiceCallback.onUploadBack(FILE_UPLOAD_SUCCESSFUL, "文件上传成功等待同步数据");
} else {
uploadFileToServiceCallback.onUploadError(response.code, "上传失败!");
}
}
/**
* 检查
*/
private fun checkUploadResult(checkUploadResultUrl: String) {
println("请耐心等待一会~")
Sleep().doSleep(3000)
val request = Request.Builder()
.url(checkUploadResultUrl)
.get()
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("检查失败,http code is ${response.code}")
}
val responseBody = response.body?.string()
val result = Gson().fromJson(responseBody, JsonObject::class.java)
val code = result["code"].asInt
val message = result["message"].asString
when (code) {
//需要过会再次请求,给蒲公英一个同步的时间
CHECK_UPLOAD_ERROR_CODE_1027, CHECK_UPLOAD_ERROR_CODE_1026 -> {
println(message)
checkUploadResult(checkUploadResultUrl)
}
//获取结果成功
SUCCESS_CODE -> {
println("上传结果:$result")
prepareNotify(result)
}
else -> {
println("上传结果:$result")
}
}
}
/**
* 准备通知内容
*/
private fun prepareNotify(result: JsonObject) {
val descFile = project.file(updateDescPath)
val updateDesc = descFile.readText(Charset.forName("utf-8"))
val dataObject = result["data"].asJsonObject
val qrCodeUrl = dataObject["buildQRCodeURL"].asString
val buildCreatedDate = dataObject["buildCreated"].asString
val buildName = dataObject["buildName"].asString
val buildVersion = dataObject["buildVersion"].asString
val buildShortcutUrl = dataObject["buildShortcutUrl"].asString
//通知内容
val notifyMessage =
"#### 应用名称:$buildName-Android\n" +
"#### 版本信息:$buildVersion\n" +
"#### 更新时间:$buildCreatedDate\n" +
"#### 更新内容:$updateDesc\n" +
"#### [点击下载 \uD83D\uDC48](https://www.pgyer.com/$buildShortcutUrl)\n" +
"#### 扫码下载 \uD83D\uDE0A\n" +
"\n$updateDescSuffix"
println("二维码地址:$qrCodeUrl")
// 发送通知
sendDingDingMessage(updateTitle, notifyMessage, dingDingRobotUrl, qrCodeUrl)
// 删除重置描述文件
descFile.delete()
descFile.createNewFile()
println("上传完成!")
}
}
private class NewProgressesRequestBody(
private val originRequestBody: RequestBody,
private val progressListener: NewProgressListener? = null
) : RequestBody() {
override fun contentType(): MediaType? = originRequestBody.contentType()
override fun contentLength(): Long {
return originRequestBody.contentLength()
}
override fun isDuplex(): Boolean {
return originRequestBody.isDuplex()
}
override fun isOneShot(): Boolean {
return originRequestBody.isOneShot()
}
override fun writeTo(sink: BufferedSink) {
val bufferedSink = ProgressSink(sink).buffer()
originRequestBody.writeTo(bufferedSink)
bufferedSink.flush()
}
inner class ProgressSink(delegate: Sink) : ForwardingSink(delegate) {
private var byteLength = 0L
override fun write(source: Buffer, byteCount: Long) {
super.write(source, byteCount)
this.byteLength += byteCount
progressListener?.update(byteLength, contentLength(), byteLength == contentLength())
}
}
}
private interface NewProgressListener {
fun update(bytesRead: Long, contentLength: Long, done: Boolean)
}
private class NewConsoleProgressListener(
private val fileName: String,
progressLoggerFactory: ProgressLoggerFactory
) : NewProgressListener {
private var percent = 0L
private val progressLogger = progressLoggerFactory.newOperation("上传apk文件")
private var started = false
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
val percent = bytesRead * 100 / contentLength
if (this.percent == percent /*|| percent - this.precent < 20*/) {
return
}
this.percent = percent
if (!started) {
started = true
progressLogger.start("上传apk文件", "上传中")
}
progressLogger.progress("\r正在上传${fileName},已完成${this.percent}%")
if (done) {
progressLogger.completed("上传成功", false)
}
}
}
/**
* 发送钉钉消息
* https://open.dingtalk.com/document/group/custom-robot-access 推送消息钉钉文档
*/
private fun sendDingDingMessage(
title: String,
message: String,
dingDingRobotUrl: String,
buildQRCodeURL: String
) {
val contentJob = JsonObject()
contentJob.addProperty("title", "## $title")
contentJob.addProperty("text", "$message android \n@x,@x")
contentJob.addProperty("messageUrl", "https://www.pgyer.com/xx")
contentJob.addProperty("picUrl", buildQRCodeURL)
val mobiles = JsonArray()
mobiles.add(x)
mobiles.add(x)
val atJob = JsonObject()
atJob.addProperty("isAtAll", false) //是否@所有人 如果不是则需要传具体@人的手机号
atJob.add("atMobiles", mobiles)//at人的手机号 (在发送内容中也需要有艾特人的手机号)
val body = JsonObject()
.apply {
addProperty("msgtype", "markdown") //发送消息的类型
add("markdown", contentJob)
add("at", atJob)
}.toString()
val request = Request
.Builder()
.url(dingDingRobotUrl)// 机器人地址
.post(body.toRequestBody("application/json".toMediaType()))
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("发送消息失败,http code:${response.code}")
}
val resultText = response.body!!.string()
val result = Gson().fromJson(resultText, JsonObject::class.java)
//println("sendDingDingMessage: $result")
require(result.get("errcode").asInt == 0) { "发送消息失败,${resultText}" }
println("发送钉钉通知成功")
}
/**
* 检查git 是否已经和远程同步
*/
private fun requireGitUpToDate() {
println("执行git status检查,防止发布时丢失commit")
val result = CommandUtil.exec("git status")
require(result.contains("up to date", true)) { "请确认本地的修改已经提交push到远程分支" }
}
/**
* http 请求回调
*/
internal interface HttpCallback {
fun onSuccess(code: Int, params: JsonObject?, key: String?, endpoint: String?)
fun onError(code: Int, errorMsg: String?)
}
/**
* 上传文件监听回调
*/
internal interface UploadFileToServiceCallback {
//上传成功 或者 同步数据接口成功返回
fun onUploadBack(code: Int, msg: String?)
//上传文件大小
fun onPackageSizeComputed(param1Long: Long)
//上传文件进度
fun onProgressChanged(param1Long: Float)
//上传失败返回
fun onUploadError(code: Int, error: String?)
}
最终代码调用就是在app模块下的build.gradle中:
def pgyerApiKey = "xxx"
def dingDingUrl = "xxx"
task uploadTest(type: PygerTask) {
group = "upload"
description = "上传debug apk文件到pgyer平台"
dependsOn("assembleDebug")//dependsOn 这里是先执行打包的task,执行完后才会执行下面的内容
mustRunAfter("clean")
apiKey(pgyerApiKey)
apkPath("build/outputs/apk/xxx/debug/app-debug.apk")
updateTitle("app-debug测试包")
dingDingRobotUrl(dingDingUrl)
updateDescSuffix("附加内容")
}