应用注册

渠道应用需要登录到 Convertlab开放平台 进行注册。

进入 Convertlab开放平台 后,点击右上角 渠道应用开发平台,进入 渠道管理

left | 0x0

渠道管理 点击右上角 新建,选择所需要开发的应用类型,如 基础型。

left | 0x0

以下为不同渠道应用类型说明。

应用类型说明版本开放支持
基础型支持基础、专一触达的渠道注册、供应商及后续对应通道开通配置(如短信、邮件、彩信等)支持
平台型支持对接外部营销生态应用,完善安装和管理(营销应用如抖音、支付宝;微信生态应用如企微、微信公众号、微信小程序等)暂不提供
扩展型支持App应用内触达功能对接,以及对接一方系统等内部需求实现(如Apppush、App站内信;如一方crm等)暂不提供

新建基础型渠道

新建选择对应渠道应用类型后,会进入不同的应用配置内容,每种应用所配置内容有部分差异。

目前在配置流程中,主要需要经过三个流程配置。

  • 渠道基础信息
  • 渠道开放配置
  • 渠道应用接入点

渠道基础信息

  • 配置渠道开发内容和基础信息
  • 配置渠道应用支持的供应商信息

left | 0x0

配置项说明
配置项配置值说明必填
基础型渠道选择根据自身需求,选择需要开发的渠道应用类型,目前提供以下几种选择 短信 彩信 超级短信 邮件
渠道触点供应商配置配置应用所支持的渠道供应商。详细配置见 新建渠道商
渠道名称应用名,该名称将会在可展示的菜单项中进行使用展示。
渠道ID应用唯一ID,区分于其他应用
渠道图标地址应用展示图标
渠道简介应用说明信息
主页地址应用说明网站

渠道应用开发,需要对所使用的国内供应商信息进行注册。

当选择具体需要开发的渠道后,将出现供应商配置。如之前未配置过供应商,可新建供应商,进行新供应商配置。

left | 0x0

left | 0x0

配置项说明
配置项配置值说明必填
供应商显示名称渠道商命名,如 短信亿美渠道。
供应商ID将根据供应商显示名称自动生成。
供应商每批次最大发送个数限制根据供应商所提供的说明配置,用于在渠道发送时最大流速控制。

渠道开发配置

该步骤需要开发者根据应用运行要求进行配置。

left | 0x0

配置项说明
配置项配置值说明必填
渠道应用调用DM Hub接口获取租户数据方式

选择所DM Hub数据调用方式

- openApi     

应用访问DM Hub接口将通过 OpenApi 公共接口行访问。Open Api 可通过DM Hub  > 右上角帮助 > API文档 > 开放API  查阅相关接口

- 内部接口     

私部环境下,应用通过内部接口内网进行访问,提高访问效率,但产品不对内部接口兼容性进行兼容性保证。

DM Hub统一调度所有对外渠道触达消息,渠道应用接受的调度方式(均有SDK支持)

选择渠道发送的方式

- 同步(HTTP)

通过Http直接发送,实时性高,但并发性能低

- 异步(Kafka)

通过Kafka 间接进行发送,并发性能较高

渠道应用插入点配置

渠道应用插入点主要对应用的一些扩展配置,目前支持以下能力能力

  • 编辑器         支持对编辑器工具进行替换,如短信编辑器。
  • 自动流         支持新增自动流动作
  • 事件            支持客户自定义事件
编辑器配置

left | 0x0

系统编辑器

系统编辑器目前支持扩展元数据字段,以提供在编辑器中使用,配置格式如下:

[
  {
    "name": "name1",
    "label": "名称1",
    "type": "string",
    "required": true,
    "value": "1"
  },
  {
    "name": "number1",
    "label": "数量1",
    "type": "number",
    "value": 904,
    "disabled": true
  },
  {
    "name": "numberChange",
    "label": "数量加减",
    "type": "number",
    "value": 0,
    "description": "正数为增加,负数为扣减"
  }
]
自定义编辑器(暂未开放)

自定义编辑器目前暂未正式开放

自动流配置
  • 支持系统自动流默认动作名调整
  • 自定义自动流动作(待验证)

left | 0x0

如果使用系统默认自动流,可以对自动流名称进行修改

left | 0x0

如需自定义自动流,则需要提供对应的动作渲染 URL 脚本实现,开发流程参考 微前端应用开发 - 开发自动流程插件

目前自定义自动流开发仅支持渲染脚本自定义,不支持自定义 webhook,因此只能对以下 actionBody 属性进行支持。

暂不推荐进行个人自定义

{
 
    "channelVersion":"20230608.1"
    "tenantId": 12,
    "batchId": "sms-1234",
    "campaignUuid": "123123123123",
    "audienceInfo": {
        "listId": 123,
        "objId": "c_car",
        "audienceFilter": {},
        "sendIdentity": "customer",
        "audienceIdType": "mobile"
    },
    "channelPluginInstanceInfo": {
        "channelType": "sms",
        "pluginVersionId": 123123,
        "messagingType": "sending_sms",
        "accountInfo": {
            "channelTunnelInfo": {
                "tunnelId": 12312312,
                "additional": {
                    "quotaPoolId": 123123
                }
            },
            "channelInstanceInfo": {
                "channelInstanceId": 12312312,
 
                "additional": {
                    "quotaPoolId": 123123
                }
            }
        }
    },
    "templateInfo": {
        "templateId": 1234
    },
    "predicateInfo": {
        "isMock": false,
        "skipDnd": true,
        "pipl": []
    },
    "task": {},
    "additionalInfo": {
        "wechatAppUrlId": 1231
    },
    "date": "(UTC)"
}

