内测分发 Webhook 接入指南
使用webhook能力能够帮助您将Bugly内测服务与其他平台进行有效集成。通过配置不同的服务url来关注内测的相关事件,在这些事件发生时Bugly内测服务会自动回调这些url。
注意:
1、为保证消息内容的完整性,回调内容将采用HmacSHA1算法进行签名,密匙为appkey。 具体签名的方式见DEMO。
2、为保证安全性,回调结果的事件内容为AES128位加密后的Base64编码字符串。 具体加密解密的方式见DEMO。
回调协议
回调方式:POST
回调格式:application/json
回调推送数据格式:
参数名 | 类型 | 说明 |
---|---|---|
eventType | String | 事件类型,如 "beta_version_release","beta_version_status" |
eventContent | String | 事件内容。AES128位加密后的Base64编码字符串。解密后的内容为json格式。 |
timestamp | long | 时间戳,时间毫秒数 |
isEncrypt | int | eventContent 是否进行过加密,加密为 1,没有加密为 0(系统默认已加密) |
signature | String | 签名串。签名算法采用 HmacSHA1,密钥为 appkey。签名因子为:回调的结构体中,除了 signature 字段外,其他字段按照字段名称 的升序排列 将字段的名称和值拼接起来组装在一起, 如eventType=c,eventContent=d,timestamp=1,isEncrypt=0, 则签名因子为eventContentdeventTypcisEncrypt0timestamp1 |
示例:
{
"eventType": "beta_version_release",
"eventContent": "Vb2MqGGMML/wguuagt7Z9sNYxmGjl2UtFC5OHf4d1OWcnm3kK1KMpT2 NFfcHx5OW6iopmSYZaVBRRpvYgpcSsc5hLxwYJJKU2aNm0cUjxDfhfojIgyOLKXveOd82E WQLIwrVSvY1BIgrzrV1Cyx+",
"timestamp": 1460097554553,
"isEncrypt": 1,
"signature": "3C9A1F4F4F07D560466964A53E43BD300041B94A"
}
事件说明
事件类型:版本发布
eventType:"beta_version_release"
eventContent(已解密):json格式, 如下:
参数名 | 类型 | 说明 |
---|---|---|
app_id | String | 应用编号,用于识别产品的唯一ID |
pid | int | 应用平台标识,用于区分产品平台 |
title | String | 版本名称 |
description | String | 版本描述 |
secret | int | 公开范围 |
password | String | 密码 |
users | String | 指定用户或者用户群 |
url | String | 下载页地址 |
download_limit | int | 下载上限 |
status | int | 版本状态,1:正常 -1:关闭 -2:删除 |
create_time | String | 版本创建时间 |
update_time | String | 版本更新时间 |
exp_id | String | 体验id,后续可根据此id查询版本信息 |
version | String | 安装包版本号 |
md5 | String | 安装包md5值 |
creator | String | 版本发布者 |
download_num | int | 用户下载次数 |
feedback_num | int | 用户反馈次数 |
示例:
"eventContent": {
"app_id": "900000000",
"pid": 1,
"title": "demo",
"description": "demo description",
"secret": 5,
"users": 123456,
"url": "http://beta.qq.com/m/abcd",
"download_limit": 10,
"status": 1,
"create_time": "2015-12-16 19:01:56",
"update_time": "2015-12-16 19:01:56",
"exp_id": "c84f98d3-af9d-4840-a42f-12f688x4fx7x",
"version": "1.47.0.10470",
"md5": "B58D2D81631E2E43C4EC378906CE6D31",
"creator": "900000000_1",
"download_num": 0,
"feedback_num": 0
}
事件类型:版本变更
eventType:"beta_version_status"
eventContent(已解密):json格式, 如下:
参数名 | 类型 | 说明 |
---|---|---|
app_id | String | 应用编号,用户识别产品的唯一ID |
pid | int | 应用平台标识,用于区分产品平台 |
exp_id | String | 版本唯一标识,后续可根据此id使用api查询版本信息 |
status | int | 版本状态,1:激活 -1:人工关闭 -2达到下载上限自动关闭 -3:删除 |
update_time | String | 变更时间 |
creator | String | 变更人(若系统的变更,则为 "system",其他情况为具体的操作人) |
示例:
"eventContent": {
"app_id": "900000000",
"pid": 1,
"status": 1,
"exp_id": "c84f98d3-af9d-4840-a42f-12f688x4fx7x",
"update_time": "2015-12-16 19:01:56",
"creator": "system"
}
DEMO
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* @author beta
* @date 2016-4-15
*/
public class SecretUtil {
public static final String HMAC_SHA1 = "HmacSHA1";
public static final String AES = "AES/GCM/NoPadding";
public static byte[] hmacSha1(byte[] datas, byte[] key) {
SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_SHA1);
Mac mac = null;
try {
mac = Mac.getInstance(HMAC_SHA1);
mac.init(signingKey);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e.getMessage(), e);
}
return mac.doFinal(datas);
}
/**
* HmacSHA1 签名
*
* @param data 需要签名的内容
* @param key 密匙(使用appkey)
* @return
*/
public static String getHmacSha1(String data, String key) {
try {
byte[] datas = data.getBytes("utf-8");
byte[] keys = key.getBytes("utf-8");
byte[] results = hmacSha1(datas, keys);
return byte2HexStr(results);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* AES128加密
*
* @param content 需要加密的内容
* @param passwd 密匙(使用appkey)
* @return
*/
public static String encryptAES128(String content, String passwd) {
try {
byte[] keys = passwd.getBytes("utf-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(keys);
kgen.init(128, secureRandom);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(enCodeFormat, "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, enCodeFormat);
Cipher cipher = Cipher.getInstance(AES);
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, spec);
byte[] result = cipher.doFinal(byteContent);
return encode(result);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException | IllegalBlockSizeException
| BadPaddingException | InvalidKeyException | NoSuchPaddingException
| InvalidAlgorithmParameterException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* AES128解密
*
* @param content 需要解密的内容
* @param passwd 密匙(使用appkey)
* @return
*/
public static String decryptAES128(String content, String passwd) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
byte[] keys = passwd.getBytes("utf-8");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(keys);
kgen.init(128, secureRandom);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(enCodeFormat, "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, enCodeFormat);
Cipher cipher = Cipher.getInstance(AES);
cipher.init(Cipher.DECRYPT_MODE, keySpec, spec);
byte[] byteContent = decode(content);
byte[] result = cipher.doFinal(byteContent);
return getStringFromByteArray(result);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException
| InvalidAlgorithmParameterException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static String getStringFromByteArray(byte[] datas) {
return getStringFromByteArray(datas, "utf-8");
}
public static String getStringFromByteArray(byte[] datas, String encoding) {
if (datas == null || datas.length < 1) {
return null;
}
String value;
try {
value = new String(datas, encoding);
} catch (UnsupportedEncodingException e) {
value = new String(datas);
}
return value;
}
public static String byte2HexStr(byte[] b) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < b.length; i++) {
String s = Integer.toHexString(b[i] & 0xFF);
if (s.length() == 1) {
sb.append("0");
}
sb.append(s.toUpperCase());
}
return sb.toString();
}
public static String encode(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
public static byte[] decode(String data) {
try {
return Base64.getDecoder().decode(data.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
return Base64.getDecoder().decode(data);
}
}
}