一、业务场景

涉及的主要场景,咋们会在纳里的那些地方遇到。
比如微信公众号订阅一个服务包后,就会有一个推送,推送的消息格式内容都是配置的。
image.png
当然还有其他方面,比如短信推送,互联网推送,系统推送等等

当然我们在运营平台就可以看到一些消息的操作:
image.png
我们对其操作,其实都是在sms.**smsOpManageService **下的各个接口,比如修改一个消息模板
image.png
调用updatePushBusTypeConfig()接口,其它查询与禁用就先不演示了。
image.png

  • 了解了一些场景,那我想推送一条消息要如何执行的呢?


二、业务流程

首先知道发送一条业务信息都是一个这样的流程(简单描述):
sms服务结构.png

1、访问接口

我们一步步来看测试消息推送流程,先从业务接口出发
image.png
可以看到这个接口请求没有太大的改动,前面部分依旧是数据处理(判断,增删改成),后面有push的方法基本是进行消息推送的入口了

  • pushRefundMsgTest(order, “RecipeConsultRefundApply”);

这个方法需要传入两个参数,一个是处理过后的消息主体(这里就使用order演示),另一个就是bustType模板的类型

  1. public void pushRefundMsgTest(ServicePackOrderDTO order, String busType) {
  2. //发送消息申请审核的消息
  3. ISmsPushService smsPushService = ApplicationUtils.getBaseService(ISmsPushService.class);
  4. SmsInfoBean smsInfo = new SmsInfoBean();
  5. smsInfo.setBusId(order.getId());
  6. smsInfo.setOrganId(order.getOrganId());
  7. smsInfo.setBusType(busType);
  8. // smsInfo.setClientId(order.getPayFlag());
  9. smsInfo.setCreateTime(new Date());
  10. smsInfo.setClientId(order.getDeviceId());
  11. smsPushService.pushMsg(smsInfo);
  12. }
  • 可以看到都会将信息传入 ISmsPushService 接口,那这个接口由谁实现呢?

ehealth-base模块中有一个 SmsPushService 消息推送改造类
image.png

  • 调用pushMsg() 方法,会将smsInfo消息体放入ONS队列,之后就由sms模块处理消息体了。

    1. /**
    2. * 扩展smsInfo字段不够的情况
    3. * 消息推送改造使用aliyunONS消息队列
    4. * 将需要发送的业务smsInfo丢进ONS队列
    5. */
    6. @RpcService
    7. public void pushMsgData2OnsExtendValue(final SmsInfo smsInfo) {
    8. SmsInfoDAO smsInfoDAO = DAOFactory.getDAO(SmsInfoDAO.class);
    9. //检验参数
    10. validatePushParam(smsInfo.getBusId(), smsInfo.getOrganId(), smsInfo.getBusType());
    11. //消息体
    12. smsInfo.setCreateTime(new Date());
    13. smsInfo.setStatus(0);
    14. setInternalClientIdIfHave(smsInfo);
    15. smsInfoDAO.save(smsInfo);
    16. logger.info("smsInfo.save成功:SmsInfo:[{}]," + JSONUtils.toString(smsInfo));
    17. //smsInfo放入ONS队列
    18. MQHelper.getMqPublisher().publish(OnsConfig.pushTopic, smsInfo, null, null, 1);
    19. }

2、Executor模板**

在包eh.sms.service2或其子包下创建一个Executor文件,是先继承extends AbstractPushExecutor(暂时),增加注解@Component (文件命名规则为push_bustype的BusType值加Executor)

之后会执行三个流程:

  • onInit() 初始化服务层的接口
  • beforeExec(…) 拼接消息体pushBody
  • processExec(…) 完善pushBody,插入要发送给哪个服务(Alidayu,wx,xinGe)的信息以及服务的相关参数


  1. onInit() 初始化

初始化服务层参数和接口
image.png

  1. beforeExec() 在处理之前

可以看到接口,包括一个模板类型,以及消息体,一个复杂参数
image.png
主要还是一个参数包装报消息体的过程,包含不同

  • propsParam 获取公用复杂参数以及修改添加简单参数

