立即注册

连接器配置

1. 连接器定义

1.1 授权定义

  • auth:连接器调用对应SaaS软件的授权方式,授权的定义是一个对象,当没有授权时可以为空

    • authType  授权类型,是第三方SaaS软件验证数环通权限的方式,目前主要有以下几种类型

      • api_token 通过AppKey + AppSecret 直接访问或者通过AppKey + AppSecret 换取AccessToken进行调用第三方SaaS的API接口,典型的SaaS有:自建钉钉,维格表等

      • oauth2 数环通作为第三方SaaS的平台服务商申请AppKey+ AppSecret, 用户通过授权数环通,由数环通获取用户的授权AccessToken调用第三方SaaS的API接口,典型的SaaS有:企业微信三方,钉钉三方

        • polling_oauth2 数环通作为第三方SaaS的平台服务商申请AppKey+ AppSecret, 用户通过授权数环通,由数环通获取用户的授权AccessToken调用第三方SaaS的API接口,但是与Oauth2 的区别在于,用户的授权信息是通过后端服务接口进行推送的,典型的SaaS有:聚水潭

  • test 测试输入账号有效性的执行方法,主要是在新增账号时调用,用于验证用户输入的数据是否有效,具体执行方法的定义参考执行方法定义

  • validate 验证当前账号的授权有效性的执行方法,主要是在账号新增账号成功后调用,用于验证当前授权信息如AccessToken等是否是有效状态,具体执行方法的定义参考执行方法定义

  • apiTokenConfig api_token类型的配置

    • autoRefresh 是否需要自动刷新,Boolean类型

      • true 需要刷新,需要配置refreshAccessToken

      • false 不需要自动刷新,AccessToken 是永久有效的

    • autoRefreshInterval 自动刷新的间隔,Integer类型 单位秒 例如3600 表示每小时刷新一次token

    • accessToken 获取AccessToken的执行方法

    • refreshAccessToken 刷新AccessToken的执行方法

  • oauth2Config oauth2 和 polling_oauth2的授权配置

    • autoRefresh 是否需要自动刷新,Boolean类型

      • true 需要刷新,需要配置refreshAccessToken

      • false 不需要自动刷新,AccessToken 是永久有效的

    • autoRefreshInterval 自动刷新的间隔,Integer类型 单位秒 例如3600 表示每小时刷新一次token

    • preAuthorize 获取授权链接前执行方法列表,例如获取预授权码

    • authorizeUrl 获取授权链接的执行方法

    • accessToken 获取AccessToken的执行方法

    • refreshAccessToken 刷新AccessToken的执行方法

  • thirdAuthConfig third_auth类型的配置

    • autoRefresh 是否需要自动刷新,Boolean类型

      • true 需要刷新,需要配置refreshAccessToken

      • false 不需要自动刷新,AccessToken 是永久有效的

    • autoRefreshInterval 自动刷新的间隔,Integer类型 单位秒 例如3600 表示每小时刷新一次token

    • accessToken 获取AccessToken的执行方法

    • refreshAccessToken 刷新AccessToken的执行方法

  • helpFields 显示提示帮助的字段列表,如提示用户如何进行配置账号和事件订阅

  • inputFields 用户需要输入的参数字段列表,例如Appkey,AppSecret等,具体的字段配置参考字段定义

  • outputFields 保存到数据的参数列表,具体的字段配置参考字段定义

授权类型的说明:

api_token类型的授权流程:

oauth2 授权流程:

polling_oauth2 类型授权流程:

 

1.2 账号定义

  • asset:连接器的账号配置,主要是配置授权的哪些数据需要保存到账号资产的数据库中

    • autoRefresh 是否需要自动刷新,Boolean类型,需要与授权定义中保持一致

      • true 需要刷新,需要配置refreshAccessToken

      • false 不需要自动刷新,AccessToken 是永久有效的

    • autoRefreshInterval 自动刷新的间隔,Integer类型 单位秒 例如3600 表示每小时刷新一次token

    • assetTemplate 保存的字段列表,用于定义哪些字段需要存进账号资产中

    • assetNameField 账号名称字段

    • assetUkField 账号唯一性标识字段

 

