本文档仅针对SaaS-非云原生版本,不支持SaaS云原生版本和私有化版本;
SaaS云原生版本和私有化版本接入请参考HTTP API文档中的上报用户属性模块。
由于通过客户端SDK(APP、网站及小程序)和服务端SDK设置的用户属性,默认的计算逻辑都是按最终值查询
,我们不会将它们与事件记录在一起,并且只会记录该属性的最新值,历史时刻的旧值会因新值的上报而被覆盖,查询时将该属性与事件按照用户口径进行关联。
如果我们希望事件能关联上事件上报时刻对应的旧值,通常推荐将该属性定义为事件属性或事件公共属性。对于某些必须将其定义为用户属性的场景,我们提供了 User Profile API。
使用这个接口,我们可以指定某个用户属性按全部值或最终值进行查询
。对于按全部值查询
的属性,我们不仅会记录最新的值,还会提前将用户属性的当前值与该用户同一时刻上报的事件记录在一起,从而保留了旧值(对于历史数据只会保留当日的最后一条旧值,详见下文1.2.2)。
下面的表格对比了通过客户端SDK(APP、网站及小程序)、服务端SDK以及User Profile API上报的用户属性的区别,希望能有助于您的理解:
区别 | 客户端SDK、服务端SDK | User Profile API |
---|---|---|
是否需要鉴权 | 否 | 是 |
是否需要提前定义属性 | 否 | 是 |
属性查询逻辑 | 最终值 | 全部值和最终值 |
是否支持上报事件 | 是 | 否 |
是否支持单独上报属性 | 是 | 是 |
是否会作用于已上报的事件 | 是 | 仅按最终值查询时 |
本文档涉及的上报和查询api接口,接口采用RestAPI规范。
仅支持火山引擎增长分析「SaaS-非云原生版本」,不支持「SaaS云原生版本」和「私有化版本」
您可以通过页面右下角的工单功能或者联系您的客户成功经理告诉我们您要使用 User Profile API。我们会为您开通此功能,并将上报数据所需的 ak/sk 发送到您指定的邮箱。
完成开通后,您再次进入数据管理 > 用户属性
就可以看到页面右上角多出“新增用户属性”的按钮。
点击“新增用户属性”可以在弹框中对您想添加的属性进行配置。
其中计算逻辑
的说明如下:
使用分析全部值时需要特别注意的数据变更
如果某个属性一天内有多个值,则实时数据中该属性会如实记录这些值而在次日构建时仅会取最后一个值构建到非实时数据当中。
例如:
“等级”是一个分析全部值的属性,某日有一个用户的“等级”从3级升到4级,之后又升级到5级。那么在当天查询包含这个属性的事件时可以查询到一些事件关联的值是“3”、“4”或“5”。在次日构建时,会取最新值“5”构建到当日所有数据当中,因此在构建完成后再发起同样的查询时,相关事件的这个属性值就只能查看到“5”这个值了。
另外,与事件公共属性相比,类型为分析全部值的用户属性会与所有事件相关联,而事件公共属性仅限于某一端上报的全部事件。
确认后,用户属性中会出现一个新的属性,接下来就可以上报数据了。
完成属性配置后,可以按下文中的鉴权以及API用法进行接口调用完成数据上报。注意数据类型一定正确,类型错误的数据会被丢弃。
当已经完成数据上报,并且属性没有被禁用的情况下。就可以在属性筛选、分组等处使用这些属性了。
1)该API使用qps上限500
2)uuid需要满足规则:[a-zA-Z_0-9\\-/]+
3)使用User Profile API进行属性上报时,对于未注册的用户,默认情况下不会进行自动注册,若需要开启自动注册功能,请联系您的客户成功经理进行配置。(注意:开启用户自动注册后,如该用户在客户端从匿名状态登录,可能会产生冗余的ssid,导致一个 uuid 对应多个 ssid,从而影响分析)
4)使用User Profile API上报公共属性/用户属性时,请不要上报带"custom_"前缀的属性,也不要上报客户端SDK或服务端HTTP API支持的事件格式header里已有的属性;
5)对于datetime类型的数据,目前支持四种格式的datetime:
2020-07-07T13:46:08 2020-07-07 13:46:08 2020-07-07T13:46:08.342 2020-07-07 13:46:08.342 2020-07-07T13:46:08.342+08:00 2020-07-07 13:46:08.342+08:00 2020-07-07T13:46:08+08:00 2020-07-07 13:46:08+08:00
由于存在时区问题,所以:
Profile
服务运行所在地的时区。Profile
服务运行所在地的时区。通过提供AccessKey/SecretKey的方式鉴权,简写为ak/sk,AccessKey是app请求的唯一标识,SecretKey是app的密钥,它们相当于用户名和密码。注册app之后就会生成一个AccessKey和SecretKey,请妥善保存。在所有请求的header中包括如下鉴权信息:
Header | Type | Description | Required |
---|---|---|---|
Authorization | string | api鉴权使用(Global) | TRUE |
- 及appSecret的生成可联系客户经理
- 可以使用我们提供的sdk帮助鉴权
- Authorization的生成工具见示例代码-5.1
- 生成Authorization示例代码见示例代码-5.2
国内: https://analytics.volcengineapi.com
海外: https://analytics.byteplusapi.com
Path: /dataprofile/openapi/v1/{app_id}/users/{user_id}?set_once=true
Method: PUT
Content-Type: application/json; charset=utf-8
Path-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
user_id | string | 用户id | TRUE |
Query-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
set_once | boolean | 不存在则设置 | TRUE |
Body:
{ "name":"name", "value":"zhangsan" }
Request-example:
curl -X PUT -H 'Content-Type: application/json; charset=utf-8' -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/751/users/185?set_once=true --data '{ "name":"name","value":zhangsan }'
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
Response-example:
{ "code":2000, "message":"success" }
Path: /dataprofile/openapi/v1/{app_id}/users/{user_id}/attributes/{attribute}
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
user_id | string | 用户id | TRUE |
attribute | string | 属性名称 | TRUE |
Body:
{ "operation":"INCREASE", "value":1 }
operation可选值见下文 3.4 Operation
Request-example:
curl -X PUT -H 'Content-Type: application/json; charset=utf-8' -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/54/users/185/attributes/age --data '{ "operation":"INCREASE","value":1 }'
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
Response-example:
{ "code":2000, "message":"success" }
批量指同一个用户的多个属性,而非多个用户
Path: /dataprofile/openapi/v1/{app_id}/users/{user_id}/attributes
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
user_id | string | 用户id | TRUE |
Body:
{ "attributes": [ { "name": "name", "value": "zhangsan", "operation": "SET" }, { "name": "height", "value": 10, "operation": "INCREASE" }, { "name": "gendar", "operation": "UNSET" } ] }
operation可选值见下文 3.4 Operation
Request-example:
curl -X PUT -H 'Content-Type: application/json; charset=utf-8' -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/778/users/185/attributes --data '{ "attributes": [ { "name": "name", "value": "zhangsan", "operation": "SET" }, { "name": "height", "value": 10, "operation": "INCREASE" }, { "name": "gendar", "operation": "UNSET" } ] }'
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
Response-example:
{ "code":2000, "message":"success" }
Path: /dataprofile/openapi/v1/{app_id}/users/{user_id}
Method: GET
Content-Type: application/json; charset=utf-8
Request-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
user_id | string | 用户id | TRUE |
Request-example:
curl -X GET -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/42/users/185
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
data | object | 属性值信息 |
data.appId | int64 | 应用id |
data.attributes | list | 具体信息 |
data.attributes[i].name | string | 属性名称 |
data.attributes[i].value | 根据注册类型而定 | 值信息 |
Response-example:
{ "code": 2000, "message": "success", "data": { "appId": 183706, "attributes": [ { "name": "vip_last", "value": 8 }, { "name": "trip_all", "value": "地铁" }, { "name": "weight_last", "value": 123 }, { "name": "trip_last", "value": "地铁" }, { "name": "weight_all", "value": 123 } ] } }
Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}?set_once=true
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
ssid | string | ssid | TRUE |
Query-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
set_once | boolean | 不存在则设置 | TRUE |
Body:
{ "name":"name", "value":"zhangsan" }
Request-example:
curl -X PUT -H 'Content-Type: application/json; charset=utf-8' -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/819/users/ssid/185?set_once=true --data '{ "name":"name","value":"zhangsan" }'
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
Response-example:
{ "code":2000, "message":"success" }
Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}/attributes/{attribute}
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
ssid | string | ssid | TRUE |
attribute | string | 属性名 | TRUE |
Body:
{ "operation":"INCREASE", "value":1 }
operation可选值见下文 3.4 Operation
Request-example:
curl -X PUT -H 'Content-Type: application/json; charset=utf-8' -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/256/users/ssid/185/attributes/age --data '{ "operation":"INCREASE","value":1 }'
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
Response-example:
{ "code":2000, "message":"success" }
Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}/attributes
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
ssid | string | ssid | TRUE |
Body:
{ "attributes": [ { "name": "name", "value": "zhangsan", "operation": "SET" }, { "name": "height", "value": 10, "operation": "INCREASE" }, { "name": "gendar", "operation": "UNSET" } ] }
operation可选值见下文 3.4 Operation
Request-example:
curl -X PUT -H 'Content-Type: application/json; charset=utf-8' -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/790/users/ssid/185/attributes --data '{ "attributes": [ { "name": "name", "value": zhangsan, "operation": "SET" }, { "name": "height", "value": 10, "operation": "INCREASE" }, { "name": "gendar", "operation": "UNSET" } ] }'
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
Response-example:
{ "code":2000, "message":"success" }
Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}
Method: GET
Content-Type: application/json; charset=utf-8
Request-parameters:
Parameter | Type | Description | Required |
---|---|---|---|
app_id | int64 | app_id | TRUE |
ssid | string | ssid | TRUE |
Request-example:
curl -X GET -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/942/users/ssid/185
Response-fields:
Field | Type | Description |
---|---|---|
code | int32 | 业务响应状态码 |
message | string | 业务响应描述信息 |
data | object | 属性值信息 |
data.appId | int64 | 应用id |
data.attributes | list | 具体信息 |
data.attributes[i].name | string | 属性名称 |
data.attributes[i].value | 根据注册类型而定 | 值信息 |
Response-example:
{ "code": 2000, "message": "success", "data": { "appId": 183706, "attributes": [ { "name": "vip_last", "value": 8 }, { "name": "trip_all", "value": "地铁" }, { "name": "weight_last", "value": 123 }, { "name": "trip_last", "value": "地铁" }, { "name": "weight_all", "value": 123 } ] } }
Operation | 说明 |
---|---|
SET | 为属性设置一个值 |
SET_ONCE | 属性不存在则设置 |
UNSET | 删除一个属性 |
INCREASE | 对数值类型的属性执行累加操作。对于属性为int或float类型的属性:
|
APPEND | 在list类型的属性值里插入一个值 |
REMOVE | 在list类型的属性值里删除一个值 |
Error code | Error Message | Description | 备注 | ||
---|---|---|---|---|---|
400 | transmission content is tampered or authorization is invalid | 生成的Authorization信息不对 | 检查生成Authorization的代码,注意params格式;每个请求的鉴权信息都是动态生成的 | ||
400 | invalid accesskey | AK/SK无效 | |||
4000 | The user ${uuid} not exists | 使用未注册的uuid上报 | 若需要自动注册,可联系产品在RA上配置auto_create_ssid | ||
4000 | Type conversion failed, property ${property_name} type is ${property_type}, if you modify the property type, please try again in 5 minutes. | 上报数据类型与定义类型不一致 | string和list类型:可接所有类型的值 | ||
4000 | Type conversion failed, if you modify the property type, please try again in 5 minutes | ||||
4000 | Make sure to successfully define when using this property: ${property_name} or try again in 5 minutes! | 使用未定义过的属性上报 | 检查是否在平台预先定义过该属性,确认定义过则5分钟之后重试 | ||
4000 | property ${property_name} is ${property_type}, don't support ${operation_name} operation. | 属性类型不支持的上报操作 | 例如:对float类型进行append操作 | ||
4000 | The datetime should be between 1900 and 2099 | datetime类型value值的年份范围不在[1900,2099]之间 | |||
4000 | invalid identifier for ${property_name | uuid | ssid} | 属性名、uuid、ssid不满足正则规则 "[a-zA-Z_0-9-]+" | |
4000 | attributes is empty | 上报数据body中attributes为空 | |||
4000 | name is blank | 上报数据body中属性name不存在 | |||
4000 | operation is null | 上报数据body中属性operation不存在 | |||
5030 | request frequency | 服务限流 | |||
5000 | something wrong, please retry later | 系统内部服务错误 | 重试仍然失败的情况下,一般是系统未兼容的问题,可以联系客服 |
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Map; public class AuthUtils { /** * * @param ak accessKey * @param sk secretKey * @param expirationSeconds 过期时间,单位秒 * @param method 方法,GET, POST, PUT * @param path 请求的path,非完整的url(不需要加域名和查询参数) * 例如:属性设置接口:/dataprofile/openapi/v1/213123/users/user_1234 这样即可 * @param params 请求参数 * @param body 请求的json体 * @return */ public static String sign(String ak, String sk, int expirationSeconds, String method, String path, Map<String, String> params, String body) { String cm = canonicalMethod(method); String cu = canonicalUrl(path); String cp = canonicalParam(params); String cb = canonicalBody(body); String text = cm + "\n" + cu + "\n" + cp + "\n" + cb; return doSign(ak, sk, expirationSeconds, text); } private static String canonicalMethod(String method) { return "HTTPMethod:" + method; } private static String canonicalUrl(String url) { return "CanonicalURI:" + url; } private static String canonicalParam(Map<String, String> params) { String res = "CanonicalQueryString:"; if (params == null || params.isEmpty()) { return res; } for (String key : params.keySet()) { res += formatKeyValue(key, params.get(key)) + "&"; } return res.substring(0, res.length() - 1); } private static String formatKeyValue(String key, String value) { return key + "=" + value; } private static String canonicalBody(String body) { String res = "CanonicalBody:"; if (body == null) { return res; } else { return res + body; } } private static String doSign(String ak, String sk, int expiration, String text) { String signKeyInfo = "ak-v1/" + ak + "/" + (int) (System.currentTimeMillis() / 1000) + "/" + expiration; String signKey = sha256Hmac(signKeyInfo, sk); String signResult = sha256Hmac(text, signKey); return signKeyInfo + "/" + signResult; } private static String sha256Hmac(String message, String secret) { String hash = ""; try { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] bytes = sha256_HMAC.doFinal(message.getBytes()); hash = byteArrayToHexString(bytes); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } return hash; } private static String byteArrayToHexString(byte[] b) { StringBuilder hs = new StringBuilder(); String stmp; for (int n = 0; b != null && n < b.length; n++) { stmp = Integer.toHexString(b[n] & 0XFF); if (stmp.length() == 1) { hs.append('0'); } hs.append(stmp); } return hs.toString().toLowerCase(); } }
import time import hashlib import hmac def _do_sign(ak, sk, expiration, text): sign_key_info = 'ak-v1/%s/%d/%d' % (ak, time.time(), expiration) sign_key = _sha256_hmac(sk, sign_key_info) sign_result = _sha256_hmac(sign_key, text) return '%s/%s' % (sign_key_info, sign_result) def _canonical_method(method): return "HTTPMethod:" + method def _canonical_url(url): return "CanonicalURI:" + url def _canonical_param(params): res = 'CanonicalQueryString:' if not params: return res kvs = [] for k, v in params.items(): kvs.append('{}={}'.format(k, v)) return res + '&'.join(kvs) def _canonical_body(body): res = "CanonicalBody:" if not body: return res return res + body def _sha256_hmac(key, data): return hmac.new(str.encode(key, 'utf-8'), str.encode(data, 'utf-8'), hashlib.sha256).hexdigest() def sign(ak, sk, expiration_seconds, method, path, params, body): """ :param ak:accessKey :param sk:secretKey :param expiration_seconds:过期时间,单位秒 :param method:方法,GET, POST, PUT :param path:请求的path,非完整的url(不需要加域名和查询参数) 例如:属性设置接口:/dataprofile/openapi/v1/213123/users/user_1234 这样即可 :param params:请求参数 :param body:请求的json体 :return: """ canonical_method = _canonical_method(method) canonical_url = _canonical_url(path) canonical_param = _canonical_param(params) canonical_body = _canonical_body(body) text = '{}\n{}\n{}\n{}'.format(canonical_method, canonical_url, canonical_param, canonical_body) return _do_sign(ak, sk, expiration_seconds, text)
const crypto = require('crypto'); function hash(ak, sk, expiration_seconds, method, path, params, body) { const timestamp = (+new Date() / 1000).toFixed(0); const signKeyInfo = `ak-v1/${ak}/${timestamp}/${expiration_seconds}`; const signKey = sha256HMAC(sk, signKeyInfo); const data = canonicalRequest(method, path, params, body); const signResult = sha256HMAC(signKey, data); return signKeyInfo + '/' + signResult; } function sha256HMAC(sk, data) { const hmac = crypto.createHmac('sha256', sk) return hmac.update(data).digest('hex') } function canonicalRequest(method, url, params, body) { let cm = canonicalMethod(method); let cu = canonicalUrl(url); let cp = canonicalParam(params); let cb = canonicalBody(body); return cm + '\n' + cu + '\n' + cp + '\n' + cb; } function canonicalMethod(method) { return 'HTTPMethod:' + method; } function canonicalUrl(url) { return 'CanonicalURI:' + url; } function canonicalParam(params) { let res = 'CanonicalQueryString:' if (!params) { return res; } return res + queryString(params); } function canonicalBody(body) { let res = "CanonicalBody:" if (!body){ return res; } return res + body; }
import java.util.HashMap; import java.util.Map; public class AuthGenDemo { //分配的accessKey和secretKey private static String accessKey = "****"; private static String secretKey = "****"; // 单位秒 private static Integer expirationSeconds = 300; public static void main(String[] args) { String method = "PUT"; String host = "https://analytics.volcengineapi.com"; String path = "/dataprofile/openapi/v1/751/users/185"; // 请求参数,对于没有请求参数的接口,在生成authorization code的时候对应的参数传null即可 HashMap<String, String> exampleQueryParams = new HashMap<>(); exampleQueryParams.put("set_once", "true"); String exampleQueryBodyJson = "{\"name\":\"name\",\"value\":\"zhangsan\"}"; String authorization = AuthUtils.sign(accessKey, secretKey, expirationSeconds, method, path, exampleQueryParams, exampleQueryBodyJson); System.out.println("authorization: " + authorization); Map<String, String> headers = new HashMap<>(); headers.put("Authorization", authorization); String url = host + path; JSONObject result = HttpUtil.put(url, exampleQueryBodyJson, exampleQueryParams, headers); } }
import auth_util import json import requests if __name__=="__main__": expiration_seconds = 300 method = "PUT" host = "https://analytics.volcengineapi.com" path = "/dataprofile/openapi/v1/751/users/185" # 请求参数,对于没有请求参数的接口,在生成authorization code的时候对应的参数传None即可 params = { "set_once": "true" } request_body = { "name": "name", "value": "zhangsan" } authorization = auth_util.sign(ak, sk, expiration_seconds, method, path, params, json.dumps(request_body)) print(authorization) headers = {"Authorization": authorization} url = host + path print(requests.put(url, data=json.dumps(request_body), params = params, headers = headers).json())
注意
计算authorization_code传进去的params以及body和发送请求时的params和body格式必须保持一致(例如空格);特别是在代码中计算authorization_code然后使用curl来请求的时候。