image.png
所有参数字段存放在这,如果消息体中有对应的key,就会去替换入相应的值
image.png

  • tplParams

image.png

  • kvMap 存储的是一个跳转链接的各个参数

image.png
如下图中详情下会产生一个url链接,后面会带参数就是kvmap中存入的,方便前端获取
image.png

  • 通过ContextUtils工具,传递数据到最后的执行方法 processExec中

image.png

  1. processExec() 正式执行

实现消息的最后分装与推送
image.png

  • 如果消息存在复杂参数,则先进行拼接,

image.png

  • 然后判断发送给谁:医生、患者、还是医生leader

image.png

  1. //用来判断是否自己手动来发送消息,true为是
  2. pushBody.ignore(true);
  • 然后通过参数可以了解是发送给谁的服务:

例如:通过title可以了解到getXinGeDoctorTitle()可以知道是使用信鸽的,然后在pushBody加好其他服务参数

  1. pushBody.addProperty("title", ContextUtils.get("title")).addProperty("custom", ContextUtils.get("msgCustom"))
  2. .addProperty("publisherId", SystemConstant.SYSMSG_PUBLISHER_SERVICEPACK)
  3. .addProperty("memberType", SystemConstant.SYSMSG_MEMBERTYPE_DOCTOR)
  4. .addProperty("receiveType", SystemConstant.SYSMSG_RECEIVETYPE_APP);
  5. //发送给医生用户信鸽推送消息变量
  6. //android端显示的标题,为app名,医生用户为纳里医生,患者用户为纳里健康
  7. String title = SignConfig.getXinGeDoctorTitle();
  • 最后通过PushResult工具统一发送,push()

image.png

  • 之后会通过PushUtils中的processTemplate()替换掉消息模板中的变量(${xxx}) ,完成消息体信息的填入,返回一个完整的消息体

image.png
image.png

三、快速开始

怎么快速完成一个消息推送呢?

