微信公众号管理平台是对多个公众号统一配置管理的平台,本文介绍了该平台的实现方案。项目地址:wechat-admin,该项目尚未开放,待对项目中的内容进行脱敏之后会公开。

# 核心问题

多个公众号如何复用同一个消息接口、同一套消息处理逻辑?

# 使用工具

微信Java开发工具包weixin-java-tools中的公众号开发工具weixin-java-mp

# 具体实现

# 实现思路

  1. 多个公众号使用统一的消息接收接口,并附带公众号在管理平台的id作为标识;

  2. 统一消息接收接口获取id,动态配置仅用于本次消息处理的消息路由;

  3. 消息路由根据实际接收到的消息事件做出响应。

# 代码说明

  • WxPortalController.java
@RestController
@RequestMapping("/portal/WxMpAccount/{id}")
public class WxPortalController {

    @Autowired
    private WxMpServiceHelper wxMpServiceHelper;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final String ENCRYPT_TYPE_AES = "aes";

    @ResponseBody
    @GetMapping(produces = "text/plain; charset=utf-8")
    public String authGet(@PathVariable Long id,
                          @RequestParam("signature") String signature,
                          @RequestParam("timestamp") String timestamp,
                          @RequestParam("nonce") String nonce,
                          @RequestParam("echostr") String echostr) {
        logger.info("\n接收到来自微信服务器的认证消息:[{},{},{},{}]", signature, timestamp, nonce, echostr);
        if (wxMpServiceHelper.wxMpService(id).checkSignature(timestamp, nonce, signature)) {
            return echostr;
        }
        return "非法请求";
    }

    @ResponseBody
    @PostMapping(produces = "application/xml; charset=utf-8")
    public String post(@PathVariable Long id,
                       @RequestBody String requestBody,
                       @RequestParam("timestamp") String timestamp,
                       @RequestParam("nonce") String nonce,
                       @RequestParam("signature") String signature,
                       @RequestParam(name = "encrypt_type", required = false) String encType,
                       @RequestParam(name = "msg_signature",required = false) String msgSignature) {
        logger.debug("\n接收微信请求:[signature=[{}], encType=[{}], msgSignature=[{}], timestamp=[{}], nonce=[{}], " +
                "requestBody=[\n{}\n] ", signature, encType, msgSignature, timestamp, nonce, requestBody);
        if (!wxMpServiceHelper.wxMpService(id).checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
        }
        String out  = null;
        if (encType == null) {
            WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
            WxMpXmlOutMessage outMessage = wxMpServiceHelper.wxMpMessageRouter(id).route(inMessage);
            out = outMessage == null ? "" : outMessage.toXml();
        } else if (ENCRYPT_TYPE_AES.equalsIgnoreCase(encType)) {
            WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody,
                    wxMpServiceHelper.wxMpService(id).getWxMpConfigStorage(), timestamp, nonce, msgSignature);
            logger.debug("\n消息解密后内容为:\n{}", inMessage.toString());
            WxMpXmlOutMessage outMessage = wxMpServiceHelper.wxMpMessageRouter(id).route(inMessage);
            out = outMessage == null ? "" : outMessage.toEncryptedXml(wxMpServiceHelper.wxMpService(id).getWxMpConfigStorage());
        }
        logger.debug("\n回复信息:{}", out);
        return out;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

微信消息统一接收接口,此处最核心的就是wxMpServiceHelper.wxMpMessageRouter(id).route(inMessage);

  • WxMpServiceHelper.java
@Component
public class WxMpServiceHelper {

    @Autowired
    private WxMpAccountRepository wxMpAccountRepository;

    @Autowired
    private SubscribeHandler subscribeHandler;

    @Autowired
    private WxEventHandlers handlers;

    @Autowired
    private MsgHandler msgHandler;

    public BaseWxServiceImpl wxMpService(Long id) {
        return new BaseWxServiceImpl(id, wxMpAccountRepository);
    }

    public WxMpMenuServiceImpl wxMpMenuService(Long id) {
        return new WxMpMenuServiceImpl(wxMpService(id));
    }

    public WxMpMessageRouter wxMpMessageRouter(Long id) {
        WxMpMessageRouter router = new WxMpMessageRouter(wxMpService(id));

        //记录所有事件的日志
        router.rule().handler(new LogHandler(id)).next();

        //关注公众号
        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler.getHandler(id)).end();

        //自定义处理逻辑
        @SuppressWarnings("unchecked")
        Map<String, WxMpMessageHandler> mpHandlerMap = (Map<String, WxMpMessageHandler>) MapUtils.getMap(handlers.getHandlerMap(), id);
        if (MapUtils.isNotEmpty(mpHandlerMap)) {
            for (Map.Entry<String, WxMpMessageHandler> handler : mpHandlerMap.entrySet()) {
                router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(handler.getKey()).handler(handler.getValue()).end();
            }
        }

        //自动回复消息
        router.rule().async(false).handler(msgHandler.getHandler(id)).end();

        return router;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

根据传入微信公众号在公众号管理平台中的id,动态获取该公众号的配置参数、消息类型与处理类路径的对应关系列表,并以此生成该公众号的消息路由。消息路由根据实际传入的消息事件作出响应。