自定义组件UDF接入管理

一、自定义组件UDF说明

自定义组件简称 UDF(User-Defined Functions),取名于常用的数据开发平台所使用的用户自定义函数。以下均以 UDF 代替自定义组件。

UDF 接入分为配置和执行两部分,配置用于界面管理、画布配置组件,执行用于旅程执行过程中的回调(执行部分和 WebHook 相似)。

二、UDF配置接入文档

为了与Quick Audience对接,您需要开发一个HTTP Server。注意是Post请求,请求超时时间为10秒。Quick Audience发起的HTTP请求和接入方回执请求约定如下通用参数,为必传项。

2.1 系统间的流转接口图

image.png

① 为“开发线下提供配置信息”如下所示

② 为iframe页面跳转,跳转到外部系统中

③ API 接口:通过令牌获取用户信息;同时需要将结果返回给iframe页面。

④ API 接口:通过令牌获取标签信息;同时需要将结果返回给iframe页面。

⑤ 前端提交数据到iframe嵌入页面中的 API,由外部系统后台通过 ⑥ 提交数据到 QA

⑥ API 接口:将配置信息提交到 QA,QA 根据选择的标签信息进行流程执行

2.1.1 产品界面配置 UDF 组件信息

image.png

字段填写限制:

字段

限制

嵌入页面地址

组件内需要iframe嵌入的链接地址,必须是合法的完整地址,包含使用的协议(如http或https),建议使用https,提高信息传输的安全性。

icon URL

组件显示icon,建议尺寸21 * 21px

请求地址

要接受消息推送的接口地址,必须是合法的完整地址,包含使用的协议(如http或https),建议使用https,提高信息传输的安全性。

回执设置

回执设置将以接口形式将营销结果返回到QA,需要配置回执结果字段,将在自动化营销中可通过结果判断基于回执结果分流。

AES密钥

AES密钥长度可以为128、192或256位,对应字符为16、24、32位。

2.1.1.1 配置的原始 JSON 信息
{
    "name":"自定义组件名称",
    "type":"marketing", //自定义组件类别:当前只支持 marketing -> 营销组件
    "version":"版本号", // 默认 1.0
    "endpoint":"自定义组件目标域名 以http或https开头,如:http://demo.com/二级路径 跳转至 $endpoint/index.html",
    "iconUrl":"icon地址",
    "enable": true // true or false是否启用
    "accessKey":"组件鉴权accessKey",
    "accessSecret":"组件鉴权accessSecret",
    "metaData":{ // 元数据信息,不通类型组件 metaData 格式不同
        "allowIDType":"该组件支持用户ID类型,如 mobile | email | idfa | imei等",
        "encryptionMethod":"推送营销消息 加密算法  null | encryptionMethod",
        "sendParam":{
            "batchSendUrl":"营销任务推送地址 以http或httpss开头,如 http://demo.com/send",
            "batchLimit":100, //批量推送条数限制 1~1000, 默认100
            "timeout": 10, //请求超时时间(单位秒) 0 ~ 60, 默认10
            "retryTimes":0, //失败重试次数  0 ~ 3, 默认0
            "retryPeriod":0, //重试时间间隔(单位秒) 0 ~ 3600, 默认 0
        },
        "callbackParam":{//营销组件回执参数
            "usableInHours": 3 * 24, //1 ~ 30 * 24,默认3 推送结果回执数据在多少天的时间内返回算有效,超过则丢弃 
            "clearInHours": 7 * 24, //1 ~ 365 * 24,默认 7 * 24 推送结果回执数据超过多少天后将被回收
            "resultEnumList":[ //推送结果回执枚举列表, 默认 success & fail 
                {
                    "code":"success",
                    "name":"成功"
                },
                {
                    "code":"fail",
                    "name":"失败"
                }
            ],
            "extParams": {
              "extParam1": "拓展参数列头001", //在效果分析中展示的列名,拓展字段01
              "extParam2": "拓展参数列头002", //在效果分析中展示的列名,拓展字段02
              "extParam3": "拓展参数列头003" //在效果分析中展示的列名,拓展字段03
            }
        }
    }
}

2.1.2 iframe 跳转

需要外部系统提供一个 iframe 页面,页面链接在上一步配置的endpoint中填写。

