Spring Boot集成WxJava实现的微信公众号Java后端图文教程
以下内容作者学习自用,仅供参考
一、公众号准备
1. 如果没有公众号先申请一个测试帐号
申请地址:微信公众平台接口测试帐号申请
直接微信扫码登录,登录后会显示需要的参数配置
2. 配置公众号账号

这个地址是微信公众号平台回调系统的。需要暴漏到公网可以访问
二、消息与事件推送介绍
1. 验证消息的确来自微信服务器
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
| 参数 | 描述 |
|---|---|
| signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
| timestamp | 时间戳 |
| nonce | 随机数 |
| echostr | 随机字符串 |
2. 集成微信开发 Java SDK
<weixin-java.version>4.7.9-20251224.161447</weixin-java.version>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId><!-- 微信支付 -->
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId><!-- 微信公众号 -->
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId><!-- 微信小程序 -->
<version>${weixin-java.version}</version>
</dependency>通过方法验证:
WxMpService /** * <pre> * 验证消息的确来自微信服务器. * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN * </pre> * * @param timestamp 时间戳,字符串格式 * @param nonce 随机串,字符串格式 * @param signature 签名,字符串格式 * @return 是否验证通过,true表示验证通过,false表示验证失败 */ boolean checkSignature(String timestamp, String nonce, String signature);
3. 解析消息
a. 根据 encrypt_type 判断消息加密类型
如果为空直接解析
WxMpXmlMessage.fromXml(content);
如果是 aes 加密
WxMpXmlMessage.fromEncryptedXml(content, mppService.getWxMpConfigStorage(),
reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getMsg_signature());b. 结果交给微信消息路由器处理
// 构建消息路由器
WxMpMessageRouter router = new WxMpMessageRouter(mpService);
// 记录所有事件的日志(异步执行)
router.rule().handler(messageReceiveHandler).next();
// 接收客服会话管理事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
.handler(kfSessionHandler).end();
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
.handler(kfSessionHandler)
.end();
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
.handler(kfSessionHandler).end();
// 门店审核事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.POI_CHECK_NOTIFY)
.handler(storeCheckNotifyHandler).end();
// 自定义菜单事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end();
// 点击菜单连接事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end();
// 关注事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler)
.end();
// 取消关注事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.UNSUBSCRIBE)
.handler(unsubscribeHandler).end();
// 上报地理位置事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.LOCATION).handler(locationHandler)
.end();
// 接收地理位置消息
router.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
.handler(locationHandler).end();
// 扫码事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SCAN).handler(scanHandler).end();
// 默认
router.rule().async(false).handler(messageAutoReplyHandler).end(); // 交给创建的路由器处理
WxMpXmlOutMessage outMessage = router.route(inMessage);c. 返回消息
if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式
return outMessage.toXml();
} else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式
return outMessage.toEncryptedXml(mppService.getWxMpConfigStorage());
}三、公众号粉丝管理
能力接入 / 用户管理 <=官方文档
自 2021-12-27 开始,公众号接口不再返回头像和昵称,只能通过微信公众号的网页登录获取。
1. 同步粉丝
第一步,从公众号递归加载粉丝
WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getId());
WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid);
if (CollUtil.isEmpty(wxUserList.getOpenids())) {
return null;
}使用的获取方法:
/**
* <pre>
* 获取用户列表
* 公众号可通过本接口来获取账号的关注者列表,
* 关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的)组成。
* 一次拉取调用最多拉取10000个关注者的OpenID,可以通过多次拉取的方式来满足需求。
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140840&token=&lang=zh_CN
* http请求方式: GET(请使用https协议)
* 接口地址:https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
* </pre>
*
* @param nextOpenid 可选,第一个拉取的OPENID,null为从头开始拉取
* @return 用户列表,包含关注者OpenID列表和下一个OpenID
* @throws WxErrorException 微信API调用异常
*/
WxMpUserList userList(String nextOpenid) throws WxErrorException;第二步,分批加载粉丝信息,并存储数据库
List<List<String>> openidsList = CollUtil.split(wxUserList.getOpenids(), 100);
for (List<String> openids : openidsList) {
log.info("[syncUser][批量加载粉丝信息,openids({})]", openids);
List<WxMpUser> wxUsers = mpService.getUserService().userInfoList(openids);
batchSaveUser(account, wxUsers);
}第三步,返回下一次的 nextOpenId 继续递归加载粉丝
return wxUserList.getNextOpenid();
完整代码
@Override
@Async
public void syncUser(Long accountId) {
MpAccountDO account = mpAccountService.getRequiredAccount(accountId);
// for 循环,避免递归出意外问题,导致死循环
String nextOpenid = null;
for (int i = 0; i < Short.MAX_VALUE; i++) {
log.info("[syncUser][第({}) 次加载公众号粉丝列表,nextOpenid({})]", i, nextOpenid);
try {
nextOpenid = syncUser0(account, nextOpenid);
} catch (WxErrorException e) {
log.error("[syncUser][第({}) 次同步粉丝异常]", i, e);
break;
}
// 如果 nextOpenid 为空,表示已经同步完毕
if (StrUtil.isEmpty(nextOpenid)) {
break;
}
}
}
private String syncUser0(MpAccountDO account, String nextOpenid) throws WxErrorException {
// 第一步,从公众号流式加载粉丝
WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getId());
WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid);
if (CollUtil.isEmpty(wxUserList.getOpenids())) {
return null;
}
// 第二步,分批加载粉丝信息
List<List<String>> openidsList = CollUtil.split(wxUserList.getOpenids(), 100);
for (List<String> openids : openidsList) {
log.info("[syncUser][批量加载粉丝信息,openids({})]", openids);
List<WxMpUser> wxUsers = mpService.getUserService().userInfoList(openids);
batchSaveUser(account, wxUsers);
}
// 返回下一次的 nextOpenId
return wxUserList.getNextOpenid();
}
private void batchSaveUser(MpAccountDO account, List<WxMpUser> wxUsers) {
if (CollUtil.isEmpty(wxUsers)) {
return;
}
// 1. 获得数据库已保存的粉丝列表
List<MpUserDO> dbUsers = mpUserMapper.selectListByAppIdAndOpenid(account.getAppId(),
CollectionUtils.convertList(wxUsers, WxMpUser::getOpenId));
Map<String, MpUserDO> openId2Users = CollectionUtils.convertMap(dbUsers, MpUserDO::getOpenid);
// 2.1 根据情况,插入或更新
List<MpUserDO> users = MpUserConvert.INSTANCE.convertList(account, wxUsers);
List<MpUserDO> newUsers = new ArrayList<>();
for (MpUserDO user : users) {
MpUserDO dbUser = openId2Users.get(user.getOpenid());
if (dbUser == null) { // 新增:稍后批量插入
newUsers.add(user);
} else { // 更新:直接执行更新
user.setId(dbUser.getId());
mpUserMapper.updateById(user);
}
}
// 2.2 批量插入
if (CollUtil.isNotEmpty(newUsers)) {
mpUserMapper.insertBatch(newUsers);
}
}
2. 粉丝标签管理
@Override
@Transactional(rollbackFor = Exception.class)
public void syncTag(Long accountId) {
MpAccountDO account = mpAccountService.getRequiredAccount(accountId);
// 第一步,从公众号平台获取最新的标签列表
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
List<WxUserTag> wxTags;
try {
wxTags = mpService.getUserTagService().tagGet();
} catch (WxErrorException e) {
throw exception(TAG_GET_FAIL, e.getError().getErrorMsg());
}
// 第二步,合并更新回自己的数据库;由于标签只有 100 个,所以直接 for 循环操作
Map<Long, MpTagDO> tagMap = convertMap(mpTagMapper.selectListByAccountId(accountId),
MpTagDO::getTagId);
wxTags.forEach(wxTag -> {
MpTagDO tag = tagMap.remove(wxTag.getId());
// 情况一,不存在,新增
if (tag == null) {
tag = MpTagConvert.INSTANCE.convert(wxTag, account);
mpTagMapper.insert(tag);
return;
}
// 情况二,存在,则更新
mpTagMapper.updateById(new MpTagDO().setId(tag.getId())
.setName(wxTag.getName()).setCount(wxTag.getCount()));
});
// 情况三,部分标签已经不存在了,删除
if (CollUtil.isNotEmpty(tagMap)) {
mpTagMapper.deleteByIds(convertList(tagMap.values(), MpTagDO::getId));
}
}3. 接收普通消息
/**
* 保存微信消息的事件处理器
*/
@Component
@Slf4j
public class MessageReceiveHandler implements WxMpMessageHandler {
@Resource
private MpMessageService mpMessageService;
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService wxMpService, WxSessionManager sessionManager) {
log.info("[handle][接收到请求消息,内容:{}]", wxMessage);
mpMessageService.receiveMessage(wxMpService, MpContextHolder.getAppId(), wxMessage);
return null;
}
}4. 被动回复用户消息
/**
* 使用公众号,给粉丝回复消息
*
* 例如说:自动回复、客服消息、菜单回复消息等场景
*
* 注意,该方法只是返回 WxMpXmlOutMessage 对象,不会真的发送消息
*
* @param sendReqBO 消息内容
* @return 微信回复消息 XML
*/
WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO); @Override
public WxMpXmlOutMessage sendOutMessage(MpMessageSendOutReqBO sendReqBO) {
// 校验消息格式
MpUtils.validateMessage(validator, sendReqBO.getType(), sendReqBO);
// 获得关联信息
MpAccountDO account = mpAccountService.getAccountFromCache(sendReqBO.getAppId());
Assert.notNull(account, "公众号账号({}) 不存在", sendReqBO.getAppId());
MpUserDO user = mpUserService.getUser(sendReqBO.getAppId(), sendReqBO.getOpenid());
Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid());
// 记录消息
MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user).
setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
downloadMessageMedia(message);
mpMessageMapper.insert(message);
// 转换返回 WxMpXmlOutMessage 对象
return MpMessageConvert.INSTANCE.convert02(message, account);
}5. 发送客服消息
/**
* 使用公众号,给粉丝发送【客服】消息
*
* 注意,该方法会真实发送消息
*
* @param sendReqVO 消息内容
* @return 消息
*/
MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO); @Override
public MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO) {
// 校验消息格式
MpUtils.validateMessage(validator, sendReqVO.getType(), sendReqVO);
// 获得关联信息
MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId());
MpAccountDO account = mpAccountService.getRequiredAccount(user.getAccountId());
// 发送客服消息
WxMpKefuMessage wxMessage = MpMessageConvert.INSTANCE.convert(sendReqVO, user);
WxMpService mpService = mpServiceFactory.getRequiredMpService(user.getAppId());
try {
mpService.getKefuService().sendKefuMessageWithResponse(wxMessage);
} catch (WxErrorException e) {
throw exception(MESSAGE_SEND_FAIL, e.getError().getErrorMsg());
}
// 记录消息
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user)
.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
downloadMessageMedia(message);
mpMessageMapper.insert(message);
return message;
}6. 自动回复
/**
* 当粉丝关注时,自动回复
*
* @param appId 微信公众号 appId
* @param wxMessage 消息
* @return 回复的消息
*/
@Override
public WxMpXmlOutMessage replyForSubscribe(String appId, WxMpXmlMessage wxMessage) {
// 第一步,匹配自动回复
List<MpAutoReplyDO> replies = mpAutoReplyMapper.selectListByAppIdAndSubscribe(appId);
MpAutoReplyDO reply = CollUtil.isNotEmpty(replies) ? CollUtil.getFirst(replies)
: buildDefaultSubscribeAutoReply(appId); // 如果不存在,提供一个默认末班
// 第二步,基于自动回复,创建消息
MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply);
return mpMessageService.sendOutMessage(sendReqBO);
}/**
* 当收到消息时,自动回复
*
* @param appId 微信公众号 appId
* @param wxMessage 消息
* @return 回复的消息
*/
@Override
public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) {
// 第一步,匹配自动回复
List<MpAutoReplyDO> replies = null;
// 1.1 关键字
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) {
// 完全匹配
replies = mpAutoReplyMapper.selectListByAppIdAndKeywordAll(appId, wxMessage.getContent());
if (CollUtil.isEmpty(replies)) {
// 模糊匹配
replies = mpAutoReplyMapper.selectListByAppIdAndKeywordLike(appId, wxMessage.getContent());
}
}
// 1.2 消息类型
if (CollUtil.isEmpty(replies)) {
replies = mpAutoReplyMapper.selectListByAppIdAndMessage(appId, wxMessage.getMsgType());
}
if (CollUtil.isEmpty(replies)) {
return null;
}
MpAutoReplyDO reply = CollUtil.getFirst(replies);
// 第二步,基于自动回复,创建消息
MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply);
return mpMessageService.sendOutMessage(sendReqBO);
}四、粉丝关注事件处理
用户关注公众号执行新增或更新粉丝信息
@Component
@Slf4j
public class SubscribeHandler implements WxMpMessageHandler {
@Resource
private MpUserService mpUserService;
@Resource
private MpAutoReplyService mpAutoReplyService;
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService weixinService, WxSessionManager sessionManager) throws WxErrorException {
// 第一步,从公众号平台,获取粉丝信息
log.info("[handle][粉丝({}) 关注]", wxMessage.getFromUser());
WxMpUser wxMpUser = null;
try {
wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
} catch (WxErrorException e) {
log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e);
// 特殊情况(个人账号,无接口权限):https://t.zsxq.com/cLFq5
if (ObjUtil.equal(e.getError().getErrorCode(), WxMpErrorMsgEnum.CODE_48001)) {
wxMpUser = new WxMpUser();
wxMpUser.setOpenId(wxMessage.getFromUser());
wxMpUser.setSubscribe(true);
wxMpUser.setSubscribeTime(System.currentTimeMillis() / 1000L);
}
}
// 第二步,保存粉丝信息
mpUserService.saveUser(MpContextHolder.getAppId(), wxMpUser);
// 第三步,回复关注的欢迎语
return mpAutoReplyService.replyForSubscribe(MpContextHolder.getAppId(), wxMessage);
}
}更新取关操作
@Component
@Slf4j
public class UnsubscribeHandler implements WxMpMessageHandler {
@Resource
@Lazy // 延迟加载,解决循环依赖的问题
private MpUserService mpUserService;
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
log.info("[handle][粉丝({}) 取消关注]", wxMessage.getFromUser());
mpUserService.updateUserUnsubscribe(MpContextHolder.getAppId(), wxMessage.getFromUser());
return null;
}
}五、模版消息
参考官网文档 能力接入 / 模板消息 / 模板消息介绍
在微信公众号平台中,新增测试模板,新增一个测试模版消息

