一、前言
当公众号接收到普通消息时,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;
}