diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFrom.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java similarity index 92% rename from yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFrom.java rename to yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java index f07b35d9a..e44178334 100644 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFrom.java +++ b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java @@ -10,7 +10,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum MpMessageSendFrom { +public enum MpMessageSendFromEnum { USER_TO_MP(1, "用户发送给公众号"), MP_TO_USER(2, "公众号发给用户"), diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java index fc504a75f..9a1067ef2 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java @@ -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 convertPage(PageResult 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; + } } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java index c6d073f4b..aead89058 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java @@ -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
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> { - - @Override - protected List
parse(String json) { - return JsonUtils.parseArray(json, Article.class); - } - - @Override - protected String toJson(List
obj) { - return JsonUtils.toJsonString(obj); - } - - } + private String event; + /** + * 事件 Key + * + * 1. {@link WxConsts.EventType} 的 SCAN:qrscene_ 为前缀,后面为二维码的参数值 + * 2. {@link WxConsts.EventType} 的 CLICK:与自定义菜单接口中 KEY 值对应 + */ + private String eventKey; } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/fansmsg/MpMessageMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java similarity index 88% rename from yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/fansmsg/MpMessageMapper.java rename to yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java index 95c46510f..d9011309f 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/fansmsg/MpMessageMapper.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java @@ -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 { default PageResult selectPage(MpMessagePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .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)); diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/accountfans/MpUserMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java similarity index 94% rename from yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/accountfans/MpUserMapper.java rename to yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java index ef74d2be2..a02159ecd 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/accountfans/MpUserMapper.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java @@ -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; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java index e168c4196..5e579e35f 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java @@ -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, diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java index c5f4802cb..e4ccaeea8 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java @@ -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; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java index 4d4486509..29ee30922 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java @@ -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); diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageLogHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java similarity index 60% rename from yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageLogHandler.java rename to yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java index b952038df..ef5fde52e 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageLogHandler.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java @@ -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 context, WxMpService wxMpService, WxSessionManager sessionManager) { - log.info("接收到请求消息,内容:{}", JsonUtils.toJsonString(wxMessage)); + log.info("[handle][接收到请求消息,内容:{}]", wxMessage); + mpMessageService.createFromUser(MpContextHolder.getAppId(), wxMessage); return null; } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java index d55958a44..1c9c152a4 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java @@ -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 getWxFansMsgPage(MpMessagePageReqVO pageReqVO); + /** + * 保存粉丝消息,来自用户发送 + * + * @param appId 微信公众号 appId + * @param wxMessage 消息 + */ + void createFromUser(String appId, WxMpXmlMessage wxMessage); } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java index d9ac4bd97..336401863 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java @@ -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 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; + } + } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java index d937f1a34..73ea4e4a3 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java @@ -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); + /** * 获得微信公众号粉丝列表 * diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java index 87f298243..1f8e134be 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java @@ -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 getUserList(Collection ids) { return mpUserMapper.selectBatchIds(ids);