微信公众号和服务号都提供了事件通知接入能力,开发者可以自行接入通知并做后续业务处理。
本文以 SpringBoot 工程为例,介绍通过开源 SDK 简化接入流程。
一、引入微信公众号 Java SDK
开源地址:https://github.com/binarywang/WxJava
Maven 依赖:
<!-- 微信公众号 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>4.6.0</version>
</dependency>
二、SpringBoot 配置
wx:
mp:
app-id: xxxxxx
secret: xxxxxx
token: xxxxxx
aes-key: xxxxxx
三、事件通知接口
package cn.junki.project.server.controller;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 微信公众号接口
*
* @author Junki
* @since 2025-02-28
*/
@Api(tags = "微信公众号接口")
@Slf4j
@RestController
@RequestMapping("mp")
public class MpController {
@Resource
private WxMpService wxMpService;
@Resource
private WxMpMessageRouter messageRouter;
@GetMapping(path = "receive", produces = "text/plain;charset=utf-8")
public String authGet(
@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echoStr) {
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
return null;
}
return echoStr;
}
@PostMapping(path = "receive", produces = "application/xml; charset=UTF-8")
public String post(
@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("openid") String openid,
@RequestParam(name = "encrypt_type", required = false) String encType,
@RequestParam(name = "msg_signature", required = false) String msgSignature) {
log.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
return null;
}
String out = null;
if (encType == null) {
// 明文传输的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toXml();
} else if ("aes".equalsIgnoreCase(encType)) {
// aes加密的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxMpService.getWxMpConfigStorage(),
timestamp, nonce, msgSignature);
log.info("\n消息解密后内容为:\n{} ", inMessage.toString());
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage());
}
log.info("\n组装回复信息:{}", out);
return out;
}
private WxMpXmlOutMessage route(WxMpXmlMessage message) {
try {
return this.messageRouter.route(message);
} catch (Exception e) {
log.error("路由消息时出现异常!", e);
}
return null;
}
}
其中 GET 请求接口用于连通性认证。
其中 POST 请求接口用于事件消息接收。针对加密和非加密消息单独做处理,并交由路由器来处理不同的事件消息,最终返回响应。
四、自定义事件处理器
用户关注事件处理:
package cn.junki.project.server.handler;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 微信公众号事件处理:用户关注
*
* @author Junki
* @since 2025-02-28
*/
@Slf4j
@Component
public class WxMpSubscribeHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
log.info("新关注用户 OPENID: " + wxMessage.getFromUser());
// 获取微信用户基本信息
try {
WxMpUser userWxInfo = wxMpService.getUserService()
.userInfo(wxMessage.getFromUser(), null);
if (userWxInfo != null) {
// TODO 处理用户信息
}
} catch (WxErrorException e) {
log.info("获取公众号用户信息错误", e);
}
WxMpXmlOutMessage responseResult = null;
try {
responseResult = this.handleSpecial(wxMessage);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
if (responseResult != null) {
return responseResult;
}
try {
return WxMpXmlOutMessage.TEXT()
.content("感谢关注")
.fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser())
.build();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
/**
* 处理特殊请求,比如如果是扫码进来的,可以做相应处理
*/
private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage)
throws Exception {
// TODO
return null;
}
}
用户取消关注事件处理:
package cn.junki.project.server.handler;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 微信公众号事件处理:用户取消关注
*
* @author Junki
* @since 2025-02-28
*/
@Slf4j
@Component
public class WxMpUnsubscribeHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
String openId = wxMessage.getFromUser();
log.info("取消关注用户 OPENID: " + openId);
// 获取微信用户基本信息
try {
WxMpUser userWxInfo = wxMpService.getUserService()
.userInfo(wxMessage.getFromUser(), null);
if (userWxInfo != null) {
// TODO 处理用户信息
}
} catch (WxErrorException e) {
log.info("获取公众号用户信息错误", e);
}
return null;
}
}
五、注册事件处理器
package cn.junki.project.server.handler;
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;
/**
* 微信公众号事件处理路由
* <p>
* https://github.com/binarywang/weixin-java-mp-demo/blob/master/src/main/java/com/github/binarywang/demo/wx/mp/config/WxMpConfiguration.java
*
* @author Junki
* @since 2025-02-28
*/
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpRouterConfiguration {
@Resource
private WxMpSubscribeHandler wxMpSubscribeHandler;
@Resource
private WxMpUnsubscribeHandler wxMpUnsubscribeHandler;
@Bean
public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 关注事件
newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.wxMpSubscribeHandler).end();
// 取消关注事件
newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.wxMpUnsubscribeHandler).end();
return newRouter;
}
}
这里注册了自定义的两个事件处理器。