Commit 665f4b2b0926f4cf10b295724ffa06be04064a92

Authored by YunaiV
1 parent 47f07156

mp:重构 message 发送消息的方法

Showing 15 changed files with 453 additions and 84 deletions
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.convert.message;
  2 +
  3 +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
  4 +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
  5 +import org.mapstruct.Mapper;
  6 +import org.mapstruct.Mapping;
  7 +import org.mapstruct.Mappings;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +@Mapper
  11 +public interface MpAutoReplyConvert {
  12 +
  13 + MpAutoReplyConvert INSTANCE = Mappers.getMapper(MpAutoReplyConvert.class);
  14 +
  15 + @Mappings({
  16 + @Mapping(source = "reply.appId", target = "appId"),
  17 + @Mapping(source = "reply.responseMessageType", target = "type"),
  18 + @Mapping(source = "reply.responseContent", target = "content"),
  19 + @Mapping(source = "reply.responseMediaId", target = "mediaId"),
  20 + @Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"),
  21 + @Mapping(source = "reply.responseTitle", target = "title"),
  22 + @Mapping(source = "reply.responseDescription", target = "description"),
  23 + @Mapping(source = "reply.responseArticle", target = "article"),
  24 + })
  25 + MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply);
  26 +
  27 +}
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java
1 package cn.iocoder.yudao.module.mp.convert.message; 1 package cn.iocoder.yudao.module.mp.convert.message;
2 2
3 -import cn.hutool.core.util.StrUtil;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;  
6 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO; 4 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 5 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
8 -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;  
9 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; 6 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
10 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; 7 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
  8 +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
11 import me.chanjar.weixin.common.api.WxConsts; 9 import me.chanjar.weixin.common.api.WxConsts;
12 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 10 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
13 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 11 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
14 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; 12 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
15 import me.chanjar.weixin.mp.builder.outxml.BaseBuilder; 13 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;  
18 import org.mapstruct.Mapper; 14 import org.mapstruct.Mapper;
19 import org.mapstruct.Mapping; 15 import org.mapstruct.Mapping;
20 import org.mapstruct.Mappings; 16 import org.mapstruct.Mappings;
@@ -51,23 +47,27 @@ public interface MpMessageConvert { @@ -51,23 +47,27 @@ public interface MpMessageConvert {
51 return message; 47 return message;
52 } 48 }
53 49
54 - default MpMessageDO convert(MpAutoReplyDO reply, MpAccountDO account, MpUserDO user) { 50 + default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) {
55 // 构建消息 51 // 构建消息
56 MpMessageDO message = new MpMessageDO(); 52 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()); 53 + message.setType(sendReqBO.getType());
  54 + switch (sendReqBO.getType()) {
  55 + case WxConsts.XmlMsgType.TEXT: // 1. 文本
  56 + message.setContent(sendReqBO.getContent());
  57 + break;
  58 + case WxConsts.XmlMsgType.IMAGE: // 2. 图片
  59 + case WxConsts.XmlMsgType.VOICE: // 3. 语音
  60 + message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl());
  61 + break;
  62 + case WxConsts.XmlMsgType.VIDEO: // 4. 视频
  63 + message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl())
  64 + .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());
  65 + break;
  66 + case WxConsts.XmlMsgType.NEWS: // 5. 图文
  67 + message.setArticles(Collections.singletonList(sendReqBO.getArticle()));
  68 + break;
  69 + default:
  70 + throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
71 } 71 }
72 72
73 // 其它字段 73 // 其它字段
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; 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 5 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
5 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; 6 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
  7 +import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableField;
  9 +import com.baomidou.mybatisplus.annotation.TableId;
  10 +import com.baomidou.mybatisplus.annotation.TableName;
