mp:实现消息的存储

This commit is contained in:
YunaiV 2023-01-03 21:30:35 +08:00
parent fefad029ad
commit c141ebef3f
13 changed files with 294 additions and 185 deletions

View File

@ -10,7 +10,7 @@ import lombok.Getter;
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum MpMessageSendFrom { public enum MpMessageSendFromEnum {
USER_TO_MP(1, "用户发送给公众号"), USER_TO_MP(1, "用户发送给公众号"),
MP_TO_USER(2, "公众号发给用户"), MP_TO_USER(2, "公众号发给用户"),

View File

@ -4,8 +4,13 @@ import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.message.MpMessageDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*; import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*;
@ -20,4 +25,19 @@ public interface MpMessageConvert {
PageResult<MpMessageRespVO> convertPage(PageResult<MpMessageDO> page); PageResult<MpMessageRespVO> convertPage(PageResult<MpMessageDO> page);
@Mappings(value = {
@Mapping(source = "msgType", target = "type"),
})
MpMessageDO convert(WxMpXmlMessage wxMessage);
default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) {
MpMessageDO message = convert(wxMessage);
if (account != null) {
message.setAccountId(account.getId()).setAppId(account.getAppId());
}
if (user != null) {
message.setUserId(user.getId()).setOpenid(user.getOpenid());
}
return message;
}
} }

View File