left | 0x0

属性说明
动作名称自动流程中action的名称,建议尽可能短,否则排版会有问题。
动作Id前缀为应用ID+”action”,可以填入字母和数字。
动作展示图标自动流程action中显示的图标对应的css样式class。
动作渲染URL在自动流程中编辑该action时的编辑页面,以iframe的形式嵌入到产品中。这里需要填入自动流插件脚本地址,具体脚本开发参考自动流开发。
自定义事件配置

自定义事件配置用于扩充渠道应用中上报的客户事件。

通过上传客户自定义事件定义文件来进行配置。

left | 0x0

应用开发

安装环境

环境要求

  • JDK 8
  • kafka
  • redis
  • mysql

脚手架获取

应用项目的开发可以点击 下载脚手架 项目快速开始。

IDE导入项目

脚手架项目通过 Gradle 进行工程项目管理,需要IDE支持 Gradle 项目构建,目前推荐使用 IntelliJ IDEA

打开 IntelliJ IDEA后,通过菜单打开导入模板项目,IntelliJ IDEA 将自动进行 Gradle项目初始化

项目说明

项目结构

脚手架项目基于 SpringBoot ,提供基础的微服务开发结构。以下为项目结构说明:

├── Dockerfile    # Docker Image 构建文件

├── README

├── build.gradle    # Gradle 项目构建配置文件

├── gradle    # Gradle Wrapper 项目工具

│     └── wrapper

├── gradlew    # Gradle Wrapper 本地脚本

├── gradlew.bat   # Gradle Wrapper 本地批处理脚本

├── settings.gradle    # Gradle 项目构建多模块配置文件

└── src    # 代码工作空间目录

      ├── main

      │    ├── java

      │    │    └── com

      │    │     └── convertlab

      │    │            └── application

      │    │               └── scaffold

      │    │                   ├── ScaffoldApplication.java

      │    │                   ├── client

      │    │                   ├── config

      │    │                   ├── consts

      │    │                   ├── controller

      │    │                   ├── job

      │    │                   ├── mapper

      │    │                   ├── model

      │    │                   └── service

      │    └── resources

      │        ├── application.yml    # Spring 项目配置文件

      │        ├── db    # 数据库初始化及升级相关

      │        │    ├── changelog-master.yml # Liquibase 数据库 changelog 配置

      │        │    └── default-init.sql    # 项目数据库初始化脚本,被 changelog-master.yml 所引用

      │        ├── env\_vars.conf    # 项目环境变量配置,被 reference.conf 所引用

      │        ├── logback-spring.xml    # logback 配置文件

      │        └── reference.conf    # 支持 HOCON 方式的项目配置

      └── test    # 项目测试工作目录

开发项目结构,可根据实际项目配置要求调整,满足渠道应用SDK实现要求即可。

开发前配置

脚手架提供的是一个范式结构,需要开发者根据自身实际开发内容,进行部分调整,以下配置内容,为开发者可能需要 调整的位置进行说明。

application.yml 配置

Spring 配置【必要】

调整 /src/main/resources/application.yml 基础 Spring 基础设施配置。

  • 对于新建应用,应先调整 spring.application.name 应用名。
  • 调整开发基础设施连接信息,如mysql、redis、kafka等
spring:

    application:

      # 服务名,根据应用进行重写命令

      name: sms-scaffold

    main:

      allow-bean-definition-overriding: true

    # springboot kafka 配置

    kafka:

      bootstrap-servers: ${kafkaServer\_bootstrap\_servers:localhost:9092}

...

    # springboot redis 配置

    redis:

      host: ${redis\_host:localhost}

      port: ${redis\_port:6379}

     password: ${redis\_password:}

      ssl: ${redis\_enableSsl:false}