发送模板消息:
// 发送模版消息
WxMpTemplateMessage templateMessage = buildTemplateMessage(template, user, sendReqVO);
try {
mpServiceFactory.getRequiredMpService(template.getAppId())
.getTemplateMsgService().sendTemplateMsg(templateMessage);
} catch (WxErrorException e) {
throw exception(MESSAGE_TEMPLATE_SEND_FAIL, e.getError().getErrorMsg());
}
构建模板消息内容
private WxMpTemplateMessage buildTemplateMessage(MpMessageTemplateDO msgTemplateDO, MpUserDO user,
MpMessageTemplateSendReqVO sendReqVO) {
List<WxMpTemplateData> data = new ArrayList<>();
WxMpTemplateMessage.WxMpTemplateMessageBuilder builder = WxMpTemplateMessage.builder()
.templateId(msgTemplateDO.getTemplateId()) // 公众号模板 ID
.data(data)
.toUser(user.getOpenid());
// 设置跳转链接
if (StrUtil.isNotBlank(sendReqVO.getUrl())) {
builder.url(sendReqVO.getUrl());
}
// 设置小程序跳转
if (StrUtil.isNotBlank(sendReqVO.getMiniprogram())) {
// https://developers.weixin.qq.com/doc/service/api/notify/template/api_sendtemplatemessage.html#Body__miniprogram
builder.miniProgram(JsonUtils.parseObject(sendReqVO.getMiniprogram(), WxMpTemplateMessage.MiniProgram.class));
}
// 设置模板数据
if (sendReqVO.getData() != null) {
sendReqVO.getData().forEach((key, value) -> data.add(new WxMpTemplateData(key, value)));
}
return builder.build();
}总结
到此这篇关于Spring Boot集成WxJava实现的微信公众号Java后端的文章就介绍到这了,更多相关WxJava实现微信公众号后端内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot从Service层获取request.getHeader()的几种方式
在SpringBoot中,可以通过多种方式从Service层获取request.getHeader(),包括通过方法参数传递、使用RequestContextHolder、ThreadLocal、拦截器、注入HttpServletRequest等,推荐使用方法参数传递,适用于大多数场景,根据具体需求,可以选择适合的方案2026-01-01
Tomcat报错:HTTP Status 500 (Wrapper cannot find servlet class)
这篇文章主要介绍了Tomcat报错:HTTP Status 500 (Wrapper cannot find servlet class)解决办法的相关资料,需要的朋友可以参考下2016-11-11
java8时间 yyyyMMddHHmmss格式转为日期的代码
这篇文章主要介绍了java8时间 yyyyMMddHHmmss格式转为日期的代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2020-09-09
Spring 中使用 Validation 注解校验参数的方法
本文介绍了如何在Spring中使用Validation注解进行参数校验,包括引入依赖、简单示例、常见校验注解分类与说明、分组校验和自定义校验,通过这些方法,可以方便地对Controller、Service等层面的参数进行校验,确保数据的合法性和一致性,感兴趣的朋友跟随小编一起看看吧2024-11-11
IDEA Error:java:无效的源发行版:13的解决过程
之前用idea运行时,也会出现这种情况,后面通过网上的资料解决了这个问题,下面这篇文章主要给大家介绍了关于IDEA Error:java:无效的源发行版:13的解决过程,需要的朋友可以参考下2023-01-01
详解Mybatis逆向工程中使用Mysql8.0版本驱动遇到的问题
今天在使用 8.0.12 版的 mysql 驱动时遇到了各种各样的坑。这篇文章主要介绍了详解Mybatis逆向工程中使用Mysql8.0版本驱动遇到的问题,感兴趣的小伙伴们可以参考一下2018-10-10
关于idea-web.xml版本过低怎么生成新的(web.xml报错)问题
今天通过本文给大家分享idea-web.xml版本过低怎么生成新的(web.xml报错)问题,通过更换web.xml版本解决此问题,感兴趣的朋友跟随小编一起看看吧2021-07-07


最新评论