采取工厂+策略模式实现微信的消息解耦处理

6 阅读3分钟

一、前言

当公众号接收到普通消息时,MsgType为text,且content为消息内容

  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>

当公众号收到关注、取关时,MsgType为event,且event为事件类型,subscribe(订阅)、unsubscribe(取消订阅)

  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>

我们要根据不同的消息类型,返回发送不同的消息给用户,为考虑后续扩展更多的类型,采取工厂+策略模式实现微信的消息解耦处理。

二、工厂 + 策略 实现

2.1 首先定义用户行为的枚举类

package com.ssm.wechat.handler;

import lombok.Getter;

@Getter
public enum WeChatMsgTypeEnum {

    SUBSCRIBE("event.subscribe", "用户关注事件"),
    TEXT_MSG("text", "接收用户文本消息");

    private String msgType;

    private String desc;

    WeChatMsgTypeEnum(String msgType, String desc) {
        this.msgType = msgType;
        this.desc = desc;
    }

    public static WeChatMsgTypeEnum getByMsgType(String msgType) {
        for(WeChatMsgTypeEnum weChatMsgTypeEnum : WeChatMsgTypeEnum.values()) {
            if(weChatMsgTypeEnum.msgType.equals(msgType)) {
                return weChatMsgTypeEnum;
            }
        }
        return null;
    }
}

2.2 使用策略模式定义父类及实现类

public interface WeChatMsgHandler {
    WeChatMsgTypeEnum getMsgType();

    String dealMsg(Map<String, String> msgMap);
}
@Component
@Log4j2
public class SubscribeMsgHandler implements WeChatMsgHandler {
    @Override
    public WeChatMsgTypeEnum getMsgType() {
        return WeChatMsgTypeEnum.SUBSCRIBE;
    }

    @Override
    public String dealMsg(Map<String, String> msgMap) {
        log.info("触发用户关注事件!");

        String toUserName = msgMap.get("ToUserName"); //公众号
        String fromUserName = msgMap.get("FromUserName"); //关注者
        String subscribeContent = "感谢您的关注,欢迎来到坤坤Club!";

        String replyContent = "<xml>\n" +
                "  <ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>\n" +
                "  <FromUserName><![CDATA[" + toUserName + "]]></FromUserName>\n" +
                "  <CreateTime>12345678</CreateTime>\n" +
                "  <MsgType><![CDATA[text]]></MsgType>\n" +
                "  <Content><![CDATA[" + subscribeContent + "]]></Content>\n" +
                "</xml>\n";
        return replyContent;
    }
}
@Component
@Log4j2
public class ReceiveTextMsgHandler implements WeChatMsgHandler {

    private static final String KEY_WORD = "验证码";

    private static final String LOGIN_PREFIX = "loginCode"; //login验证码前缀
    @Resource
    private RedisUtil redisUtil;

    @Override
    public WeChatMsgTypeEnum getMsgType() {
        return WeChatMsgTypeEnum.TEXT_MSG;
    }

    @Override
    public String dealMsg(Map<String, String> msgMap) {
        log.info("接收到文本消息事件");

        String toUserName = msgMap.get("ToUserName"); //公众号
        String fromUserName = msgMap.get("FromUserName"); //关注者
        String content = msgMap.get("Content"); //用户发送的消息

        //如果发送的不是验证码则返回(后续可对接AI大模型)
        if(!content.equals(KEY_WORD)) {
            return "<xml>\n" +
                    "  <ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>\n" +
                    "  <FromUserName><![CDATA[" + toUserName + "]]></FromUserName>\n" +
                    "  <CreateTime>12345678</CreateTime>\n" +
                    "  <MsgType><![CDATA[text]]></MsgType>\n" +
                    "  <Content><![CDATA[" + "聊天功能对接AI大模型中..." + "]]></Content>\n" +
                    "</xml>\n";
        }

        //生成随机数验证码
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 6; i ++) {
            sb.append(random.nextInt(10));
        }
        String subscribeContent = "您当前的验证码是:" + sb.toString() + ",5分钟内有效";

        //存入redis
        String numKey = redisUtil.buildKey(LOGIN_PREFIX, sb.toString());
        redisUtil.set(numKey, fromUserName, 5L, TimeUnit.MINUTES);

        String replyContent = "<xml>\n" +
                "  <ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>\n" +
                "  <FromUserName><![CDATA[" + toUserName + "]]></FromUserName>\n" +
                "  <CreateTime>12345678</CreateTime>\n" +
                "  <MsgType><![CDATA[text]]></MsgType>\n" +
                "  <Content><![CDATA[" + subscribeContent + "]]></Content>\n" +
                "</xml>\n";
        return replyContent;
    }

}

2.3 使用工厂模式根据入参时的用户行为类型获取不同实现类

@Component
public class WeChatMsgFactory implements InitializingBean {

    @Resource
    private List<WeChatMsgHandler> weChatMsgHandlerList;

    private Map<WeChatMsgTypeEnum, WeChatMsgHandler> msgHandlerMap = new HashMap<>();

    public WeChatMsgHandler getHandlerByMsgType(String msgType) {
        WeChatMsgTypeEnum msgTypeEnum = WeChatMsgTypeEnum.getByMsgType(msgType);
        return msgHandlerMap.get(msgTypeEnum);
    }

    /**
     * 初始化完当前Bean后执行(为map赋值)
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        for(WeChatMsgHandler weChatMsgHandler : weChatMsgHandlerList) {
            msgHandlerMap.put(weChatMsgHandler.getMsgType(), weChatMsgHandler);
        }
    }
}

三、controller入口层

/**
 * 接收普通信息接口(接收消息、关注、取关等):微信服务器向配置的URL地址发送POST请求,将xml数据包发送过来
 * @requestBody: 请求的消息体会放到requestBody里
 * @msg_signature: 开启加密模式时,post请求还会发送meg_signature消息用于验签,明文时不会发送
 */
@PostMapping(value = "callback", produces = "application/xml;charset=UTF-8")
public String callback(
        @RequestBody String requestBody,
                       @RequestParam("signature") String signature,
                       @RequestParam("timestamp") String timestamp,
                       @RequestParam("nonce") String nonce,
                       @RequestParam(value = "meg_signature", required = false) String msgSignature) {
    log.info("接收到微信的请求:request:{}", requestBody);
    Map<String, String> msgMap = MessageUtil.parseXml(requestBody);
    String msgType = msgMap.get("MsgType");
    String event = msgMap.get("Event") == null ? null : msgMap.get("Event");
    log.info("msgType:{}, event:{}", msgType, event);
    //构建key(text || event.subscribe)
    String msgTypeKey = msgType +  (event == null ? "" : "." + event);
    WeChatMsgHandler weChatMsgHandler = weChatMsgFactory.getHandlerByMsgType(msgTypeKey);
    //关注 和 发消息 以外的事件返回unknown
    if(Objects.isNull(weChatMsgHandler)) {
        return "unknown"; //只有xml形式的数据,公众号才会显示,返回unknown,不会显示
    }
    String replayContent = weChatMsgHandler.dealMsg(msgMap);
    log.info("replayContent:{}", replayContent);
    return replayContent;
}