@ -1,20 +1,15 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.message; package cn.iocoder.yudao.module.mp.dal.dataobject.message;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFrom; import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*; import lombok.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.api.WxConsts;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 微信消息 DO * 微信消息 DO
* *
@ -35,6 +30,10 @@ public class MpMessageDO extends BaseDO {
*/ */
@TableId @TableId
private Long id; private Long id;
/**
* 微信公众号消息 id
*/
private Long msgId;
/** /**
* 微信公众号 ID * 微信公众号 ID
* *
@ -56,17 +55,9 @@ public class MpMessageDO extends BaseDO {
/** /**
* 用户标识 * 用户标识
* *
* 冗余 {@link MpUserDO#getId()} * 冗余 {@link MpUserDO#getOpenid()}
*/ */
private String openId; private String openid;
// /**
// * 昵称
// */
// private String nickname;
// /**
// * 头像
// */
// private String headImageUrl;
/** /**
* 消息类型 * 消息类型
@ -77,11 +68,11 @@ public class MpMessageDO extends BaseDO {
/** /**
* 消息来源 * 消息来源
* *
* 枚举 {@link MpMessageSendFrom} * 枚举 {@link MpMessageSendFromEnum}
*/ */
private Integer sendFrom; private Integer sendFrom;
// ========= 消息内容 ========== // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
/** /**
* 消息内容 * 消息内容
@ -96,10 +87,26 @@ public class MpMessageDO extends BaseDO {
* 消息类型为 {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO * 消息类型为 {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/ */
private String mediaId; private String mediaId;
/**
* 媒体文件的 URL
*/
private String mediaUrl;
/**
* 语音识别后文本
*
* 消息类型为 {@link WxConsts.XmlMsgType} VOICE
*/
private String recognition;
/**
* 语音格式 amrspeex
*
* 消息类型为 {@link WxConsts.XmlMsgType} VOICE
*/
private String format;
/** /**
* 标题 * 标题
* *
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEOMUSIC * 消息类型为 {@link WxConsts.XmlMsgType} VIDEOMUSICLINK
*/ */
private String title; private String title;
/** /**
@ -109,38 +116,25 @@ public class MpMessageDO extends BaseDO {
*/ */
private String description; private String description;
/**
* 音乐链接
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String musicURL;
/**
* 高质量音乐链接WIFI 环境优先使用该链接播放音乐
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String hqMusicUrl;
/** /**
* 缩略图的媒体 id通过素材管理中的接口上传多媒体文件得到的 id * 缩略图的媒体 id通过素材管理中的接口上传多媒体文件得到的 id
* *
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC * 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/ */
private String thumbMediaId; private String thumbMediaId;
/**
* 缩略图的媒体 URL
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/
private String thumbMediaUrl;
/** /**
* 图文消息个数限制为 10 条以内 * 点击图文消息跳转链接
* *
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS * 消息类型为 {@link WxConsts.XmlMsgType} LINK
*/ */
private Integer articleCount; private String url;
/**
* 图文消息数组
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = ArticleTypeHandler.class)
private List<Article> articles;
/** /**
* 地理位置维度 * 地理位置维度
@ -160,47 +154,29 @@ public class MpMessageDO extends BaseDO {
* 消息类型为 {@link WxConsts.XmlMsgType} LOCATION * 消息类型为 {@link WxConsts.XmlMsgType} LOCATION
*/ */
private Double scale; private Double scale;
/** /**
* 文章 * 详细地址
*/
@Data
public static class Article implements Serializable {
/**
* 图文消息标题
*/
private String title;
/**
* 图文消息描述
*/
private String description;
/**
* 图片链接
* *
* 支持JPGPNG格式较好的效果为大图 360*200小图 200*200 * 消息类型为 {@link WxConsts.XmlMsgType} LOCATION
*
* 例如说杨浦区黄兴路 221-4 号临
*/ */
private String picUrl; private String label;
// ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
/** /**
* 点击图文消息跳转链接 * 事件类型
*
* 枚举 {@link WxConsts.EventType}
*/ */
private String url; private String event;
/**
} * 事件 Key
*
// TODO @芋艿可以找一些新的思路 * 1. {@link WxConsts.EventType} SCANqrscene_ 为前缀后面为二维码的参数值
public static class ArticleTypeHandler extends AbstractJsonTypeHandler<List<Article>> { * 2. {@link WxConsts.EventType} CLICK与自定义菜单接口中 KEY 值对应
*/
@Override private String eventKey;
protected List<Article> parse(String json) {
return JsonUtils.parseArray(json, Article.class);
}
@Override
protected String toJson(List<Article> obj) {
return JsonUtils.toJsonString(obj);
}
}
} }

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.dal.mysql.fansmsg; package cn.iocoder.yudao.module.mp.dal.mysql.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
@ -13,7 +13,7 @@ public interface MpMessageMapper extends BaseMapperX<MpMessageDO> {
default PageResult<MpMessageDO> selectPage(MpMessagePageReqVO reqVO) { default PageResult<MpMessageDO> selectPage(MpMessagePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<MpMessageDO>() return selectPage(reqVO, new LambdaQueryWrapperX<MpMessageDO>()
.eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId()) .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
.eqIfPresent(MpMessageDO::getOpenId, reqVO.getOpenId()) .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenId())
// .likeIfPresent(MpMessageDO::getNickname, reqVO.getNickname()) // .likeIfPresent(MpMessageDO::getNickname, reqVO.getNickname())
.eqIfPresent(MpMessageDO::getType, reqVO.getType()) .eqIfPresent(MpMessageDO::getType, reqVO.getType())
.orderByDesc(MpMessageDO::getId)); .orderByDesc(MpMessageDO::getId));

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.mp.dal.mysql.accountfans; package cn.iocoder.yudao.module.mp.dal.mysql.user;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.mp.framework.mp.config;
import cn.iocoder.yudao.module.mp.framework.mp.core.DefaultMpServiceFactory; import cn.iocoder.yudao.module.mp.framework.mp.core.DefaultMpServiceFactory;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler; import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler;
import cn.iocoder.yudao.module.mp.service.handler.message.MessageLogHandler; import cn.iocoder.yudao.module.mp.service.handler.message.MessageReceiveHandler;
import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler; import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler;
import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler; import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler;
import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler;
@ -36,7 +36,7 @@ public class MpConfiguration {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public MpServiceFactory mpServiceFactory(RedisTemplateWxRedisOps redisTemplateWxRedisOps, public MpServiceFactory mpServiceFactory(RedisTemplateWxRedisOps redisTemplateWxRedisOps,
WxMpProperties wxMpProperties, WxMpProperties wxMpProperties,
MessageLogHandler logHandler, MessageReceiveHandler logHandler,
KfSessionHandler kfSessionHandler, KfSessionHandler kfSessionHandler,
StoreCheckNotifyHandler storeCheckNotifyHandler, StoreCheckNotifyHandler storeCheckNotifyHandler,
MenuHandler menuHandler, MenuHandler menuHandler,

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.mp.framework.mp.core;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler; import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler;
import cn.iocoder.yudao.module.mp.service.handler.message.MessageLogHandler; import cn.iocoder.yudao.module.mp.service.handler.message.MessageReceiveHandler;
import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler; import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler;
import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler; import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler;
import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler;
@ -49,7 +49,7 @@ public class DefaultMpServiceFactory implements MpServiceFactory {
// ========== 各种 Handler ========== // ========== 各种 Handler ==========
private final MessageLogHandler logHandler; private final MessageReceiveHandler logHandler;
private final KfSessionHandler kfSessionHandler; private final KfSessionHandler kfSessionHandler;
private final StoreCheckNotifyHandler storeCheckNotifyHandler; private final StoreCheckNotifyHandler storeCheckNotifyHandler;
private final MenuHandler menuHandler; private final MenuHandler menuHandler;

View File

@ -52,91 +52,91 @@ public class MessageAutoReplyHandler implements WxMpMessageHandler {
log.info("收到信息内容:", JsonUtils.toJsonString(wxMessage)); log.info("收到信息内容:", JsonUtils.toJsonString(wxMessage));
log.info("关键字:", wxMessage.getContent()); log.info("关键字:", wxMessage.getContent());
if (!wxMessage.getMsgType().equals(WxConsts.XmlMsgType.EVENT)) { // if (!wxMessage.getMsgType().equals(WxConsts.XmlMsgType.EVENT)) {
//可以选择将消息保存到本地 // //可以选择将消息保存到本地
//
// 获取微信用户基本信息 // // 获取微信用户基本信息
try { // try {
WxMpUser wxmpUser = weixinService.getUserService() // WxMpUser wxmpUser = weixinService.getUserService()
.userInfo(wxMessage.getFromUser(), null); // .userInfo(wxMessage.getFromUser(), null);
if (wxmpUser != null) { // if (wxmpUser != null) {
MpAccountDO wxAccount = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser()); // MpAccountDO wxAccount = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser());
if (wxAccount != null) { // if (wxAccount != null) {
//
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) { // if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) {
WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO(); // WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO();
wxFansMsg.setOpenid(wxmpUser.getOpenId()); // wxFansMsg.setOpenid(wxmpUser.getOpenId());
try { // try {
wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8")); // wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) { // } catch (UnsupportedEncodingException e) {
e.printStackTrace(); // e.printStackTrace();
} // }
wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl()); // wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl());
wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId())); // wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId()));
wxFansMsg.setMsgType(wxMessage.getMsgType()); // wxFansMsg.setMsgType(wxMessage.getMsgType());
wxFansMsg.setContent(wxMessage.getContent()); // wxFansMsg.setContent(wxMessage.getContent());
wxFansMsg.setIsRes("1"); // wxFansMsg.setIsRes("1");
//
//组装回复消息 // //组装回复消息
String content = processContent(wxMessage); // String content = processContent(wxMessage);
content = HtmlUtil.escape(content); // content = HtmlUtil.escape(content);
wxFansMsg.setResContent(content); // wxFansMsg.setResContent(content);
//
mpMessageService.createWxFansMsg(wxFansMsg); // mpMessageService.createWxFansMsg(wxFansMsg);
return new TextBuilder().build(content, wxMessage, weixinService); // return new TextBuilder().build(content, wxMessage, weixinService);
//
} // }
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.IMAGE)) { // if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.IMAGE)) {
WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO(); // WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO();
wxFansMsg.setOpenid(wxmpUser.getOpenId()); // wxFansMsg.setOpenid(wxmpUser.getOpenId());
try { // try {
wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8")); // wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) { // } catch (UnsupportedEncodingException e) {
e.printStackTrace(); // e.printStackTrace();
} // }
wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl()); // wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl());
wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId())); // wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId()));
wxFansMsg.setMsgType(wxMessage.getMsgType()); // wxFansMsg.setMsgType(wxMessage.getMsgType());
wxFansMsg.setMediaId(wxMessage.getMediaId()); // wxFansMsg.setMediaId(wxMessage.getMediaId());
wxFansMsg.setPicUrl(wxMessage.getPicUrl()); // wxFansMsg.setPicUrl(wxMessage.getPicUrl());
String downloadDirStr = fileApi.createFile(HttpUtil.downloadBytes(wxMessage.getPicUrl())); // String downloadDirStr = fileApi.createFile(HttpUtil.downloadBytes(wxMessage.getPicUrl()));
File downloadDir = new File(downloadDirStr); // File downloadDir = new File(downloadDirStr);
if (!downloadDir.exists()) { // if (!downloadDir.exists()) {
downloadDir.mkdirs(); // downloadDir.mkdirs();
} // }
String filepath = downloadDirStr + System.currentTimeMillis() + ".png"; // String filepath = downloadDirStr + System.currentTimeMillis() + ".png";
//微信pic url下载到本地防止失效 // //微信pic url下载到本地防止失效
long size = HttpUtil.downloadFile(wxMessage.getPicUrl(), FileUtil.file(filepath)); // long size = HttpUtil.downloadFile(wxMessage.getPicUrl(), FileUtil.file(filepath));
log.info("download pic size : {}", size); // log.info("download pic size : {}", size);
wxFansMsg.setPicPath(filepath); // wxFansMsg.setPicPath(filepath);
wxFansMsg.setIsRes("0"); // wxFansMsg.setIsRes("0");
mpMessageService.createWxFansMsg(wxFansMsg); // mpMessageService.createWxFansMsg(wxFansMsg);
} // }
//
} // }
} // }
} catch (WxErrorException e) { // } catch (WxErrorException e) {
if (e.getError().getErrorCode() == 48001) { // if (e.getError().getErrorCode() == 48001) {
log.info("该公众号没有获取用户信息权限!"); // log.info("该公众号没有获取用户信息权限!");
} // }
} catch (Exception e) { // } catch (Exception e) {
e.printStackTrace(); // e.printStackTrace();
} // }
//
} // }
//
//当用户输入关键词如你好客服并且有客服在线时把消息转发给在线客服 // //当用户输入关键词如你好客服并且有客服在线时把消息转发给在线客服
try { // try {
if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服") // if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服")
&& weixinService.getKefuService().kfOnlineList() // && weixinService.getKefuService().kfOnlineList()
.getKfOnlineList().size() > 0) { // .getKfOnlineList().size() > 0) {
return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE() // return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE()
.fromUser(wxMessage.getToUser()) // .fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser()).build(); // .toUser(wxMessage.getFromUser()).build();
} // }
} catch (WxErrorException e) { // } catch (WxErrorException e) {
e.printStackTrace(); // e.printStackTrace();
} // }
//组装默认回复消息 //组装默认回复消息
return new TextBuilder().build("测试", wxMessage, weixinService); return new TextBuilder().build("测试", wxMessage, weixinService);

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mp.service.handler.message; package cn.iocoder.yudao.module.mp.service.handler.message;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler; import me.chanjar.weixin.mp.api.WxMpMessageHandler;
@ -9,21 +10,26 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map; import java.util.Map;
/** /**
* 保存微信消息的事件处理器 * 保存微信消息的事件处理器
* *
* // TODO 芋艿实现一下 * @author 芋道源码
*/ */
@Component @Component
@Slf4j @Slf4j
public class MessageLogHandler implements WxMpMessageHandler { public class MessageReceiveHandler implements WxMpMessageHandler {
@Resource
private MpMessageService mpMessageService;
@Override @Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
WxMpService wxMpService, WxSessionManager sessionManager) { WxMpService wxMpService, WxSessionManager sessionManager) {
log.info("接收到请求消息,内容:{}", JsonUtils.toJsonString(wxMessage)); log.info("[handle][接收到请求消息,内容:{}]", wxMessage);
mpMessageService.createFromUser(MpContextHolder.getAppId(), wxMessage);
return null; return null;
} }

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.mp.service.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
/** /**
* 粉丝消息表 Service 接口 * 粉丝消息表 Service 接口
@ -12,11 +13,18 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
public interface MpMessageService { public interface MpMessageService {
/** /**
* 获得粉丝消息表 分页 * 获得粉丝消息表分页
* *
* @param pageReqVO 分页查询 * @param pageReqVO 分页查询
* @return 粉丝消息表 分页 * @return 粉丝消息表 分页
*/ */
PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO); PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO);
/**
* 保存粉丝消息来自用户发送
*
* @param appId 微信公众号 appId
* @param wxMessage 消息
*/
void createFromUser(String appId, WxMpXmlMessage wxMessage);
} }