6 import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; 11 import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
7 -import lombok.*;  
8 -  
9 -import com.baomidou.mybatisplus.annotation.*;  
10 -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;  
11 -import me.chanjar.weixin.common.bean.menu.WxMenu;  
12 -import me.chanjar.weixin.common.bean.menu.WxMenuButton; 12 +import lombok.Data;
  13 +import lombok.EqualsAndHashCode;
  14 +import lombok.ToString;
  15 +import me.chanjar.weixin.common.api.WxConsts;
  16 +import me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
13 17
14 import java.util.List; 18 import java.util.List;
15 19
@@ -48,8 +52,8 @@ public class MpMenuDO extends BaseDO { @@ -48,8 +52,8 @@ public class MpMenuDO extends BaseDO {
48 /** 52 /**
49 * 按钮列表 53 * 按钮列表
50 */ 54 */
51 - @TableField(typeHandler = WxMenuButtonTypeHandler.class)  
52 - private List<WxMenuButton> buttons; 55 + @TableField(typeHandler = ButtonTypeHandler.class)
  56 + private List<Button> buttons;
53 /** 57 /**
54 * 同步状态 58 * 同步状态
55 * 59 *
@@ -58,16 +62,113 @@ public class MpMenuDO extends BaseDO { @@ -58,16 +62,113 @@ public class MpMenuDO extends BaseDO {
58 */ 62 */
59 private Boolean syncStatus; 63 private Boolean syncStatus;
60 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 +
61 // TODO @芋艿:可以找一些新的思路 162 // TODO @芋艿:可以找一些新的思路
62 - public static class WxMenuButtonTypeHandler extends AbstractJsonTypeHandler<List<WxMenuButton>> { 163 + public static class ButtonTypeHandler extends AbstractJsonTypeHandler<List<Button>> {
63 164
64 @Override 165 @Override
65 - protected List<WxMenuButton> parse(String json) {  
66 - return JsonUtils.parseArray(json, WxMenuButton.class); 166 + protected List<Button> parse(String json) {
  167 + return JsonUtils.parseArray(json, Button.class);
67 } 168 }
68 169
69 @Override 170 @Override
70 - protected String toJson(List<WxMenuButton> obj) { 171 + protected String toJson(List<Button> obj) {
71 return JsonUtils.toJsonString(obj); 172 return JsonUtils.toJsonString(obj);
72 } 173 }
73 174
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java
@@ -16,7 +16,6 @@ import lombok.ToString; @@ -16,7 +16,6 @@ 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;  
20 import java.util.Set; 19 import java.util.Set;
21 20
22 /** 21 /**
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java
@@ -12,7 +12,9 @@ import com.baomidou.mybatisplus.annotation.TableName; @@ -12,7 +12,9 @@ import com.baomidou.mybatisplus.annotation.TableName;
12 import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; 12 import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
13 import lombok.*; 13 import lombok.*;
14 import me.chanjar.weixin.common.api.WxConsts; 14 import me.chanjar.weixin.common.api.WxConsts;
  15 +import me.chanjar.weixin.mp.builder.kefu.NewsBuilder;
15 16
  17 +import javax.validation.constraints.NotEmpty;
16 import java.io.Serializable; 18 import java.io.Serializable;
17 import java.util.List; 19 import java.util.List;
18 20
@@ -199,20 +201,24 @@ public class MpMessageDO extends BaseDO { @@ -199,20 +201,24 @@ public class MpMessageDO extends BaseDO {
199 /** 201 /**
200 * 图文消息标题 202 * 图文消息标题
201 */ 203 */
  204 + @NotEmpty(message = "图文消息标题不能为空", groups = NewsBuilder.class)
202 private String title; 205 private String title;
203 /** 206 /**
204 * 图文消息描述 207 * 图文消息描述
205 */ 208 */
  209 + @NotEmpty(message = "图文消息描述不能为空", groups = NewsBuilder.class)
206 private String description; 210 private String description;
207 /** 211 /**
208 * 图片链接 212 * 图片链接
209 * 213 *
210 - * 支持JPG、PNG格式,较好的效果为大图 360*200,小图 200*200 214 + * 支持 JPG、PNG 格式,较好的效果为大图 360*200,小图 200*200
211 */ 215 */
  216 + @NotEmpty(message = "图片链接不能为空", groups = NewsBuilder.class)
212 private String picUrl; 217 private String picUrl;
213 /** 218 /**
214 * 点击图文消息跳转链接 219 * 点击图文消息跳转链接
215 */ 220 */
  221 + @NotEmpty(message = "点击图文消息跳转链接不能为空", groups = NewsBuilder.class)
216 private String url; 222 private String url;
217 223
218 } 224 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java
@@ -7,4 +7,8 @@ import org.apache.ibatis.annotations.Mapper; @@ -7,4 +7,8 @@ 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);
  12 + }
  13 +
10 } 14 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.framework.mp.core.util;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import me.chanjar.weixin.common.api.WxConsts;
  6 +
  7 +import javax.validation.Validator;
  8 +
  9 +/**
  10 + * 公众号工具类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Slf4j
  15 +public class MpUtils {
  16 +
  17 + /**
  18 + * Text 类型的消息,参数校验 Group
  19 + */
  20 + public interface TextGroup {}
  21 +
  22 + /**
  23 + * Image 类型的消息,参数校验 Group
  24 + */
  25 + public interface ImageGroup {}
  26 +
  27 + /**
  28 + * Voice 类型的消息,参数校验 Group
  29 + */
  30 + public interface VoiceGroup {}
  31 +
  32 + /**
  33 + * Video 类型的消息,参数校验 Group
  34 + */
  35 + public interface VideoGroup {}
  36 +
  37 + /**
  38 + * News 类型的消息,参数校验 Group
  39 + */
  40 + public interface NewsGroup {}
  41 +
  42 + /**
  43 + * 校验消息的格式是否符合要求
  44 + *
  45 + * @param type 类型
  46 + * @param message 消息
  47 + */
  48 + public static void validateMessage(Validator validator, String type, Object message) {
  49 + // 获得对应的校验 group
  50 + Class<?> group;
  51 + switch (type) {
  52 + case WxConsts.XmlMsgType.TEXT:
  53 + group = TextGroup.class;
  54 + break;
  55 + case WxConsts.XmlMsgType.IMAGE:
  56 + group = ImageGroup.class;
  57 + break;
  58 + case WxConsts.XmlMsgType.VOICE:
  59 + group = VoiceGroup.class;
  60 + break;
  61 + case WxConsts.XmlMsgType.VIDEO:
  62 + group = VideoGroup.class;
  63 + break;
  64 + case WxConsts.XmlMsgType.NEWS:
  65 + group = NewsGroup.class;
  66 + break;
  67 + default:
  68 + log.error("[validateMessage][未知的消息类型({})]", message);
  69 + throw new IllegalArgumentException("不支持的消息类型:" + type);
  70 + }
  71 + // 执行校验
  72 + ValidationUtils.validate(validator, message, group);
  73 + }
  74 +
  75 +}
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java
1 package cn.iocoder.yudao.module.mp.service.handler.menu; 1 package cn.iocoder.yudao.module.mp.service.handler.menu;
2 2
  3 +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
  4 +import cn.iocoder.yudao.module.mp.service.menu.MpMenuService;
