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}×tamp=${timestamp}
String signString = "contentMD5=h/CXjCQMPF2sbbvU6GpUJw==&nonce=60369af2-e3f6-48ad-9bf4-d97c0a24e872×tamp=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);
}
}