# Conflicts:
#	yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
This commit is contained in:
YunaiV
2025-05-05 09:33:51 +08:00
39 changed files with 1025 additions and 725 deletions

View File

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mp.controller.admin.open;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO;
@@ -35,26 +36,6 @@ public class MpOpenController {
@Resource
private MpAccountService mpAccountService;
/**
* 接收微信公众号的校验签名
*
* 对应 <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">文档</a>
*/
@Operation(summary = "校验签名") // 参见
@GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8")
public String checkSignature(@PathVariable("appId") String appId,
MpOpenCheckSignatureReqVO reqVO) {
log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO);
// 校验请求签名
WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
// 校验通过
if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {
return reqVO.getEchostr();
}
// 校验不通过
return "非法请求";
}
/**
* 接收微信公众号的消息推送
*
@@ -62,6 +43,7 @@ public class MpOpenController {
*/
@Operation(summary = "处理消息")
@PostMapping(value = "/{appId}", produces = "application/xml; charset=UTF-8")
@TenantIgnore
public String handleMessage(@PathVariable("appId") String appId,
@RequestBody String content,
MpOpenHandleMessageReqVO reqVO) {
@@ -79,6 +61,27 @@ public class MpOpenController {
}
}
/**
* 接收微信公众号的校验签名
*
* 对应 <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">文档</a>
*/
@Operation(summary = "校验签名") // 参见
@GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8")
@TenantIgnore
public String checkSignature(@PathVariable("appId") String appId,
MpOpenCheckSignatureReqVO reqVO) {
log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO);
// 校验请求签名
WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
// 校验通过
if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {
return reqVO.getEchostr();
}
// 校验不通过
return "非法请求";
}
private String handleMessage0(String appId, String content, MpOpenHandleMessageReqVO reqVO) {
// 校验请求签名
WxMpService mppService = mpServiceFactory.getRequiredMpService(appId);

View File

@@ -21,7 +21,7 @@ public interface MpUserMapper extends BaseMapperX<MpUserDO> {
}
default MpUserDO selectByAppIdAndOpenid(String appId, String openid) {
return selectOne(MpUserDO::getAppId, appId,
return selectFirstOne(MpUserDO::getAppId, appId,
MpUserDO::getOpenid, openid);
}

View File

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mp.service.handler.menu;
import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
import cn.iocoder.yudao.module.mp.service.menu.MpMenuService;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMenuService;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
@@ -13,8 +12,6 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
/**
* 自定义菜单的事件处理器
*

View File

@@ -29,7 +29,7 @@ public class MessageReceiveHandler implements WxMpMessageHandler {
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService wxMpService, WxSessionManager sessionManager) {
log.info("[handle][接收到请求消息,内容:{}]", wxMessage);
mpMessageService.receiveMessage(MpContextHolder.getAppId(), wxMessage);
mpMessageService.receiveMessage(wxMpService, MpContextHolder.getAppId(), wxMessage);
return null;
}

View File

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageP
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
@@ -31,7 +32,7 @@ public interface MpMessageService {
* @param appId 微信公众号 appId
* @param wxMessage 消息
*/
void receiveMessage(String appId, WxMpXmlMessage wxMessage);
void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage);
/**
* 使用公众号,给粉丝回复消息

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.mp.service.message;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;
@@ -18,6 +18,9 @@ import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
import jakarta.annotation.Resource;
import jakarta.validation.Validator;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
@@ -25,12 +28,12 @@ import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
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.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MESSAGE_SEND_FAIL;
@@ -69,18 +72,31 @@ public class MpMessageServiceImpl implements MpMessageService {
}
@Override
public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
@SneakyThrows
public void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage) {
// 获得关联信息
MpAccountDO account = mpAccountService.getAccountFromCache(appId);
Assert.notNull(account, "公众号账号({}) 不存在", appId);
// 订阅事件不记录,因为此时公众号粉丝表中还没有此粉丝的数据
// TODO @芋艿:这个修复,后续看看还有啥问题
if (ObjUtil.equal(wxMessage.getEvent(), WxConsts.EventType.SUBSCRIBE)) {
return;
}
// 获取用户
MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser());
if (user == null) {
// 特殊情况:因为 receiveMessage 是异步记录,可能 SubscribeHandler 还没存储好 User此时 sleep 轮询
for (int i = 0; i < 3; i++) {
ThreadUtil.sleep(5, TimeUnit.SECONDS);
user = mpUserService.getUser(appId, wxMessage.getFromUser());
if (user != null) {
break;
}
log.warn("[receiveMessage][粉丝({}/{}) 不存在,第 {} 次重试失败]", appId, wxMessage.getFromUser(), i + 1);
}
}
// 特殊情况:可能 SubscribeHandler 没处理正确(例如说发生异常),则主动创建
if (user == null) {
log.warn("[receiveMessage][粉丝({}/{}) 不存在,主动创建]", appId, wxMessage.getFromUser());
WxMpUser wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
user = mpUserService.saveUser(appId, wxMpUser);
}
Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
// 记录消息