Commit 71beeabe9c57053f0e913fefccafdb454479f01d

Authored by YunaiV
1 parent 665f4b2b

mp:完成 menu 点击时,自动回复消息的逻辑

yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http
@@ -16,5 +16,19 @@ tenant-id: {{adminTenentId}} @@ -16,5 +16,19 @@ tenant-id: {{adminTenentId}}
16 "name":"搜索", 16 "name":"搜索",
17 "type":"view", 17 "type":"view",
18 "url":"http://www.soso.com/" 18 "url":"http://www.soso.com/"
  19 + },
  20 + {
  21 + "name": "父按钮",
  22 + "subButtons": [
  23 + {
  24 + "type":"click",
  25 + "name":"归去来兮",
  26 + "key":"MUSIC"
  27 + },
  28 + {
  29 + "name":"不说",
  30 + "type":"view",
  31 + "url":"https://www.soso.com/"
  32 + }]
19 }] 33 }]
20 } 34 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java
@@ -18,7 +18,4 @@ public class MpMenuBaseVO { @@ -18,7 +18,4 @@ public class MpMenuBaseVO {
18 @NotNull(message = "公众号账号的编号不能为空") 18 @NotNull(message = "公众号账号的编号不能为空")
19 private Long accountId; 19 private Long accountId;
20 20
21 - @NotNull(message = "按钮不能为空")  
22 - private List<WxMenuButton> buttons;  
23 -  
24 } 21 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java
@@ -2,6 +2,10 @@ package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; @@ -2,6 +2,10 @@ package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
2 2
3 import lombok.*; 3 import lombok.*;
4 import io.swagger.annotations.*; 4 import io.swagger.annotations.*;
  5 +import me.chanjar.weixin.common.bean.menu.WxMenuButton;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.util.List;
5 9
6 @ApiModel("管理后台 - 微信菜单保存 Request VO") 10 @ApiModel("管理后台 - 微信菜单保存 Request VO")
7 @Data 11 @Data
@@ -9,4 +13,7 @@ import io.swagger.annotations.*; @@ -9,4 +13,7 @@ import io.swagger.annotations.*;
9 @ToString(callSuper = true) 13 @ToString(callSuper = true)
10 public class MpMenuSaveReqVO extends MpMenuBaseVO { 14 public class MpMenuSaveReqVO extends MpMenuBaseVO {
11 15
  16 + @NotNull(message = "按钮不能为空")
  17 + private List<WxMenuButton> buttons;
  18 +
12 } 19 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java
@@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.mp.convert.menu; @@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.mp.convert.menu;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4
  5 +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
5 import org.mapstruct.Mapper; 6 import org.mapstruct.Mapper;
  7 +import org.mapstruct.Mapping;
  8 +import org.mapstruct.Mappings;
6 import org.mapstruct.factory.Mappers; 9 import org.mapstruct.factory.Mappers;
7 import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*; 10 import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*;
8 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; 11 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
@@ -16,4 +19,16 @@ public interface MpMenuConvert { @@ -16,4 +19,16 @@ public interface MpMenuConvert {
16 19
17 MpMenuRespVO convert(MpMenuDO bean); 20 MpMenuRespVO convert(MpMenuDO bean);
18 21
  22 + @Mappings({
  23 + @Mapping(source = "menu.appId", target = "appId"),
  24 + @Mapping(source = "menu.replyMessageType", target = "type"),
  25 + @Mapping(source = "menu.replyContent", target = "content"),
  26 + @Mapping(source = "menu.replyMediaId", target = "mediaId"),
  27 + @Mapping(source = "menu.replyMediaUrl", target = "mediaUrl"),
  28 + @Mapping(source = "menu.replyTitle", target = "title"),
  29 + @Mapping(source = "menu.replyDescription", target = "description"),
  30 + @Mapping(source = "menu.replyArticles", target = "articles"),
  31 + })
  32 + MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);
  33 +
19 } 34 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java
@@ -20,7 +20,7 @@ public interface MpAutoReplyConvert { @@ -20,7 +20,7 @@ public interface MpAutoReplyConvert {
20 @Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"), 20 @Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"),
21 @Mapping(source = "reply.responseTitle", target = "title"), 21 @Mapping(source = "reply.responseTitle", target = "title"),
22 @Mapping(source = "reply.responseDescription", target = "description"), 22 @Mapping(source = "reply.responseDescription", target = "description"),
23 - @Mapping(source = "reply.responseArticle", target = "article"), 23 + @Mapping(source = "reply.responseArticles", target = "articles"),
24 }) 24 })
25 MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply); 25 MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply);
26 26
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java
@@ -64,7 +64,7 @@ public interface MpMessageConvert { @@ -64,7 +64,7 @@ public interface MpMessageConvert {
64 .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()); 64 .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());
65 break; 65 break;
66 case WxConsts.XmlMsgType.NEWS: // 5. 图文 66 case WxConsts.XmlMsgType.NEWS: // 5. 图文
67 - message.setArticles(Collections.singletonList(sendReqBO.getArticle())); 67 + message.setArticles(sendReqBO.getArticles());
68 break; 68 break;
69 default: 69 default:
70 throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); 70 throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java
1 package cn.iocoder.yudao.module.mp.dal.dataobject.menu; 1 package cn.iocoder.yudao.module.mp.dal.dataobject.menu;
2 2
3 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 4 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
6 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; 5 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
@@ -8,7 +7,6 @@ import com.baomidou.mybatisplus.annotation.KeySequence; @@ -8,7 +7,6 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableField; 7 import com.baomidou.mybatisplus.annotation.TableField;
9 import com.baomidou.mybatisplus.annotation.TableId; 8 import com.baomidou.mybatisplus.annotation.TableId;
10 import com.baomidou.mybatisplus.annotation.TableName; 9 import com.baomidou.mybatisplus.annotation.TableName;
11 -import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;  
12 import lombok.Data; 10 import lombok.Data;
13 import lombok.EqualsAndHashCode; 11 import lombok.EqualsAndHashCode;
14 import lombok.ToString; 12 import lombok.ToString;
@@ -20,8 +18,6 @@ import java.util.List; @@ -20,8 +18,6 @@ import java.util.List;
20 /** 18 /**
21 * 微信菜单 DO 19 * 微信菜单 DO
22 * 20 *
23 - * 一个公众号,只有一个 MpMenuDO 记录。一个公众号的多个菜单,对应到就是 {@link #buttons} 多个按钮  
24 - *  
25 * @author 芋道源码 21 * @author 芋道源码
26 */ 22 */
27 @TableName(value = "mp_menu", autoResultMap = true) 23 @TableName(value = "mp_menu", autoResultMap = true)
@@ -32,7 +28,12 @@ import java.util.List; @@ -32,7 +28,12 @@ import java.util.List;
32 public class MpMenuDO extends BaseDO { 28 public class MpMenuDO extends BaseDO {
33 29
34 /** 30 /**
35 - * 主键 31 + * 编号 - 顶级菜单
  32 + */
  33 + public static final Long ID_ROOT = 0L;
  34 +
  35 + /**
  36 + * 编号
36 */ 37 */
37 @TableId 38 @TableId
38 private Long id; 39 private Long id;
@@ -50,127 +51,105 @@ public class MpMenuDO extends BaseDO { @@ -50,127 +51,105 @@ public class MpMenuDO extends BaseDO {
50 private String appId; 51 private String appId;
51 52
52 /** 53 /**
53 - * 按钮列表  
54 - */  
55 - @TableField(typeHandler = ButtonTypeHandler.class)  
56 - private List<Button> buttons;  
57 - /**  
58 - * 同步状态  
59 - *  
60 - * true - 已同步  
61 - * false - 未同步  
62 - */  
63 - private Boolean syncStatus;  
64 -  
65 - /**  
66 - * 按钮  
67 - */  
68 - @Data  
69 - public static class Button {  
70 -  
71 - /**  
72 - * 类型  
73 - *  
74 - * 枚举 {@link MenuButtonType}  
75 - */  
76 - private String type;  
77 - /**  
78 - * 消息类型  
79 - *  
80 - * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG  
81 - *  
82 - * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS  
83 - */  
84 - private String messageType;  
85 - /**  
86 - * 名称  
87 - */  
88 - private String name;  
89 - /**  
90 - * 标识  
91 - */  
92 - private String key;  
93 - /**  
94 - * 二级菜单列表  
95 - */  
96 - private List<Button> subButtons;  
97 - /**  
98 - * 网页链接  
99 - *  
100 - * 用户点击菜单可打开链接,不超过 1024 字节  
101 - *  
102 - * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM  
103 - */  
104 - private String url;  
105 -  
106 - /**  
107 - * 小程序的 appId  
108 - *  
109 - * 类型为 {@link WxConsts.XmlMsgType} 的 MINIPROGRAM  
110 - */  
111 - private String appId;  
112 -  
113 - /**  
114 - * 小程序的页面路径  
115 - *  
116 - * 类型为 {@link WxConsts.XmlMsgType} 的 MINIPROGRAM  
117 - */  
118 - private String pagePath;  
119 -  
120 - /**  
121 - * 消息内容  
122 - *  
123 - * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT  
124 - */  
125 - private String content;  
126 -  
127 - /**  
128 - * 媒体 id  
129 - *  
130 - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO  
131 - */  
132 - private String mediaId;  
133 - /**  
134 - * 媒体 URL  
135 - *  
136 - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO  
137 - */  
138 - private String mediaUrl;  
139 -  
140 - /**  
141 - * 回复的标题  
142 - *  
143 - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO  
144 - */  
145 - private String title;  
146 - /**  
147 - * 回复的描述  
148 - *  
149 - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO  
150 - */  
151 - private String description;  
152 -  
153 - /**  
154 - * 图文消息  
155 - *  
156 - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS  
157 - */  
158 - private MpMessageDO.Article article;  
159 -  
160 - }  
161 -  
162 - // TODO @芋艿:可以找一些新的思路  
163 - public static class ButtonTypeHandler extends AbstractJsonTypeHandler<List<Button>> {  
164 -  
165 - @Override  
166 - protected List<Button> parse(String json) {  
167 - return JsonUtils.parseArray(json, Button.class);  
168 - }  
169 -  
170 - @Override  
171 - protected String toJson(List<Button> obj) {  
172 - return JsonUtils.toJsonString(obj);  
173 - }  
174 -  
175 - } 54 + * 菜单名称
  55 + */
  56 + private String name;
  57 + /**
  58 + * 菜单标识
  59 + *
  60 + * 支持多 DB 类型时,无法直接使用 key + @TableField("menuKey") 来实现转换,原因是 "menuKey" AS key 而存在报错
  61 + */
  62 + private String menuKey;
  63 + /**
  64 + * 父菜单编号
  65 + */
  66 + private Long parentId;
  67 + /**
  68 + * 排序
  69 + */
  70 + private Integer sort;
  71 +
  72 + // ========== 按钮操作 ==========
  73 +
  74 + /**
  75 + * 按钮类型
  76 + *
  77 + * 枚举 {@link MenuButtonType}
  78 + */
  79 + private String type;
  80 +
  81 + /**
  82 + * 网页链接
  83 + *
  84 + * 用户点击菜单可打开链接,不超过 1024 字节
  85 + *
  86 + * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM
  87 + */
  88 + private String url;
  89 +
  90 + /**
  91 + * 小程序的 appId
  92 + *
  93 + * 类型为 {@link MenuButtonType} 的 MINIPROGRAM
  94 + */
  95 + private String miniProgramAppId;
  96 + /**
  97 + * 小程序的页面路径
  98 + *
  99 + * 类型为 {@link MenuButtonType} 的 MINIPROGRAM
  100 + */
  101 + private String miniProgramPagePath;
  102 +
  103 + // ========== 消息内容 ==========
  104 +
  105 + /**
  106 + * 消息类型
  107 + *
  108 + * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG
  109 + *
  110 + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
  111 + */
  112 + private String replyMessageType;
  113 +
  114 + /**
  115 + * 回复的消息内容
  116 + *
  117 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
  118 + */
  119 + private String replyContent;
  120 +
  121 + /**
  122 + * 回复的媒体 id
  123 + *
  124 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
  125 + */
  126 + private String replyMediaId;
  127 + /**
  128 + * 回复的媒体 URL
  129 + *
  130 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
  131 + */
  132 + private String replyMediaUrl;
  133 +
  134 + /**
  135 + * 回复的标题
  136 + *
  137 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
  138 + */
  139 + private String replyTitle;
  140 + /**
  141 + * 回复的描述
  142 + *
  143 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
  144 + */
  145 + private String replyDescription;
  146 +
  147 + /**
  148 + * 回复的图文消息数组
  149 + *
  150 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
  151 + */
  152 + @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
  153 + private List<MpMessageDO.Article> replyArticles;
  154 +
176 } 155 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java
@@ -16,6 +16,7 @@ import lombok.ToString; @@ -16,6 +16,7 @@ import lombok.ToString;
16 import me.chanjar.weixin.common.api.WxConsts; 16 import me.chanjar.weixin.common.api.WxConsts;
17 import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; 17 import me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
18 18
  19 +import java.util.List;
19 import java.util.Set; 20 import java.util.Set;
20 21
21 /** 22 /**
@@ -130,6 +131,6 @@ public class MpAutoReplyDO extends BaseDO { @@ -130,6 +131,6 @@ public class MpAutoReplyDO extends BaseDO {
130 * 131 *
131 * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS 132 * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
132 */ 133 */
133 - @TableField(typeHandler = JacksonTypeHandler.class)  
134 - private MpMessageDO.Article responseArticle; 134 + @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
  135 + private List<MpMessageDO.Article> responseArticles;
135 } 136 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java
@@ -7,8 +7,9 @@ import org.apache.ibatis.annotations.Mapper; @@ -7,8 +7,9 @@ import org.apache.ibatis.annotations.Mapper;
7 @Mapper 7 @Mapper
8 public interface MpMenuMapper extends BaseMapperX<MpMenuDO> { 8 public interface MpMenuMapper extends BaseMapperX<MpMenuDO> {
9 9
10 - default MpMenuDO selectByAppId(String appId) {  
11 - return selectOne(MpMenuDO::getAppId, appId); 10 + default MpMenuDO selectByAppIdAndMenuKey(String appId, String menuKey) {
  11 + return selectOne(MpMenuDO::getAppId, appId,
  12 + MpMenuDO::getMenuKey, menuKey);
12 } 13 }
13 14
14 } 15 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java
1 package cn.iocoder.yudao.module.mp.service.menu; 1 package cn.iocoder.yudao.module.mp.service.menu;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.hutool.core.lang.Assert;  
5 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; 4 import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert;
7 -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;  
8 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; 5 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
9 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; 6 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
10 -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; 7 +import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
  8 +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
11 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
12 import me.chanjar.weixin.common.bean.menu.WxMenu; 10 import me.chanjar.weixin.common.bean.menu.WxMenu;
13 import me.chanjar.weixin.common.error.WxErrorException; 11 import me.chanjar.weixin.common.error.WxErrorException;
@@ -38,6 +36,9 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; @@ -38,6 +36,9 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
38 public class MpMenuServiceImpl implements MpMenuService { 36 public class MpMenuServiceImpl implements MpMenuService {
39 37
40 @Resource 38 @Resource
  39 + private MpMessageService mpMessageService;
  40 +
  41 + @Resource
41 @Lazy // 延迟加载,避免循环引用报错 42 @Lazy // 延迟加载,避免循环引用报错
42 private MpServiceFactory mpServiceFactory; 43 private MpServiceFactory mpServiceFactory;
43 44
@@ -87,47 +88,21 @@ public class MpMenuServiceImpl implements MpMenuService { @@ -87,47 +88,21 @@ public class MpMenuServiceImpl implements MpMenuService {
87 88
88 @Override 89 @Override
89 public WxMpXmlOutMessage reply(String appId, String key, String openid) { 90 public WxMpXmlOutMessage reply(String appId, String key, String openid) {
90 - // 获得菜单  
91 - MpMenuDO menu = mpMenuMapper.selectByAppId(appId); 91 + // 第一步,获得菜单
  92 + MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key);
92 if (menu == null) { 93 if (menu == null) {
93 - log.error("[reply][appId({}) 找不到对应的菜单]", appId);  
94 - return null;  
95 - }  
96 - // 匹配对应的按钮  
97 - MpMenuDO.Button button = getMenuButton(menu, key);  
98 - if (button == null) {  
99 - log.error("[reply][appId({}) key({}) 找不到对应的菜单按钮]", appId, key); 94 + log.error("[reply][appId({}) key({}) 找不到对应的菜单]", appId, key);
100 return null; 95 return null;
101 } 96 }
102 // 按钮必须要有消息类型,不然后续无法回复消息 97 // 按钮必须要有消息类型,不然后续无法回复消息
103 - if (StrUtil.isEmpty(button.getMessageType())) {  
104 - log.error("[reply][appId({}) key({}) 不存在消息类型({})]", appId, key, button); 98 + if (StrUtil.isEmpty(menu.getReplyMessageType())) {
  99 + log.error("[reply][menu({}) 不存在对应的消息类型]", menu);
105 return null; 100 return null;
106 } 101 }
107 102
108 - // 回复消息  
109 - return null;  
110 - }  
111 -  
112 - private MpMenuDO.Button getMenuButton(MpMenuDO menu, String key) {  
113 - // 先查询子按钮  
114 - for (MpMenuDO.Button button : menu.getButtons()) {  
115 - if (CollUtil.isEmpty(button.getSubButtons())) {  
116 - continue;  
117 - }  
118 - for (MpMenuDO.Button subButton : button.getSubButtons()) {  
119 - if (StrUtil.equals(subButton.getKey(), key)) {  
120 - return subButton;  
121 - }  
122 - }  
123 - }  
124 - // 再查询父按钮  
125 - for (MpMenuDO.Button button : menu.getButtons()) {  
126 - if (StrUtil.equals(button.getKey(), key)) {  
127 - return button;  
128 - }  
129 - }  
130 - return null; 103 + // 第二步,回复消息
  104 + MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu);
  105 + return mpMessageService.sendOutMessage(sendReqBO);
131 } 106 }
132 107
133 } 108 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java
@@ -9,6 +9,7 @@ import me.chanjar.weixin.common.api.WxConsts; @@ -9,6 +9,7 @@ import me.chanjar.weixin.common.api.WxConsts;
9 import javax.validation.Valid; 9 import javax.validation.Valid;
10 import javax.validation.constraints.NotEmpty; 10 import javax.validation.constraints.NotEmpty;
11 import javax.validation.constraints.NotNull; 11 import javax.validation.constraints.NotNull;
  12 +import java.util.List;
12 13
13 /** 14 /**
14 * 公众号消息发送 Request BO 15 * 公众号消息发送 Request BO
@@ -85,6 +86,6 @@ public class MpMessageSendOutReqBO { @@ -85,6 +86,6 @@ public class MpMessageSendOutReqBO {
85 */ 86 */
86 @Valid 87 @Valid
87 @NotNull(message = "图文消息不能为空", groups = NewsGroup.class) 88 @NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
88 - private MpMessageDO.Article article; 89 + private List<MpMessageDO.Article> articles;
89 90
90 } 91 }