注意:会存在跨域问题,需要增加跨域支持,X-Frame-Options: allow-from https://example.com/

跳转 URL

/endPoint?comeFrom=xxx&qaEndpoint=xxx&token=xxx

返回结果

{
  "success":true,
  "errorDesc":"错误信息,可以为空"
}

2.1.3 API 接口:通过令牌获取用户信息

API:/openapi/v3/account/query?token=xxx

返回值:

{
  "success": false,
  "errorDesc": "",
  "data": {
    "organizationId":"组织 ID",
    "organizationName":"组织名称",
    "userId":"用户 ID",
    "nickName":"用户昵称",
  },
  "@comment": {
    "success": "/**\n     * 枚举值:true | false\n     */",
    "errorDesc": "/**\n     * 错误信息描述\n     */",
    "data": "/**\n     * 用户信息\n     */"
  }
}

只有成功获取到用户信息,才说明正常可用,如果未获取到用户信息,渲染页面时应该返回 token 失效错误信息。

2.1.4 API 接口:保存UDF选择的人群、事件 id 等

API:/restapi/marketing/udf_component/genUdfComponentNode

入参

{
    "componentId": "1001",
    "sourceId": "c1f5691dd5864479820c10c0089bece9 (这里传人群 id,事件 id)",
    "sourceNodeId": "",
    "nodeId": "83a2d328-030d-436a-b607-21da8ee2e34b",
    "sourceType": 1  // 这里传类型,1是人群 2是事件
}

返回值

{
    "data": {
        "nodeId": "83a2d328-030d-436a-b607-21da8ee2e34b",
        "token": "zKBfv7flRQmhd+XP1l1uuKCjE6seEOq8l02cBFWWRr5xQoOUPxb3+tj+ZPctWDG4rM5vC2PMQLxG0Xw7Qw6TpsDfXSYFwD8kmTjpUOV4AjxQfZPfC5q0wPrEFhvgfjz9X1OKDu4V4p1oJnJcbt5nELidsVk6jxkbVwHEL8ezXcyXFRKbC+jVYxOwG5ElUhS0+hraAK5IiwR1XgZCarFgOGf5ZZo/Nacjrp5AP8HbF3tl4+thAjp8ZuMWTYXsBJbe"
    },
    "errorCode": null,
    "errorDesc": null,
    "exStack": null,
    "opers": [],
    "solution": null,
    "success": true,
    "traceId": "48d6e570-1918-47a4-83ee-27c3f32d3a99"
}

2.1.5 API 接口:通过令牌获取人群标签列表/事件属性列表 (依赖1.4)

API:/openapi/v3/udf_component/marketing/getNodeUsableLabels?token=xxx&nodeId=xxx&version=VERSION_2

返回值:

[
    {
        "id": "标签ID/属性ID",
        "name": "标签名称/属性名称"
    }
]

选择的标签/属性会在最终外调三方系统的时候作为参数传入。

2.1.6 前端提交数据到iframe嵌入页面中的 API

该页面参数由三方自行定义,最终执行时,由 QA 调用到三方后,三方自行执行。但需要将选中的 QA 标签通过⑥的接口回传给 QA。

2.1.7 提交 UDF 配置信息

API:/openapi/v3/udf_component/marketing/saveOrUpdateNodeConfig?token=xxx

请求体:

{
  "nodeId": "节点ID",
  "nodeName": "节点名称",
  "labelIdList": [
    "标签ID1",
    "标签ID2"
  ]
}

返回值:无返回值,判断 HTTP 状态200即可

三、UDF执行接入文档

系统流转图如下所示

image.png

3.1 节点执行请求

为了与Quick Audience对接,您需要开发一个HTTP Server。注意是Post请求,请求超时时间为10秒。Quick Audience发起的请求和接入方回执请求约定如下通用参数,为必传项。

请求URL:{UDF配置的 sendParam.batchSendUrl }

请求Header信息

Header信息读取后需要通过java.net.URLDecoder#decode(java.lang.String, java.lang.String),编码为UTF-8。

v=4 // 固定参数,用于区分新老版本,新版本为4,老版本没有这个字段

请求体

请求入参

备注

参数

参数

类型

是否必须

请求时间戳

time

Long

请求ID

requstId

String

组件ID

componentId

String

组件accessKey

accessKey

String

AES密钥长度可以为128、192或256位,对应字符为16、24、32位。

