服务端埋点参数获取

  1. 进入管理控制台-->点击采集信息

  2. 主域名:服务端埋点数据上报地址

  3. 副域名:默认和主域名一致,私部署客户可以提供副域名作为备用域名

  4. ServiceSecret和ServiceID为校验参数,注意保密不可泄露。

注意:因为上述信息涉及系统安全,所以只有用管理员权限才可以看到!

image.png

请求

接口地址: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生成规则说明

  1. 先将除了sign字段的其他所有请求字段的key和value封装成一个map对象

  2. 将这个map对象序列化成JSON字符串,生成时需要将key按照自然排序即ascii码排序,java可使用JSONObject.toJSONString(mapObject, SerializerFeature.MapSortField),(注:json需要进行压缩,去掉个字段之间的空格,如后面的代码块)

  3. 获取serviceSecret

  4. sign = MD5(排序完生成的JSON字符串 + serviceSecret)

  5. 将计算得到的sign放到原始的map对象中,再序列化成JSON字符串即为最终完整的JSON请求报文

sign校验流程图

image

响应说明

响应数据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("加密"));
    }
}