mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	mp:实现消息的存储
This commit is contained in:
		| @@ -10,7 +10,7 @@ import lombok.Getter; | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum MpMessageSendFrom { | ||||
| public enum MpMessageSendFromEnum { | ||||
| 
 | ||||
|     USER_TO_MP(1, "用户发送给公众号"), | ||||
|     MP_TO_USER(2, "公众号发给用户"), | ||||
| @@ -4,8 +4,13 @@ import java.util.*; | ||||
|  | ||||
| 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.user.MpUserDO; | ||||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | ||||
| import org.mapstruct.Mapper; | ||||
| import org.mapstruct.Mapping; | ||||
| import org.mapstruct.Mappings; | ||||
| import org.mapstruct.factory.Mappers; | ||||
| import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*; | ||||
|  | ||||
| @@ -20,4 +25,19 @@ public interface MpMessageConvert { | ||||
|  | ||||
|     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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,15 @@ | ||||
| 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.user.MpUserDO; | ||||
| import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFrom; | ||||
| import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; | ||||
| import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; | ||||
| import com.baomidou.mybatisplus.annotation.KeySequence; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import lombok.*; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.*; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 微信消息 DO | ||||
|  * | ||||
| @@ -35,6 +30,10 @@ public class MpMessageDO extends BaseDO { | ||||
|      */ | ||||
|     @TableId | ||||
|     private Long id; | ||||
|     /** | ||||
|      * 微信公众号消息 id | ||||
|      */ | ||||
|     private Long msgId; | ||||
|     /** | ||||
|      * 微信公众号 ID | ||||
|      * | ||||
| @@ -56,17 +55,9 @@ public class MpMessageDO extends BaseDO { | ||||
|     /** | ||||
|      * 用户标识 | ||||
|      * | ||||
|      * 冗余 {@link MpUserDO#getId()} | ||||
|      * 冗余 {@link MpUserDO#getOpenid()} | ||||
|      */ | ||||
|     private String openId; | ||||
| //    /** | ||||
| //     * 昵称 | ||||
| //     */ | ||||
| //    private String nickname; | ||||
| //    /** | ||||
| //     * 头像 | ||||
| //     */ | ||||
| //    private String headImageUrl; | ||||
|     private String openid; | ||||
|  | ||||
|     /** | ||||
|      * 消息类型 | ||||
| @@ -77,11 +68,11 @@ public class MpMessageDO extends BaseDO { | ||||
|     /** | ||||
|      * 消息来源 | ||||
|      * | ||||
|      * 枚举 {@link MpMessageSendFrom} | ||||
|      * 枚举 {@link MpMessageSendFromEnum} | ||||
|      */ | ||||
|     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} 的 IMAGE、VOICE、VIDEO | ||||
|      */ | ||||
|     private String mediaId; | ||||
|     /** | ||||
|      * 媒体文件的 URL | ||||
|      */ | ||||
|     private String mediaUrl; | ||||
|     /** | ||||
|      * 语音识别后文本 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE | ||||
|      */ | ||||
|     private String recognition; | ||||
|     /** | ||||
|      * 语音格式,如 amr,speex 等 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE | ||||
|      */ | ||||
|     private String format; | ||||
|     /** | ||||
|      * 标题 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC、LINK | ||||
|      */ | ||||
|     private String title; | ||||
|     /** | ||||
| @@ -109,38 +116,25 @@ public class MpMessageDO extends BaseDO { | ||||
|      */ | ||||
|     private String description; | ||||
|  | ||||
|     /** | ||||
|      * 音乐链接 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC | ||||
|      */ | ||||
|     private String musicURL; | ||||
|     /** | ||||
|      * 高质量音乐链接,WIFI 环境优先使用该链接播放音乐 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC | ||||
|      */ | ||||
|     private String hqMusicUrl; | ||||
|     /** | ||||
|      * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO | ||||
|      */ | ||||
|     private String thumbMediaId; | ||||
|     /** | ||||
|      * 缩略图的媒体 URL | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO | ||||
|      */ | ||||
|     private String thumbMediaUrl; | ||||
|  | ||||
|     /** | ||||
|      * 图文消息个数,限制为 10 条以内 | ||||
|      * 点击图文消息跳转链接 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 LINK | ||||
|      */ | ||||
|     private Integer articleCount; | ||||
|     /** | ||||
|      * 图文消息数组 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS | ||||
|      */ | ||||
|     @TableField(typeHandler = ArticleTypeHandler.class) | ||||
|     private List<Article> articles; | ||||
|     private String url; | ||||
|  | ||||
|     /** | ||||
|      * 地理位置维度 | ||||
| @@ -160,47 +154,29 @@ public class MpMessageDO extends BaseDO { | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION | ||||
|      */ | ||||
|     private Double scale; | ||||
|     /** | ||||
|      * 详细地址 | ||||
|      * | ||||
|      * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION | ||||
|      * | ||||
|      * 例如说杨浦区黄兴路 221-4 号临 | ||||
|      */ | ||||
|     private String label; | ||||
|  | ||||
|     // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html | ||||
|  | ||||
|     /** | ||||
|      * 文章 | ||||
|      * 事件类型 | ||||
|      * | ||||
|      * 枚举 {@link WxConsts.EventType} | ||||
|      */ | ||||
|     @Data | ||||
|     public static class Article implements Serializable { | ||||
|  | ||||
|         /** | ||||
|          * 图文消息标题 | ||||
|          */ | ||||
|         private String title; | ||||
|         /** | ||||
|          * 图文消息描述 | ||||
|          */ | ||||
|         private String description; | ||||
|         /** | ||||
|          * 图片链接 | ||||
|          * | ||||
|          * 支持JPG、PNG格式,较好的效果为大图 360*200,小图 200*200 | ||||
|          */ | ||||
|         private String picUrl; | ||||
|         /** | ||||
|          * 点击图文消息跳转链接 | ||||
|          */ | ||||
|         private String url; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     // TODO @芋艿:可以找一些新的思路 | ||||
|     public static class ArticleTypeHandler extends AbstractJsonTypeHandler<List<Article>> { | ||||
|  | ||||
|         @Override | ||||
|         protected List<Article> parse(String json) { | ||||
|             return JsonUtils.parseArray(json, Article.class); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         protected String toJson(List<Article> obj) { | ||||
|             return JsonUtils.toJsonString(obj); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     private String event; | ||||
|     /** | ||||
|      * 事件 Key | ||||
|      * | ||||
|      * 1. {@link WxConsts.EventType} 的 SCAN:qrscene_ 为前缀,后面为二维码的参数值 | ||||
|      * 2. {@link WxConsts.EventType} 的 CLICK:与自定义菜单接口中 KEY 值对应 | ||||
|      */ | ||||
|     private String eventKey; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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.mybatis.core.mapper.BaseMapperX; | ||||
| @@ -13,7 +13,7 @@ public interface MpMessageMapper extends BaseMapperX<MpMessageDO> { | ||||
|     default PageResult<MpMessageDO> selectPage(MpMessagePageReqVO reqVO) { | ||||
|         return selectPage(reqVO, new LambdaQueryWrapperX<MpMessageDO>() | ||||
|                 .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId()) | ||||
|                 .eqIfPresent(MpMessageDO::getOpenId, reqVO.getOpenId()) | ||||
|                 .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenId()) | ||||
| //                .likeIfPresent(MpMessageDO::getNickname, reqVO.getNickname()) | ||||
|                 .eqIfPresent(MpMessageDO::getType, reqVO.getType()) | ||||
|                 .orderByDesc(MpMessageDO::getId)); | ||||
| @@ -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.mybatis.core.mapper.BaseMapperX; | ||||
| @@ -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.MpServiceFactory; | ||||
| 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.other.KfSessionHandler; | ||||
| import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; | ||||
| @@ -36,7 +36,7 @@ public class MpConfiguration { | ||||
|     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||||
|     public MpServiceFactory mpServiceFactory(RedisTemplateWxRedisOps redisTemplateWxRedisOps, | ||||
|                                              WxMpProperties wxMpProperties, | ||||
|                                              MessageLogHandler logHandler, | ||||
|                                              MessageReceiveHandler logHandler, | ||||
|                                              KfSessionHandler kfSessionHandler, | ||||
|                                              StoreCheckNotifyHandler storeCheckNotifyHandler, | ||||
|                                              MenuHandler menuHandler, | ||||
|   | ||||
| @@ -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.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.other.KfSessionHandler; | ||||
| import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; | ||||
| @@ -49,7 +49,7 @@ public class DefaultMpServiceFactory implements MpServiceFactory { | ||||
|  | ||||
|     // ========== 各种 Handler ========== | ||||
|  | ||||
|     private final MessageLogHandler logHandler; | ||||
|     private final MessageReceiveHandler logHandler; | ||||
|     private final KfSessionHandler kfSessionHandler; | ||||
|     private final StoreCheckNotifyHandler storeCheckNotifyHandler; | ||||
|     private final MenuHandler menuHandler; | ||||
|   | ||||
| @@ -52,91 +52,91 @@ public class MessageAutoReplyHandler implements WxMpMessageHandler { | ||||
|         log.info("收到信息内容:{}", JsonUtils.toJsonString(wxMessage)); | ||||
|         log.info("关键字:{}", wxMessage.getContent()); | ||||
|  | ||||
|         if (!wxMessage.getMsgType().equals(WxConsts.XmlMsgType.EVENT)) { | ||||
|             //可以选择将消息保存到本地 | ||||
|  | ||||
|             // 获取微信用户基本信息 | ||||
|             try { | ||||
|                 WxMpUser wxmpUser = weixinService.getUserService() | ||||
|                         .userInfo(wxMessage.getFromUser(), null); | ||||
|                 if (wxmpUser != null) { | ||||
|                     MpAccountDO wxAccount = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser()); | ||||
|                     if (wxAccount != null) { | ||||
|  | ||||
|                         if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) { | ||||
|                             WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO(); | ||||
|                             wxFansMsg.setOpenid(wxmpUser.getOpenId()); | ||||
|                             try { | ||||
|                                 wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8")); | ||||
|                             } catch (UnsupportedEncodingException e) { | ||||
|                                 e.printStackTrace(); | ||||
|                             } | ||||
|                             wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl()); | ||||
|                             wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId())); | ||||
|                             wxFansMsg.setMsgType(wxMessage.getMsgType()); | ||||
|                             wxFansMsg.setContent(wxMessage.getContent()); | ||||
|                             wxFansMsg.setIsRes("1"); | ||||
|  | ||||
|                             //组装回复消息 | ||||
|                             String content = processContent(wxMessage); | ||||
|                             content = HtmlUtil.escape(content); | ||||
|                             wxFansMsg.setResContent(content); | ||||
|  | ||||
|                             mpMessageService.createWxFansMsg(wxFansMsg); | ||||
|                             return new TextBuilder().build(content, wxMessage, weixinService); | ||||
|  | ||||
|                         } | ||||
|                         if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.IMAGE)) { | ||||
|                             WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO(); | ||||
|                             wxFansMsg.setOpenid(wxmpUser.getOpenId()); | ||||
|                             try { | ||||
|                                 wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8")); | ||||
|                             } catch (UnsupportedEncodingException e) { | ||||
|                                 e.printStackTrace(); | ||||
|                             } | ||||
|                             wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl()); | ||||
|                             wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId())); | ||||
|                             wxFansMsg.setMsgType(wxMessage.getMsgType()); | ||||
|                             wxFansMsg.setMediaId(wxMessage.getMediaId()); | ||||
|                             wxFansMsg.setPicUrl(wxMessage.getPicUrl()); | ||||
|                             String downloadDirStr = fileApi.createFile(HttpUtil.downloadBytes(wxMessage.getPicUrl())); | ||||
|                             File downloadDir = new File(downloadDirStr); | ||||
|                             if (!downloadDir.exists()) { | ||||
|                                 downloadDir.mkdirs(); | ||||
|                             } | ||||
|                             String filepath = downloadDirStr + System.currentTimeMillis() + ".png"; | ||||
|                             //微信pic url下载到本地,防止失效 | ||||
|                             long size = HttpUtil.downloadFile(wxMessage.getPicUrl(), FileUtil.file(filepath)); | ||||
|                             log.info("download pic size : {}", size); | ||||
|                             wxFansMsg.setPicPath(filepath); | ||||
|                             wxFansMsg.setIsRes("0"); | ||||
|                             mpMessageService.createWxFansMsg(wxFansMsg); | ||||
|                         } | ||||
|  | ||||
|                     } | ||||
|                 } | ||||
|             } catch (WxErrorException e) { | ||||
|                 if (e.getError().getErrorCode() == 48001) { | ||||
|                     log.info("该公众号没有获取用户信息权限!"); | ||||
|                 } | ||||
|             } catch (Exception e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         //当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服 | ||||
|         try { | ||||
|             if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服") | ||||
|                     && weixinService.getKefuService().kfOnlineList() | ||||
|                     .getKfOnlineList().size() > 0) { | ||||
|                 return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE() | ||||
|                         .fromUser(wxMessage.getToUser()) | ||||
|                         .toUser(wxMessage.getFromUser()).build(); | ||||
|             } | ||||
|         } catch (WxErrorException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| //        if (!wxMessage.getMsgType().equals(WxConsts.XmlMsgType.EVENT)) { | ||||
| //            //可以选择将消息保存到本地 | ||||
| // | ||||
| //            // 获取微信用户基本信息 | ||||
| //            try { | ||||
| //                WxMpUser wxmpUser = weixinService.getUserService() | ||||
| //                        .userInfo(wxMessage.getFromUser(), null); | ||||
| //                if (wxmpUser != null) { | ||||
| //                    MpAccountDO wxAccount = mpAccountService.findBy(MpAccountDO::getAccount, wxMessage.getToUser()); | ||||
| //                    if (wxAccount != null) { | ||||
| // | ||||
| //                        if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) { | ||||
| //                            WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO(); | ||||
| //                            wxFansMsg.setOpenid(wxmpUser.getOpenId()); | ||||
| //                            try { | ||||
| //                                wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8")); | ||||
| //                            } catch (UnsupportedEncodingException e) { | ||||
| //                                e.printStackTrace(); | ||||
| //                            } | ||||
| //                            wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl()); | ||||
| //                            wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId())); | ||||
| //                            wxFansMsg.setMsgType(wxMessage.getMsgType()); | ||||
| //                            wxFansMsg.setContent(wxMessage.getContent()); | ||||
| //                            wxFansMsg.setIsRes("1"); | ||||
| // | ||||
| //                            //组装回复消息 | ||||
| //                            String content = processContent(wxMessage); | ||||
| //                            content = HtmlUtil.escape(content); | ||||
| //                            wxFansMsg.setResContent(content); | ||||
| // | ||||
| //                            mpMessageService.createWxFansMsg(wxFansMsg); | ||||
| //                            return new TextBuilder().build(content, wxMessage, weixinService); | ||||
| // | ||||
| //                        } | ||||
| //                        if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.IMAGE)) { | ||||
| //                            WxFansMsgCreateReqVO wxFansMsg = new WxFansMsgCreateReqVO(); | ||||
| //                            wxFansMsg.setOpenid(wxmpUser.getOpenId()); | ||||
| //                            try { | ||||
| //                                wxFansMsg.setNickname(wxmpUser.getNickname().getBytes("UTF-8")); | ||||
| //                            } catch (UnsupportedEncodingException e) { | ||||
| //                                e.printStackTrace(); | ||||
| //                            } | ||||
| //                            wxFansMsg.setHeadimgUrl(wxmpUser.getHeadImgUrl()); | ||||
| //                            wxFansMsg.setWxAccountId(String.valueOf(wxAccount.getId())); | ||||
| //                            wxFansMsg.setMsgType(wxMessage.getMsgType()); | ||||
| //                            wxFansMsg.setMediaId(wxMessage.getMediaId()); | ||||
| //                            wxFansMsg.setPicUrl(wxMessage.getPicUrl()); | ||||
| //                            String downloadDirStr = fileApi.createFile(HttpUtil.downloadBytes(wxMessage.getPicUrl())); | ||||
| //                            File downloadDir = new File(downloadDirStr); | ||||
| //                            if (!downloadDir.exists()) { | ||||
| //                                downloadDir.mkdirs(); | ||||
| //                            } | ||||
| //                            String filepath = downloadDirStr + System.currentTimeMillis() + ".png"; | ||||
| //                            //微信pic url下载到本地,防止失效 | ||||
| //                            long size = HttpUtil.downloadFile(wxMessage.getPicUrl(), FileUtil.file(filepath)); | ||||
| //                            log.info("download pic size : {}", size); | ||||
| //                            wxFansMsg.setPicPath(filepath); | ||||
| //                            wxFansMsg.setIsRes("0"); | ||||
| //                            mpMessageService.createWxFansMsg(wxFansMsg); | ||||
| //                        } | ||||
| // | ||||
| //                    } | ||||
| //                } | ||||
| //            } catch (WxErrorException e) { | ||||
| //                if (e.getError().getErrorCode() == 48001) { | ||||
| //                    log.info("该公众号没有获取用户信息权限!"); | ||||
| //                } | ||||
| //            } catch (Exception e) { | ||||
| //                e.printStackTrace(); | ||||
| //            } | ||||
| // | ||||
| //        } | ||||
| // | ||||
| //        //当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服 | ||||
| //        try { | ||||
| //            if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服") | ||||
| //                    && weixinService.getKefuService().kfOnlineList() | ||||
| //                    .getKfOnlineList().size() > 0) { | ||||
| //                return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE() | ||||
| //                        .fromUser(wxMessage.getToUser()) | ||||
| //                        .toUser(wxMessage.getFromUser()).build(); | ||||
| //            } | ||||
| //        } catch (WxErrorException e) { | ||||
| //            e.printStackTrace(); | ||||
| //        } | ||||
|  | ||||
|         //组装默认回复消息 | ||||
|         return new TextBuilder().build("测试", wxMessage, weixinService); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| 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 me.chanjar.weixin.common.session.WxSessionManager; | ||||
| 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 org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import javax.annotation.Resource; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * 保存微信消息的事件处理器 | ||||
|  * | ||||
|  * // TODO 芋艿:实现一下 | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Component | ||||
| @Slf4j | ||||
| public class MessageLogHandler implements WxMpMessageHandler { | ||||
| 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("接收到请求消息,内容:{}", JsonUtils.toJsonString(wxMessage)); | ||||
|         log.info("[handle][接收到请求消息,内容:{}]", wxMessage); | ||||
|         mpMessageService.createFromUser(MpContextHolder.getAppId(), wxMessage); | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.mp.service.message; | ||||
| 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.dal.dataobject.message.MpMessageDO; | ||||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | ||||
|  | ||||
| /** | ||||
|  * 粉丝消息表 Service 接口 | ||||
| @@ -12,11 +13,18 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; | ||||
| public interface MpMessageService { | ||||
|  | ||||
|     /** | ||||
|      * 获得粉丝消息表 分页 | ||||
|      * 获得粉丝消息表分页 | ||||
|      * | ||||
|      * @param pageReqVO 分页查询 | ||||
|      * @return 粉丝消息表 分页 | ||||
|      */ | ||||
|     PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO); | ||||
|  | ||||
|     /** | ||||
|      * 保存粉丝消息,来自用户发送 | ||||
|      * | ||||
|      * @param appId 微信公众号 appId | ||||
|      * @param wxMessage 消息 | ||||
|      */ | ||||
|     void createFromUser(String appId, WxMpXmlMessage wxMessage); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,26 @@ | ||||
| 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.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 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.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; | ||||
|  | ||||
| @@ -21,14 +43,77 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU | ||||
|  */ | ||||
| @Service | ||||
| @Validated | ||||
| @Slf4j | ||||
| public class MpMessageServiceImpl implements MpMessageService { | ||||
|  | ||||
|     @Resource | ||||
|     @Lazy // 延迟加载,避免循环依赖 | ||||
|     private MpAccountService mpAccountService; | ||||
|     @Resource | ||||
|     private MpUserService mpUserService; | ||||
|  | ||||
|     @Resource | ||||
|     private MpMessageMapper mpMessageMapper; | ||||
|  | ||||
|     @Resource | ||||
|     @Lazy // 延迟加载,解决循环依赖的问题 | ||||
|     private MpServiceFactory mpServiceFactory; | ||||
|  | ||||
|     @Resource | ||||
|     private FileApi fileApi; | ||||
|  | ||||
|     @Override | ||||
|     public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO 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; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -23,6 +23,15 @@ public interface MpUserService { | ||||
|      */ | ||||
|     MpUserDO getUser(Long id); | ||||
|  | ||||
|     /** | ||||
|      * 使用 appId + openId,获得微信公众号粉丝 | ||||
|      * | ||||
|      * @param appId 微信公众号 appId | ||||
|      * @param openId 微信公众号 openId | ||||
|      * @return 微信公众号粉丝 | ||||
|      */ | ||||
|     MpUserDO getUser(String appId, String openId); | ||||
|  | ||||
|     /** | ||||
|      * 获得微信公众号粉丝列表 | ||||
|      * | ||||
|   | ||||
| @@ -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.dal.dataobject.account.MpAccountDO; | ||||
| 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 lombok.extern.slf4j.Slf4j; | ||||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | ||||
| @@ -41,6 +41,11 @@ public class MpUserServiceImpl implements MpUserService { | ||||
|         return mpUserMapper.selectById(id); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MpUserDO getUser(String appId, String openId) { | ||||
|         return mpUserMapper.selectByAppIdAndOpenid(appId, openId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<MpUserDO> getUserList(Collection<Long> ids) { | ||||
|         return mpUserMapper.selectBatchIds(ids); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV