HTTP 验签上报说明


#1. 接口信息

#1.1 接口名称

report服务器地址:8847/signData

#1.2 调用方式

HTTP POST

#1.3 请求参数

如有使用SDK,可直接封装 ReportDto 实体类

参数标识 参数名称 参数类型 是否必须 说明
dataArr 批量数据上报 JSONArray false 存放单条或多条数据
id 所属账号id String false 所属账号id,非必填,但是不可与访客id同时为空
distinctId 访客id String false 访客id,非必填,但是不可与账号id同时为空
appid 所属产品id String true 可在熵析数据平台 - 产品管理中查看
type 数据类型 String true event(事件) user(账号)
values 上报数据 String true 具体格式参考:事件数据封装、用户数据构建

#1.4 事件数据封装

JSONObject data = new JSONObject();
// 必要属性
// 游戏id
data.put("_game_id", "1");
// 账号id
data.put("_account_id", "123");
// 事件发生时间 - 毫秒级
data.put("_event_time", System.currentTimeMillis());
// 事件发生时间 - 秒级
data.put("_event_time_seconds", System.currentTimeMillis() / 1000);
// 数据来源 - 当前必须标记为data_center,否则会被当做非法数据遗弃
data.put("_data_origin", "data_center");

// 其余自定义属性 - 非必要属性
data.put("_role_id", "123456");
data.put("_role_name", "role_name");

#1.5 用户数据构建

// userArr中存放的是需要更新的字段,如下列例子中,将更新name、age两个字段
JSONArray userArr = new JSONArray();

// 每个需要更新的字段需要构建成一个JSONObject,必须带有action、key、value 三个属性
// 【当前账号数据必带两个属性:游戏id(_game_id),账号id(_account_id)】
JSONObject user = new JSONObject();
user.put("action", "add_or_update");
user.put("key", "name");
user.put("value", "new_name");
userArr.add(user);
user = new JSONObject();
user.put("action", "add_or_update");
user.put("key", "age");
user.put("value", 18);
userArr.add(user);

#1.6 响应参数

参数标识 参数名称 参数类型 说明
status 接口状态 Integer 接口返回状态,0为正常,其余为异常
msg 消息 String 接口返回消息

请求参数示例

{"dataArr":[{"event_key":"send_goods","id":"111_94392","type":"event","appid":"1682c307a3424c3fbbd625d2cef24feb","values":{"_os":"ios","_game_id":"111"}}]}

成功返回

{
    "msg": "success",
    "code": 200
}

失败返回

{
    "msg": "The signature verification fails",
    "code": 401
}

#2. 签名算法

#2.1 请求头

参数标识 参数名称 参数类型 是否必须 说明
AppId 所属项目id String true 可在熵析数据平台-项目管理-项目配置中查看
Content-MD5 MD5加密结果 String true 根据工具类生成的MD5结果
X-Authorization 组成的认证串 String true 构造待签名串,按照字段顺序合并

#2.2 签名字段

参数标识 参数名称 参数类型 是否必须 说明
contentMD5 所属产品id String true HTTP 头字段定义于 RFC 1864, 为推送实体内容的 MD5 加密结果(Base64 编码) ,用于提供实体内容的端 到端消息完整性检查
timestamp 毫米级时间戳 String true 请求的毫秒级时间戳
nonce 随机数 String true 自行生成的随机数,通常可使用 UUID 生成,最大长度不 超过 128 位

#2.3 签名算法及示例

#2.3.1 获取 contentMD5: 对推送实体内容(JSON)进行 MD5 加密(Base64 编码)
{
  "event_key": "send_goods",
  "id": "111_94392",
  "type": "event",
  "appid": "1682c307a3424c3fbbd625d2cef24feb",
  "values": {
    "_os": "ios",
    "_game_id": "111"
  }
}
#2.3.2 加密方法
// 1.获取 contentMD5: 对推送实体内容(JSON)进行 MD5 加密(Base64 编码)
byte[] md5Byte = getMD5Byte("{\"dataArr\":[{\"event_key\":\"send_goods\",\"id\":\"111_94392\",\"type\":\"event\",\"appid\":\"1682c307a3424c3fbbd625d2cef24feb\",\"values\":{\"_os\":\"ios\",\"_game_id\":\"111\"}}]}");
String md5 = base64Encoder(md5Byte);
System.out.println("md5:" + md5);

// 2.获取 nonce: 获取随机数
String nonce = UUID.randomUUID().toString();
System.out.println("nonce:" + nonce);

// 3.获取 timestamp: 获取当前毫秒级时间戳
long timestamp = System.currentTimeMillis();
System.out.println("timestamp:" + timestamp);

打印结果

md5:h/CXjCQMPF2sbbvU6GpUJw==
nonce:60369af2-e3f6-48ad-9bf4-d97c0a24e872
timestamp:1698977406174
#2.3.3 组装待签名串
// 按照顺序构造签名串
// contentMD5=${contentMD5}&nonce=${nonce}&timestamp=${timestamp}
String signString = "contentMD5=h/CXjCQMPF2sbbvU6GpUJw==&nonce=60369af2-e3f6-48ad-9bf4-d97c0a24e872&timestamp=1698977406174";
#2.3.4 将 AppId 作为 key, 生成待签名串的 Hmac-sha256 签名(Hex 编码)
String sign = signWithHmacSHA256(AppId, signString);
System.out.println("sign:" + sign);

打印结果

sign:6617196d4efddae0aa74320d9326b2400b8df95d89dae0c30e64a925f23cfa9f
#2.3.4 构造认证字符串
//按照顺序构造认证串
//Timestamp=${timestamp}&Nonce=${nonce}&AppId=${appid}&Signature=${sign}
String authorization = "Timestamp=1698977406174&Nonce=60369af2-e3f6-48ad-9bf4-d97c0a24e872&AppId=appid&Signature=6617196d4efddae0aa74320d9326b2400b8df95d89dae0c30e64a925f23cfa9f"

#2.3.5 完整请求如下

POST /signData
Content-Type: application/json; charset=UTF-8 
Content-MD5: h/CXjCQMPF2sbbvU6GpUJw==
X-Authorization: Timestamp=1698977406174&Nonce=60369af2-e3f6-48ad-9bf4-d97c0a24e872&AppId=appid&Signature=6617196d4efddae0aa74320d9326b2400b8df95d89dae0c30e64a925f23cfa9f
Body: {"dataArr":[{"event_key":"send_goods","id":"111_94392","type":"event","appid":"1682c307a3424c3fbbd625d2cef24feb","values":{"_os":"ios","_game_id":"111"}}]}

#3. 工具类

public class SignUtil {

    /**
     * MD5 加密(二进制)
     */
    public static byte[] getMD5Byte(String str) {
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
            mdTemp.update(str.getBytes(StandardCharsets.UTF_8));
            return mdTemp.digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Base64 编码
     */
    public static String base64Encoder(byte[] byte0) {
        return byte0 == null ? null : Base64.getEncoder().encodeToString(byte0);
    }

    /**
     * 字节数组十六进制转换
     */
    private static String bytesToHex(byte[] bytes) {
        char[] hexArray = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    /**
     * 获取 HmacSHA256 签名
     */
    public static String signWithHmacSHA256(String secretAccessKey, String data) {
        byte[] byteSignature;
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secretAccessKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            byteSignature = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException("toSignWithHmacSha256 error", e);
        }
        return bytesToHex(byteSignature).toLowerCase(Locale.US);
    }

}