3 import me.chanjar.weixin.common.session.WxSessionManager; 5 import me.chanjar.weixin.common.session.WxSessionManager;
  6 +import me.chanjar.weixin.mp.api.WxMpMenuService;
4 import me.chanjar.weixin.mp.api.WxMpMessageHandler; 7 import me.chanjar.weixin.mp.api.WxMpMessageHandler;
5 import me.chanjar.weixin.mp.api.WxMpService; 8 import me.chanjar.weixin.mp.api.WxMpService;
6 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 9 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
7 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 10 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
8 import org.springframework.stereotype.Component; 11 import org.springframework.stereotype.Component;
9 12
  13 +import javax.annotation.Resource;
10 import java.util.Map; 14 import java.util.Map;
11 15
12 import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; 16 import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
@@ -14,24 +18,20 @@ import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; @@ -14,24 +18,20 @@ import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
14 /** 18 /**
15 * 自定义菜单的事件处理器 19 * 自定义菜单的事件处理器
16 * 20 *
17 - * // TODO 芋艿:待实现 21 + * 逻辑:用户点击菜单时,触发对应的回复
  22 + *
  23 + * @author 芋道源码
18 */ 24 */
19 @Component 25 @Component
20 public class MenuHandler implements WxMpMessageHandler { 26 public class MenuHandler implements WxMpMessageHandler {
21 27
22 - @Override  
23 - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,  
24 - Map<String, Object> context, WxMpService weixinService,  
25 - WxSessionManager sessionManager) {  
26 -  
27 - String msg = wxMessage.getEventKey();  
28 - if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) {  
29 - return null;  
30 - } 28 + @Resource
  29 + private MpMenuService mpMenuService;
31 30
32 - return WxMpXmlOutMessage.TEXT().content(msg)  
33 - .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())  
34 - .build(); 31 + @Override
  32 + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
  33 + WxMpService weixinService, WxSessionManager sessionManager) {
  34 + return mpMenuService.reply(MpContextHolder.getAppId(), wxMessage.getEventKey(), wxMessage.getFromUser());
35 } 35 }
36 36
37 } 37 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java
@@ -29,7 +29,7 @@ public class MessageReceiveHandler implements WxMpMessageHandler { @@ -29,7 +29,7 @@ public class MessageReceiveHandler implements WxMpMessageHandler {
29 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, 29 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
30 WxMpService wxMpService, WxSessionManager sessionManager) { 30 WxMpService wxMpService, WxSessionManager sessionManager) {
31 log.info("[handle][接收到请求消息,内容:{}]", wxMessage); 31 log.info("[handle][接收到请求消息,内容:{}]", wxMessage);
32 - mpMessageService.createFromUser(MpContextHolder.getAppId(), wxMessage); 32 + mpMessageService.receiveMessage(MpContextHolder.getAppId(), wxMessage);
33 return null; 33 return null;
34 } 34 }
35 35
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java
@@ -4,6 +4,8 @@ import javax.validation.*; @@ -4,6 +4,8 @@ import javax.validation.*;
4 4
5 import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*; 5 import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*;
6 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; 6 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
  7 +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
  8 +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