1.3 模板配置

  • requestTemplate:连接器的请求模板配置,主要是配置当前连接器发起Http请求时的公共参数,如appKey,签名配置等公共的配置,可以减少请求的配置

    • method 发起请求的方法,支持以下选项

      • GET

      • PUT

      • POST

      • PATCH

      • DELETE

      • HEAD

    • url 请求的完整地址

    • body 请求body配置

      • bodyType  请求体类型

        • NULL 为空

        • STRING 字符串

        • ARRAY 数组

        • OBJECT JSON对象类型

        • XML XML格式

      • bodyContent 请求体内容,根据bodyType传入具体的值

    • queryParams 查询请求参数,KV形式

    • formParams  表单请求参数,KV形式

    • headers 头部请求参数

    • signConfig 签名配置

      • signType 签名类型,签名的实现,目前可以使用签名脚本类型

        • script 通过签名脚本进行签名

      • signOutField 签名输出字段

      • signInField 签名字段参数,KV形式

      • signSetting 签名配置,针对脚本签名需要增加以下参数

        • connectorId 连接器ID

        • scriptType 脚本类型

        • sceneCode 场景编码

        • version 脚本版本

    • responseType 请求返回值类型,默认按照STRING处理

      • BINARY 二进制

      • JSON JSON格式

      • STRING 纯文本格式

    • beforeRequest 请求前执行方法列表,一般用于对请求参数进行处理

    • afterResponse 请求后执行方法列表,一般用于对于返回数据进行处理

  • triggerTemplate:消息通知和回调的配置。主要是配置当前连接器接收平台Http请求时的参数,可用来做触发器配置

    • pushUrl:消息推送回调地址。如:${eventDomainHost}/event/customPush/com.yuque.app/${assetId}/test

      • com.yuque.app 为连接器ID,用户务必配置为当前连接器的ID

      • test 为用户自定义参数

    • pushExecutor:推送数据的脚本配置

      • executeType:执行类型。设置为script

      • connectorId:当前连接器的ID

      • connectorVersion:当前连接器的版本

      • sceneCode:场景码。这里设置为dealPushEventContent

      • type:脚本类型。可选择java,js,pythone

      • version:脚本的版本号。可设置为1~9

1.4 触发器

  • triggers 触发器列表,用于触发器定义,触发器列表是个KV列表,其中Key是触发器的关键字定义,Value是一个触发器定义

    • key 用于唯一标识触发器

    • value 触发器的具体定义

触发器对象的定义:

  • key 触发器的key 和前文的key 保持一致

  • paramClass 参数对象类型,主要为了以后对参数映射进行扩展使用,当前暂时可以不配置

  • resultClass 结果对象类型,主要为了以后对参数映射进行扩展使用,当前暂时可以不配置

  • resultIsList 结果是否是一个数组列表,此处主要针对的是一级结果,默认为false

    • true 表示结果是一个数组列表

    • false 表示结果不是一个数组列表

  • display 展示配置,用于前端页面展示触发器的名称等信息m

    • label 触发器展示的名称

    • description 触发器的描述信息

    • directions 链接信息,可以设置为该触发器的来源页面地址

    • important 是否置顶,默认为false

      • true 表示需要置顶

      • false 表示不需要置顶

    • hidden 是否隐藏,当设置为true时前端不展示该触发器,默认为false

      • true 表示需要隐藏

      • false 表示不需要隐藏

  • triggerMode 触发器的模式,主要是定义触发器的数据来源,目前的选项有:轮询和事件触发

    • polling 通过轮询方式查询数据,一般是通过时间来查询某个列表

    • pushing 事件或者消息来源,一般是第三方SaaS通过配置回调接口,进行事件推送

  • triggerConfig 触发器配置,主要是针对轮询方式的触发器的配置

    • cron 轮询的时间表达式,

    • outOfFrequencyControl 配置为true cron表达式才生效,否则会根据用户版本设置轮训周期

    • startTime 开始触发的时间

    • ukFields 唯一标识记录的字段列表,主要用于标识该条记录有没有触发过

    • supportPaging 是否支持分页,支持分页时需要

      • true 支持

      • false 不支持

    • pageSize 分页大小设置

    • pageSizeField 分页大小对应的字段名称

    • pageIndexField 分页页码对应的字段名称

  • operation 获取触发数据的执行方法,对于轮询的触发器需要进行定义,事件触发的无需定义,要求执行方法返回的数据是数组

  • sample 获取样本数据的执行方法,对于轮询的触发器需要进行定义,事件触发的无需定义,要求执行方法返回的数据是单个对象

  • beforeTrigger 触发器执行请求前执行方法列表,一般用于对请求参数进行处理,beforeTrigger的执行时机是在获取到触发事件的数据之后,进行流程触发之前,对于事件触发的是在收到第三方的事件触发之后,对于轮询触发的是在执行轮询operation,获取到单个对象之后

  • afterTrigger 触发器后执行方法列表,一般用于对于返回数据进行处理,执行时机是在发出触发事件之后

