# 消息通道集成
# 功能说明
消息通道主要是用于将平台内的消息发送到外部渠道,如钉钉、微信等,目前平台的消息通道功能主要是流程在使用(如超时消息、催办消息等),同时平台提供API供业务自身集成,将消息以自定义的格式,最终发送到目标通道。
消息通道的具体扩展、流程中具体配置使用说明、以及开放的集成接口等,请参考消息中心使用说明。
# 消息通道配置
对于消息通道,产品要求企业微信、钉钉和飞书配置文件中只能开启一个,不能同时配置enabled=true
# 邮件通道
消息中心发送邮件消息时,需要在主配置文件application.properties文件中配置邮件相关属性
注意:发送邮件消息不支持附件功能。
afc.email.enabled=true
#配置smtp服务器地址
afc.email.account.host=smtp.qq.com
#配置smtp服务端口号
afc.email.account.port=465
#设置需要用户名密码验证
afc.email.account.auth=true
#设置邮箱地址
afc.email.account.from=
#设置邮箱用户名,一般为域名前的部分
afc.email.account.user=
#设置用户登陆密码,使用qq邮箱时填写qq邮箱单独生成的授权码
afc.email.account.pass=
#是否使用 STARTTLS安全连接
afc.email.account.starttls-enable=true
#是否使用 SSL安全连接
afc.email.account.ssl-enable=true
#指定实现javax.net.SocketFactory接口的类的名称
afc.email.account.socket-factory-class=javax.net.ssl.SSLSocketFactory
#如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类
afc.email.account.socket-factory-fallback=true
#指定的端口连接到在使用指定的套接字工厂
afc.email.account.socket-factory-port=465
#设置SMTP超时时长,单位毫秒,缺省值不超时
afc.email.account.timeout=0
#设置Socket连接超时值,单位毫秒,缺省值不超时
afc.email.account.connection-timeout=0
#消息中心邮件发送失败重试次数 默认0
message.push.sender.channel.email.fail-retry-times=0
#消息中心邮件发送最大线程数 默认10
message.push.sender.channel.email.thread-pool.max-pool-size=10
# 企业微信通道
消息中心发送企业微信消息时,需要在主配置文件application.properties文件中配置企业微信相关属性
#三方消息企业微信发送开关
afc.third-party.wechat.enable=true
#企业微信平台-我的企业-企业信息页面底部【企业ID】
afc.third-party.wechat.crop-id=
#企业微信平台-应用管理-创建的应用中AgentID
afc.third-party.wechat.agent-id=
#企业微信平台-应用管理-创建的应用中Secret
afc.third-party.wechat.secret=
#消息中心企业微信发送失败重试次数 默认0
message.push.sender.channel.wecom.fail-retry-times=0
#消息中心企业微信发送最大线程数 默认10
message.push.sender.channel.wecom.thread-pool.max-pool-size=10
#消息中心企业微信发送消息是否使用员工编码作为userId进行消息发送,默认值是false不使用
message.push.sender.channel.wecom.use-user-id=false
注意:发送markdowm消息时,如果需要包含任务处理链接,需要在链接url中拼接thirdPartyType=weixin参数才能正常跳转任务处理页面,同时markdown语法需参考企业微信文档 (opens new window)markdown消息类型。
消息模板例子:
您的会议室已经预定,稍后会同步到`邮箱` \n>**事项详情** \n>事 项:<font color=\"info\">开会</font> \n>组织者:@miglioguan \n>参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang \n> \n>会议室:<font color=\"info\">广州TIT 1楼 301</font> \n>日 期:<font color=\"warning\">2018年5月18日</font> \n>时 间:<font color=\"comment\">上午9:00-11:00</font> \n> \n>请准时参加会议。 \n> \n>如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)
只要符合markdown的语法即可。
# 钉钉通道
消息中心发送钉钉消息时,需要在主配置文件application.properties文件中配置钉钉相关属性
#三方消息钉钉发送开关
afc.third-party.ding.enable=true
#钉钉开放平台中创建的应用-基础信息-应用凭证AppKey
afc.third-party.ding.app-key=
#钉钉开放平台中创建的应用-基础信息-应用凭证AppSeccret
afc.third-party.ding.secret=
#钉钉开放平台中创建的应用-基础信息-应用凭证AgentId
afc.third-party.ding.agent-id=
#消息中心钉钉发送失败重试次数 默认0
message.push.sender.channel.ding.fail-retry-times=0
#消息中心钉钉发送最大线程数 默认10
message.push.sender.channel.ding.thread-pool.max-pool-size=10
注意:发送markdowm消息时,如果需要包含任务处理链接,需要在链接url中拼接thirdPartyType=dingding参数以及corpId=%24CORPID%24才能正常跳转任务处理页面,同时markdown语法需参考钉钉文档 (opens new window)markdown消息类型。
钉钉发送消息时同一天时间内不允许给同一个用户多次发送相同内容,如业务需要多次发送时,可在模板配置时添加${dateTime}变量,通过时间来约束内容唯一。
消息模板例子:
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
只要符合markdown的语法即可。
# 短信通道
默认提供了阿里云的短信服务,进行消息推送,发送消息时需要在主配置文件application.properties文件中配置阿里云短信相关属性
# 阿里云短信服务AccessKey ID
sms.aliyun.accessKeyId=xxx
# 阿里云短信服务AccessKey Secret
sms.aliyun.accessKeySecret=xxxx
sms.aliyun.regionId=xxx
# 阿里云短信签名,需要在官网申请
sms.aliyun.signName=xxx
# 阿里云短信模板ID,需要在官网申请
sms.aliyun.templateCode=xxx
# 阿里云短信消息模板替换变量名称
sms.aliyun.template-param-code=context
# 是否使用批量发送短信,不配置默认是false,默认使用单条循环发送,批量发送需要阿里云账号具有批量发送权限
sms.aliyun.batch-send=true
# 飞书通道
消息中心发送飞书消息时,需要在主配置文件application.properties文件中配置飞书相关属性
#三方消息飞书发送开关
afc.third-party.feishu.enable=true
#飞书平台创建应用后的appid
afc.third-party.feishu.appId=xxx
#飞书平台创建应用后的secret
afc.third-party.feishu.appSecret=xxx
#飞书平台获取用户信息及给用户发送消息的相关权限,都需要提前在飞书配置,可以增加不能减少
afc.third-party.feishu.scope=contact:contact.base:readonly contact:user.base:readonly contact:user.phone:readonly contact:user.id:readonly contact:contact:readonly_as_app contact:user.employee_id:readonly im:message im:message:send_as_bot im:message:send_multi_users
注意:发送markdowm消息时,如果需要包含任务处理链接,需要在链接url中拼接thirdPartyType=feishu参数才能正常跳转任务处理页面,同时markdown语法需参考飞书文档 (opens new window)富文本消息类型。
消息模板例子:
{
"post": {
"zh_cn": {
"title": "我是一个标题",
"content": [
[{
"tag": "text",
"text": "第一行"
}, {
"tag": "a",
"href": "http://www.feishu.cn",
"text": "飞书"
}]
]
}
}
}
只需要修改content里面的内容即可,消息的tag参考飞书文档。
# 消息通道扩展
如果默认实现的渠道不满足企业要求,比如企业需要跟自身已有的OA系统对接,可参考平台现有的渠道实现进行扩展,下面以钉钉为例。
# 添加相关依赖
添加消息中心starter对应发布版本
<dependency>
<groupId>com.primeton.gocom.bfp</groupId>
<artifactId>com.primeton.gocom.bfp.message.starter</artifactId>
</dependency>
# 实现相关类
- 实现com.primeton.gocom.bfp.message.configuration.IChannelConfiguration接口
描述:消息渠道相关配置类,自定义配置消息发送失败重试次数、当前消息渠道发送最大线程数。具体实现方案可参照已实现渠道。
示例:
@Configuration
public class DingChannelConfiguration implements IChannelConfiguration{
@Value("${message.push.sender.channel.ding.fail-retry-times:0}")
private int failRetryTimes;
@Value("${message.push.sender.channel.ding.thread-pool.max-pool-size:10}")
private int maxThreadPoolSize;
@Override
public String getChannelType() {
return "ding";
}
@Override
public int getFailRetryTimes() {
return failRetryTimes;
}
@Override
public int getMaxThreadPoolSize() {
return maxThreadPoolSize;
}
}
- 实现com.primeton.gocom.bfp.message.ext.api.IChannelSend接口
描述:消息渠道发送消息实现类,通过实现该接口中sendMessage方法来实现消息统一推送。
示例:
@Component
public class DingChannelSendServiceImpl implements IChannelSend {
private static final Logger log = LoggerFactory.getLogger(DingChannelSendServiceImpl.class);
@Autowired
private DingChannelConfiguration dingChannelConfiguration;
@Override
public String getChannelType() {
return dingChannelConfiguration.getChannelType();
}
@Override
public SendResult sendMessage(BaseMessageModel baseMessageModel) {
IAFCClient afcClient = AFCClientFactory.getInstance().createAFCClient();
Set<String> employeePhoneNumList = new HashSet<>();
SendResult sendResult = new SendResult();
String sendTarget = baseMessageModel.getSendTarget();
JSONObject userBodyJson = getUserBodyJson(sendTarget);
String userBody = userBodyJson.getString(MESSAGE_USER_KEY);
if(StringUtil.isNotNullAndBlank(userBody)){
//根据账号查询员工信息
List<String> users = Arrays.asList(userBody.split(","));
for(String empId : users){
Employee employee = afcClient.getEmployeeAPI().queryEmployeeById(empId);
if(Objects.nonNull(employee)){
employeePhoneNumList.add(employee.getPhoneNumber());
}
}
}
String roleBody = userBodyJson.getString(MESSAGE_ROLE_KEY);
if(StringUtil.isNotNullAndBlank(roleBody)){
//根据角色查询员工信息
List<String> roles = Arrays.asList(roleBody.split(","));
for (String roleId : roles){
List<Employee> employees = afcClient.getEmployeeAPI().queryEmployeeByRoleId(roleId);
if(CollectionUtil.isNotEmpty(employees)){
employeePhoneNumList.addAll(employees.stream().map(e->e.getPhoneNumber()).collect(Collectors.toList()));
}
}
}
String orgBody = userBodyJson.getString(MESSAGE_ORG_KEY);
if(StringUtil.isNotNullAndBlank(orgBody)){
List<String> orgs = Arrays.asList(orgBody.split(","));
//判断是否需要查询子机构
if(baseMessageModel.isChildOrg()){
for (String orgId : orgs){
List<Employee> thisOrgAndAllSubOrgEmployees = afcClient.getEmployeeAPI().getThisOrgAndAllSubOrgEmployees(orgId);
if(CollectionUtil.isNotEmpty(thisOrgAndAllSubOrgEmployees)){
employeePhoneNumList.addAll(thisOrgAndAllSubOrgEmployees.stream().map(e->e.getPhoneNumber()).collect(Collectors.toList()));
}
}
}else{
for (String orgId : orgs){
List<Employee> employeeList = afcClient.getEmployeeAPI().queryEmployeeByOrgId(orgId);
if(CollectionUtil.isNotEmpty(employeeList)){
employeePhoneNumList.addAll(employeeList.stream().map(e->e.getPhoneNumber()).collect(Collectors.toList()));
}
}
}
}
JSONObject messageBodyJson = getMessageBodyJson(baseMessageModel);
//正文
String content = messageBodyJson.getString(MESSAGE_BODY_KEY_CONTENT);
try {
boolean result = afcClient.getThirdMessageAPI().sendMessage(new ArrayList<>(employeePhoneNumList),
content);
if(result){
sendResult.Ok();
}else {
sendResult.fail("发送三方消息失败");
}
}catch (Exception e){
log.error("钉钉消息发送失败-异常信息:{}",e.getMessage());
sendResult.fail(e.getMessage());
throw new RuntimeException(e.getMessage());
}
return sendResult;
}
@Override
public int getFailRetryTimes() {
return dingChannelConfiguration.getFailRetryTimes();
}
@Override
public int getOrder() {
return 0;
}
}
- 继承com.primeton.gocom.bfp.message.ext.api.PushChannelType类
描述:消息渠道类型枚举类,该类定义枚举用于前端页面显示、后端消息推送具体渠道推送消息实现类获取。
示例:
@Component
public class DingPushChannelType extends PushChannelType {
@Override
public String getChannelClassRefence() {
return DingChannelSendServiceImpl.class.getName();
}
@Override
public String getChannelDisplayName() {
return "钉钉";
}
@Override
public String getChannelType() {
return BeanFactory.newInstance().getBean(DingChannelConfiguration.class).getChannelType();
}
}