消息体加密算法

encryptionMethod

String

默认不加密 null

目前仅支持AES 加密,加密和填充方式为:AES/ECB/PKCS5Padding

签名

signature

String

time +

componentId +

accessKey + accessSecret 经过hmac 加密后

注:签名算法和 WebHook 一样。

消息体

data

String

经过encryptionMethod加密后消息体:

  • processId=旅程ID

  • processName=旅程名称

  • processSchedulerId=旅程每次周期调度的ID

  • processSchedulerName=旅程每次周期调度的名称:旅程ID+周期的时间拼接字符串

  • processSchedulerStartTime=旅程周期开始时间,时间戳格式

  • processNodeId=旅程节点ID(每个周期的节点ID都是一样的)

  • processNodeName=旅程节点名称(每个周期的节点ID都是一样的)

  • batchId=批次号,每批次调用的时候唯一ID,可做批次幂等ID(2022年07月07日增加)

  • activityId=营销日历活动ID

  • activityName=营销日历活动名称

  • subActivityId=营销日历子活动ID

  • subActivityName=营销日历子活动名称

  • v=4 // 固定参数,用于区分新老版本,新版本为4,老版本没有这个字段

  • jobId=废弃:等于processId,推荐用processId

  • jobName=废弃:等于processName,推荐用processName

  • nodeId=废弃:等于processNodeId,推荐用processNodeId

  • nodeName=废弃:等于processNodeName,推荐用processNodeName

  • taskStartTimestamp=废弃:等于processSchedulerStartTime,是时间戳格式,推荐用processSchedulerStartTime

  • processInstanceId=废弃:兼容老版本:等于processSchedulerId

  • processInstanceName=废弃:兼容老版本:等于processSchedulerName

  • processInstanceStartTime=废弃:兼容老版本:等于processSchedulerStartTime

  • taskId=无此字段,等于processNodeInstanceId,无法兼容

  • processNodeInstanceId=无此字段,无法兼容

  • processNodeInstanceStartTime=无此字段,无法兼容

  • nodeStartTimestamp=无此字段,等于processNodeInstanceStartTime,无法兼容

  • data

  • messageId 消息id

  • customerList 数组

    • customerId用户id

    • labels => {"$labelId":"$labelValue"} 标签列表,map格式

    • featureMap => [{"key","value"},{"key","value"}] //featureMap在回执时必须带回,作为回执参数

    • processInfo => 如下描述

请求体示例:

{
  "time": 0,
  "requestId": "111",
  "componentId": "111",
  "accessKey": "111",
  "signature": "111",
  "encryptionMethod": [
    "AES"
  ],
  "data": "字符串格式,详细格式如下"
}

// data 的格式
{
    "jobId": "",
    "jobName": "",
    "nodeId": "",
    "nodeName": "",
    "componentName": "",
    "taskId": "",
    "taskStartTimestamp": 0,
    "nodeStartTimestamp": 0,
    "data": {
      "messageId": "",
      "customerList": [
        {
          "customerId": "",
          "labels": {},
          "featureMap": {
            "回执字段":"原样带回"
          },
          "processInfo": {
            "processInstanceId": "新版:旅程周期中,每个用户的旅程实例ID",
            "processInstanceStartTime": "新版:旅程周期中,每个用户的旅程实例开始时间,时间戳格式",
            "processNodeInstanceId": "新版:旅程节点实例ID(每次不同)",
            "processNodeInstanceStartTime": "新版:旅程周期中,每个用户的旅程的节点实例开始时间,时间戳格式",
            "groupName":"nodeId:nodeName:groupResult(node.result),groupName(node.resultExt);nodeId:nodeName:groupResult(node.result),groupName(node.resultExt)",
            "@comment":{
              "groupName" : "节点id:节点名称(界面配置的):分组结果(随机分组的百分比数字,两位小数):分组名称(界面配置的);多个用分号隔开"
            }
        	}
          "@comment": {
            "featureMap": "/**\n         * 拓展字段,异步回执时下游必须带回\n         */"
          }
        }
      ]
    },
    "activityId": "",
    "activityName": "",
    "subActivityId": "",
    "subActivityName": "",
    "processId": "",
    "processName": "",
    "processInstanceId": "",
    "processInstanceName": "",
    "processInstanceStartTime": "",
    "processNodeId": "",
    "processNodeName": "",
    "processNodeInstanceId": "",
    "processNodeInstanceStartTime": "",
    "groupId": "",
    "groupName": "",
    "@comment": {
      "activityId": "/**\n     * 活动ID\n     */",
      "activityName": "/**\n     * 活动名称\n     */",
      "subActivityId": "/**\n     * 子活动ID\n     */",
      "subActivityName": "/**\n     * 子活动名称\n     */",
      "processId": "/**\n     * 旅程ID(自动化ID);活动ID\n     */",
      "processName": "/**\n     * 旅程名称(自动化名称);活动名称\n     */",
      "processInstanceId": "/**\n     * 旅程实例ID;活动实例ID\n     */",
      "processInstanceName": "/**\n     * 活动实例名称(有实例的时间)\n     */",
      "processInstanceStartTime": "/**\n     * 活动实例开始时间\n     */",
      "processNodeId": "/**\n     * 活动节点ID\n     */",
      "processNodeName": "/**\n     * 活动节点名称\n     */",
      "processNodeInstanceId": "/**\n     * 活动节点实例ID\n     */",
      "processNodeInstanceStartTime": "/**\n     * 活动节点实例开始时间\n     */",
      "groupId": "/**\n     * ABTest分组ID(暂未实现,为空)\n     */",
      "groupName": "/**\n     * ABTest分组名称(暂未实现,为空)\n     */"
    }
  }