1.5 执行器

  • operations 执行器列表,用于执行器定义,执行器列表是个KV列表,其中Key是执行器的关键字定义,Value是一个执行器定义

  • key 用于唯一标识执行器

  • value 执行器的具体定义

执行器对象的定义:

  • key 执行器的key 和前文的key 保持一致

  • paramClass 参数对象类型,主要为了以后对参数映射进行扩展使用,当前暂时可以不配置

  • resultClass 结果对象类型,主要为了以后对参数映射进行扩展使用,当前暂时可以不配置

  • resultIsList 结果是否是一个数组列表,此处主要针对的是一级结果,默认为false

    • true 表示结果是一个数组列表

    • false 表示结果不是一个数组列表

  • hidden 是否隐藏,当设置为true时前端不展示该执行器,默认为false

    • true 表示需要隐藏

    • false 表示不需要隐藏

  • display 展示配置,用于前端页面展示执行器的名称等信息

    • label 执行器展示的名称

    • description 执行器的描述信息

    • directions 链接信息,可以设置为该执行器的来源页面地址

    • important 是否置顶,默认为false

      • true 表示需要置顶

      • false 表示不需要置顶

  • categoryName 分类名,把同一类的放在一个组下,如 订单、商品、

  • operation 执行器的请求定义,在此定义执行器方法(具体定义看 3.8 执行方法定义)

  • inputFields 执行器输入字段定义

  • outputFields 执行器输出字段定义

  • tags 该执行器的备注信息

1.6 执行方法定义

执行方法是引擎支持的某个具体的执行定义,当前支持4种类型,分别为:

  • request 请求类型

  • function 函数类型

  • dependency 依赖类型

  • script 脚本类型

根据每个类型有具体的定义:

1.6.1 请求类型

请求类型是指主体功能是调用HTTP API

 

  • executeType: request  请求类型

  • method 发起请求的方法,支持以下选项

    • GET

    • PUT

    • POST

    • PATCH

    • DELETE

    • HEAD

  • url 请求的完整地址

  • body 请求body配置

    • bodyType  请求体类型

      • NULL 为空

      • STRING 字符串

      • ARRAY 数组

      • OBJECT JSON对象类型

      • XML XML格式

    • bodyContent 请求体内容,根据bodyType传入具体的值

  • queryParams 查询请求参数,KV形式

  • formParams  表单请求参数,KV形式

  • headers 头部请求参数 (在此定义优先级高于requestTemplate中的定义)

  • signConfig 签名配置

    • signType 签名类型,签名的实现,目前可以使用签名脚本类型

      • script 通过签名脚本进行签名

    • signOutField 签名输出字段

    • signInField 签名字段参数,KV形式

    • signSetting 签名配置,针对脚本签名需要增加以下参数

      • connectorId 脚本连接器ID

      • scriptType 脚本类型

      • sceneCode 场景编码version 脚本版本

  • beforeRequest 请求前执行方法列表,一般用于对请求参数进行处理

  • afterResponse 请求后执行方法列表,一般用于对于返回数据进行处理

 

1.6.2 函数类型

函数类型是指调用一个内置类的函数,或者内置表达式

  • executeType  function  函数类型

  • isExpress 是否是表达式

    • true 是 执行的是内置表达式

    • false 否 执行的是类方法

  • providerClass 执行类的全路径,isExpress 为false时必填

示例:

✅ "providerClass":"com.shuhuan.ipaas.connector.spi.yunpian.InputAndOutputBuilder"

  • function 执行的具体方法,对于表达式来说是具体的表达式,对于执行类方法时是类的方法,对于是表达式时是具体的表达式

示例:

✅ "function":"generateTemplateContentInput"

