From c141ebef3f04e99b17df6b4e8cb80a01bac5947d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 3 Jan 2023 21:30:35 +0800 Subject: [PATCH] mp:实现消息的存储 --- yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFrom.java | 28 ---------------------------- yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java | 28 ++++++++++++++++++++++++++++ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java | 20 ++++++++++++++++++++ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/accountfans/MpUserMapper.java | 26 -------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/fansmsg/MpMessageMapper.java | 22 ---------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java | 22 ++++++++++++++++++++++ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java | 26 ++++++++++++++++++++++++++ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java | 4 ++-- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java | 4 ++-- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageLogHandler.java | 30 ------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java | 10 +++++++++- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java | 9 +++++++++ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java | 7 ++++++- 17 files changed, 390 insertions(+), 281 deletions(-) delete mode 100644 yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFrom.java create mode 100644 yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/accountfans/MpUserMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/fansmsg/MpMessageMapper.java create mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java create mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageLogHandler.java create mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java 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/MpMessageSendFrom.java deleted file mode 100644 index f07b35d..0000000 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFrom.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.mp.enums.message; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 微信公众号消息的发送来源 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum MpMessageSendFrom { - - USER_TO_MP(1, "用户发送给公众号"), - MP_TO_USER(2, "公众号发给用户"), - ; - - /** - * 来源 - */ - private final Integer from; - /** - * 来源的名字 - */ - private final String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java new file mode 100644 index 0000000..e441783 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.mp.enums.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 微信公众号消息的发送来源 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum MpMessageSendFromEnum { + + USER_TO_MP(1, "用户发送给公众号"), + MP_TO_USER(2, "公众号发给用户"), + ; + + /** + * 来源 + */ + private final Integer from; + /** + * 来源的名字 + */ + private final String name; + +} 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 fc504a7..9a1067e 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 c6d073f..aead890 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 * @@ -36,6 +31,10 @@ public class MpMessageDO extends BaseDO { @TableId private Long id; /** + * 微信公众号消息 id + */ + private Long msgId; + /** * 微信公众号 ID * * 关联 {@link MpAccountDO#getId()} @@ -56,17 +55,9 @@ public class MpMessageDO extends BaseDO { /** * 用户标识 * - * 冗余 {@link MpUserDO#getId()} - */ - private String openId; -// /** -// * 昵称 -// */ -// private String nickname; -// /** -// * 头像 -// */ -// private String headImageUrl; + * 冗余 {@link MpUserDO#getOpenid()} + */ + 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 /** * 消息内容 @@ -97,50 +88,53 @@ public class MpMessageDO extends BaseDO { */ private String mediaId; /** - * 标题 + * 媒体文件的 URL + */ + private String mediaUrl; + /** + * 语音识别后文本 * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE */ - private String title; + private String recognition; /** - * 描述 + * 语音格式,如 amr,speex 等 * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE */ - private String description; - + private String format; /** - * 音乐链接 + * 标题 * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC、LINK */ - private String musicURL; + private String title; /** - * 高质量音乐链接,WIFI 环境优先使用该链接播放音乐 + * 描述 * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC */ - private String hqMusicUrl; + private String description; + /** * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO */ private String thumbMediaId; - /** - * 图文消息个数,限制为 10 条以内 + * 缩略图的媒体 URL * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO */ - private Integer articleCount; + private String thumbMediaUrl; + /** - * 图文消息数组 + * 点击图文消息跳转链接 * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + * 消息类型为 {@link WxConsts.XmlMsgType} 的 LINK */ - @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 号临 */ - @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; + private String label; - } + // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html - // 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); - } - - } + /** + * 事件类型 + * + * 枚举 {@link WxConsts.EventType} + */ + 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/accountfans/MpUserMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/accountfans/MpUserMapper.java deleted file mode 100644 index ef74d2b..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/accountfans/MpUserMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.accountfans; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface MpUserMapper extends BaseMapperX { - - default PageResult selectPage(MpUserPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(MpUserDO::getOpenid, reqVO.getOpenid()) - .likeIfPresent(MpUserDO::getNickname, reqVO.getNickname()) - .eqIfPresent(MpUserDO::getAccountId, reqVO.getAccountId()) - .orderByDesc(MpUserDO::getId)); - } - - default MpUserDO selectByAppIdAndOpenid(String appId, String openId) { - return selectOne(MpUserDO::getAppId, appId, - MpUserDO::getOpenid, openId); - } - -} 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/fansmsg/MpMessageMapper.java deleted file mode 100644 index 95c4651..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/fansmsg/MpMessageMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.fansmsg; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -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()) -// .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/message/MpMessageMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java new file mode 100644 index 0000000..d901130 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java @@ -0,0 +1,22 @@ +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; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +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()) +// .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/user/MpUserMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java new file mode 100644 index 0000000..a02159e --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java @@ -0,0 +1,26 @@ +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; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MpUserMapper extends BaseMapperX { + + default PageResult selectPage(MpUserPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(MpUserDO::getOpenid, reqVO.getOpenid()) + .likeIfPresent(MpUserDO::getNickname, reqVO.getNickname()) + .eqIfPresent(MpUserDO::getAccountId, reqVO.getAccountId()) + .orderByDesc(MpUserDO::getId)); + } + + default MpUserDO selectByAppIdAndOpenid(String appId, String openId) { + return selectOne(MpUserDO::getAppId, appId, + MpUserDO::getOpenid, openId); + } + +} 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 e168c41..5e579e3 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 c5f4802..e4ccaee 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 4d44865..29ee309 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/MessageLogHandler.java deleted file mode 100644 index b952038..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageLogHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.message; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * 保存微信消息的事件处理器 - * - * // TODO 芋艿:实现一下 - */ -@Component -@Slf4j -public class MessageLogHandler implements WxMpMessageHandler { - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService, WxSessionManager sessionManager) { - log.info("接收到请求消息,内容:{}", JsonUtils.toJsonString(wxMessage)); - return null; - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java new file mode 100644 index 0000000..ef5fde5 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.mp.service.handler.message; + +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; +import me.chanjar.weixin.mp.api.WxMpService; +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; + +/** + * 保存微信消息的事件处理器 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class MessageReceiveHandler implements WxMpMessageHandler { + + @Resource + private MpMessageService mpMessageService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService wxMpService, WxSessionManager sessionManager) { + 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 d55958a..1c9c152 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 d9ac4bd..3364018 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 d937f1a..73ea4e4 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 @@ -24,6 +24,15 @@ public interface MpUserService { MpUserDO getUser(Long id); /** + * 使用 appId + openId,获得微信公众号粉丝 + * + * @param appId 微信公众号 appId + * @param openId 微信公众号 openId + * @return 微信公众号粉丝 + */ + MpUserDO getUser(String appId, String openId); + + /** * 获得微信公众号粉丝列表 * * @param ids 编号 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 87f2982..1f8e134 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; @@ -42,6 +42,11 @@ public class MpUserServiceImpl implements MpUserService { } @Override + public MpUserDO getUser(String appId, String openId) { + return mpUserMapper.selectByAppIdAndOpenid(appId, openId); + } + + @Override public List getUserList(Collection ids) { return mpUserMapper.selectBatchIds(ids); } -- libgit2 0.21.4