Commit 44f0bcd182a9a9e7b23dcadff7071e548d4a4160

Authored by YunaiV
1 parent 48bea21a

mp:实现关键字,自动回复

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;
4 4 import lombok.Getter;
5 5  
6 6 /**
7   - * 微信消息自动回复的匹配模式
  7 + * 公众号消息自动回复的匹配模式
8 8 *
9 9 * @author 芋道源码
10 10 */
... ...
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;
4 4 import lombok.Getter;
5 5  
6 6 /**
7   - * 微信消息自动回复的类型
  7 + * 公众号消息自动回复的类型
8 8 *
9 9 * @author 芋道源码
10 10 */
... ... @@ -14,7 +14,7 @@ public enum MpAutoReplyTypeEnum {
14 14  
15 15 SUBSCRIBE(1, "关注时回复"),
16 16 MESSAGE(2, "收到消息回复"),
17   - KEYWORD(2, "关键词回复"),
  17 + KEYWORD(3, "关键词回复"),
18 18 ;
19 19  
20 20 /**
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java
1 1 package cn.iocoder.yudao.module.mp.convert.message;
2 2  
3   -import java.util.*;
4   -
  3 +import cn.hutool.core.util.StrUtil;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -
  5 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  6 +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
7 7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
  8 +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
8 9 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
9 10 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
  11 +import me.chanjar.weixin.common.api.WxConsts;
10 12 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
  13 +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
  14 +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
  15 +import me.chanjar.weixin.mp.builder.outxml.BaseBuilder;
  16 +import me.chanjar.weixin.mp.builder.outxml.TextBuilder;
  17 +import me.chanjar.weixin.mp.builder.outxml.VideoBuilder;
11 18 import org.mapstruct.Mapper;
12 19 import org.mapstruct.Mapping;
13 20 import org.mapstruct.Mappings;
14 21 import org.mapstruct.factory.Mappers;
15   -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*;
  22 +
  23 +import java.util.Collections;
  24 +import java.util.List;
16 25  
17 26 @Mapper
18 27 public interface MpMessageConvert {
... ... @@ -41,4 +50,64 @@ public interface MpMessageConvert {
41 50 }
42 51 return message;
43 52 }
  53 +
  54 + default MpMessageDO convert(MpAutoReplyDO reply, MpAccountDO account, MpUserDO user) {
  55 + // 构建消息
  56 + MpMessageDO message = new MpMessageDO();
  57 + message.setType(reply.getResponseMessageType());
  58 + // 1. 文本
  59 + if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.TEXT)) {
  60 + message.setContent(reply.getResponseContent());
  61 + } else if (ObjectUtils.equalsAny(reply.getResponseMessageType(), WxConsts.XmlMsgType.IMAGE, // 2. 图片
  62 + WxConsts.XmlMsgType.VOICE)) { // 3. 语音
  63 + message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl());
  64 + } else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.VIDEO)) { // 4. 视频
  65 + message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl())
  66 + .setTitle(reply.getResponseTitle()).setDescription(reply.getResponseDescription());
  67 + } else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.NEWS)) { // 5. 图文
  68 + message.setArticles(Collections.singletonList(reply.getResponseArticle()));
  69 + } else {
  70 + throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
  71 + }
  72 +
  73 + // 其它字段
  74 + if (account != null) {
  75 + message.setAccountId(account.getId()).setAppId(account.getAppId());
  76 + }
  77 + if (user != null) {
  78 + message.setUserId(user.getId()).setOpenid(user.getOpenid());
  79 + }
  80 + return message;
  81 + }
  82 +
  83 + default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) {
  84 + BaseBuilder<?, ? extends WxMpXmlOutMessage> messageBuilder;
  85 + // 个性化字段
  86 + switch (message.getType()) {
  87 + case WxConsts.XmlMsgType.TEXT:
  88 + messageBuilder = WxMpXmlOutMessage.TEXT().content(message.getContent());
  89 + break;
  90 + case WxConsts.XmlMsgType.IMAGE:
  91 + messageBuilder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());
  92 + break;
  93 + case WxConsts.XmlMsgType.VOICE:
  94 + messageBuilder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());
  95 + break;
  96 + case WxConsts.XmlMsgType.VIDEO:
  97 + messageBuilder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())
  98 + .title(message.getTitle()).description(message.getDescription());
  99 + break;
  100 + case WxConsts.XmlMsgType.NEWS:
  101 + messageBuilder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));
  102 + break;
  103 + default:
  104 + throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
  105 + }
  106 + // 通用字段
  107 + messageBuilder.fromUser(account.getAccount());
  108 + messageBuilder.toUser(message.getOpenid());
  109 + return messageBuilder.build();
  110 + }
  111 + List<WxMpXmlOutNewsMessage.Item> convertList02(List<MpMessageDO.Article> list);
  112 +
44 113 }
... ...
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;
6 6 import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
7 7 import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
8 8 import com.baomidou.mybatisplus.annotation.KeySequence;
  9 +import com.baomidou.mybatisplus.annotation.TableField;
9 10 import com.baomidou.mybatisplus.annotation.TableId;
10 11 import com.baomidou.mybatisplus.annotation.TableName;
  12 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
11 13 import lombok.Data;
12 14 import lombok.EqualsAndHashCode;
13 15 import lombok.ToString;
14 16 import me.chanjar.weixin.common.api.WxConsts;
15 17 import me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
16 18  
  19 +import java.util.List;
17 20 import java.util.Set;
18 21  
19 22 /**
20   - * 微信消息自动回复 DO
  23 + * 公众号消息自动回复 DO
21 24 *
22 25 * @author 芋道源码
23 26 */
... ... @@ -99,4 +102,35 @@ public class MpAutoReplyDO extends BaseDO {
99 102 */
100 103 private String responseContent;
101 104  
  105 + /**
  106 + * 回复的媒体 id
  107 + *
  108 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
  109 + */
  110 + private String responseMediaId;
  111 + /**
  112 + * 回复的媒体 URL
  113 + */
  114 + private String responseMediaUrl;
  115 +
  116 + /**
  117 + * 回复的标题
  118 + *
  119 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
  120 + */
  121 + private String responseTitle;
  122 + /**
  123 + * 回复的描述
  124 + *
  125 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
  126 + */
  127 + private String responseDescription;
  128 +
  129 + /**
  130 + * 回复的图文消息
  131 + *
  132 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
  133 + */
  134 + @TableField(typeHandler = JacksonTypeHandler.class)
  135 + private MpMessageDO.Article responseArticle;
102 136 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java
1 1 package cn.iocoder.yudao.module.mp.dal.dataobject.message;
2 2  
  3 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
3 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 5 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
5 6 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
6 7 import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
7 8 import com.baomidou.mybatisplus.annotation.KeySequence;
  9 +import com.baomidou.mybatisplus.annotation.TableField;
8 10 import com.baomidou.mybatisplus.annotation.TableId;
9 11 import com.baomidou.mybatisplus.annotation.TableName;
  12 +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
10 13 import lombok.*;
11 14 import me.chanjar.weixin.common.api.WxConsts;
12 15  
  16 +import java.io.Serializable;
  17 +import java.util.List;
  18 +
13 19 /**
14   - * 微信消息 DO
  20 + * 公众号消息 DO
15 21 *
16 22 * @author 芋道源码
17 23 */
... ... @@ -160,6 +166,14 @@ public class MpMessageDO extends BaseDO {
160 166 */
161 167 private String label;
162 168  
  169 + /**
  170 + * 图文消息数组
  171 + *
  172 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
  173 + */
  174 + @TableField(typeHandler = ArticleTypeHandler.class)
  175 + private List<Article> articles;
  176 +
163 177 // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
164 178  
165 179 /**
... ... @@ -176,4 +190,45 @@ public class MpMessageDO extends BaseDO {
176 190 */
177 191 private String eventKey;
178 192  
  193 + /**
  194 + * 文章
  195 + */
  196 + @Data
  197 + public static class Article implements Serializable {
  198 +
  199 + /**
  200 + * 图文消息标题
  201 + */
  202 + private String title;
  203 + /**
  204 + * 图文消息描述
  205 + */
  206 + private String description;
  207 + /**
  208 + * 图片链接
  209 + *
  210 + * 支持JPG、PNG格式,较好的效果为大图 360*200,小图 200*200
  211 + */
  212 + private String picUrl;
  213 + /**
  214 + * 点击图文消息跳转链接
  215 + */
  216 + private String url;
  217 +
  218 + }
  219 +
  220 + // TODO @芋艿:可以找一些新的思路
  221 + public static class ArticleTypeHandler extends AbstractJsonTypeHandler<List<Article>> {
  222 +
  223 + @Override
  224 + protected List<Article> parse(String json) {
  225 + return JsonUtils.parseArray(json, Article.class);
  226 + }
  227 +
  228 + @Override
  229 + protected String toJson(List<Article> obj) {
  230 + return JsonUtils.toJsonString(obj);
  231 + }
  232 +
  233 + }
179 234 }
... ...
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;
5 5 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
6 6 import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
7 7 import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
  8 +import org.apache.ibatis.annotations.Mapper;
8 9  
9 10 import java.util.List;
10 11  
  12 +@Mapper
11 13 public interface MpAutoReplyMapper extends BaseMapperX<MpAutoReplyDO> {
12 14  
13 15 default List<MpAutoReplyDO> selectListByAppIdAndKeywordAll(String appId, String requestKeyword) {
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java
1 1 package cn.iocoder.yudao.module.mp.service.message;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.mp.builder.TextBuilder;
4 5 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
5 6 import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper;
6 7 import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
7 8 import me.chanjar.weixin.common.api.WxConsts;
8 9 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
9 10 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
  11 +import org.springframework.stereotype.Service;
10 12 import org.springframework.validation.annotation.Validated;
11 13  
12 14 import javax.annotation.Resource;
... ... @@ -17,18 +19,21 @@ import java.util.List;
17 19 *
18 20 * @author 芋道源码
19 21 */
20   -@Resource
  22 +@Service
21 23 @Validated
22 24 public class MpAutoReplyServiceImpl implements MpAutoReplyService {
23 25  
24 26 @Resource
25   - private MpAutoReplyService mpAutoReplyService;
  27 + private MpMessageService mpMessageService;
26 28  
27 29 @Resource
28 30 private MpAutoReplyMapper mpAutoReplyMapper;
29 31  
30 32 @Override
31 33 public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) {
  34 +// if (true) {
  35 +// return new TextBuilder().build("nihao", wxMessage, null);
  36 +// }
32 37 // 第一步,匹配自动回复
33 38 List<MpAutoReplyDO> replies = null;
34 39 // 1.1 关键字
... ... @@ -47,12 +52,10 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService {
47 52 if (CollUtil.isEmpty(replies)) {
48 53 return null;
49 54 }
  55 + MpAutoReplyDO reply = CollUtil.getFirst(replies);
50 56  
51 57 // 第二步,基于自动回复,创建消息
52   -
53   -
54   - // 第三步,将消息转换成 WxMpXmlOutMessage 返回
55   - return null;
  58 + return mpMessageService.createFromAutoReply(wxMessage.getFromUser(), reply);
56 59 }
57 60  
58 61 }
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
  5 +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
5 6 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
6 7 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
  8 +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
7 9  
8 10 /**
9 11 * 粉丝消息表 Service 接口
... ... @@ -28,4 +30,14 @@ public interface MpMessageService {
28 30 * @param wxMessage 消息
29 31 */
30 32 void createFromUser(String appId, WxMpXmlMessage wxMessage);
  33 +
  34 + /**
  35 + * 创建粉丝消息,通过自动回复
  36 + *
  37 + * @param openid 公众号粉丝 openid
  38 + * @param reply 自动回复
  39 + * @return 微信回复消息 XML
  40 + */
  41 + WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply);
  42 +
31 43 }
... ...
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;
9 9 import cn.iocoder.yudao.module.infra.api.file.FileApi;
10 10 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
11 11 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
  12 +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
12 13 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
13 14 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
14 15 import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
... ... @@ -20,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
20 21 import me.chanjar.weixin.common.error.WxErrorException;
21 22 import me.chanjar.weixin.mp.api.WxMpService;
22 23 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
  24 +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
23 25 import org.springframework.context.annotation.Lazy;
24 26 import org.springframework.stereotype.Service;
25 27  
... ... @@ -74,7 +76,7 @@ public class MpMessageServiceImpl implements MpMessageService {
74 76 MpAccountDO account = mpAccountService.getAccountFromCache(appId);
75 77 Assert.notNull(account, "公众号账号({}) 不存在", appId);
76 78 MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser());
77   - Assert.notNull(account, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
  79 + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
78 80  
79 81 // 记录消息
80 82 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
... ... @@ -94,6 +96,23 @@ public class MpMessageServiceImpl implements MpMessageService {
94 96 // wxMessage.getEventKey()
95 97 }
96 98  
  99 + @Override
  100 + public WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply) {
  101 + // 获得关联信息
  102 + MpAccountDO account = mpAccountService.getAccountFromCache(reply.getAppId());
  103 + Assert.notNull(account, "公众号账号({}) 不存在", reply.getAppId());
  104 + MpUserDO user = mpUserService.getUser(reply.getAppId(), openid);
  105 + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", reply.getAppId(), openid);
  106 +
  107 + // 记录消息
  108 + MpMessageDO message = MpMessageConvert.INSTANCE.convert(reply, account, user);
  109 + message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
  110 + mpMessageMapper.insert(message);
  111 +
  112 + // 转换返回 WxMpXmlOutMessage 对象
  113 + return MpMessageConvert.INSTANCE.convert02(message, account);
  114 + }
  115 +
97 116 /**
98 117 * 下载微信媒体文件的内容,并上传到文件服务
99 118 *
... ...