✅ "function":"${assert:equals(0,object:eval(body, '/msg'))}"

  • paramsExpression 参数表达式,对于执行类方法时传入

  • returnType 返回值类型

    • VOID 无返回值

    • STRING 字符串

    • BOOLEAN 布尔值

    • NUM 数字

    • INT 整形数字

    • ARRAY 数组或者列表

    • OBJECT 对象或者Map类型

  • returnItemType 返回列表的数据类型,returnType是ARRAY时需要

    • VOID 无返回值

    • STRING 字符串

    • BOOLEAN 布尔值

    • NUM 数字

    • INT 整形数字

    • ARRAY 数组或者列表

    • OBJECT 对象或者Map类型

1.6.3 依赖类型

依赖类型是指可以调用一组已经配置的执行动作,并做一些数据处理

  • executeType  dependency  依赖类型

  • dependencyOperationKeys 依赖的执行动作的Key列表

示例:

✅ "dependencyOperationKeys": ["getUidByMobile", "getUserDetailByUserId"]

  • afterResponse 所有依赖的执行动作执行完成后执行方法列表,一般用于对于返回数据进行处理

 

1.6.4 脚本类型

脚本类型是指执行一个脚本

  • executeType  script  脚本类型

  • connectorId 脚本对应的连接器ID

  • sceneCode 脚本场景码,用来标识该脚本应用场景

  • type 脚本类型

    • java

    • js

    • python

  • version 脚本版本

  • paramsExpression 执行的参数表达式,表达式为空时将会将整个参数上下文传入,不为空时计算完成表达式后传入

  • returnType 返回值类型

    • VOID 无返回值

    • STRING 字符串

    • BOOLEAN 布尔值

    • NUM 数字

    • INT 整形数字

    • ARRAY 数组或者列表

    • OBJECT 对象或者Map类型

  • returnItemType 返回列表的数据类型,returnType是ARRAY时需要

    • VOID 无返回值

    • STRING 字符串

    • BOOLEAN 布尔值

    • NUM 数字

    • INT 整形数字

    • ARRAY 数组或者列表

    • OBJECT 对象或者Map类型

1.7 字段定义

  • key 用于该执行方法中唯一标识字段

  • label 展示配置,用于前端页面展示字段名称

  • helpText 帮助信息,作为输入字段时需要,非必要不填写

  • placeholder 输入框提示信息,作为输入字段时需要

  • type 字段类型

  • widget 前端控件widgetConfig 配置说明文档

  • widgetConfig 前端组件的配置 KV形式

  • required 是否为必填选项,作为输入字段时需要

    • true 必填

    • false 选填

  • defaultValue 默认值

  • supportMultiple 是否支持多选,默认false(搭配选择控件时使用)

    • true 支持

    • false 不支持

  • datasource 可选数据源,KV形式

    • label 展示键

    • value 实际值

  • dynamicDataSource 动态数据源 动态数据路由

    • routerType 动态类型

      • beforeRequest 在请求之前

      • afterRequest 在请求响应之后

      • constant 常量

    • condition 判断是否符合路由条件

    • params  执行动态数据源的参数列表

    • selectCondition 判断是否可选的条件表达式

    • deepCondition 判断是否可下拉的条件表达式

    • refOperation 获取动态数据源的执行动作 

    • refLabel 动态label的取值方式

    • refValue  动态value的取值方式

    • router 下级的路由规则

    • datasource 静态数据源,routerType 为constant时设置

  • dynamicFields 动态字段 dynamicFields

    • routerType 动态类型

      • beforeRequest 在请求之前

      • afterRequest 在请求响应之后

      • constant 常量

    • condition 判断是否符合路由条件

    • params 参数列表

    • refOperation 获取动态字段的执行动作 

    • refInputFields 动态输入字段的取值表达式

    • refOutputFields 动态输出字段的取值表达式

    • inputFields 对于routerType 是constant时设置

    • outputFields 对于routerType 是constant时设置

  • childrenType 子节点类型

  • children 子节点

2.签名脚本开发

在应用shuhuan-ipaas-app-libs的signer目录下开发签名类,签名算法改成通过java执行脚本的方案实现签名算法,签名类需要继承JavaScriptRunner类重写execute方法    

/**
 * @author yunmu
 * @date 2022-03-04 6:44 下午
 */
public abstract class JavaScriptRunner {

    public abstract <T> T execute(Object params);
}

