package cn.iocoder.yudao.module.mp.service.message;
|
|
import cn.hutool.core.lang.Assert;
|
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;
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;
|
import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
|
import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper;
|
import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
|
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
|
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
|
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;
|
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 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;
|
|
/**
|
* 粉丝消息 Service 实现类
|
*
|
* @author 芋道源码
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class MpMessageServiceImpl implements MpMessageService {
|
|
@Resource
|
@Lazy // 延迟加载,避免循环依赖
|
private MpAccountService mpAccountService;
|
@Resource
|
private MpUserService mpUserService;
|
@Resource
|
private MpMaterialService mpMaterialService;
|
|
@Resource
|
private MpMessageMapper mpMessageMapper;
|
|
@Resource
|
@Lazy // 延迟加载,解决循环依赖的问题
|
private MpServiceFactory mpServiceFactory;
|
|
@Resource
|
private Validator validator;
|
|
@Override
|
public PageResult<MpMessageDO> getMessagePage(MpMessagePageReqVO pageReqVO) {
|
return mpMessageMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
@SneakyThrows
|
public void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage) {
|
// 获得关联信息
|
MpAccountDO account = mpAccountService.getAccountFromCache(appId);
|
Assert.notNull(account, "公众号账号({}) 不存在", appId);
|
|
// 获取用户
|
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());
|
|
// 记录消息
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user)
|
.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
downloadMessageMedia(message);
|
mpMessageMapper.insert(message);
|
}
|
|
@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);
|
}
|
|
@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;
|
}
|
|
/**
|
* 下载消息使用到的媒体文件,并上传到文件服务
|
*
|
* @param message 消息
|
*/
|
private void downloadMessageMedia(MpMessageDO message) {
|
if (StrUtil.isNotEmpty(message.getMediaId())) {
|
message.setMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),
|
message.getMediaId(), MpUtils.getMediaFileType(message.getType())));
|
}
|
if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
|
message.setThumbMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),
|
message.getThumbMediaId(), WxConsts.MediaFileType.THUMB));
|
}
|
}
|
|
}
|