返回值

{
  "success": true,
  "errorDesc": "错误信息",
  "customerList": [
    {
      "messageId": "消息 ID",
      "customerId": "客户 ID",
      "returnType": "回执结果,该结果和回执结果一样,如果有则直接当做回执结果,如果不希望直接填写回执结果,不要传递",
      "returnMessage": "回执消息,长度限制255,超过会自动截取掉"
    }
  ]
}

3.1.1 鉴权方式

签名算法

long sendTime = System.currentTimeMillis();
// 组件id
// 组件ak
// 组件sk

String sign = getSignatureByHmacSHA1(sendTime, componentModel.getComponentId(), componentModel.getAccessKey(), componentModel.getAccessSecret());

public static String getSignatureByHmacSHA1(long time, String prametersString, String accessKey, String accessSecret) {
    String str = time + prametersString + accessKey;
    return HmacUtils.hmacSha256Hex(accessSecret, str.replaceAll("\\s+", ""));
}

3.2回执上报

接入方执行营销任务后,向Quick Audience上报回执,反馈营销消息的发送结果。

HTTP请求URL

API:/restapi/marketing2/udfComponent/receiveCallback?

请求体

{
  "time": 0,
  "componentId": "UDF组件ID",
  "signature": "签名",
  "data": "加密后回执信息,字符串",
  "@comment":{
    "time":"回执时间戳,毫秒"
  }
}

// data的原始格式
{
  "nodeId": "节点ID",
  "taskId": "任务ID",
  "customerList": [
    {
      "messageId": "消息 ID",
      "customerId": "客户 ID",
      "returnType": "回执结果",
      "sendTimestamp": 0,
      "featureMap": {
        "请求参数": "原样回传回来"
      }
      "@comment": {
        "messageId": "/**\n         * 消息ID,QA推送的时候  会带上这个ID\n         */",
        "customerId": "/**\n         * 用户ID\n         */",
        "returnType": "/**\n         * 回执结果\n         */",
        "sendTimestamp": "/**\n         * 消息推送时间\n         */",
        "featureMap": "发送请求时的参数,需要在回执时原样带回来"
      },
      "extParam1": "拓展参数001-自定义返回值",
      "extParam2": "拓展参数002-自定义返回值",
      "extParam3": "拓展参数003-自定义返回值"
    }
  ],
  "@comment": {
    "nodeId": "/**\n     * 节点ID\n     */",
    "taskId": "/**\n     * 任务ID\n     */",
    "customerList": "/**\n     * 回执用户/粉丝列表\n     */"
  }
}

返回值:

{
  "success": true,
  "errorDesc": "",
  "@comment": {
    "success": "/**\n     * 枚举值:true | false\n     */",
    "errorDesc": "/**\n     * 错误信息描述\n     */",
    "data": "/**\n     * 用户信息\n     */"
  }
}