params 是一个Map对象,Map中会包含以下数据:

  • 上下文参数数据

  • signRequest 待签名的http请求获取方式:

HttpApiRequest apiRequest = (HttpApiRequest) paramsMap.get(SignConfigConstant.SIGN_REQUEST);
  • 签名配置中的signInField 中的信息

  • signOutField 签名的输出字段

签名脚本示例:

package com.shuhuan.ipaas.applibs.signer;

import com.aliyun.unicorn.http.MultipartFormData;
import com.aliyun.unicorn.http.MultipartFormValue;
import com.aliyun.unicorn.http.QueryString;
import com.aliyun.unicorn.sdk.HttpApiRequest;
import com.aliyun.unicorn.type.Consumer;
import com.aliyun.unicorn.type.Parameter;
import com.shuhuan.ipaas.common.ResultCode;
import com.shuhuan.ipaas.core.encryption.Md5Util;
import com.shuhuan.ipaas.exception.IpaasException;
import com.shuhuan.ipaas.meta.constants.SignConfigConstant;
import com.shuhuan.ipaas.script.java.JavaScriptRunner;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author yunmu
 * @date 2022-03-05 4:22 下午
 */
public class BanniuScriptSigner extends JavaScriptRunner {

    private static final String METHOD = "method";
    private static final String ACCESS_TOKEN = "access_token";
    public static final String TIMESTAMP = "timestamp";
    private static final String SIGN = "sign";

    @Override
    public <T> T execute(Object params) {
        if (!(params instanceof Map)){
            throw new IpaasException(ResultCode.PARAM_ERROR, "params is not map");
        }
        Map<String,Object> paramsMap = (Map<String, Object>) params;
        HttpApiRequest apiRequest = (HttpApiRequest) paramsMap.get(SignConfigConstant.SIGN_REQUEST);
        String appKey = (String) paramsMap.get(SignConfigConstant.APP_KEY);
        String appSecret = (String) paramsMap.get(SignConfigConstant.APP_SECRET);

        QueryString queryString = apiRequest.getQueryString();

        TreeMap<String, String> pmap = new TreeMap<>();
        queryString.foreachValues((paramKey, value) -> {
            pmap.put(paramKey, value);
        });

        MultipartFormData formData = apiRequest.getForm();
        if (formData != null && !formData.isMultipart()) {
            apiRequest.getForm().foreach(new Consumer<Parameter<MultipartFormValue>>() {
                @Override
                public void accept(Parameter<MultipartFormValue> a) {
                    String v = a.getFirstValue() != null ? a.getFirstValue().getValue() : null;
                    if (METHOD.equals(a.getName())) {
                        pmap.put(METHOD, v);
                    }
                    if (ACCESS_TOKEN.equals(a.getName())) {
                        pmap.put(ACCESS_TOKEN, v);
                    }
                    if (TIMESTAMP.equals(a.getName())) {
                        pmap.put(METHOD, v);
                    }
                    if (ACCESS_TOKEN.equals(a.getName())) {
                        pmap.put(ACCESS_TOKEN, v);
                    }
                }
            });
        }

        String stringToSign = sign(pmap, appSecret);
        String signature = Md5Util.md5(stringToSign, "utf-8").toUpperCase();
        queryString.add(SIGN, signature);
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put(SignConfigConstant.SIGNATURE, signature);
        resultMap.put(SignConfigConstant.STRING_TO_SIGN, stringToSign);
        return (T) resultMap;
    }

    // 代码一, 签名排序代码.
    // pmap 为所有参数, TreeMap 表示为树形结构的哈希容器
    // appSecret 班牛分配给您的密钥
    private String sign(TreeMap<String, String> pmap, String appSecret) {
        StringBuilder sb = new StringBuilder(appSecret);
        Iterator i$ = pmap.entrySet().iterator();
        while (i$.hasNext()) {
            Map.Entry<String, String> entry = (Map.Entry) i$.next();
            String name = entry.getKey();
            String value = entry.getValue();
            if (StringUtils.isNoneEmpty(new String[]{name, value})) {
                sb.append(name).append(value);
            }
        }
        sb.append(appSecret);

        return sb.toString();
    }
}

 

3.触发器脚本开发