View File

@ -1,6 +1,26 @@
package cn.iocoder.yudao.module.mp.service.message; package cn.iocoder.yudao.module.mp.service.message;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
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.message.MpMessageDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
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.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -10,7 +30,9 @@ import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*; import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.dal.mysql.fansmsg.MpMessageMapper; import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper;
import java.io.File;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -21,14 +43,77 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
*/ */
@Service @Service
@Validated @Validated
@Slf4j
public class MpMessageServiceImpl implements MpMessageService { public class MpMessageServiceImpl implements MpMessageService {
@Resource
@Lazy // 延迟加载避免循环依赖
private MpAccountService mpAccountService;
@Resource
private MpUserService mpUserService;
@Resource @Resource
private MpMessageMapper mpMessageMapper; private MpMessageMapper mpMessageMapper;
@Resource
@Lazy // 延迟加载解决循环依赖的问题
private MpServiceFactory mpServiceFactory;
@Resource
private FileApi fileApi;
@Override @Override
public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) { public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) {
return mpMessageMapper.selectPage(pageReqVO); return mpMessageMapper.selectPage(pageReqVO);
} }
@Override
public void createFromUser(String appId, WxMpXmlMessage wxMessage) {
WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);
// 获得关联信息
MpAccountDO account = mpAccountService.getAccountFromCache(appId);
Assert.notNull(account, "公众号账号({}) 不存在", appId);
MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser());
Assert.notNull(account, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
// 记录消息
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
if (StrUtil.isNotEmpty(message.getMediaId())) {
message.setMediaUrl(mediaDownload(mpService, message.getMediaId()));
}
if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
message.setThumbMediaUrl(mediaDownload(mpService, message.getThumbMediaId()));
}
mpMessageMapper.insert(message);
// WxConsts.MenuButtonType.VIEW
// wxMessage.getEventKey()
// WxConsts.MenuButtonType.CLICK
// wxMessage.getEventKey()
}
/**
* 下载微信媒体文件的内容并上传到文件服务
*
* 为什么要下载媒体文件在微信后台保存时间为 3 3 天后 media_id 失效
*
* @param mpService 微信公众号 Service
* @param mediaId 媒体文件编号
* @return 上传后的 URL
*/
private String mediaDownload(WxMpService mpService, String mediaId) {
try {
// 第一步从公众号下载媒体文件
File file = mpService.getMaterialService().mediaDownload(mediaId);
// 第二步上传到文件服务
String path = mediaId + "." + FileTypeUtil.getType(file);
return fileApi.createFile(path, FileUtil.readBytes(file));
} catch (WxErrorException e) {
log.error("[mediaDownload][media({}) 下载失败]", mediaId);
}
return null;
}
} }

