应用注册
渠道应用需要登录到 Convertlab开放平台 进行注册。
进入 Convertlab开放平台 后,点击右上角 渠道应用开发平台,进入 渠道管理。
在 渠道管理 点击右上角 新建,选择所需要开发的应用类型,如 基础型。
以下为不同渠道应用类型说明。
应用类型 | 说明 | 版本开放支持 |
---|---|---|
基础型 | 支持基础、专一触达的渠道注册、供应商及后续对应通道开通配置(如短信、邮件、彩信等) | 支持 |
平台型 | 支持对接外部营销生态应用,完善安装和管理(营销应用如抖音、支付宝;微信生态应用如企微、微信公众号、微信小程序等) | 暂不提供 |
扩展型 | 支持App应用内触达功能对接,以及对接一方系统等内部需求实现(如Apppush、App站内信;如一方crm等) | 暂不提供 |
新建基础型渠道
新建选择对应渠道应用类型后,会进入不同的应用配置内容,每种应用所配置内容有部分差异。
目前在配置流程中,主要需要经过三个流程配置。
- 渠道基础信息
- 渠道开放配置
- 渠道应用接入点
渠道基础信息
- 配置渠道开发内容和基础信息
- 配置渠道应用支持的供应商信息
配置项说明
配置项 | 配置值说明 | 必填 |
---|---|---|
基础型渠道选择 | 根据自身需求,选择需要开发的渠道应用类型,目前提供以下几种选择 短信 彩信 超级短信 邮件 | 是 |
渠道触点供应商配置 | 配置应用所支持的渠道供应商。详细配置见 新建渠道商。 | 是 |
渠道名称 | 应用名,该名称将会在可展示的菜单项中进行使用展示。 | 是 |
渠道ID | 应用唯一ID,区分于其他应用 | 是 |
渠道图标地址 | 应用展示图标 | 是 |
渠道简介 | 应用说明信息 | 否 |
主页地址 | 应用说明网站 | 否 |
渠道应用开发,需要对所使用的国内供应商信息进行注册。
当选择具体需要开发的渠道后,将出现供应商配置。如之前未配置过供应商,可新建供应商,进行新供应商配置。
配置项说明
配置项 | 配置值说明 | 必填 |
---|---|---|
供应商显示名称 | 渠道商命名,如 短信亿美渠道。 | 是 |
供应商ID | 将根据供应商显示名称自动生成。 | 是 |
供应商每批次最大发送个数限制 | 根据供应商所提供的说明配置,用于在渠道发送时最大流速控制。 | 是 |
渠道开发配置
该步骤需要开发者根据应用运行要求进行配置。
配置项说明
配置项 | 配置值说明 | 必填 |
---|---|---|
渠道应用调用DM Hub接口获取租户数据方式 | 选择所DM Hub数据调用方式 - openApi
- 内部接口
| 是 |
DM Hub统一调度所有对外渠道触达消息,渠道应用接受的调度方式(均有SDK支持) | 选择渠道发送的方式 - 同步(HTTP)
- 异步(Kafka)
| 是 |
渠道应用插入点配置
渠道应用插入点主要对应用的一些扩展配置,目前支持以下能力能力
- 编辑器 支持对编辑器工具进行替换,如短信编辑器。
- 自动流 支持新增自动流动作
- 事件 支持客户自定义事件
编辑器配置
系统编辑器
系统编辑器目前支持扩展元数据字段,以提供在编辑器中使用,配置格式如下:
[
{
"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": "正数为增加,负数为扣减"
}
]
自定义编辑器(暂未开放)
自定义编辑器目前暂未正式开放
自动流配置
- 支持系统自动流默认动作名调整
- 自定义自动流动作(待验证)
如果使用系统默认自动流,可以对自动流名称进行修改
如需自定义自动流,则需要提供对应的动作渲染 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)"
}
属性 | 说明 | |
---|---|---|
动作名称 | 自动流程中action的名称,建议尽可能短,否则排版会有问题。 | |
动作Id | 前缀为应用ID+”action”,可以填入字母和数字。 | |
动作展示图标 | 自动流程action中显示的图标对应的css样式class。 | |
动作渲染URL | 在自动流程中编辑该action时的编辑页面,以iframe的形式嵌入到产品中。这里需要填入自动流插件脚本地址,具体脚本开发参考自动流开发。 |
自定义事件配置
自定义事件配置用于扩充渠道应用中上报的客户事件。
通过上传客户自定义事件定义文件来进行配置。
应用开发
安装环境
环境要求
- 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 模板内容的 create、delete、put 业务内容。
@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 | 参数 | 说明 |
---|---|---|---|
create | POST /{channelType}/ext/templates | - channelType 渠道类型 - sms 邮件 - mms 彩信 - vms 超级短信 - app_push 推送 - email 邮件 - wechat_pub_account 微信公众号 - tunnelKey 通道id - req 请求体 | 创建模板 |
delete | DEL /{channelType}/ext/templates/{id} | - channelType 渠道类型 - sms 邮件 - mms 彩信 - vms 超级短信 - app_push 推送 - email 邮件 - wechat_pub_account 微信公众号 - tunnelKey 通道id - id 模板id | 删除指定模板 |
update | PUT /{channelType}/ext/templates/{id} | - channelType 渠道类型 - sms 邮件 - mms 彩信 - vms 超级短信 - app_push 推送 - email 邮件 - wechat_pub_account 微信公众号 - tunnelKey 通道id - id 模板id - req 请求体 | 更新指定模板 |
模板对象定义(ChannelTemplateDTO)
属性 | 类型 | 说明 |
---|---|---|
tenantId | Long | 租户 ID |
uniqueId | Long | 唯一 id |
resourceType | String | convertlab-ResourceType |
id | Long | id |
channelType | String | 渠道类型 |
messagingType | String | 消息传递类型 |
messagingPurpose | String | 触达目的 |
vendorCode | String | 供应商编码 |
tunnelId | Long | 通道 id |
channelInstanceId | Long | 渠道示例 id |
name | String | 名称 |
desc | String | 描述信息 |
text | String | 内容文本,由 type 决定;例如:签名 |
status | String | 状态 draft-草稿 valid-有效的 StatusEnum |
auditStatus | String | 状态:success、processing、failure |
auditTime | Instant | 审核时间 |
approvalId | Long | 审批 id |
approvalStatus | String | 审批状态 |
approvalTime | Instant | 审批时间 |
deleted | Integer | 逻辑删除 :0-正常 1-删除 |
ownerId | Long | 归属者ID,数据权限相关 |
ownerName | String | 归属者姓名,数据权限相关 |
extFieldList | List<ChannelTemplateExtFieldDTO> | 拓展字段 |
扩展字段定义(ChannelTemplateExtFieldDTO)
属性 | 类型 | 说明 |
---|---|---|
id | Long | id |
tenantId | Long | 租户id |
name | String | 字段名称 |
type | String | 字段类型:long、int、str、text、datetime |
longValue | Long | long 字段值 |
intValue | Integer | int 字段值 |
strValue | String | str 字段值 |
textValue | String | text 字段值 |
datetimeValue | Instant | datetime 字段值 |
渠道发送
渠道发送目前不区分群发、单发,通过统一Sender实现。
脚手架中提供了两种组件用于支持不同供应商实现
- 供应商 Http API 适配:可通过 AbstractHttpChannelSender 实现对 供应商 提供的 API 进行 HTTP 请求,Request、Response 实际实现类型为 ChannelHttpRequest、ChannelHttpResponse,可以通过对ChannelHttpRequest构造所需要的请求内容。
- 供应商 Http Client 适配(不局限Http协议):可通过 AbstractScaffoldChannelSender 实现对 供应商 提供的 Client 再包装, Request、Respopnse 为自定义数据结构,需要实现自身 realRequest 、mockRequest 的行为,对之前自定义的 Request、Response 数据的使用。
通过供应商 Http API 发送
- 创建Component组件类, 并继承AbstractHttpChannelSender 。
- 配置 Sender 所需要 ScaffoldConfig、AbstractEventService。
- 实现 buildRequest、buildResponseData 、predicateResponse 对供应商请求和响应的处理。
实现参考
@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 执行发生过程中始终传递的上下资料,根据该对象可提取需要的上下文信息进行响应。
属性 | 类型 | 说明 |
---|---|---|
rows | List<Map<String, Object>> | 需要发送的合法数据,包含变量映射信息 |
tenantId | Long | 租户id |
batchId | String | 批次id |
messagingType | String | 触点 |
channelType | String | 渠道类型 |
channelPluginInstanceInfo | ChannelPluginInstanceInfoDTO | 渠道应用信息,包含应用id、渠道类型 |
templateInfo | TemplateInfoDTO | 发送模板信息 |
predicateInfo | String | text 字段值 |
datetimeValue | Instant | datetime 字段值 |
通过供应商自定义Http Client发送
- 创建Component组件类, 并实现 AbstractScaffoldChannelSender 。
- 配置 Sender 所需要 ScaffoldConfig、AbstractEventService。
- 实现 buildRequest、responseToEvents、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);
}
...
}
方法实现说明
以下参数说明提到的 providerRes、providerReq为 AbstractScaffoldChannelSender<providerReq, providerRes> 所对应的泛型声明,在以上实现参考例子中分别对应于 SendBatchSmsRequest、SendBatchSmsResponse 类型。
方法 | 参数 | 返回值 | 说明 |
---|---|---|---|
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、deserializationForPeakClippingConsume、singleWar、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 ,完成消费者注册。 |