PRIMETON TECHNOLOGIES, LTD. 普元信息技术股份有限公司

消息中心使用说明

No part of this document may be reproduced, stored in any electronic retrieval system, or transmitted in any form or by any means, mechanical, photocopying, recording, otherwise, without the written permission of the copyright owner.

[toc]

# 消息中心

# 功能

消息中心模块是用来做消息发送和管理功能,从被调用方式来看有两种集成方案。

1、被三方系统依赖sdk远程调用或作为应用单独部署对外暴露接口供三方系统远程rest调用,这种方式作为统一消息中心功能。

依赖:

     <dependency>
      <groupId>com.primeton.gocom.bfp</groupId>
      <artifactId>com.primeton.gocom.bfp.message.sdk</artifactId>
      <version>8.3.0</version>
    </dependency>

结构图:

image-20230627154454688

2、依赖starter实现消息中心本地调用(同进程)。

三方应用依赖消息中心starter时,此时三方应用即是一个独立消息中心系统,消息数据独立维护。

依赖:

 <dependency>
      <groupId>com.primeton.gocom.bfp</groupId>
      <artifactId>com.primeton.gocom.bfp.message.starter</artifactId>
      <version>8.3.0</version>
 </dependency>

# 架构图

image-20230627165151271

# 原理

消息中心应用在启动后会对每个渠道创建守护线程池,线程池中线程循环扫描渠道对应的队列中是否有消息,在一定循环次数后一直未查询到队列中消息时会转入低频扫描,即线程睡眠一定时间。 守护线程扫描到队列中消息后开始发送消息到对应渠道。每个消息渠道支持配置对应的发送失败重试次数和发送最大线程数。

# 接口说明

消息中心提供公共消息接口,以下为部分接口,其他相关接口参考swagger-ui

接口path 接口描述 接口说明
/api/bfp/messagecenter/send-message 发送普通消息 该接口通过简单入参,发送对应消息功能
/api/bfp/messagecenter/send-process-message 发送流程消息 该接口参数来源于流程引擎对象,存在需要替换流程相关变量,实现消息内容变量参数替换功能。
/api/bfp/messagecenter/message-channel 获取消息通道枚举 该接口返回数据为内置和扩展后的消息渠道,用于前端页面展示
/api/bfp/messagecenter/page-message-history 分页查询发送消息历史信息 消息中心发送消息接口会保存相关发送消息记录,包括发送成功未成功数据记录。
/api/bfp/messagetemplate/pageTemplate 分页查询模板信息 消息中心支持默认内容配置,其中支持变量传递,变量传递需在流程绑定情况下使用

# 消息中心使用说明

消息中心提供流程消息发送接口和普通消息发送接口,二者再原理相同,区别在于对外接口参数不同可供不同场景使用。

消息中心发送接口接收的发送目标参数为组织机构实现类中各参与者类型数据,接收各类型参与者,消息引擎在解析发送消息时会根据参与者类型查询具体的员工信息根据对应消息渠道需要员工中属性进行批量发送。

发送对象参与者类型有以下三类

类型 属性名 组织机构实现类方法 解释说明
emp 员工 com.primeton.gocom.afcenter.sdk.api.IEmployeeAPI#findEmployeeById 根据员工id查询员工信息
role 角色 com.primeton.gocom.afcenter.sdk.api.IEmployeeAPI#queryEmployeeByRoleId 根据角色id查询角色下员工信息
org 机构 1、com.primeton.gocom.afcenter.sdk.api.IEmployeeAPI#getThisOrgAndAllSubOrgEmployees
2、com.primeton.gocom.afcenter.sdk.api.IEmployeeAPI#queryEmployeeByOrgId
1、根据机构id查询本机构和子机构下所有员工信息
2、根据机构id查询本机构不包含子机构员工信息

org机构类型参与者解析员工时 通过参数isChildOrg来决定是否查询子机构下员工。

# 发送普通消息

# 消息发送接口详情

接口path:/api/bfp/messagecenter/send-message

请求方式:post

参数:

参数属性名 参数类型 参数说明
messageContent String 发送消息内容,json格式数据
{”content“:”消息体“,"subTitle":"消息标题"}
sendTarget String 发送目标,json格式数据{type:data},
type为组织、角色、用户(org,role,emp)。data为对应类型id,同一类型多个值时逗号分隔
{"org":"orgId1,orgId2","role":"roleId1,roleId2","emp":"empId1,empId2"}
messageChannel String 消息发送渠道
邮件:com.primeton.gocom.bfp.message.sender.service.impl.EmailChannelSendServiceImpl
企业微信:com.primeton.gocom.bfp.message.sender.service.impl.WecomChannelSendServiceImpl
钉钉:com.primeton.gocom.bfp.message.sender.service.impl.DingChannelSendServiceImpl
isChildOrg boolean 是否给子机构发送 默认false

# 发送流程消息

# 消息发送接口详情

