一、业务场景
涉及的主要场景,咋们会在纳里的那些地方遇到。
比如微信公众号订阅一个服务包后,就会有一个推送,推送的消息格式内容都是配置的。
当然还有其他方面,比如短信推送,互联网推送,系统推送等等
当然我们在运营平台就可以看到一些消息的操作:
我们对其操作,其实都是在sms.**smsOpManageService **下的各个接口,比如修改一个消息模板
调用updatePushBusTypeConfig()接口,其它查询与禁用就先不演示了。
- 了解了一些场景,那我想推送一条消息要如何执行的呢?
二、业务流程
1、访问接口
我们一步步来看测试消息推送流程,先从业务接口出发
可以看到这个接口请求没有太大的改动,前面部分依旧是数据处理(判断,增删改成),后面有push的方法基本是进行消息推送的入口了
- pushRefundMsgTest(order, “RecipeConsultRefundApply”);
这个方法需要传入两个参数,一个是处理过后的消息主体(这里就使用order演示),另一个就是bustType模板的类型
public void pushRefundMsgTest(ServicePackOrderDTO order, String busType) {
//发送消息申请审核的消息
ISmsPushService smsPushService = ApplicationUtils.getBaseService(ISmsPushService.class);
SmsInfoBean smsInfo = new SmsInfoBean();
smsInfo.setBusId(order.getId());
smsInfo.setOrganId(order.getOrganId());
smsInfo.setBusType(busType);
// smsInfo.setClientId(order.getPayFlag());
smsInfo.setCreateTime(new Date());
smsInfo.setClientId(order.getDeviceId());
smsPushService.pushMsg(smsInfo);
}
- 可以看到都会将信息传入 ISmsPushService 接口,那这个接口由谁实现呢?
在ehealth-base模块中有一个 SmsPushService 消息推送改造类
调用pushMsg() 方法,会将smsInfo消息体放入ONS队列,之后就由sms模块处理消息体了。
/**
* 扩展smsInfo字段不够的情况
* 消息推送改造使用aliyunONS消息队列
* 将需要发送的业务smsInfo丢进ONS队列
*/
@RpcService
public void pushMsgData2OnsExtendValue(final SmsInfo smsInfo) {
SmsInfoDAO smsInfoDAO = DAOFactory.getDAO(SmsInfoDAO.class);
//检验参数
validatePushParam(smsInfo.getBusId(), smsInfo.getOrganId(), smsInfo.getBusType());
//消息体
smsInfo.setCreateTime(new Date());
smsInfo.setStatus(0);
setInternalClientIdIfHave(smsInfo);
smsInfoDAO.save(smsInfo);
logger.info("smsInfo.save成功:SmsInfo:[{}]," + JSONUtils.toString(smsInfo));
//smsInfo放入ONS队列
MQHelper.getMqPublisher().publish(OnsConfig.pushTopic, smsInfo, null, null, 1);
}
2、Executor模板**
在包eh.sms.service2或其子包下创建一个Executor文件,是先继承extends AbstractPushExecutor(暂时),增加注解@Component (文件命名规则为push_bustype的BusType值加Executor)
之后会执行三个流程:
- onInit() 初始化服务层的接口
- beforeExec(…) 拼接消息体pushBody
- processExec(…) 完善pushBody,插入要发送给哪个服务(Alidayu,wx,xinGe)的信息以及服务的相关参数
- onInit() 初始化
初始化服务层参数和接口
- beforeExec() 在处理之前
可以看到接口,包括一个模板类型,以及消息体,一个复杂参数
主要还是一个参数包装报消息体的过程,包含不同
- propsParam 获取公用复杂参数以及修改添加简单参数
所有参数字段存放在这,如果消息体中有对应的key,就会去替换入相应的值
- tplParams
- kvMap 存储的是一个跳转链接的各个参数
如下图中详情下会产生一个url链接,后面会带参数就是kvmap中存入的,方便前端获取
- 通过ContextUtils工具,传递数据到最后的执行方法 processExec中
- processExec() 正式执行
实现消息的最后分装与推送
- 如果消息存在复杂参数,则先进行拼接,
- 然后判断发送给谁:医生、患者、还是医生leader
//用来判断是否自己手动来发送消息,true为是
pushBody.ignore(true);
- 然后通过参数可以了解是发送给谁的服务:
例如:通过title可以了解到getXinGeDoctorTitle()可以知道是使用信鸽的,然后在pushBody加好其他服务参数
pushBody.addProperty("title", ContextUtils.get("title")).addProperty("custom", ContextUtils.get("msgCustom"))
.addProperty("publisherId", SystemConstant.SYSMSG_PUBLISHER_SERVICEPACK)
.addProperty("memberType", SystemConstant.SYSMSG_MEMBERTYPE_DOCTOR)
.addProperty("receiveType", SystemConstant.SYSMSG_RECEIVETYPE_APP);
//发送给医生用户信鸽推送消息变量
//android端显示的标题,为app名,医生用户为纳里医生,患者用户为纳里健康
String title = SignConfig.getXinGeDoctorTitle();
- 最后通过PushResult工具统一发送,push()
- 之后会通过PushUtils中的processTemplate()替换掉消息模板中的变量(${xxx}) ,完成消息体信息的填入,返回一个完整的消息体
三、快速开始
怎么快速完成一个消息推送呢?
只需要三步就能完成一个消息推送:
先写一个调用,完成封装消息对象,并且传入pushRefundMsgTest一个推送的上下文
/**
* 修改服务包订单的状态,并且发送推送给用户的微信、系统消息
* @param orderId:订单id,modifiedOrderStatus:修改后的订单状态
* @author ColaEsom
*/
@RpcService
public Map<String, Object> pushMsqTest(Integer orderId, Integer modifiedOrderStatus) {
Map<String, Object> result = Maps.newHashMap();
//数据收集
PatientOrderService orderService = ServicepackAPI.getService(PatientOrderService.class);
ServicePackOrderDTO order = orderService.getOrderById(orderId); //通过订单id查询整个订单
if (order == null) {
LOGGER.info("pushMsgTest ==== orderId = {},当前接口管理的订单不存在!", orderId);
result.put("result", false);
result.put("result", 0);
return result;
}
//数据修改
Map<String, Object> updataField = Maps.newHashMap();
updataField.put("oderStatus", modifiedOrderStatus);
updataField.put("lastModified", new Date());
//消息推送
pushRefundMsgTest(order, "RecipeConsultRefundApply");
//发送推送成功
result.put("result", true);
result.put("code", 200);
return result;
}
写一个方法(pushRefundMsgTest与上面对应),实现ISmsPushService接口,完善smsInfo消息体,调用pushMsg(smsInfo),将消息体存入消息队列
public void pushRefundMsgTest(ServicePackOrderDTO order, String busType) {
//发送消息申请审核的消息
smsPushService = ApplicationUtils.getBaseService(ISmsPushService.class);
SmsInfoBean smsInfo = new SmsInfoBean();
smsInfo.setBusId(order.getId());
smsInfo.setOrganId(order.getOrganId());
smsInfo.setBusType(busType);
// smsInfo.setClientId(order.getPayFlag());
smsInfo.setCreateTime(new Date());
smsInfo.setClientId(order.getDeviceId());
smsPushService.pushMsg(smsInfo);
}
最后只需完成之前定义bustype+Executor模板(发送消息的格式和内容都在这之中处理)
@Component
public class RecipeConsultRefundApplyExecutor extends AbstractPushExecutor {
@Autowired
private DoctorService docService;
@Override
protected void onInit() {
}
@Override
protected void beforeExec(PushBusType busType, SmsInfo smsInfo, TplParams tplParams) {
RevisitBean revisitBean = revisitService.getById(smsInfo.getBusId());
RevisitExDTO revisitExDTO = revisitExService.getByConsultId(smsInfo.getBusId());
PatientDTO patient = patientService.getPatientByMpiId(revisitBean.getMpiid());
String patientName = patient.getPatientName();
//获取公用复杂参数以及修改添加简单参数
Map<String, Object> propsParam = RevisitParamUtils.getConsultParams(revisitBean, patient, tplParams);
//数据获取
DoctorDTO doctor = docService.getByDoctorId(doctorId);
tplParams.put("urt", doctor.getUrt());
tplParams.put("patientRefundDate", DateConversion.formatDate(revisitExDTO.getPatientRefundDate()));
final Map<String, String> kvMap = Maps.newHashMap();
if (ConsultConstant.CONSULT_TYPE_GRAPHIC.equals(revisitBean.getRequestMode())) {
kvMap.put("module", "consultModule");
//数据存入
kvMap.put("did", String.valueOf(doctorId));
kvMap.put("dsex", doctor.getGender());
ContextUtils.put("kvMap", kvMap); //用于数据的跨方法存取 。功能类似于session
ContextUtils.put("propsParam", propsParam);
}
@Override
protected void processExec(PushBusType busType, SmsInfo smsInfo, PushConfig pushConfig, PushBody pushBody) {
Map<String, Object> propsParam = ContextUtils.get("propsParam", Map.class);
Map<String, String> kvMap = ContextUtils.get("kvMap", Map.class);
Integer clientId = (Integer) pushBody.params().get("clientId");
//患者端wx公众号
pushBody.addProperty(WxTemplateFieldConstant.KVMAP, kvMap).clientId(clientId);
//系统消息
Integer urt = (Integer) pushBody.params().get("urt");
pushBody.addProperty("publisherId", SystemConstant.SYSTEM_MSG_PUBLISH_TYPE_HOMEPAGE)
.addProperty("memberType", SystemConstant.SYSMSG_MEMBERTYPE_DOCTOR)
.addProperty("receiveType", SystemConstant.SYSMSG_RECEIVETYPE_APP)
.urt(urt);
pushBody.complicatedParamArgs("keyword1", propsParam);
pushBody.complicatedParamArgs("keyword2", propsParam);
}
}
四、数据结构
1、sms消息所用到的表(预发布的库中)
- 以下是push_config(包含消息的主体)与push_bustype(对push_config进行一个描述)
其中push_config表中的busType与name字段匹配,push_config中包含对此模板的描述
- 以下是push_config(包含消息的主体)与push_param(对push_config复杂参数的填充)
其中push_config表中的id与pushConfigId字段匹配。
其中tplcontent的字段包含的参数就是通过paramName中多个对应字段的填充。
2、举例
以ServicepackBuySuccess模板为例
查询到了所有的参数与复杂参数,并在processExec()中比较,能进行对应