7 9
8 /** 10 /**
9 * 微信菜单 Service 接口 11 * 微信菜单 Service 接口
@@ -35,4 +37,14 @@ public interface MpMenuService { @@ -35,4 +37,14 @@ public interface MpMenuService {
35 */ 37 */
36 MpMenuDO getMenu(Long id); 38 MpMenuDO getMenu(Long id);
37 39
  40 + /**
  41 + * 用户点击菜单按钮时,回复对应的消息
  42 + *
  43 + * @param appId 公众号 AppId
  44 + * @param key 菜单按钮的标识
  45 + * @param openid 用户的 openid
  46 + * @return 消息
  47 + */
  48 + WxMpXmlOutMessage reply(String appId, String key, String openid);
  49 +
38 } 50 }
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.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; 6 import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert;
  7 +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
4 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; 8 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
5 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; 9 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
  10 +import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
  11 +import lombok.extern.slf4j.Slf4j;
6 import me.chanjar.weixin.common.bean.menu.WxMenu; 12 import me.chanjar.weixin.common.bean.menu.WxMenu;
7 import me.chanjar.weixin.common.error.WxErrorException; 13 import me.chanjar.weixin.common.error.WxErrorException;
8 import me.chanjar.weixin.mp.api.WxMpService; 14 import me.chanjar.weixin.mp.api.WxMpService;
  15 +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
9 import org.springframework.context.annotation.Lazy; 16 import org.springframework.context.annotation.Lazy;
10 import org.springframework.stereotype.Service; 17 import org.springframework.stereotype.Service;
11 18
@@ -27,6 +34,7 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; @@ -27,6 +34,7 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
27 */ 34 */
28 @Service 35 @Service
29 @Validated 36 @Validated
  37 +@Slf4j
