From 44f0bcd182a9a9e7b23dcadff7071e548d4a4160 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 4 Jan 2023 21:10:54 +0800 Subject: [PATCH] =?UTF-8?q?mp=EF=BC=9A=E5=AE=9E=E7=8E=B0=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E5=AD=97=EF=BC=8C=E8=87=AA=E5=8A=A8=E5=9B=9E=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/message/MpAutoReplyMatchEnum.java | 2 +- .../mp/enums/message/MpAutoReplyTypeEnum.java | 4 +- .../mp/convert/message/MpMessageConvert.java | 77 ++++++++++++++++++- .../dal/dataobject/message/MpAutoReplyDO.java | 36 ++++++++- .../dal/dataobject/message/MpMessageDO.java | 57 +++++++++++++- .../dal/mysql/message/MpAutoReplyMapper.java | 2 + .../message/MpAutoReplyServiceImpl.java | 15 ++-- .../mp/service/message/MpMessageService.java | 12 +++ .../service/message/MpMessageServiceImpl.java | 21 ++++- 9 files changed, 210 insertions(+), 16 deletions(-) diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java index b9fcf3671..a5298dbf3 100644 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java +++ b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java @@ -4,7 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * 微信消息自动回复的匹配模式 + * 公众号消息自动回复的匹配模式 * * @author 芋道源码 */ diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java index 8df7e704b..300f1f2ba 100644 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java +++ b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java @@ -4,7 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * 微信消息自动回复的类型 + * 公众号消息自动回复的类型 * * @author 芋道源码 */ @@ -14,7 +14,7 @@ public enum MpAutoReplyTypeEnum { SUBSCRIBE(1, "关注时回复"), MESSAGE(2, "收到消息回复"), - KEYWORD(2, "关键词回复"), + KEYWORD(3, "关键词回复"), ; /** 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 e3cfea7ec..9b7eae008 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 @@ -1,18 +1,27 @@ package cn.iocoder.yudao.module.mp.convert.message; -import java.util.*; - +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; - +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO; import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; +import me.chanjar.weixin.mp.builder.outxml.BaseBuilder; +import me.chanjar.weixin.mp.builder.outxml.TextBuilder; +import me.chanjar.weixin.mp.builder.outxml.VideoBuilder; 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.*; + +import java.util.Collections; +import java.util.List; @Mapper public interface MpMessageConvert { @@ -41,4 +50,64 @@ public interface MpMessageConvert { } return message; } + + default MpMessageDO convert(MpAutoReplyDO reply, MpAccountDO account, MpUserDO user) { + // 构建消息 + MpMessageDO message = new MpMessageDO(); + message.setType(reply.getResponseMessageType()); + // 1. 文本 + if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.TEXT)) { + message.setContent(reply.getResponseContent()); + } else if (ObjectUtils.equalsAny(reply.getResponseMessageType(), WxConsts.XmlMsgType.IMAGE, // 2. 图片 + WxConsts.XmlMsgType.VOICE)) { // 3. 语音 + message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl()); + } else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.VIDEO)) { // 4. 视频 + message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl()) + .setTitle(reply.getResponseTitle()).setDescription(reply.getResponseDescription()); + } else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.NEWS)) { // 5. 图文 + message.setArticles(Collections.singletonList(reply.getResponseArticle())); + } else { + throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); + } + + // 其它字段 + if (account != null) { + message.setAccountId(account.getId()).setAppId(account.getAppId()); + } + if (user != null) { + message.setUserId(user.getId()).setOpenid(user.getOpenid()); + } + return message; + } + + default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) { + BaseBuilder messageBuilder; + // 个性化字段 + switch (message.getType()) { + case WxConsts.XmlMsgType.TEXT: + messageBuilder = WxMpXmlOutMessage.TEXT().content(message.getContent()); + break; + case WxConsts.XmlMsgType.IMAGE: + messageBuilder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId()); + break; + case WxConsts.XmlMsgType.VOICE: + messageBuilder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId()); + break; + case WxConsts.XmlMsgType.VIDEO: + messageBuilder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId()) + .title(message.getTitle()).description(message.getDescription()); + break; + case WxConsts.XmlMsgType.NEWS: + messageBuilder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles())); + break; + default: + throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); + } + // 通用字段 + messageBuilder.fromUser(account.getAccount()); + messageBuilder.toUser(message.getOpenid()); + return messageBuilder.build(); + } + List convertList02(List list); + } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java index 0dd15fef6..c07cc2eb6 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java @@ -6,18 +6,21 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; +import java.util.List; import java.util.Set; /** - * 微信消息自动回复 DO + * 公众号消息自动回复 DO * * @author 芋道源码 */ @@ -99,4 +102,35 @@ public class MpAutoReplyDO extends BaseDO { */ private String responseContent; + /** + * 回复的媒体 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO + */ + private String responseMediaId; + /** + * 回复的媒体 URL + */ + private String responseMediaUrl; + + /** + * 回复的标题 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + private String responseTitle; + /** + * 回复的描述 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + private String responseDescription; + + /** + * 回复的图文消息 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private MpMessageDO.Article responseArticle; } 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 5fb62f0d3..1aea757ba 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,17 +1,23 @@ 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.MpMessageSendFromEnum; import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; import lombok.*; import me.chanjar.weixin.common.api.WxConsts; +import java.io.Serializable; +import java.util.List; + /** - * 微信消息 DO + * 公众号消息 DO * * @author 芋道源码 */ @@ -160,6 +166,14 @@ public class MpMessageDO extends BaseDO { */ private String label; + /** + * 图文消息数组 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @TableField(typeHandler = ArticleTypeHandler.class) + private List
articles; + // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html /** @@ -176,4 +190,45 @@ public class MpMessageDO extends BaseDO { */ private String eventKey; + /** + * 文章 + */ + @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); + } + + } } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java index 3cd0dd386..4cd570bf8 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java @@ -5,9 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; +import org.apache.ibatis.annotations.Mapper; import java.util.List; +@Mapper public interface MpAutoReplyMapper extends BaseMapperX { default List selectListByAppIdAndKeywordAll(String appId, String requestKeyword) { diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java index ba350942a..35de3629e 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java @@ -1,12 +1,14 @@ package cn.iocoder.yudao.module.mp.service.message; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.mp.builder.TextBuilder; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; @@ -17,18 +19,21 @@ import java.util.List; * * @author 芋道源码 */ -@Resource +@Service @Validated public class MpAutoReplyServiceImpl implements MpAutoReplyService { @Resource - private MpAutoReplyService mpAutoReplyService; + private MpMessageService mpMessageService; @Resource private MpAutoReplyMapper mpAutoReplyMapper; @Override public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) { +// if (true) { +// return new TextBuilder().build("nihao", wxMessage, null); +// } // 第一步,匹配自动回复 List replies = null; // 1.1 关键字 @@ -47,12 +52,10 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService { if (CollUtil.isEmpty(replies)) { return null; } + MpAutoReplyDO reply = CollUtil.getFirst(replies); // 第二步,基于自动回复,创建消息 - - - // 第三步,将消息转换成 WxMpXmlOutMessage 返回 - return null; + return mpMessageService.createFromAutoReply(wxMessage.getFromUser(), reply); } } 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 e34f91bf3..4270cbe8a 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 @@ -2,8 +2,10 @@ 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.MpAutoReplyDO; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; /** * 粉丝消息表 Service 接口 @@ -28,4 +30,14 @@ public interface MpMessageService { * @param wxMessage 消息 */ void createFromUser(String appId, WxMpXmlMessage wxMessage); + + /** + * 创建粉丝消息,通过自动回复 + * + * @param openid 公众号粉丝 openid + * @param reply 自动回复 + * @return 微信回复消息 XML + */ + WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply); + } 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 336401863..9d74d317a 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 @@ -9,6 +9,7 @@ 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.MpAutoReplyDO; 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; @@ -20,6 +21,7 @@ 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 me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -74,7 +76,7 @@ public class MpMessageServiceImpl implements MpMessageService { MpAccountDO account = mpAccountService.getAccountFromCache(appId); Assert.notNull(account, "公众号账号({}) 不存在", appId); MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser()); - Assert.notNull(account, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser()); + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser()); // 记录消息 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user); @@ -94,6 +96,23 @@ public class MpMessageServiceImpl implements MpMessageService { // wxMessage.getEventKey() } + @Override + public WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply) { + // 获得关联信息 + MpAccountDO account = mpAccountService.getAccountFromCache(reply.getAppId()); + Assert.notNull(account, "公众号账号({}) 不存在", reply.getAppId()); + MpUserDO user = mpUserService.getUser(reply.getAppId(), openid); + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", reply.getAppId(), openid); + + // 记录消息 + MpMessageDO message = MpMessageConvert.INSTANCE.convert(reply, account, user); + message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); + mpMessageMapper.insert(message); + + // 转换返回 WxMpXmlOutMessage 对象 + return MpMessageConvert.INSTANCE.convert02(message, account); + } + /** * 下载微信媒体文件的内容,并上传到文件服务 *