You need to enable JavaScript to run this app.
导航

User Profile API(SaaS查看)

最近更新时间2023.09.05 13:56:06

首次发布时间2021.02.23 10:41:48

本文档仅针对SaaS版本,不支持SaaS云原生版本和私有化版本;
SaaS云原生版本和私有化版本接入请参考HTTP API文档中的上报用户属性模块。

1.概述

由于通过客户端SDK(APP、网站及小程序)和服务端SDK设置的用户属性,默认的计算逻辑都是按最终值查询,我们不会将它们与事件记录在一起,并且只会记录该属性的最新值,历史时刻的旧值会因新值的上报而被覆盖,查询时将该属性与事件按照用户口径进行关联。
如果我们希望事件能关联上事件上报时刻对应的旧值,通常推荐将该属性定义为事件属性或事件公共属性。对于某些必须将其定义为用户属性的场景,我们提供了 User Profile API。
使用这个接口,我们可以指定某个用户属性按全部值或最终值进行查询。对于按全部值查询的属性,我们不仅会记录最新的值,还会提前将用户属性的当前值与该用户同一时刻上报的事件记录在一起,从而保留了旧值(对于历史数据只会保留当日的最后一条旧值,详见下文1.2.2)。
下面的表格对比了通过客户端SDK(APP、网站及小程序)、服务端SDK以及User Profile API上报的用户属性的区别,希望能有助于您的理解:

区别客户端SDK、服务端SDKUser Profile API
是否需要鉴权
是否需要提前定义属性
属性查询逻辑最终值全部值和最终值
是否支持上报事件
是否支持单独上报属性
是否会作用于已上报的事件仅按最终值查询时

本文档涉及的上报和查询api接口,接口采用RestAPI规范。

1.1 支持的版本

火山引擎增长分析「SaaS版本」,不支持「SaaS云原生版本」和「私有化版本」

1.2 快速开始

1.2.1 开通功能

您可以通过页面右下角的工单功能或者联系您的客户成功经理告诉我们您要使用 User Profile API。我们会为您开通此功能,并将上报数据所需的 ak/sk 发送到您指定的邮箱。
完成开通后,您再次进入数据管理 > 用户属性就可以看到页面右上角多出“新增用户属性”的按钮。

1.2.2 配置属性

点击“新增用户属性”可以在弹框中对您想添加的属性进行配置。

其中计算逻辑的说明如下:

  • 分析全部值:分析该属性变化过程中出现的所有取值。例如:属性为“会员类别”,用户a在某天从“普通会员”升级到“VIP”,“VIP”是最新取值,则用户a发生的访问、购买等事件改变前会被归到“普通用户”上,改变当天和改变后会被归到“VIP”上。
  • 分析最终值:仅分析该属性变化的最终取值。例如:属性为“会员类别”,用户a在某天从“普通会员”升级到“VIP”,“VIP”是最新取值,则用户a历史上发生的访问、购买等所有事件都会归到“VIP”上。

使用分析全部值时需要特别注意的数据变更
如果某个属性一天内有多个值,则实时数据中该属性会如实记录这些值而在次日构建时仅会取最后一个值构建到非实时数据当中。
例如:
“等级”是一个分析全部值的属性,某日有一个用户的“等级”从3级升到4级,之后又升级到5级。那么在当天查询包含这个属性的事件时可以查询到一些事件关联的值是“3”、“4”或“5”。在次日构建时,会取最新值“5”构建到当日所有数据当中,因此在构建完成后再发起同样的查询时,相关事件的这个属性值就只能查看到“5”这个值了。
另外,与事件公共属性相比,类型为分析全部值的用户属性会与所有事件相关联,而事件公共属性仅限于某一端上报的全部事件。

确认后,用户属性中会出现一个新的属性,接下来就可以上报数据了。

1.2.3 上报数据

完成属性配置后,可以按下文中的鉴权以及API用法进行接口调用完成数据上报。注意数据类型一定正确,类型错误的数据会被丢弃。

1.2.4 进行分析

当已经完成数据上报,并且属性没有被禁用的情况下。就可以在属性筛选、分组等处使用这些属性了。

1.2.5 注意事项

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

由于存在时区问题,所以:

  • 如果有特殊的时区要求,使用后4种格式上报,并指定正确的UTC;
    • 如果上报的UTC不对导致无法正确解析出时区,则先取接入app时配置的时区,若没有配置,则取Profile服务运行所在地的时区。
  • 对于使用前四种格式上报的时区默认先取接入app时配置的时区,若没有配置,则取Profile服务运行所在地的时区。
2.鉴权

通过提供AccessKey/SecretKey的方式鉴权,简写为ak/sk,AccessKey是app请求的唯一标识,SecretKey是app的密钥,它们相当于用户名和密码。注册app之后就会生成一个AccessKey和SecretKey,请妥善保存。在所有请求的header中包括如下鉴权信息:

HeaderTypeDescriptionRequired
Authorizationstringapi鉴权使用(Global)TRUE
  • 及appSecret的生成可联系客户经理
  • 可以使用我们提供的sdk帮助鉴权
  • Authorization的生成工具见示例代码-5.1
  • 生成Authorization示例代码见示例代码-5.2