...

    # mysql 配置

    datasource:

      driver-class-name: com.mysql.cj.jdbc.Driver

      url: ${dataSource\_url:jdbc:mysql://localhost:3306/xiaoshu?useunicode=true&characterencoding=utf8&servertimezone=utc}

      username: ${dataSource\_username:root}

      password: ${dataSource\_password:root}

    # liquibase yml路径配置

    liquibase:

      change-log: classpath:db/changelog-master.yml
网络配置

SDK 使用了 openFeign 网络框架,在 application.yml ,可根据需要调整 openFeign 配置。

  • 调整需要的日志等级 loggerlLevel
## Scaffold 里面使用到的 openFeign 配置

feign:

    client:

      config:

        default:

          # 根据环境需要调整日志等级,目前为全日志

          loggerLevel: full

    # 默认启用 okhttp 作为 http client, 可根据需要调整 client 实现

    okhttp:

      enabled: true

    # 请求代理配置,调整该配置,可改变 SDK 请求代理

    proxyConfig:

      host:

      port:

      username:

      password:
OpenApi配置【内部接口二选一】

应用如果启用了 OpenApi ,可通过以下配置设置基础配置信息,相关配置信息,可通过 DMHub 产品 设置 → 应用集成 → 开发与集成 进行创建

dmHubOpenApi:

    serverUrl: "https://master-api.dmhub.cn"

    appId: ""

    secret: ""
内部接口配置【OpanApi二选一】

应用如果启用了内部接口,可通过以下配置设置基础配置信息。

内部接口没有appId、secrer 等预配置信息,但是需要声明接口 URL 信息。


# internal 服务的地址配置

## 私有部署需要配置如果用了这个:InternalCustomerFeignClient,需要配置 customer 服务的地址

customerService:

    serverUrl: ${customer\_internal\_url:http://127.0.0.1:8009}

channelServer:

    serverUrl: ${engage-channel\_internal\_url:localhost:9189}

# internal 服务的地址配置

internalService:

    serverUrl: ${internalapi\_internal\_url:http://internalapi.dmhub.cn}

基础服务地址配置变量,默认情况下也是不需要进行变更。如有新增内部接口需求,也是需要和部署人员进行变量声明同步。

以上配置中,提供了变量支持,如无特殊要求应保持命名一致性。如果为私部环境,对于新增的变量,应和部署人员确定好统一配置名。

Gradle 配置

脚手架使用 Gradle 项目构建工具进行管理,配置文件包含两个。

setting.gradle
  • 重新命名 rootProject.name 为自己的项目名。

pluginManagement {

      repositories {

          gradlePluginPortal()

          // xsio 仓库为 convertlab 私有仓库,SDK Gradle 插件相关 maven 依赖位于该仓库下

          maven {

              def branch = System.getProperty("branch") ?: "master"

              def repo = branch == "master" ? "xsio-test" : "xsio-jar"

              url "http://nexus.xsio.cn/repository/${repo}"

              allowInsecureProtocol true

              credentials {

                  username mavenUser

                  password mavenPassword

              }

          }

      }

}

// 重命名自己的项目名

rootProject.name = "sms-scaffold"

目前渠道SDK相关依赖发布在 xsio 仓库上,必须声明 xsio 私部maven仓库源

build.gradle
  • 调整 bootJar.archiveBaseName 应用名
...

plugins {

      id 'org.springframework.boot' version "${springBootVersion}"

      id 'io.spring.dependency-management' version "1.0.11.RELEASE"

      // convertlab 插件工具,引入该工具会自动进行maven 仓库和基础依赖管理

      id 'com.convertlab.convention.repositories-declaration' version "${convertlabGradleConventionVersion}"

      // convertlab 插件工具,引入该工具会自动进行maven 仓库和基础依赖管理

      id 'com.convertlab.convention.application' version "${convertlabGradleConventionVersion}"

      id "com.github.shalousun.smart-doc" version "2.4.2"

      id 'java'

}

...
bootJar {

      // 更改该产物包名,该产物包名,会在 Dockerfile 中被使用

      archiveBaseName = 'engage-sms'

      archiveVersion = 'latest'

}

...

项目重新执行 gradle clean build ,才能对项目配置进行生效。如在 intellij IDE 中,可通过 Build > Rebuild Project 进行重新构建 或 File > Invalidate Caches 重置项目缓存。

Dockerfile 配置

Dockerfile 负责对Spring 微服务进行 image 构建,根据 Spring application.yml name 配置,调整image 构建信息。

  • 调整 WAR 变量 ,需要和所打包的微服务产物包保持一致
  • 调整 SERVICE_NAME 变量微服务命名,一般和 application.yml name 配置一致
#目前项目基于 jdk8 环境,如有特殊运行环境要求,根据实际需要调整base image

FROM registry.cn-hangzhou.aliyuncs.com/clab-docker/alpine-oraclejdk8:latest

WORKDIR /opt/

# WAR 为 build.gradle 构建产物包名,需和gradle构建产物配置 bootJar.archiveBaeName (-latest 为 archiveVersion 后缀版本)保持一致

ENV WAR=sms-scaffold-latest.jar \

      ENV=test \

# SERVICE\_NAME 为运行时所使用微服务名

      SERVICE\_NAME=sms-scaffold \

# 运行环境的动态库PATH基础路径,如未有特殊引入其他动态库路径,不需要进行调整

      LD\_LIBRARY\_PATH=/lib:/usr/lib/gcc/x86\_64-alpine-linux-musl/6.4.0

COPY build/libs/${WAR} /opt/

RUN apk --no-cache add libstdc++6

CMD ["./start.sh"]

Applictaoin 配置

SDK 内置了 Spring 组件,因此在 SpringBoot Application 中需要进行声明。

  • 如调整自身渠道应用项目包名,则需要调整 渠道应用自身路径
  • 保留内置SDK组件声明的 Scaffold-Sdk 路径
@EnableCaching

@EnableFeignClients(

          basePackages = {

                  "com.convertlab.channel.*", // Scaffold-Sdk 路径

                  "com.convertlab.application.*", // 渠道应用自身路径

          }

)

@EnableEncryptableProperties

@SpringBootApplication

@ComponentScan(basePackages = {

          "com.convertlab.application.*",// 渠道应用自身路径

          "com.convertlab.channel.scaffold.sdk.*",// Scaffold-Sdk 路径

          "com.convertlab.spring.akka"// Message-Engine-Sdk 需要的配置

})

@MapperScan(value = {

          "com.convertlab.application.sms.mapper",

          "com.convertlab.channel.scaffold.sdk.mapper",// Scaffold-Sdk 路径

})

public class Application {

      public static void main(String[] args) {

          SpringApplication.run(Application.class, args);

      }

}

基础型开发

渠道应用目前支持开发内容包括:

  • 模板内容
  • 渠道发送
  • 渠道回执

模板内容

渠道应用所发送的内容管理一般会有一套自身的模板管理,渠道应用SDK支持对模板进行管理。

可通过实现 ExtChannelTemplateApi 接口对模板资源行为管理。

实现模板内容行为

ExtChannelTemplateApi 依赖于 Controller 组件能力,默认声明 /{channelType}/ext/templates 作为基础路径,在该接口中定义了模板创建、删除、更新动作。

  • 创建Controller组件类,并实现 ExtChannelTemplateApi 默认接口方法。
  • 实现对 ChannelTemplateDTO 模板内容的 createdeleteput 业务内容。

@RestController

public class ExtChannelTemplateController implements ExtChannelTemplateApi {

      // 创建模板行为

      // POST  /{channelType}/ext/templates

      @Override

      public ChannelTemplateDTO create(String channelType, String tunnelKey, ChannelTemplateDTO req) {

...

      }

      // 删除模板行为

      // DEL  /{channelType}/ext/templates/{id}

      @Override

      public Boolean delete(String channelType, String tunnelKey, Long id) {

...

      }

      // 更新模板行为

      // PUT /{channelType}/ext/templates/{id}

      @Override

      public Boolean update(String channelType, String tunnelKey, Long id, ChannelTemplateDTO req)  {

...

      }

}
行为接口定义(ExtChannelTemplateApi)
方法Path参数说明
createPOST /{channelType}/ext/templates

- channelType 渠道类型

- sms 邮件

- mms 彩信

- vms 超级短信

- app_push 推送

- email 邮件

- wechat_pub_account 微信公众号

- tunnelKey 通道id

- req 请求体

创建模板
deleteDEL /{channelType}/ext/templates/{id}

- channelType 渠道类型

- sms 邮件

- mms 彩信

- vms 超级短信

- app_push 推送

- email 邮件

- wechat_pub_account 微信公众号

- tunnelKey 通道id

- id 模板id

删除指定模板
updatePUT /{channelType}/ext/templates/{id}

- channelType 渠道类型

- sms 邮件

- mms 彩信

- vms 超级短信

- app_push 推送

- email 邮件

- wechat_pub_account 微信公众号

- tunnelKey 通道id

- id 模板id

- req 请求体

更新指定模板
模板对象定义(ChannelTemplateDTO)
属性类型说明
tenantIdLong租户 ID
uniqueIdLong唯一 id
resourceTypeStringconvertlab-ResourceType
idLongid
channelTypeString渠道类型
messagingTypeString消息传递类型
messagingPurposeString触达目的
vendorCodeString供应商编码
tunnelIdLong通道 id
channelInstanceIdLong渠道示例 id
nameString名称
descString描述信息
textString内容文本,由 type 决定;例如:签名
statusString状态 draft-草稿 valid-有效的 StatusEnum
auditStatusString状态:success、processing、failure
auditTimeInstant审核时间
approvalIdLong审批 id
approvalStatusString审批状态
approvalTimeInstant审批时间
deletedInteger逻辑删除 :0-正常 1-删除
ownerIdLong归属者ID,数据权限相关
ownerNameString归属者姓名,数据权限相关
extFieldListList<ChannelTemplateExtFieldDTO>拓展字段
扩展字段定义(ChannelTemplateExtFieldDTO)
属性类型说明
idLongid
tenantIdLong租户id
nameString字段名称
typeString字段类型:long、int、str、text、datetime
longValueLonglong 字段值
intValueIntegerint 字段值
strValueStringstr 字段值
textValueStringtext 字段值
datetimeValueInstantdatetime 字段值

渠道发送

渠道发送目前不区分群发、单发,通过统一Sender实现。

脚手架中提供了两种组件用于支持不同供应商实现

  • 供应商 Http API 适配:可通过 AbstractHttpChannelSender 实现对 供应商 提供的 API 进行 HTTP 请求,Request、Response 实际实现类型为 ChannelHttpRequestChannelHttpResponse,可以通过对ChannelHttpRequest构造所需要的请求内容。
  • 供应商 Http Client 适配(不局限Http协议):可通过 AbstractScaffoldChannelSender 实现对 供应商 提供的 Client 再包装, Request、Respopnse 为自定义数据结构,需要实现自身 realRequest 、mockRequest 的行为,对之前自定义的 Request、Response 数据的使用。
通过供应商 Http API 发送
  • 创建Component组件类, 并继承AbstractHttpChannelSender
  • 配置 Sender 所需要 ScaffoldConfigAbstractEventService
  • 实现 buildRequestbuildResponseDatapredicateResponse 对供应商请求和响应的处理。
实现参考
@Component
public class EMaySMSChannelSender extends AbstractHttpChannelSender {
    @Resource
    private SMSEventService smsEventService;
    ...
    // 提供匹配的渠道应用事件记录事件服务,AbstractEventService 提供了几种渠道的基础实现 SMSEventService
    // MMSEventService 等,也可根据事件记录需要实现 AbstractEventService
    @Override
    public AbstractEventService<?> getEventService() {
        return this.smsEventService;
    }
 
    ...
    /**
     * 初始化ScaffoldConfig PluginCode、VendorCode配置
     */
    @Override
    public ScaffoldConfigPayload initScaffoldConfigPayload() {
        ScaffoldConfigPayload scaffoldConfigPayload = super.initScaffoldConfigPayload();
        // 设置 ScaffoldConfig 信息
        scaffoldConfigPayload.setPluginCode("engage-sms");
        scaffoldConfigPayload.setConfigMapVendorCode("emay-sms");
        return scaffoldConfigPayload;
    }
...
    // 实现供应商请求体构建逻辑,该逻辑中主要实现  ScaffoldChannelMessageDTO 请求信息验证,并生成新的请求信息 ValidVendorRequestDTO。
    @Override
    public ValidVendorRequestDTO<ChannelHttpRequest> buildRequest(ScaffoldChannelMessageDTO scaffoldChannelMessage) {
       ....
       // scaffoldChannelMessage rows 需要维护
       // 更新有效 row,只放有效的 row
       scaffoldChannelMessage.setRows(validRowsDataList);
       return new ValidVendorRequestDTO<>(
                        // 通过 ChannelHttpRequest 构建请求信息
                        ChannelHttpRequest.POST(endpoint + "/vms/message/send").withUrlEncodedForm(queryMap).withHeaders(headerMap),
                        //  scaffoldChannelMessage 中验证失败导致无法作为 ChannelHttpRequest 合法请求的数据
                        invalidRowsDataList,
                        null
           );
     }
    ...
    // 实现对发送后Response的响应处理,如响应信息被供应商所加密,需要通过该接口进行解密,返回正确的response
    @Override
    @SneakyThrows
    public String buildResponseData(ScaffoldChannelMessageDTO scaffoldChannelMessage, ChannelHttpResponse response) {
       ...
       // 处理reponse 可能的结果校验、解密、解压等内容,断言返回res
       return res;
    }
    ...
 
    // 对 buildResponseData  结果进行校验,如果当前结果视为成功应该返回 true,否则 false,事件将根据该状态判断是否发送成功
    @Override
    public boolean predicateResponse(String response, ScaffoldChannelMessageDTO scaffoldChannelMessage) {
       ...
    }
    
}
方法实现说明
方法参数返回值说明
initScaffoldConfigPayload- ScaffoldConfigPayload:ScaffoldConfig 配置根据插件Code 、供应商 Code进行配置隔离。- 使用 super.initScaffoldConfigPayload() 对 ScaffoldConfig 的插件Code 、供应商 Code 进行配置。
getEventService- AbstractEventService:渠道事件实现类,不同的渠道需要不同的事件数据封装。

- 返回一个 AbstractEventService 事件服务实现对象。目前SDK提供了几种基础实现,根据所开发的渠道类型选择:

- 短信:SMSEventService

- 超级短信:VMSEventService

- 彩信:MMSEventService

- 邮件:SendCloudEmailEventService

- App推送:AppPushEventService

buildRequest- ScaffoldChannelMessageDTO scaffoldChannelMessage:需要被发送的渠道消息,通过 getRows() 获取待发送数据。- ValidVendorRequestDTO:包含两个内容,需要回传请求代理和被排除发送的数据

实现发送请求构建。

- 验证 scaffoldChannelMessage.getRows() 待发送数据的合法性,如果存在非法数据,需要从 scaffoldChannelMessage.getRows() 移出并记录,保证 scaffoldChannelMessage rows 中的数据是合法数据

- 对 scaffoldChannelMessage 合法待发送数据进行发送数据格式包装,生成 ChannelHttpRequest 请求对象

- 将ChannelHttpRequest 请求对象和不发送的rows数据集合作为参数,创建并返回ValidVendorRequestDTO

buildResponseData

- ScaffoldChannelMessageDTO scaffoldChannelMessage:被发送的message数据集。

- ChannelHttpResponse response:供应商返回的 reponse

- String:reponse消息

实现发送结果解析。

- 解析供应商返回的 response 信息,根据供应商约定,对response 进行解析处理。

- 返回一个response 信息。

predicateResponse

- String response:buildResponeData 返回的解析后的 response 信息

- ScaffoldChannelMessageDTO scaffoldChannelMessage:buildResponseData 中 scaffoldChannelMessage参数

- boolean:断言发送状态,事件将更加该状态进行响应,true 为成功。

实现发送结果断言。

- 根据 buildResponseData 处理后的response信息进行此次发送结果的断言。当返回值为 true ,则此次发送成功。

mockRequest- channelHttpReuest:Http 客户端对象,可以使用该对象进行真实请求。- ChannelHttpResponse:返回信息

实现Mock请求执行。

如果需要实现mock场景,可以实现该方法,默认 

AbstractHttpChannelSender 没实现该场景,需要配合  isMock 场景断言实现使用

其他参数定义说明

ScaffoldChannelMessageDTO 是Sender 执行发生过程中始终传递的上下资料,根据该对象可提取需要的上下文信息进行响应。

属性类型说明
rowsList<Map<String, Object>>需要发送的合法数据,包含变量映射信息
tenantIdLong租户id
batchIdString批次id
messagingTypeString触点
channelTypeString渠道类型
channelPluginInstanceInfoChannelPluginInstanceInfoDTO渠道应用信息,包含应用id、渠道类型
templateInfoTemplateInfoDTO发送模板信息
predicateInfoStringtext 字段值
datetimeValueInstantdatetime 字段值
通过供应商自定义Http Client发送
  • 创建Component组件类, 并实现 AbstractScaffoldChannelSender
  • 配置 Sender 所需要 ScaffoldConfigAbstractEventService
  • 实现 buildRequestresponseToEvents、realRequest 对供应商请求和响应的处理。
实现参考
@Service
public class AliYunSDKChannelSender extends AbstractScaffoldChannelSender<SendBatchSmsRequest, SendBatchSmsResponse>{
    ...
    // 供应商提供的 Http Client 工具
    @Resource
    private com.aliyun.dysmsapi20170525.Client client;
 
    @Resource
    private SMSEventService smsEventService;
    ...
    /**
     * 初始化ScaffoldConfig PluginCode、VendorCode配置
     */
    @Override
    public ScaffoldConfigPayload initScaffoldConfigPayload() {
        ScaffoldConfigPayload scaffoldConfigPayload = super.initScaffoldConfigPayload();
        scaffoldConfigPayload.setPluginCode(ScaffoldConsts.SMSPlugin.CODE);
        scaffoldConfigPayload.setConfigMapVendorCode(ScaffoldConsts.SMSPlugin.VendorCode.ALI_YUN);
        return scaffoldConfigPayload;
    }
    ...
    // 提供匹配的渠道应用事件记录事件服务,AbstractEventService 提供了几种渠道的基础实现 SMSEventService
    // MMSEventService 等,也可根据事件记录需要实现 AbstractEventService
    @Override
    public AbstractEventService<?> getEventService() {
        return this.smsEventService;
    }
    ...
 
    // 根据 ScaffoldChannelMessageDTO 所提供的 rows 期望发送的数据,对有效数据进行过滤,返回对应请求体的构造
    // 到ValidVendorRequestDTO 中
    @Override
    public ValidVendorRequestDTO<SendBatchSmsRequest> buildRequest(ScaffoldChannelMessageDTO scaffoldChannelMessage){
        ...
        // scaffoldChannelMessage rows 需要维护
        // 更新有效 row,只放有效的 row
        scaffoldChannelMessage.setRows(validRowsDataList);
        // SendBatchSmsRequest 为自定义封装的请求信息,将必要的请求信息进行设置,并设置到 ValidVendorRequestDTO 中,进行传递。
        return new ValidVendorRequestDTO<>(new SendBatchSmsRequest()
                .setTemplateCode(templateCode)
                .setSignNameJson(JacksonUtils.toJSONString(signNameList))
                .setPhoneNumberJson(JacksonUtils.toJSONString(phoneNumberList))
                .setTemplateParamJson(JacksonUtils.toJSONString(templateParamList)),
                //  scaffoldChannelMessage 中验证失败导致无法作为 ChannelHttpRequest 合法请求的数据
                invalidRowsDataList,
                null);        
    }
 
    ...
     
    // 由于 Request、Response 为自定义数据体,需要开发者自己根据上下文参数,包装 PartialEventResultDTO 事件对象,用于客户事件
    // 跟踪
    @Override
    public PartialEventResultDTO responseToEvents(SendBatchSmsResponse providerRes,
                                                  SendBatchSmsRequest providerReq,
                                                  ScaffoldChannelMessageDTO scaffoldChannelMessage) {
        ...
        List<Map<String, Object>> successDataList = new ArrayList<>();
        // 遍历 scaffoldChannelMessage.getRows() 每条数据,根据客户产生对每个发送对象对应的 Map<String, Object> 事件对象,
        // 添加到对应成功或失败集合对象中
        for (Map<String, Object> row : scaffoldChannelMessage.getRows()) {
            row.put(TRACK_KEY, bizId);
            Map<String, Object> tmpStoreData = getEventService().buildEventMap(acquireScaffoldConfig(), SingleScaffoldChannelMessageDTO.from(scaffoldChannelMessage,
                    SimpleScaffoldChannelMessageRowDTO.build(scaffoldChannelMessage.getBatchId(),mobile,row)
            ), acquireEventConfig().getEventMeta().getSent());
 
            successDataList.add(tmpStoreData);
        }
        ...
        // 返回事件包装
        return new PartialEventResultDTO(
                // 成功事件集合
                successDataList, 
                // 失败事件集合 
                new ArrayList<>(),
                null
    }
 
    ...
    // 实现使用供应商自己 Http Client 发送 request 信息,并返回自定义实现的 response(SendBatchSmsResponse)
    @Override
    public SendBatchSmsResponse realRequest(SendBatchSmsRequest providerReq) {
         return client.sendBatchSms(providerReq);
    }
    ...
}
方法实现说明

以下参数说明提到的 providerResproviderReqAbstractScaffoldChannelSender<providerReq, providerRes> 所对应的泛型声明,在以上实现参考例子中分别对应于 SendBatchSmsRequestSendBatchSmsResponse 类型。

方法参数返回值说明
initScaffoldConfigPayload- ScaffoldConfigPayload:ScaffoldConfig 配置信息,每个单独应用、供应商可单独设置的配置信息。- 使用 super.initScaffoldConfigPayload() 对 ScaffoldConfig 的插件Code 、供应商 Code 进行配置。
getEventService- AbstractEventService:渠道事件实现类,不同的渠道需要不同的事件数据封装。

- 返回一个 AbstractEventService 事件服务实现对象。目前SDK提供了几种基础实现,根据所开发的渠道类型选择:

- 短信:SMSEventService

- 超级短信:VMSEventService

- 彩信:MMSEventService

- 邮件:SendCloudEmailEventService

- App推送:AppPushEventService

buildRequest- ScaffoldChannelMessageDTO scaffoldChannelMessage:需要被发送的渠道消息,通过 getRows() 获取待发送数据。- ValidVendorRequestDTO:包含两个内容,需要回传请求代理和被排除发送的数据

实现发送请求构建。

- 验证 scaffoldChannelMessage.getRows() 待发送数据的合法性,如果存在非法数据,需要从 scaffoldChannelMessage.getRows() 移出并记录,保证 scaffoldChannelMessage rows 中的数据是合法数据

- 对 scaffoldChannelMessage 合法待发送数据进行发送数据格式包装,生成 ChannelHttpRequest 请求对象

- 将ChannelHttpRequest 请求对象和不发送的rows数据集合作为参数,创建并返回ValidVendorRequestDTO。

responseToEvents

- providerRes:根据泛型定义的自定义 Response

- providerReq:根据泛型定义的自定义 Request

- scaffoldChannelMessage:渠道发送数据

- PartialEventResultDTO:事件集包装对象,包含 成功、失败。

实现响应结果事件处理。

- 根据 scaffoldChannelMessage.getRows() 发送结果,生成对应客户事件

- 根据客户成功事件集合客户失败事件集合创建 PartialEventResultDTO 事件结果包装对象。返回该对象结果

realRequest- providerReq:根据泛型定义的自定义 Request- ProviderRes:该返回值根据 AbstractScaffoldChannelSender 泛型定义 ProviderRes 为准。

实现发送请求执行。

- 使用供应商提供的 Http Client 工具,实现 buildRequest 的发送请求,返回供应商发送结果。

mockRequest- channelHttpReuest:Http 客户端对象,可以使用该对象进行真实请求。- ChannelHttpResponse:返回信息

实现Mock请求执行。

如果需要实现mock场景,可以实现该方法,默认

AbstractHttpChannelSender 没实现该场景,需要配合 isMock 场景断言实现使用

渠道回执

渠道回执一般用来接收供应商的推送或Callback 信息,在渠道应用需要实现对应供应商回调处理。

回执配置

目前对于供应商的回调配置提供以下几种路径配置格式,需要配置到对应供应商平台

  • {域名}/nc/{服务名}/callback/{渠道类型}/{供应商编码}
  • {域名}/nc/{服务名}/e-callback/{渠道类型}/{供应商编码}
  • {域名}/nc/{服务名}/callback/{渠道类型}/{供应商编码}/{资源编码}

不同的回调路径对应于 BaseHttpCallbackController 中不同的响应方法,根据响应方法所能支持的处理方式,进行对应配置。

回执响应实现
  • 创建Controller组件类, 并继承实现 BaseHttpCallbackController
  • 实现对应路径回调 ,为每个回调提供对应的实现 Reporter 子类BeanName 作为返回值。
回调方法说明
方法Path参数说明
httpCallbackHandleBeanName{域名}/nc/{服务名}/callback/{渠道类型}/{供应商编码}

request:HttpServletRequest 请求体信息

channelType:对应于{渠道类型}

vendorKey:对应于{供应商编码}

httpEncryptionCallbackHandleBeanName{域名}/nc/{服务名}/e-callback/{渠道类型}/{供应商编码} 

request:HttpServletRequest 请求体信息

channelType:对应于{渠道类型}

vendorKey:对应于{供应商编码}

区别于 httpCallbackHandleBeanName 的响应,e-callback 主要用于处理加密回执
httpCallbackHandleBeanName{域名}/nc/{服务名}/callback/{渠道类型}/{供应商编码}/{资源编码} 

request:HttpServletRequest 请求体信息

channelType:对应于{渠道类型}

vendorKey:对应于{供应商编码}

resourceType:对应于{资源编码}

增加 资源编码维度 响应内容
实现参考
@RestController
public class HttpReportCallbackController extends BaseHttpCallbackController {
    // {域名}/nc/{服务名}/callback/{渠道类型}/{供应商编码}
    @Override
    public String httpCallbackHandleBeanName(HttpServletRequest request, String channelType, String vendorKey) {
      ...
      // 返回具体实现该回执处理的Reporter 组件名
      return "eMaySMSPushModelHttpMethodReporter"
    }
 
 
    // {域名}/nc/{服务名}/e-callback/{渠道类型}/{供应商编码}
    // 该方法和 {@link  HttpReportCallbackController#httpCallbackHandleBeanName(HttpServletRequest, String, String)} 的区别仅仅在于供应商回执
    // 有时候会区分加密和非加密的情况,通过额外的路径配置来支持响应。
    @Override
    public String httpEncryptionCallbackHandleBeanName(HttpServletRequest request, String channelType, String vendorKey) {
      ...
      // 返回具体实现该回执处理的Reporter 组件名
       return "beanName"
    }
 
    // {域名}/nc/{服务名}/callback/{渠道类型}/{供应商编码}/{资源编码}
    // 相较于之前两个方法,额外提供了资源编码响应
    @Override
    public String httpCallbackHandleBeanName(HttpServletRequest request, String channelType, String vendorKey, String resourceType) {              
      ...
      // 返回具体实现该回执处理的Reporter 组件名
       return "beanName" 
    }
}
实现回执处理

回执目前支持 HttpServletRequest 数据源响应。

  • 创建 Component 组件类并指定 value属性作为组件名,该值即为BaseHttpCallbackController 方法指定返回值。
  • 继承 AbstractPushModeReporter<SourceData, TargetData, Event, TempStoreData>,并将 SourcetData 泛型数据源类型指定为 HttpServletRequest,TargetData 为供应商回执数据Bean,Event 为回执相关客户事件基类Bean,TempStoreData 为回执相关的业务数据Bean
  • 实现 initScaffoldConfigPayload 所需要 ScaffoldConfig 配置,设置 pluginCode 和 configMapVendorCode
  • 实现 parse、deserializationForPeakClippingConsumesingleWar、realExecute 方法。
  • 根据是否需要开启削峰,选择重写 getPeakClippingTopci(),如果不为空,则视为开启kafka削峰能力
  • 如果需要开启削峰,需要为 peakClippingConsume 方法声明 @KafkaListener 注解,并设置 groupId和对应topic等消费者属性。所设置的topic,需要和 getPeakClippingTopcic()一致
实现参考
@Component(value = "eMaySMSPushModelHttpMethodReporter")
public class EMaySMSPushModelHttpMethodReporter extends AbstractPushModeReporter<HttpServletRequest, ReportDTO, Object, SingleScaffoldChannelMessageDTO> {
    ...
    @Value("${scaffold.globalConfig.kafkaConfig.peakClippingTopic.demoSmsSendReport}")
    private String eMaySendReportPeakClippingTopic;
    ...
     // 与 Sender一样,需要声明 ScaffoldConfig 配置信息
    @Override
    public ScaffoldConfigPayload initScaffoldConfigPayload() {
      ...
    }
    ...
 
    // 解析供应商回调数据为 ReportDTO,ReportDTO 将做渠道回调数据返回
    @Override
    public List<ReportDTO> parse(HttpServletRequest request) {
     ...
    }
 
    ...
    // 无论是否开启削峰,都需要实现 Kafka 消息数据反序列化
    @Override
    public List<List<ReportDTO>> deserializationForPeakClippingConsume(ConsumerRecord<String, String> consumerRecord)      {
        ...
    }
 
    ...
    // 根据回执数据,为每个客户单独创建对应客户事件(BaseEventDTO)
    // 事件对象基于 BaseEventDTO 实现,该例中 SMSMoEventDTO 为 BaseEventDTO 的短信相关事件子类
    @Override
    public Object singleWrap(SingleScaffoldChannelMessageDTO tempStoreData, ReportDTO targetData) {
            ...
            // 通过内部接口,根据租户和手机号查询对应客户id
            Long customerId = customerInternalApiServiceImpl.getCustomerId(tenantId, mobile);
            ...
            // 事件元数据获取
            ScaffoldConfig scaffoldConfig = acquireScaffoldConfig();
            String eventGroup = scaffoldConfig.getEventConfig().getEventGroup();
            EventMeta event = scaffoldConfig.getEventConfig().getEventMeta();
 
            // 当前为短信回执,当触发成功,为该客户创建短信回复事件
            SMSMoEventDTO moEventDTO = new SMSMoEventDTO();
            moEventDTO.setTenantId(tenantId);
            moEventDTO.setChannelType(ChannelTypeEnum.SMS.getCode());
            moEventDTO.setEvent(event.getReply());
            moEventDTO.setEventGroup(eventGroup);
            moEventDTO.setCustomerId(customerId);
            ...
            return moEventDTO;
    }
 
    // 回执调用,该方法需要根据callBackResultDTO 检索出之前 SingleScaffoldChannelMessageDTO 并返回,该返回值将提供给 singleWrap 接收处理。
    Override
    public SingleScaffoldChannelMessageDTO realExecute(ReportDTO callBackResultDTO) {
        ...
    }
  
    // 返回削峰 topic ,如果需要开启,则和 peakClippingConsume KafkaListener所声明的 topic 一致,否则为空,关闭削峰
    @Override
    public String getPeakClippingTopic() {
        return eMaySendReportPeakClippingTopic;
    }
 
    // peakClippingConsume 为实际削峰方法,需要通过 @KafkaListener 静态注册kafka消费者
    // 注意:所声明削峰topic需要和已启用的 getPeakClippingTopic 保持一致,该处引用yml声明的变量
    @Override
    @KafkaListener(groupId = "${spring.application.name}", topics = "${scaffold.globalConfig.kafkaConfig.peakClippingTopic.demoSmsSendReport}")
    public void peakClippingConsume(ConsumerRecord<String, String> consumerRecord) {
        super.peakClippingConsume(consumerRecord);
    }
}
方法实现说明

以下参数说明提到的 SourceData、TempStoreData、TargetData、Event 为 AbstractPushModeReporter<SourceData, TargetData, Event, TempStoreData> 所对应的泛型声明,在以上实现参考例子中分别对应于 HttpServletRequest、SingleScaffoldChannelMessageDTO、ReportDTO、Object 类型。

方法参数返回值说明
initScaffoldConfigPayload- ScaffoldConfigPayload:ScaffoldConfig 配置信息,每个单独应用、供应商可单独设置的配置信息。- 使用 super.initScaffoldConfigPayload() 对 ScaffoldConfig 的插件Code 、供应商 Code 进行配置。
parse- HttpServletRequest request:回执数据请求信息- TargetData : 返回值为 AbstractPushModeReporter 泛型数据,TargetData 可根据实际供应商回调数据自定义 Bean 。以上例子中,实现了 ReportDTO 来解析对应的供应商数据。- 实现将供应商回执原始数据转换为渠道应用相关的业务数据。
deserializationForPeakClippingConsume

- ConsumerRecord<String, String> consumerRecord:

封装有 List<TargetData>

的 Kafka 消费数据

- List<List<TargetData>>

:反序列为之前相关回执数据信息

实现回执数据从Kafka 消费数据中的反序列化

singleWrap

- TempStoreData:与回执数据相关的业务数据

- TargetData:回执数据

- Event:回执相关事件

实现根据提供回执上下文信息,生成对应客户事件对象

realExecute- TargetData:回执数据- TempStoreData:返回和 TargetData 回执数据相关的业务相关数据,如 在短信发送场景中,则短信回执的业务数据可以为,之前发送给该客户的消息数据实现回执数据和之前相关数据关联,并返回相关业务数据。
getPeakClippingTopic- String:返回削峰 topic如果不为空则会开启削峰,该topic如果设置了,应该和 peakClippingConsume 所声明的 @KafkaListener topic 一致
peakClippingConsume- ConsumerRecord<String, String> consumerRecord:削峰topic 的消息该方法为实际Kafka 削峰消费方法,不需要重写,但如果希望开启削峰,则需要通过 @KafkaListener 声明 groupId 和 削峰 topic ,完成消费者注册。