30 public class MpMenuServiceImpl implements MpMenuService { 38 public class MpMenuServiceImpl implements MpMenuService {
31 39
32 @Resource 40 @Resource
@@ -77,4 +85,49 @@ public class MpMenuServiceImpl implements MpMenuService { @@ -77,4 +85,49 @@ public class MpMenuServiceImpl implements MpMenuService {
77 return mpMenuMapper.selectById(id); 85 return mpMenuMapper.selectById(id);
78 } 86 }
79 87
  88 + @Override
  89 + public WxMpXmlOutMessage reply(String appId, String key, String openid) {
  90 + // 获得菜单
  91 + MpMenuDO menu = mpMenuMapper.selectByAppId(appId);
  92 + 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);
  100 + return null;
  101 + }
  102 + // 按钮必须要有消息类型,不然后续无法回复消息
  103 + if (StrUtil.isEmpty(button.getMessageType())) {
  104 + log.error("[reply][appId({}) key({}) 不存在消息类型({})]", appId, key, button);
  105 + return null;
  106 + }
  107 +
  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;
  131 + }
  132 +
80 } 133 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java
@@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message; @@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.module.mp.convert.message.MpAutoReplyConvert;
5 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 6 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
6 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; 7 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
7 import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper; 8 import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper;
8 import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; 9 import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
9 import cn.iocoder.yudao.module.mp.service.account.MpAccountService; 10 import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
  11 +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
10 import me.chanjar.weixin.common.api.WxConsts; 12 import me.chanjar.weixin.common.api.WxConsts;
11 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 13 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
12 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 14 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
@@ -58,7 +60,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService { @@ -58,7 +60,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService {
58 MpAutoReplyDO reply = CollUtil.getFirst(replies); 60 MpAutoReplyDO reply = CollUtil.getFirst(replies);
59 61
60 // 第二步,基于自动回复,创建消息 62 // 第二步,基于自动回复,创建消息
61 - return mpMessageService.createFromAutoReply(wxMessage.getFromUser(), reply); 63 + MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply);
  64 + return mpMessageService.sendOutMessage(sendReqBO);
62 } 65 }
63 66
64 @Override 67 @Override
@@ -69,7 +72,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService { @@ -69,7 +72,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService {
69 : buildDefaultSubscribeAutoReply(appId); // 如果不存在,提供一个默认末班 72 : buildDefaultSubscribeAutoReply(appId); // 如果不存在,提供一个默认末班
70 73
71 // 第二步,基于自动回复,创建消息 74 // 第二步,基于自动回复,创建消息
72 - return mpMessageService.createFromAutoReply(wxMessage.getFromUser(), reply); 75 + MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply);
  76 + return mpMessageService.sendOutMessage(sendReqBO);
73 } 77 }
74 78
75 private MpAutoReplyDO buildDefaultSubscribeAutoReply(String appId) { 79 private MpAutoReplyDO buildDefaultSubscribeAutoReply(String appId) {
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java
@@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message; @@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; 4 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
5 -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;  
6 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; 5 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
  6 +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
7 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; 7 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
8 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 8 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
9 9
  10 +import javax.validation.Valid;
  11 +
10 /** 12 /**
11 * 粉丝消息表 Service 接口 13 * 粉丝消息表 Service 接口
12 * 14 *
@@ -24,20 +26,23 @@ public interface MpMessageService { @@ -24,20 +26,23 @@ public interface MpMessageService {
24 PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO); 26 PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO);
25 27
26 /** 28 /**
27 - * 保存粉丝消息,来自用户发送 29 + * 从公众号,接收到用户消息
28 * 30 *
29 * @param appId 微信公众号 appId 31 * @param appId 微信公众号 appId
30 * @param wxMessage 消息 32 * @param wxMessage 消息
31 */ 33 */
32 - void createFromUser(String appId, WxMpXmlMessage wxMessage); 34 + void receiveMessage(String appId, WxMpXmlMessage wxMessage);
33 35
34 /** 36 /**
35 - * 创建粉丝消息,通过自动回复 37 + * 使用公众号,给用户回复消息
  38 + *
  39 + * 例如说:自动回复、客服消息、菜单回复消息等场景
  40 + *
  41 + * 注意,该方法只是返回 WxMpXmlOutMessage 对象,不会真的发送消息
36 * 42 *
37 - * @param openid 公众号粉丝 openid  
38 - * @param reply 自动回复 43 + * @param sendReqBO 消息内容
39 * @return 微信回复消息 XML 44 * @return 微信回复消息 XML
40 */ 45 */
41 - WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply); 46 + WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO);
42 47
43 } 48 }
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
@@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.mp.service.message; @@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.mp.service.message;
2 2
3 import cn.hutool.core.io.FileTypeUtil; 3 import cn.hutool.core.io.FileTypeUtil;
4 import cn.hutool.core.io.FileUtil; 4 import cn.hutool.core.io.FileUtil;
5 -import cn.hutool.core.io.IoUtil;  
6 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
7 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
8 -import cn.iocoder.yudao.framework.common.util.io.FileUtils; 7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 import cn.iocoder.yudao.module.infra.api.file.FileApi; 8 import cn.iocoder.yudao.module.infra.api.file.FileApi;
  9 +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
