服务端埋点参数获取
进入管理控制台-->点击采集信息
主域名:服务端埋点数据上报地址
副域名:默认和主域名一致,私部署客户可以提供副域名作为备用域名
ServiceSecret和ServiceID为校验参数,注意保密不可泄露。
注意:因为上述信息涉及系统安全,所以只有用管理员权限才可以看到!
请求
接口地址:https://<收数域名+端口>/server
请求参数content-type:application/json
请求参数
上报事件请求JSON参数
JSON字段名 (大小写也需保持完全一致) | 数据类型 | 是否必传 | 字段描述 | 示例 |
sign | string | 是 | 校验签名 | f564cae6a8ad458648id9d607a124322 |
app_id | string | 是 | 对应服务端埋点中的serviceID | OA8kI9Jis7YJNh5uh |
appkey | string | 是 | 应用key,从QT管理后台获取 | 9moqdsuia8hvxm7k8shf82n |
id | string | 是 | 事件编码,用户在QT后台创建事件时设置的编码 | click |
umid | string | 二者必须至少有一个上传,建议可以获取puid就尽量上传,保证分析时的IDMapping符合业务需求 | 设备ID | djajdjdk1 |
puid | string | 用户ID | user_001 | |
page_name | string | 否 | 页面名称 | page_index |
ts | string | 是 | 上报的毫秒级时间戳 | 1659493170125 |
cusp | object | 否 | 事件属性 | {"foo": "bar"} |
gp | object | 否 | 全局属性 | {"foo": "bar"} |
sdk_type | string | 是 | SDK类型,固定值为httpapi | httpapi |
server_ts | string | 否,QT服务端可自动生成。若您手动上传,则以您上传的服务端时间为准,QT服务端不再自动生成。 | 服务端毫秒级时间戳 | 1659493170125 |
uuid | string | 否,生成log_id的因子(2.4.17版本支持) | 如果希望log_id可以有较强的唯一性,可以为每条日志上报唯一的uuid,QT服务端将使用该uuid生成log_id | 1234567890 |
log_id | string | 否,QT服务端自动生成 | 日志标识,默认生成方式为uuid生成,若uuid没有上报,则使用时间戳生成随机id。 | 1234567890 |
事件报文协议
{
"sign":"27c95768438645138cfb010ded871091", //必填,签名
"app_id":"xxxxxxxxx", //对应服务端埋点信息中的:ServiceID
"appkey":"1344444", //必填,应用appkey
"id" : "get_coupons", //必填,事件编码
"umid" : "edfafs", //设备id
"puid" : "123456", //登录用户id
//umid和puid务必至少上传一个
"page_name":"home_page", //选填,页面编码
"ts" : "1614667799165", //必填,事件发生的时间戳
"cusp" : { //事件自定义参数
"p1" : "1",
"p2" : "2",
"p3" : "3"
},
"sdk_type":"httpapi", // 固定值,采集服务自动加上该字段
"server_ts":"1614667799165", // 如果主动传入,会使用传入值,不传入会自动使用采集服务采集日志时间戳,采集服务自动加上该字段
"_id":'123344' //日志唯一编码,采集服务自动加上该字段
}
上报用户属性请求JSON参数
JSON字段名 (大小写也需保持完全一致) | 数据类型 | 是否必传 | 字段描述 | 示例 |
sign | string | 是 | 校验签名 | f564cae6a8ad458648id9d607a124322 |
app_id | string | 是 | 对应服务端埋点中的serviceID | OA8kI9Jis7YJNh5uh |
appkey | string | 是 | 应用key,从QT管理后台获取 | 9moqdsuia8hvxm7k8shf82n |
id | string | 是 | 上报用户属性的事件编码,固定为$$_user_profile | $$_user_profile |
umid | string | 否 | 设备ID | djajdjdk1 |
puid | string | 是 | 用户ID | user_001 |
ts | string | 是 | 上报的毫秒级时间戳 | 1659493170125 |
cusp | object | 是 | 用户属性 | {"name": "tom", "gender": "1"} |
sdk_type | string | 是 | SDK类型,固定值为httpapi | httpapi |
server_ts | string | 否,QT服务端可自动生成。若您手动上传,则以您上传的服务端时间为准,QT服务端不再自动生成。 | 服务端毫秒级时间戳 | 1659493170125 |
uuid | string | 否,生成log_id的因子(2.4.17版本支持) | 如果希望log_id可以有较强的唯一性,可以为每条日志上报唯一的uuid,QT服务端将使用该uuid生成log_id | 1234567890 |
log_id | string | 否,QT服务端自动生成 | 日志标识,默认生成方式为uuid生成,若uuid没有上报,则使用时间戳生成随机id。 | 1234567890 |
用户属性报文协议
{
"sign":"27c95768438645138cfb010ded871091", //必填,签名
"app_id":"xxxxxxxxx", //必填,对应服务端埋点信息中的:ServiceID
"appkey":"1344444", //必填,应用appkey
"id" : "$$_user_profile", //必填,标识为用户属性
"umid" : "edfafs", //选填,设备id
"puid" : "123456", //必填,登录用户账号ID
"ts" : "1614667799165", //必填,事件发生的时间戳
"cusp" : { //必填,自定义用户属性
"gender" : "1", //性别
"birthday" : "1988-12-24" //出生年月
},
"sdk_type" : "httpapi", //固定值,采集服务自动加上该字段
"server_ts":"1614667799165", // 如果主动传入,会使用传入值,不传入会自动使用采集服务采集日志时间戳,采集服务自动加上该字段
"_id":'123344' //日志唯一编码,采集服务自动加上该字段
}
sign生成规则说明
先将除了sign字段的其他所有请求字段的key和value封装成一个map对象
将这个map对象序列化成JSON字符串,生成时需要将key按照自然排序即ascii码排序,java可使用JSONObject.toJSONString(mapObject, SerializerFeature.MapSortField),(注:json需要进行压缩,去掉个字段之间的空格,如后面的代码块)
获取serviceSecret
sign = MD5(排序完生成的JSON字符串 + serviceSecret)
将计算得到的sign放到原始的map对象中,再序列化成JSON字符串即为最终完整的JSON请求报文
sign校验流程图
响应说明
响应数据content-type:application/json
响应的JSON字符串中有两个字段,分别为code、message
code | message | 描述 |
Httpapi_300_200 | 上报成功 | 成功 |
Httpapi_300_101 | 非法的签名 | 签名校验失败,请参考sign生成规则说明 |
Httpapi_300_102 | 上报的数据类型非JSON格式 | 请求参数的数据格式不是JSON格式 |
Httpapi_300_103 | 缺少必要字段 | 参考请求参数表,检查是否遗漏了必填的参数 |
Httpapi_300_104 | 用户属性缺少必要字段 | 参考请求参数表,检查是否遗漏了必填的参数 |
Httpapi_300_105 | 非法事件ID | 事件ID不正确 |
Httpapi_300_106 | ak/sk不正确 |
Java Demo参考
package com.aliyun.app.quickaplus.mock.once_mock;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.aliyun.app.quickaplus.mock.util.MD5Util;
import okhttp3.*;
import java.io.IOException;
/**
* 服务端ApiDemo
*
* @author chengtao
*/
public class ServiceApiDemo {
/**
* 主域名
*/
private static final String API_URL = "*****";
/**
* 对应服务端埋点信息中的:ServiceID
*/
private static final String SERVICE_ID = "*******";
/**
* 对应服务端埋点信息中的:ServiceSecret
*/
private static final String SERVICE_SECRET = "*******";
/**
* client日志
*/
private static final OkHttpClient client = new OkHttpClient();
/**
* 主函数
*
* @param args 参数
*/
public static void main(String[] args) {
String srcJsonString = getMockData();
JSONObject json = JSON.parseObject(srcJsonString);
//向报文中添加服务ID
json.put("app_id",SERVICE_ID);
//向报文中添加ts
json.put("ts", String.valueOf(System.currentTimeMillis()));
//计算签名
String sign = MD5Util.md5(JSONObject.toJSONString(json,
SerializerFeature.MapSortField) + SERVICE_SECRET);
json.put("sign", sign);
String desJsonString = JSON.toJSONString(json,
SerializerFeature.DisableCircularReferenceDetect);
Request request = new Request.Builder()
.url(API_URL + "/server")
.post(RequestBody.create(desJsonString,
MediaType.parse("application/json")))
.build();
try {
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
System.out.printf("[DEMO] 发送日志失败 %s%n", response);
} else {
System.out.printf("[DEMO] 发送成功 %s%n",
response.body().string());
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取MockData
*
* @return 返回值
*/
private static String getMockData() {
return "{\n" +
" \"appkey\": \"1344444\",\n" +
" \"id\": \"get_coupons\",\n" +
" \"umid\": \"uuid()\",\n" +
" \"puid\": \"puid1\",\n" +
" \"page_name\": \"home_page\",\n" +
" \"cusp\": {\n" +
" \"p1\": \"1\",\n" +
" \"p2\": \"2\",\n" +
" \"p3\": \"3\"\n" +
" },\n" +
" \"gp\": {\n" +
" \"p1\": \"1\",\n" +
" \"p2\": \"2\",\n" +
" \"p3\": \"3\"\n" +
" },\n" +
" \"sdk_type\": \"httpapi\"\n" +
"}";
}
}
Python Demo 参考
# 正确的签名格式
{"appkey": "4b6G49PAkLUb4212","cusp":{"p1":"1","p2":"2","p3":"3"},"gp":{"p1":"1","p2":"2","p3":"3"},"id":"get_coupons","page_name":"home_page","puid":"puid1","sdk_type":"httpapi","umid":"uuid()"}tEkNnx8VDuR0mwEl3hXd7aozYh8Q2qS4
# 错误的签名格式
{"appkey": "您环境下的AppKey", "cusp": {"p1": "1", "p2": "2", "p3": "3"}, "gp": {"p1": "1", "p2": "2", "p3": "3"}, "id": "get_coupons", "page_name": "home_page", "puid": "puid1", "sdk_type": "httpapi", "umid": "uuid()"}tEkNnx8VDuR0mwEl3hXd7aozYh8Q2qS4
import json
import hashlib
import http.client
import ssl
import time
# service_id
service_id = 您环境下的service_id
# service_secret
service_secret = 您环境下的service_secret
# 收数服务配置
qlc_end_point = 您环境下的收数服务地址
# Mock的报文
def get_millisecond():
# 获取精确毫秒时间戳,13位
millis = int(round(time.time() * 1000))
return str(millis)
ts1 = get_millisecond()
print(ts1)
data = {
#代码上线时必须删除,和在“埋点验证-服务端验证”输入的DebugKey完全一致
"dk":"",
#您的appkey
"appkey":"",
"app_id": service_id,
"id": "get_coupons",
"umid": "uuid1",
"puid": "puid2",
"page_name": "home_page",
"ts": ts1,
"cusp": {
"card_type": "自营",
"scene": "主动购买",
"card_level": "体验会员",
"carrier": "够花APP",
"card_no": "d7tyk",
"user_id": "zsfMzsvHyA==",
"card_name": "7天体验卡",
"resource_id": "eedf17067cf64be299a844bbc8101426"
},
"gp": {
"p1": "1",
"p2": "2",
"p3": "3"
},
"sdk_type": "httpapi"
}
def sort_dict(d):
# 排序字典的主键
sorted_dict = {k: sort_dict(v) if isinstance(v, dict) else v for k, v in sorted(d.items())}
return sorted_dict
# 按照key的字典序排序
sorted_data = sort_dict(data)
sorted_dict = dict(sorted_data)
#string = json.dumps(sorted_dict, separators=(',', ':')) + service_secret
string = json.dumps(sorted_dict,ensure_ascii=False, separators=(',', ':')) + service_secret
print(string)
data['sign'] = hashlib.md5(string.encode('utf-8')).hexdigest()
data = json.dumps(data)
protocl,domain = qlc_end_point.split('://')
if (protocl == 'http'):
conn = http.client.HTTPConnection(domain)
if (protocl == 'https'):
conn = http.client.HTTPSConnection(domain)
conn.request("POST", "/server", body=data, headers={"Content-Type": "application/json"})
response = conn.getresponse()
print(response.read().decode())
conn.close()
MD5Util类代码
package com.aliyun.app.quickaplus.mock.util;
import java.security.MessageDigest;
/**
* md5加密工具类
*
* @author zheng.zhang
*/
public class MD5Util {
public static String md5(String s) {
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] btInput = s.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
/**
* 单元测试
*/
public static void main(String[] args) {
StringBuilder dtskey = new StringBuilder("abc12390");
dtskey.append('\0').append("~!@#$%^&*()_+");
System.out.println(MD5Util.md5(dtskey.toString()));
System.out.println(MD5Util.md5("20121221"));
System.out.println(MD5Util.md5("加密"));
}
}