preface

The latest to do a message notification service, involving message monitoring, message encapsulation, message sending and other aspects, today we introduce the message template processing referenced for your reference.

The business scenario

  • Instant notification (notification immediately after receiving a message)
  • Delay notification (specified period notification)

The flow chart

The message service monitors the message in the message queue and determines whether the message needs to be notified in real time (immediately sent) or at a specified time (sent at a specified time). In real time, the message service is immediately called to send the message (specific design needs to be made according to the amount of messages in business scenarios. Batch consumption, multithreading consumption, notification can be divided into multiple topics for sending and consumption according to the type of notification, such as SMS topic, email topic, push topic, etc. The code implementation of periodic notification is shown below.)

Regularly inform

Message entity

public class MessageInfo implements Serializable {

    /** * Notification type SMS mail push */
    private String type;

    /** * Notify user ID */
    private String userId;

    /** * Key information */
    private Map<String, Object> info;

    /** * Notification time (real-time push when it is empty but timed push when it is not empty) */
    private Long time;

    // omit get set...
}
Copy the code

Listen to the message

@Component public class MessageListener { @Autowired private TimingMessageCache timingMessageCache; @Autowired private SenderManager senderManager; @Autowired private TemplateService templateService; @KafkaListener(topics = "message", group = "message-server-group") public void messageListener(String message, Acknowledgment ack) { try { MessageInfo messageInfo = JSONUtil.toBean(message, MessageInfo.class); Long time = messageInfo.getTime(); if (time ! TimingMessageCache. Add (messageInfo); } else {/ / directly to get the template String templateCode = messageInfo. GetTemplateCode (); String template = templateService.getTemplateByCode(templateCode); String msg = TemplateUtils.buildMessage(template, messageInfo.getInfo()); String type = messageInfo.getType(); senderManager.send(messageInfo.getType(), messageInfo.getUserId(), msg); } ack.acknowledge(); } catch (Exception e) {// Exception log}}Copy the code

Monitor the messages sent by the business system, and judge whether the time is empty to determine real-time notification or timing notification. Timing notification is put into timing notification cache, real-time notification is directly sent, and template tool and sending implementation are called

Timed message cache

@Component public class TimingMessageCache { @Resource private RedisTemplate<String, MessageInfo> redisTemplate; private static final String TIMING_MESSAGE_PREFIX = "timing_message:"; Public void add(messageInfo messageInfo) {Long time = messageinfo.getTime (); String minute = FastDateFormat.getInstance("yyyy-MM-dd HH:mm").format(time); String key = String.join(":", TIMING_MESSAGE_PREFIX, minute); redisTemplate.opsForList().leftPush(key, messageInfo); } public List<MessageInfo> consumeAll(long); /** * consumeAll(long) time) { String minute = FastDateFormat.getInstance("yyyy-MM-dd HH:mm").format(time); String key = String.join(":", TIMING_MESSAGE_PREFIX, minute); List<MessageInfo> messageInfoList = redisTemplate.opsForList().range(key, 0, -1); redisTemplate.delete(key); return messageInfoList; } /** * consume a specified number of messages, suitable for large batch processing ** @param time * @param limit * @return */ public List<MessageInfo> consumePart(long  time, int limit) { List<MessageInfo> messageInfoList = new ArrayList<>(); for (int i = 0; i < limit; i++) { String minute = FastDateFormat.getInstance("yyyy-MM-dd HH:mm").format(time); String key = String.join(":", TIMING_MESSAGE_PREFIX, minute); MessageInfo messageInfo = redisTemplate.opsForList().rightPop(key); if (messageInfo ! = null) { messageInfoList.add(messageInfo); } else {// Read empty end loop break; } } return messageInfoList; } @param time @return */ public long size(long time) {String minute = FastDateFormat.getInstance("yyyy-MM-dd HH:mm").format(time); String key = String.join(":", TIMING_MESSAGE_PREFIX, minute); return redisTemplate.opsForList().size(key); }}Copy the code

Timed message cache. Redis list is used to store data in the same list in the same minute. Key example timing_message:2021-10-01 08:01

Timed message processing

@Component @EnableScheduling public class TimingMessageHandle { @Autowired private TimingMessageCache timingMessageCache; @Autowired private SenderManager senderManager; @Autowired private TemplateService templateService; /** ** Scheduled(cron = "0 */1 ** *? ) @Async public void handle() { Calendar calendar = Calendar.getInstance(); long time = calendar.getTimeInMillis(); consume(time); Public void consume(long time) {long size = timingMessageCache. Size (time); If (size < 1000) {fewConsume(time); fewConsume(time); } else {for (long I = 0, limit; i < size; i += limit) { limit = i + 1000 > size ? size - i : 1000; send(timingMessageCache.consumePart(time, limit)); }} /** * consume (long time) {List<MessageInfo> messageInfoList = timingMessageCache.consumeAll(time); send(messageInfoList); } @param messageInfoList */ private void send(List<MessageInfo> messageInfoList) {for (MessageInfo) MessageInfo: messageInfoList) {/ / according to the template code to obtain the corresponding template String templateCode = messageInfo. GetTemplateCode (); / / according to the template code for the template String template. = templateService getTemplateByCode (templateCode); / / key data to generate the message content according to the template and message String MSG = TemplateUtils. BuildMessage (template, messageInfo getInfo ()); // Call the corresponding sending methods according to the type SMS, mail, push... senderManager.send(messageInfo.getType(), messageInfo.getUserId(), msg); }}}Copy the code

Previously, the scheduled message is used as the cache key according to the prefix + minute time, so the scheduled task is executed once every minute. The key is generated at the current time to read the cache. For example, the message sent by 2021-10-01 08:10 can be read from the cache and processed by the scheduled task at 2021-10-01 08:10.

Send implementation (pseudocode)

The sending implementation adopts the interface multiple implementation, the way of specifying the name, can refer to the article

The article links

Public interface MessageSender {void send(String userId, String MSG); } @Component(" SMS ") public class SmsSender implements MessageSender {@override public void send(String) userId, String MSG) {@Component("mail") public class MailSender (" MailSender ") public class MailSender (" MailSender ") MessageSender { @Override public void send(String userId, String MSG) {Component public class SenderManager {private static final @component Public class SenderManager {private static final Logger LOGGER = LoggerFactory.getLogger(SenderManager.class); */ private static final MessageSender EMPTY_RECEIVER = (userId, userId) msg) -> { LOGGER.info("none sender, userId = {}, msg = {}", userId, msg); }; @Autowired private Map<String, MessageSender> senderMessagesMap; ** @param type * @param userId * @param MSG */ public void send(String type, String userId, String msg) { senderMessagesMap.get(type).send(userId, msg); }}Copy the code

Template dependency (pseudocode)

@service public class TemplateService {public String getTemplateByCode(String templateCode) {// Templates are usually stored in a database. If templates are few and changes are few, use enumerations to manage them. Public class TemplateUtils {private TemplateUtils() {} public static String buildMessage(String template, buildMessage);  Map<String, Object> info) { ST st = new ST(template, '{', '}'); st.add("info", info); return st.render(); }}Copy the code

Template management can through database management or configuration file, the specific implementation can according to their scenario, fixed template code needs to be greater than the configuration (agreement), so in the business system corresponding to the template code written to death according to the business, in the message notification service as long as it doesn’t change the template code, can adjust template; The following sections describe the dependencies used for template processing and using Fan

Template processing

Through several demos to demonstrate the use of template operations for your reference

Dear customer, the XXX product you purchased has been delivered and is expected to arrive at the destination in XXX time. Please note that check.

Introducing the ST4

<! Antlr </groupId> <artifactId>ST4</artifactId> <version>4.0.8</version> <scope>compile</scope> </dependency>Copy the code

The sample code

Public static void main(String[] args) {String template = "Dear customer, the < info.goodsname > product you purchased has been shipped, ArrivalTime is expected to arrive in < info.arrivaltime >, please check. ; Map<String, Object> info = new HashMap<>(2); Info.put ("goodsName", "iPhone 13 Pro Max 1T far peak blue "); info.put("arrivalTime", "2021-10-01"); ST st = new ST(template, '<', '>'); st.add("info", info); System.out.println(st.render()); }Copy the code

ST placeholder to template variable method is more flexible, but this need the appointment, variable content boundary using ‘{}’ or ‘< >’ is fine, variable content needs are XXX. XXX, because our complex template data may come from many different data, such as a logistics information data may come from the user basic information and order information and logistics information, These data may have the same variable name, as shown below:

Public static void main(String[] args) {String template = "dear {user.name}, {goods.name} The logistics number is {logistics. Number} and the estimated arrivalTime is {logistics. ArrivalTime}. ; Map<String, Object> goods = new HashMap<>(2); Goods. Put ("name", "iPhone 13 Pro Max 1T Far Blue "); goods.put("price", 12999); Map<String, Object> user = new HashMap<>(2); User. Put ("name", "Ms. Yang "); user.put("age", 38); Map<String, Object> logistics = new HashMap<>(2); logistics.put("arrivalTime", "2021-10-01"); Logistics. Put ("destination", "Hangzhou "); logistics.put("number", "SF10000001"); ST st = new ST(template, '{', '}'); st.add("user", user); st.add("goods", goods); st.add("logistics", logistics); System.out.println(st.render()); }Copy the code

conclusion

A message notification service technical difficulties is not large, more is combined with their own business scenarios, business to design, in micro service scenario, a convenient and flexible notification service that can reduce the entire business system on the news to the business development, the business process of timing notice to inform the service is also a feasible scheme, Combined with redis List + scheduled task, the technical solution is simple and the business flexibility is strong. The business system needs to send messages to different users at different points in time. All the messages need to be sent to the message system at one point in time. Just need to start a business system daily timing to perform a task, a will all of the notice sent to the messaging system, messaging system according to the notice time to notice, such business easier for the business system, all notification can be through a regular daily performs a task all system can be sent to the news, and reduces the frequency of the task, The stress of message notification only needs to be handled by the message service.

Upgrade space

  • Kafka changed to batch consumption, see article
  • Topic partitioned consumption, distributed deployment of messaging services, see article
  • Split different types of notifications into multiple topics, such as message-SMS message-mail message-push, which are consumed separately
  • The sending part is upgraded to multithreading

The purpose of this paper is to share technical solutions and business solutions. The business details are abstract, and some of them are replaced by pseudo-codes. If you have any questions, you can raise them in comments