编写事件回调数据处理脚本。例如

  1. package com.shuhuan.ipaas.applibs.dealPushEventContent.caoliao;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.google.common.collect.Lists;
    import com.shuhuan.ipaas.script.java.JavaScriptRunner;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 推送事件  数据处理
     * 文档地址: https://cli.im/help/65847
     */
    @Slf4j
    public class caoliaoDemo extends JavaScriptRunner {
    
        private static final String EVENT_TYPE = "eventType";
    
        private static final String BODY = "body";
    
        private static final String EVENT_CONTENTS = "eventContents";
    
        private static final String EVENT_CONTENT = "eventContent";
    
        private static final String ASSET = "asset";
    
        private static final String RESULT = "result";
    
    
        @Override
        public <T> T execute(Object params) {
            return (T) buildPushEventOutput(params);
        }
    
        /**
         * @param params
    
         *
         * {
         *     "asset": {
         *     },
         *  "body": {
         *     "time": "2022-06-30 17:06:39",
         *     "event": "FORM_DATA_SUBMIT",
         *     "data": {
         *         "ref_data": {
         *             "created_at": "2022-06-30 17:06:39",
         *             "serial_number": "L1000001",
         *             "form": {
         *                 "number": "D20",
         *                 "name": "会议签到"
         *             },
         *             "fields": {
         *                 "姓名": "草料",
         *                 "手机": "18888648888",
         *                 "微信名": "CHEN",
         *                 "身份证号": "330200000000000000",
         *                 "工号": "FBI100",
         *                 "单选项": "是"
         *             }
         *         }
         *     }
         * }
         *
         *
         * @return
         * {
         *     "result": {
         *         "code": 200,
         *         "message": "success"
         *     },
         *     "eventContents": [
         *         {
         *             "eventType": "FORM_DATA_SUBMIT",
         *             "eventContent": {
         *                 "ref_data": {
         *                     "created_at": "2022-06-30 17:06:39",
         *                     "serial_number": "L1000001",
         *                     "form": {
         *                         "number": "D20",
         *                         "name": "会议签到"
         *                     },
         *                     "fields": {
         *                         "姓名": "草料",
         *                         "手机": "18888648888",
         *                         "微信名": "CHEN",
         *                         "身份证号": "330200000000000000",
         *                         "工号": "FBI100",
         *                         "单选项": "是"
         *                     }
         *                 }
         *             }
         *         }
         *     ]
         * }
         */
        public static JSONObject buildPushEventOutput(Object params) {
            JSONObject output = new JSONObject();
            if (!(params instanceof Map)) {
                log.error("当前脚本入参类型错误!期望是Map,但是实际为{},具体值为{}", params.getClass().getName(), JSON.toJSONString(params));
                return output;
            }
            JSONObject input = (JSONObject) params;
    
            /**
             * 这里的事件内容的数据类型,有可能是数组,也可能是对象,具体按照推送方格式为准,作匹配的处理:
             * 1.解密(不是所有的消息都需要做)
             * 2.获取事件的标识
             * 3.将事件标识转换为触发器的key
             * 4.构造接口返回,务必与推送方要求保持一致
             */
    
            List<JSONObject> eventContents = Lists.newArrayList();
            JSONObject data = (JSONObject) input.get(BODY);
            JSONObject item = new JSONObject();
            //2. 获取对应事件的key。
            String event = (String) data.get("event");
            //3. 做触发器的key与事件的key映射
            if ("FORM_DATA_SUBMIT".equals(event)) {
                item.put(EVENT_TYPE, "FORM_DATA_SUBMIT");
            } else if ("FORM_DATA_EDITED".equals(event)) {
                item.put(EVENT_TYPE, "FORM_DATA_EDITED");
            } else if ("FORM_DATA_REVIEW".equals(event)) {
                item.put(EVENT_TYPE, "FORM_DATA_REVIEW");
            }
            //4. 对应事件的输出内容,与触发器的输出字段保持一致
            item.put(EVENT_CONTENT, JSON.parseObject(data.getString("data")));
            eventContents.add(item);
    
            output.put(EVENT_CONTENTS, eventContents);
    
    
            //推送事件后的响应结果,必须和推送方要求的返回结果一致
            Map<String, Object> result = new HashMap<>(16);
            result.put("code", 0);
            result.put("message", "success");
    
            output.put(RESULT, result);
            return output;
        }
    
    
    }
    

     

 

 

本篇目录

{{item.innerText}}

{{item.categoryName}}