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

核心问题

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

使用工具

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

具体实现

实现思路

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

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

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

代码说明

  • WxPortalController.java
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
@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;
}
}

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

  • WxMpServiceHelper.java
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
@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;
}
}

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