Earlier, we introduced the configuration of each processor on the message processing chain of the server. This part is actually equivalent to the technical framework. Today, we will introduce the processor that processes specific business messages.
The processing framework consists of several main parts:
1. Message topic: we convert events into message topics according to business requirements. Message topics are equivalent to classifying different types of messages, which is the basic supporting part of our business processing.
2. Message processor factory: find the complete class name of the corresponding processor according to the message subject, and then create a specific message processor object through reflection technology.
3. Message processor: where specific business logic processing is really carried out, messages are divided into two types, and message processors also have two types. On the one hand, common parts can be extracted to form parent classes, on the other hand, they can be inherited by specific business logic processors.
Let's introduce them one by one.
Message subject
As mentioned above, the message subject is basic data. In fact, it needs to be defined and maintained separately in platform management. The main attributes are as follows:
Code: subject code, which is consistent with the business event code and can uniquely identify a type of message;
handler: processor, which stores the complete path (package name plus class name) of the specific logical processor corresponding to the message subject, and then the processor factory instantiates the processor through reflection according to this attribute;
responseTopicCode: response subject code. For request messages, the default response subject code needs to be configured to realize message response.
/** * Message subject * @author wqliu * @date 2021-08-21 * */ @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) @TableName("ip_api_message_topic") public class ApiMessageTopic extends BaseEntity { private static final long serialVersionUID = 1L; /** * code */ @TableField("code") private String code; /** * name */ @TableField("name") private String name; /** * processor */ @TableField("handler") private String handler; /** * Response subject code */ @TableField("response_topic_code") private String responseTopicCode; /** * classification */ @TableField("category") private String category; /** * state */ @TableField("status") private String status; /** * remarks */ @TableField("remark") private String remark; /** * Sort number */ @TableField("order_no") private String orderNo; }
Message processor factory
The simple factory pattern in the design pattern is used to get the complete path of the processor according to the message subject coding, and the instantiation of the processor is realized through reflection.
/** * Message processor factory * @author wqliu * @date 2021-10-13 9:07 **/ public class MessageHandlerFactory { private MessageHandlerFactory(){}; public static MessageHandler createHandler(String topic){ //Get classes using reflection techniques Class<MessageHandler> messageHandler=null; try { //Get the corresponding message processing class name according to the message subject ApiMessageTopicService service = SpringUtil.getBean(ApiMessageTopicService.class); String handlerName = service.getHandlerByCode(topic); messageHandler = (Class<MessageHandler>) Class.forName(handlerName); //Returns an instance of the message processing class return messageHandler.newInstance(); } catch (CustomException e){ throw new MessageException("S101",e.getMessage()); }catch (Exception e){ throw new MessageException("S102","Message processor does not exist"); } } }
In the technical framework of the message processing chain we designed, the request message processor and the response message processor are instantiated through the following code.
//Transfer to specific message processor for processing ResponseMessageHandler handler = (ResponseMessageHandler)MessageHandlerFactory.createHandler(topic); handler.handleMessage(message,ctx.channel());
Message processor
Because there are two types of messages, and the processing logic for request messages and response messages is different. Accordingly, there are also two message processors: request message processor RequestMessageHandler and response message processor ResponseMessageHandler. The common operations of the two processors can be extracted to form an abstract parent class MessageHandler.
The specific business message processor will inherit RequestMessageHandler or ResponseMessageHandler and override some of its methods.
Let's start with a relatively simple response message processor.
Response message processor
Mainly do three things
1. Validation data
The verification here is different from the basic verification in the technical framework. It mainly verifies the following contents: message subject verification (whether it exists and is available), application verification (whether it exists and is available), permission verification and timeliness verification.
2. Update log
Find the message log according to the request message ID, and then fill in the response part. The design and implementation of the message log will be described in detail later.
3. Execute proprietary logic
A messageOperation method is reserved. When a specific message processor has additional personalized logic to process, it only needs to cover this method. This is actually the application of the template method in the design pattern
/** * Response message processor * @author wqliu * @date 2022-1-8 11:07 **/ public class ResponseMessageHandler extends MessageHandler{ /** * Message processing * * @param message news * @param channel passageway */ public void handleMessage(ResponseMessage responseMessage, Channel channel) { //Validation framework validateFramework(responseMessage); // Update message log apiMessageLogService.updateResponsePart(responseMessage); //Special treatment messageOperation(responseMessage, channel); } /** * Response message processing * * @param message * @param channel */ protected void messageOperation(ResponseMessage message, Channel channel) { } }
Request message processor
The request message processor is much more complex than the response processor. In addition to verifying messages, creating message logs and reserving business logic methods, it also needs to implement the following two important logic:
- Send a response message to the client sending the request, and update the response part of the corresponding log record
- Copy and forward the message, find all clients subscribing to the message topic, and push the message.
/** * Request message processor * * @author wqliu * @date 2022-1-8 10:42 **/ @Slf4j public class RequestMessageHandler extends MessageHandler { /** * Message processing * * @param message news * @param channel passageway */ public void handleMessage(RequestMessage requestMessage, Channel channel) { // Log message requests apiMessageLogService.createRequestPart(requestMessage); //Validation framework validateFramework(requestMessage); //The request message status is set to no need to send by default apiMessageLogService.updateStatus(MessageStatusEnum.NOT_TO_REQUEST.name(),requestMessage.getId()); //Special treatment messageOperation(requestMessage, channel); //Send response sendResponse(channel, MessageResponseResultEnum.SUCCESS.name(), "", requestMessage.getId(), requestMessage.getTopic()); //Message processing (copy and forward) repostMessage(requestMessage); } /** * Message replication and forwarding * * @param requestMessage */ protected void repostMessage(RequestMessage requestMessage) { //Find subscriptions String topic = requestMessage.getTopic(); List<String> subscriberList = apiMessageSubscriptionService.getSubscriberList(topic); if (CollectionUtils.isEmpty(subscriberList)) { return; } //Create request message RequestMessage message = new RequestMessage(); //TODO: for the convenience of testing, the same client is used as the producer and consumer of the message. When forwarding the message, the message center attaches the message subject temp suffix message.setTopic(topic+".temp"); message.setContent(requestMessage.getContent()); message.setPublishAppCode(appConfig.getApiPlatformMessage().getMessageServerAppCode()); //Set the status to wait for sending message.setStatus(MessageStatusEnum.WAIT_REQUEST.name()); //Traverse subscribers and send messages subscriberList.stream().forEach(appCode -> { //Copy the message through prototype mode and change the response application code RequestMessage newMessage = message.clone(); newMessage.setResponseAppCode(appCode); //TODO: Data permission filtering boolean hasDataPermission = dataPermissionFilter(requestMessage, appCode); if (hasDataPermission) { //send message sendMessage(newMessage); } }); } /** * send message * * @param message */ protected void sendMessage(RequestMessage message) { String appCode = message.getResponseAppCode(); //Get docking mode String integrationModel = apiAppService.getIntegrationModelByAppCode(appCode); if (integrationModel.equals(IntegrationModelEnum.CLIENT.name())) { //Client mode Channel channel = MessageServerHolder.appChannelMap.get(appCode); // log.info("the server sends a business message to the subscriber {}: {}", appCode, JSON.toJSONString(message)); ChannelFuture channelFuture = channel.writeAndFlush(message); channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { //Set status message.setStatus(MessageStatusEnum.REQUESTED.name()); message.setSendCount(message.getSendCount() + 1); // Log message requests apiMessageLogService.createRequestPart(message); } }); } else if (integrationModel.equals(IntegrationModelEnum.INTERFACE.name())) { //The status is set to pending message.setStatus(MessageStatusEnum.WAIT_HANDLE.name()); // Log message requests apiMessageLogService.createRequestPart(message); } } /** * Request message processing * * @param message * @param channel */ protected void messageOperation(RequestMessage message, Channel channel) { } }
Among them, the related logic of message replication and forwarding is relatively complex. In the next part, we will specifically talk about it.
Common processor
The parent class of request and response processor is mainly the reuse of common part, which mainly completes the work of data verification and sending response.
/** * Message processing parent class * * @author wqliu * @date 2021-10-13 9:07 **/ @Slf4j public class MessageHandler { protected AppConfig appConfig = SpringUtil.getBean(AppConfig.class); protected ApiMessageLogService apiMessageLogService = SpringUtil.getBean(ApiMessageLogService.class); protected ApiMessageSubscriptionService apiMessageSubscriptionService= SpringUtil.getBean(ApiMessageSubscriptionService.class); protected ApiAppService apiAppService = SpringUtil.getBean(ApiAppService.class); protected ApiMessageTopicService apiMessageTopicService=SpringUtil.getBean(ApiMessageTopicService.class); protected ApiMessagePermissionService apiMessagePermissionService= SpringUtil.getBean(ApiMessagePermissionService.class); protected ApiDataPermissionService apiDataPermissionService= SpringUtil.getBean(ApiDataPermissionService.class); /** * Data permission wildcard */ public static final String DATA_PERMISSION_ALL = "*"; /** * Validation framework */ protected void validateFramework(BaseMessage message){ // Message subject verification (whether it exists and is available) validateTopic(message.getTopic()); // Application verification (existence and availability) validateAppCode(message.getPublishAppCode()); // Permission verification validatePermission(message.getPublishAppCode(),message.getTopic()); // Timeliness verification validatePublishTimeValid(message.getPublishTime()); } /** * Send response * * @param channel * @param result * @param errorCode * @param errorMessage * @param requestMessageId */ public void sendResponse(Channel channel, String result, String errorMessage, String requestMessageId, String topic) { // Organization response message ResponseMessage messageResponse = new ResponseMessage(); //By default, the response subject code is obtained from the message subject entity class, which can be overridden by subclasses messageResponse.setTopic(this.getResponseTopicCode(topic)); messageResponse.setContent(this.getResponseContent()); messageResponse.setPublishAppCode(appConfig.getApiPlatformMessage().getMessageServerAppCode()); messageResponse.setResult(result); messageResponse.setErrorMessage(errorMessage); messageResponse.setRequestMessageId(requestMessageId); // Send response to requestor channel.writeAndFlush(messageResponse); // Update message log apiMessageLogService.updateResponsePart(messageResponse); } /** * Verify permissions * * @param publishAppCode Application coding * @param topicCode Subject code */ protected void validatePermission(String publishAppCode, String topicCode) { boolean hasPermission = apiMessagePermissionService.checkPermission(publishAppCode, topicCode); if(hasPermission==false){ throw new MessageException("301", "Application has no permission"); } } /** * Verify timeliness * * @param publishTimeString Publishing time string */ protected void validatePublishTimeValid(String publishTimeString) { // The data validation phase has verified that it can be converted. The conversion exception will not be processed here SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date publishTime = null; try { publishTime = dateFormat.parse(publishTimeString); } catch (ParseException e) { // The pre sequence phase has verified the date format. No exception will be thrown here. It is only for compilation } // Get the current time of the system Date currentTime = new Date(); // Comparison time long diff = Math.abs(currentTime.getTime() - publishTime.getTime()); //Maximum allowable time difference in milliseconds int maxTimeSpan = 10*60*1000; if (diff > maxTimeSpan) { // Request time out of reasonable range (10 minutes) throw new MessageException("S401", "The release time is beyond the reasonable range"); } } /** * Verification application * * @param publishAppCode Application coding */ protected void validateAppCode(String publishAppCode) { try { ApiApp app =apiAppService.getByCode(publishAppCode); if(app.getStatus().equals(StatusEnum.DEAD.name())){ throw new MessageException("S202", "App disabled"); } }catch (Exception ex){ throw new MessageException("S201", "Invalid app ID"); } } /** * Verify subject code * * @param topicCode Subject code */ protected void validateTopic(String topicCode) { try { ApiMessageTopic messageTopic = apiMessageTopicService.getByCode(topicCode); if(messageTopic.getStatus().equals(StatusEnum.DEAD.name())){ throw new MessageException("S102", "Message subject not available"); } }catch (Exception ex){ throw new MessageException("S101", "Message subject does not exist"); } } /** * Get response message subject * * @return */ protected String getResponseTopicCode(String topic) { //By default, it is obtained from the message subject entity class ApiMessageTopicService service = SpringUtil.getBean(ApiMessageTopicService.class); String topicCode = service.getResponseTopicCodeByCode(topic); return topicCode; } /** * Get response message content * * @return */ protected String getResponseContent() { return StringUtils.EMPTY; } /** * Data permission filtering * @param message news * @param appCode Responder application code * @return true,Yes, false, no permission */ protected boolean dataPermissionFilter(RequestMessage message,String appCode){ //By default, it returns true, without Data permission control, and can be overridden by subclasses that need data permission control return true; } }