10 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; 10 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
11 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; 11 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
12 -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;  
13 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; 12 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
14 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; 13 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
  14 +import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper;
15 import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; 15 import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
16 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; 16 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
  17 +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
17 import cn.iocoder.yudao.module.mp.service.account.MpAccountService; 18 import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
  19 +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
18 import cn.iocoder.yudao.module.mp.service.user.MpUserService; 20 import cn.iocoder.yudao.module.mp.service.user.MpUserService;
19 -import lombok.SneakyThrows;  
20 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
21 import me.chanjar.weixin.common.error.WxErrorException; 22 import me.chanjar.weixin.common.error.WxErrorException;
22 import me.chanjar.weixin.mp.api.WxMpService; 23 import me.chanjar.weixin.mp.api.WxMpService;
@@ -24,20 +25,12 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; @@ -24,20 +25,12 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
24 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; 25 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
25 import org.springframework.context.annotation.Lazy; 26 import org.springframework.context.annotation.Lazy;
26 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
27 -  
28 -import javax.annotation.Resource;  
29 -  
30 import org.springframework.validation.annotation.Validated; 28 import org.springframework.validation.annotation.Validated;
31 29
32 -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*;  
33 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
34 -  
35 -import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper;  
36 - 30 +import javax.annotation.Resource;
  31 +import javax.validation.Validator;