只需要三步就能完成一个消息推送:

  1. 先写一个调用,完成封装消息对象,并且传入pushRefundMsgTest一个推送的上下文

    1. /**
    2. * 修改服务包订单的状态,并且发送推送给用户的微信、系统消息
    3. * @param orderId:订单id,modifiedOrderStatus:修改后的订单状态
    4. * @author ColaEsom
    5. */
    6. @RpcService
    7. public Map<String, Object> pushMsqTest(Integer orderId, Integer modifiedOrderStatus) {
    8. Map<String, Object> result = Maps.newHashMap();
    9. //数据收集
    10. PatientOrderService orderService = ServicepackAPI.getService(PatientOrderService.class);
    11. ServicePackOrderDTO order = orderService.getOrderById(orderId); //通过订单id查询整个订单
    12. if (order == null) {
    13. LOGGER.info("pushMsgTest ==== orderId = {},当前接口管理的订单不存在!", orderId);
    14. result.put("result", false);
    15. result.put("result", 0);
    16. return result;
    17. }
    18. //数据修改
    19. Map<String, Object> updataField = Maps.newHashMap();
    20. updataField.put("oderStatus", modifiedOrderStatus);
    21. updataField.put("lastModified", new Date());
    22. //消息推送
    23. pushRefundMsgTest(order, "RecipeConsultRefundApply");
    24. //发送推送成功
    25. result.put("result", true);
    26. result.put("code", 200);
    27. return result;
    28. }
  2. 写一个方法(pushRefundMsgTest与上面对应),实现ISmsPushService接口,完善smsInfo消息体,调用pushMsg(smsInfo),将消息体存入消息队列

    1. public void pushRefundMsgTest(ServicePackOrderDTO order, String busType) {
    2. //发送消息申请审核的消息
    3. smsPushService = ApplicationUtils.getBaseService(ISmsPushService.class);
    4. SmsInfoBean smsInfo = new SmsInfoBean();
    5. smsInfo.setBusId(order.getId());
    6. smsInfo.setOrganId(order.getOrganId());
    7. smsInfo.setBusType(busType);
    8. // smsInfo.setClientId(order.getPayFlag());
    9. smsInfo.setCreateTime(new Date());
    10. smsInfo.setClientId(order.getDeviceId());
    11. smsPushService.pushMsg(smsInfo);
    12. }
  3. 最后只需完成之前定义bustype+Executor模板(发送消息的格式和内容都在这之中处理)

    1. @Component
    2. public class RecipeConsultRefundApplyExecutor extends AbstractPushExecutor {
    3. @Autowired
    4. private DoctorService docService;
    5. @Override
    6. protected void onInit() {
    7. }
    8. @Override
    9. protected void beforeExec(PushBusType busType, SmsInfo smsInfo, TplParams tplParams) {
    10. RevisitBean revisitBean = revisitService.getById(smsInfo.getBusId());
    11. RevisitExDTO revisitExDTO = revisitExService.getByConsultId(smsInfo.getBusId());
    12. PatientDTO patient = patientService.getPatientByMpiId(revisitBean.getMpiid());
    13. String patientName = patient.getPatientName();
    14. //获取公用复杂参数以及修改添加简单参数
    15. Map<String, Object> propsParam = RevisitParamUtils.getConsultParams(revisitBean, patient, tplParams);
    16. //数据获取
    17. DoctorDTO doctor = docService.getByDoctorId(doctorId);
    18. tplParams.put("urt", doctor.getUrt());
    19. tplParams.put("patientRefundDate", DateConversion.formatDate(revisitExDTO.getPatientRefundDate()));
    20. final Map<String, String> kvMap = Maps.newHashMap();
    21. if (ConsultConstant.CONSULT_TYPE_GRAPHIC.equals(revisitBean.getRequestMode())) {
    22. kvMap.put("module", "consultModule");
    23. //数据存入
    24. kvMap.put("did", String.valueOf(doctorId));
    25. kvMap.put("dsex", doctor.getGender());
    26. ContextUtils.put("kvMap", kvMap); //用于数据的跨方法存取 。功能类似于session
    27. ContextUtils.put("propsParam", propsParam);
    28. }
    29. @Override
    30. protected void processExec(PushBusType busType, SmsInfo smsInfo, PushConfig pushConfig, PushBody pushBody) {
    31. Map<String, Object> propsParam = ContextUtils.get("propsParam", Map.class);
    32. Map<String, String> kvMap = ContextUtils.get("kvMap", Map.class);
    33. Integer clientId = (Integer) pushBody.params().get("clientId");
    34. //患者端wx公众号
    35. pushBody.addProperty(WxTemplateFieldConstant.KVMAP, kvMap).clientId(clientId);
    36. //系统消息
    37. Integer urt = (Integer) pushBody.params().get("urt");
    38. pushBody.addProperty("publisherId", SystemConstant.SYSTEM_MSG_PUBLISH_TYPE_HOMEPAGE)
    39. .addProperty("memberType", SystemConstant.SYSMSG_MEMBERTYPE_DOCTOR)
    40. .addProperty("receiveType", SystemConstant.SYSMSG_RECEIVETYPE_APP)
    41. .urt(urt);
    42. pushBody.complicatedParamArgs("keyword1", propsParam);
    43. pushBody.complicatedParamArgs("keyword2", propsParam);
    44. }
    45. }

四、数据结构

1、sms消息所用到的表(预发布的库中)

  • 以下是push_config(包含消息的主体)与push_bustype(对push_config进行一个描述)

image.png
其中push_config表中的busType与name字段匹配,push_config中包含对此模板的描述


  • 以下是push_config(包含消息的主体)与push_param(对push_config复杂参数的填充)

image.png
其中push_config表中的id与pushConfigId字段匹配。
其中tplcontent的字段包含的参数就是通过paramName中多个对应字段的填充。

2、举例

以ServicepackBuySuccess模板为例
image.png

查询到了所有的参数与复杂参数,并在processExec()中比较,能进行对应
image.png