内测分发 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);
    }
  }
}