37 import java.io.File; 32 import java.io.File;
38 33
39 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
40 -  
41 /** 34 /**
42 * 粉丝消息表 Service 实现类 35 * 粉丝消息表 Service 实现类
43 * 36 *
@@ -64,13 +57,16 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -64,13 +57,16 @@ public class MpMessageServiceImpl implements MpMessageService {
64 @Resource 57 @Resource
65 private FileApi fileApi; 58 private FileApi fileApi;
66 59
  60 + @Resource
  61 + private Validator validator;
  62 +
67 @Override 63 @Override
68 public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) { 64 public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) {
69 return mpMessageMapper.selectPage(pageReqVO); 65 return mpMessageMapper.selectPage(pageReqVO);
70 } 66 }
71 67
72 @Override 68 @Override
73 - public void createFromUser(String appId, WxMpXmlMessage wxMessage) { 69 + public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
74 WxMpService mpService = mpServiceFactory.getRequiredMpService(appId); 70 WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);
75 // 获得关联信息 71 // 获得关联信息
76 MpAccountDO account = mpAccountService.getAccountFromCache(appId); 72 MpAccountDO account = mpAccountService.getAccountFromCache(appId);
@@ -88,24 +84,21 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -88,24 +84,21 @@ public class MpMessageServiceImpl implements MpMessageService {
88 message.setThumbMediaUrl(mediaDownload(mpService, message.getThumbMediaId())); 84 message.setThumbMediaUrl(mediaDownload(mpService, message.getThumbMediaId()));
89 } 85 }
90 mpMessageMapper.insert(message); 86 mpMessageMapper.insert(message);
91 -  
92 -// WxConsts.MenuButtonType.VIEW TODO 芋艿:待测试  
93 -// wxMessage.getEventKey()  
94 -  
95 -// WxConsts.MenuButtonType.CLICK  
96 -// wxMessage.getEventKey()  
97 } 87 }
98 88
99 @Override 89 @Override
100 - public WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply) { 90 + public WxMpXmlOutMessage sendOutMessage(MpMessageSendOutReqBO sendReqBO) {
  91 + // 校验消息格式
  92 + MpUtils.validateMessage(validator, sendReqBO.getType(), sendReqBO);
  93 +
101 // 获得关联信息 94 // 获得关联信息
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); 95 + MpAccountDO account = mpAccountService.getAccountFromCache(sendReqBO.getAppId());
  96 + Assert.notNull(account, "公众号账号({}) 不存在", sendReqBO.getAppId());
  97 + MpUserDO user = mpUserService.getUser(sendReqBO.getAppId(), sendReqBO.getOpenid());
  98 + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid());
106 99
107 // 记录消息 100 // 记录消息
108 - MpMessageDO message = MpMessageConvert.INSTANCE.convert(reply, account, user); 101 + MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user);
109 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); 102 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
110 mpMessageMapper.insert(message); 103 mpMessageMapper.insert(message);
111 104
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.service.message.bo;
  2 +
  3 +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
  4 +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
  5 +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
  6 +import lombok.Data;
  7 +import me.chanjar.weixin.common.api.WxConsts;
  8 +
  9 +import javax.validation.Valid;
  10 +import javax.validation.constraints.NotEmpty;
  11 +import javax.validation.constraints.NotNull;
  12 +
  13 +/**
  14 + * 公众号消息发送 Request BO
  15 + *
  16 + * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给用户发送消息,所以使用该 BO 统一承接
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@Data
  21 +public class MpMessageSendOutReqBO {
  22 +
  23 + /**
  24 + * 公众号 appId
  25 + */
  26 + @NotEmpty(message = "公众号 appId 不能为空")
  27 + private String appId;
  28 + /**
  29 + * 公众号用户 openid
  30 + */
  31 + @NotEmpty(message = "公众号用户 openid 不能为空")
  32 + private String openid;
  33 +
  34 + // ========== 消息内容 ==========
  35 + /**
  36 + * 消息类型
  37 + *
  38 + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
  39 + */
  40 + @NotEmpty(message = "消息类型不能为空")
  41 + public String type;
  42 +
  43 + /**
  44 + * 消息内容
  45 + *
  46 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
  47 + */
  48 + @NotEmpty(message = "消息内容不能为空", groups = TextGroup.class)
  49 + private String content;
  50 +
  51 + /**
  52 + * 媒体 id
  53 + *
  54 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
  55 + */
  56 + @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
  57 + private String mediaId;
  58 + /**
  59 + * 媒体 URL
  60 + *
  61 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
  62 + */
  63 + @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
  64 + private String mediaUrl;
  65 +
  66 + /**
  67 + * 标题
  68 + *
  69 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
  70 + */
  71 + @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
  72 + private String title;
  73 + /**
  74 + * 描述
  75 + *
  76 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
  77 + */
  78 + @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
  79 + private String description;
  80 +
  81 + /**
  82 + * 图文消息
  83 + *
  84 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
  85 + */
  86 + @Valid
  87 + @NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
  88 + private MpMessageDO.Article article;
  89 +
  90 +}