接口path:/api/bfp/messagecenter/send-process-message

请求方式:post

参数:

参数属性名 参数类型 参数说明
relativeData Map<String, Object> 流程相关数据
processContext ProcessContext 流程上下文
messageType String 消息类型(taskNotice-任务通知,pressNotice-催办通知,timeoutNotice-超时通知)
participants String[] 参与者信息 id,name,type
sendParams Map<String,String> map集合 消息渠道-模板id
isDefaultConfig boolean 是否默认配置

# 流程触发事件调用

消息中心提供对外发送消息接口,通过配置触发事件来实现发送消息功能

# 流程消息配置自动发送消息

# 配置员工相关信息

流程发送消息时通过获取员工信息中个人信息-电话来发送钉钉、企业微信,通过个人信息-邮件来发送邮件消息。流程环节若配置任务通知,但环节参与者对应员工信息未填写电话或邮件时,消息发送失败。

image-20230117162408048

# 配置消息模板

1、默认消息配置。消息配置界面可初始化对应消息类型的默认配置,该默认配置中模板需要自行从自定义模板配置中选择,系统提供默认模板初始化功能,该按钮在点击后为以下三种消息类型创建对应默认模板。后台程序自动初始化库中默认数据。初始化完成后可在模板配置界面查看到。

image-20230620100658719

2、自定义消息模板。流程消息配置方式需要先配置流程对应消息模板,流程消息模板再与流程环节进行关联。

编辑模板时绑定流程即可配置对应流程相关表单属性变量,消息中心发送内容时会进行变量值替换

image-20230117163853860

image-20230117164019036

# 流程环节配置任务通知

AFCenter业务配置中可将已创建消息模板与流程活动相关联,流程引擎在启动当前环节任务后会自动触发任务通知,消息中心针对流程环节已开启的消息渠道发送对应模板消息。

1、任务通知可选默认配置和自定义,选择默认配置时,任务通知会根据消息配置中默认配置来发送对应消息。

image-20230620142716486

2、选择自定义配置时需要自行勾选消息渠道和模板信息

渠道只能选择对应类型消息模板,渠道支持多选(企业微信和钉钉只支持二选一)

image-20230620142934800

# 流程人工环节配置参与者

配置了任务通知的人工环节需要配置对应参与者,该参与者对应员工信息中需要有消息渠道所需信息(手机号、邮箱)

1689911052082

1689911089668

# 人工活动环节启动自动发送消息

当流程启动到配置了任务通知的环节时会触发调用消息中心对参与者发送消息。

image-20230117164844931

# 消息渠道说明

产品约定企业微信和钉钉配置文件中只能开启一个,不能同时配置enabled=true

# 邮件

消息中心发送邮件消息时,需要在主配置文件application.properties文件中配置邮件相关属性

注意:

发送邮件消息不支持附件功能。

bfp.email.enabled=true
#配置smtp服务器地址
bfp.email.account.host=smtp.qq.com
#配置smtp服务端口号
bfp.email.account.port=465
#设置需要用户名密码验证
bfp.email.account.auth=true
#设置邮箱地址
bfp.email.account.from=
#设置邮箱用户名,一般为域名前的部分
bfp.email.account.user=
#设置用户登陆密码,使用qq邮箱时填写qq邮箱单独生成的授权码
bfp.email.account.pass=
#是否使用 STARTTLS安全连接
bfp.email.account.starttls-enable=true
#是否使用 SSL安全连接
bfp.email.account.ssl-enable=true
#指定实现javax.net.SocketFactory接口的类的名称
bfp.email.account.socket-factory-class=javax.net.ssl.SSLSocketFactory
#如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类
bfp.email.account.socket-factory-fallback=true
#指定的端口连接到在使用指定的套接字工厂
bfp.email.account.socket-factory-port=465
#设置SMTP超时时长,单位毫秒,缺省值不超时
bfp.email.account.timeout=0
#设置Socket连接超时值,单位毫秒,缺省值不超时
bfp.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

# 钉钉

消息中心发送钉钉消息时,需要在主配置文件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

钉钉发送消息时同一天时间内不允许给同一个用户多次发送相同内容,如业务需要多次发送时,可在模板配置时添加${dateTime}变量,通过时间来约束内容唯一。

# 消息扩展开发说明

消息中心渠道目前支持邮件、钉钉、企业微信三种,如需其他发送渠道可自行通过扩展方式实现。

# 添加相关依赖

添加消息中心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();
    }
}

# 特殊注意事项:

bps引擎服务开启消息中心发送消息时配置文件application.properties需配置开关,如下:

message.workitem-enable=true

业务应用在集成sdk模式使用时需要在application.properties中配置消息中心应用名称属性

使用nacos注册中心时该配置可为nacos注册服务名

示例:message.application.name=AFCENTER

非nacos注册中心场景时该配置为消息中心服务地址

示例:message.application.name=http://127.0.0.1:8080

上次更新: 2023/7/25下午2:17:33