3.API用法

3.1 域名

国内: https://analytics.volcengineapi.com
海外: https://analytics.byteplusapi.com

3.2 通过UUID上报

3.2.1 属性设置接口

Path: /dataprofile/openapi/v1/{app_id}/users/{user_id}?set_once=true
Method: PUT
Content-Type: application/json; charset=utf-8
Path-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
user_idstring用户idTRUE

Query-parameters:

ParameterTypeDescriptionRequired
set_onceboolean不存在则设置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:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息

Response-example:

{
        "code":2000,
        "message":"success"
}

3.2.2 更新单数属性值接口

Path: /dataprofile/openapi/v1/{app_id}/users/{user_id}/attributes/{attribute}
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
user_idstring用户idTRUE
attributestring属性名称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:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息

Response-example:

{
        "code":2000,
        "message":"success"
}

3.2.3 用户属性批量操作接口

批量指同一个用户的多个属性,而非多个用户

Path**:** /dataprofile/openapi/v1/{app_id}/users/{user_id}/attributes
Method
:
PUT
Content-Type: application/json; charset=utf-8
Request-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
user_idstring用户idTRUE

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:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息

Response-example:

{
        "code":2000,
        "message":"success"
}

3.2.4 用户属性查询接口

Path: /dataprofile/openapi/v1/{app_id}/users/{user_id}
Method: GET
Content-Type: application/json; charset=utf-8
Request-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
user_idstring用户idTRUE

Request-example:

curl -X GET -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/42/users/185

Response-fields:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息
dataobject属性值信息
data.appIdint64应用id
data.attributeslist具体信息
data.attributes[i].namestring属性名称
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
            }
        ]
    }
}

3.3 通过SSID上报

3.3.1 属性设置接口

Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}?set_once=true
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
ssidstringssidTRUE

Query-parameters:

ParameterTypeDescriptionRequired
set_onceboolean不存在则设置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:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息

Response-example:

{
        "code":2000,
        "message":"success"
}

3.3.2 更新单数属性值接口

Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}/attributes/{attribute}
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
ssidstringssidTRUE
attributestring属性名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:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息

Response-example:

{
        "code":2000,
        "message":"success"
}

3.3.3 用户属性批量操作接口

Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}/attributes
Method: PUT
Content-Type: application/json; charset=utf-8
Request-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
ssidstringssidTRUE

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:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息

Response-example:

{
        "code":2000,
        "message":"success"
}

3.3.4 用户属性查询接口

Path: /dataprofile/openapi/v1/{app_id}/users/ssid/{ssid}
Method: GET
Content-Type: application/json; charset=utf-8
Request-parameters:

ParameterTypeDescriptionRequired
app_idint64app_idTRUE
ssidstringssidTRUE

Request-example:

curl -X GET -H 'Authorization: ******' -i https://analytics.volcengineapi.com/dataprofile/openapi/v1/942/users/ssid/185

Response-fields:

FieldTypeDescription
codeint32业务响应状态码
messagestring业务响应描述信息
dataobject属性值信息
data.appIdint64应用id
data.attributeslist具体信息
data.attributes[i].namestring属性名称
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
            }
        ]
    }
}

3.4 Operation

Operation说明
SET为属性设置一个值
SET_ONCE属性不存在则设置
UNSET删除一个属性
INCREASE对数值类型的属性执行累加操作
APPEND在list类型的属性值里插入一个值
REMOVE在list类型的属性值里删除一个值
4.错误码列表
Error codeError MessageDescription备注
400transmission content is tampered or authorization is invalid生成的Authorization信息不对检查生成Authorization的代码,注意params格式;每个请求的鉴权信息都是动态生成的
400invalid accesskeyAK/SK无效
4000The 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类型:可接所有类型的值
float类型:可接收int/float类型的值
int类型:只能接收int类型的值
datetime:只能接收时间格式的字符串
修改类型有5分钟延迟

4000Type conversion failed, if you modify the property type, please try again in 5 minutes
4000Make sure to successfully define when using this property: ${property_name} or try again in 5 minutes!使用未定义过的属性上报检查是否在平台预先定义过该属性,确认定义过则5分钟之后重试
4000property ${property_name} is ${property_type}, don't support ${operation_name} operation.属性类型不支持的上报操作例如:对float类型进行append操作
4000The datetime should be between 1900 and 2099datetime类型value值的年份范围不在[1900,2099]之间
4000invalid identifier for ${property_nameuuidssid}
4000attributes is empty上报数据body中attributes为空
4000name is blank上报数据body中属性name不存在
4000operation is null上报数据body中属性operation不存在
5030request frequency服务限流
5000something wrong, please retry later系统内部服务错误重试仍然失败的情况下,一般是系统未兼容的问题,可以联系客服
5.示例代码

5.1 鉴权

5.1.1 Java (AuthUtil.class)

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();
    }
}

5.1.2 Python (auth_util.py)

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)

5.1.3 Js

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;
}

5.2 Authorization

5.2.1 Java

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);
    }
}

5.2.2 Python

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来请求的时候。