View File

@ -23,6 +23,15 @@ public interface MpUserService {
*/ */
MpUserDO getUser(Long id); MpUserDO getUser(Long id);
/**
* 使用 appId + openId获得微信公众号粉丝
*
* @param appId 微信公众号 appId
* @param openId 微信公众号 openId
* @return 微信公众号粉丝
*/
MpUserDO getUser(String appId, String openId);
/** /**
* 获得微信公众号粉丝列表 * 获得微信公众号粉丝列表
* *

View File

@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO;
import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert; import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.dal.mysql.accountfans.MpUserMapper; import cn.iocoder.yudao.module.mp.dal.mysql.user.MpUserMapper;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService; import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.bean.result.WxMpUser; import me.chanjar.weixin.mp.bean.result.WxMpUser;
@ -41,6 +41,11 @@ public class MpUserServiceImpl implements MpUserService {
return mpUserMapper.selectById(id); return mpUserMapper.selectById(id);
} }
@Override
public MpUserDO getUser(String appId, String openId) {
return mpUserMapper.selectByAppIdAndOpenid(appId, openId);
}
@Override @Override
public List<MpUserDO> getUserList(Collection<Long> ids) { public List<MpUserDO> getUserList(Collection<Long> ids) {
return mpUserMapper.selectBatchIds(ids); return mpUserMapper.selectBatchIds(ids);