From 0499226c3d3f15fb8b0581b01e46d207c7a4be3f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 15 Jan 2023 18:28:08 +0800 Subject: [PATCH] =?UTF-8?q?mp=EF=BC=9A=E5=AE=8C=E5=96=84=E3=80=90=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E3=80=91=E7=9A=84=E5=9B=9E=E5=A4=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/menu/vo/MpMenuBaseVO.java | 98 ++++---- .../admin/message/vo/MpMessageSendReqVO.java | 12 +- .../module/mp/convert/menu/MpMenuConvert.java | 3 + .../convert/message/MpAutoReplyConvert.java | 1 - .../mp/convert/message/MpMessageConvert.java | 10 +- .../mp/dal/dataobject/menu/MpMenuDO.java | 30 ++- .../dal/dataobject/message/MpMessageDO.java | 4 +- .../mp/framework/mp/core/util/MpUtils.java | 121 +++++++--- .../mp/service/menu/MpMenuServiceImpl.java | 70 ++++-- .../service/message/MpMessageServiceImpl.java | 14 +- .../message/bo/MpMessageSendOutReqBO.java | 34 +-- yudao-ui-admin/src/views/mp/menu/index.vue | 216 +++++++++++++----- 12 files changed, 411 insertions(+), 202 deletions(-) diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java index 0b210990e..12d34152d 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java @@ -1,12 +1,18 @@ package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import me.chanjar.weixin.common.api.WxConsts; +import org.hibernate.validator.constraints.URL; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import java.util.List; -// TODO 芋艿:完善 swagger 注解 +import static cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; + /** * 微信菜单 Base VO,提供给添加、修改、详细的子 VO 使用 * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 @@ -38,70 +44,51 @@ public class MpMenuBaseVO { */ private String type; - /** - * 网页链接 - * - * 用户点击菜单可打开链接,不超过 1024 字节 - * - * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM - */ + @ApiModelProperty(value = "网页链接", example = "https://www.iocoder.cn/") + @NotEmpty(message = "网页链接不能为空", groups = {ViewButtonGroup.class, MiniProgramButtonGroup.class}) + @URL(message = "网页链接必须是 URL 格式") private String url; - /** - * 小程序的 appId - * - * 类型为 {@link WxConsts.MenuButtonType} 的 MINIPROGRAM - */ + @ApiModelProperty(value = "小程序的 appId", example = "wx1234567890") + @NotEmpty(message = "小程序的 appId 不能为空", groups = MiniProgramButtonGroup.class) private String miniProgramAppId; - /** - * 小程序的页面路径 - * - * 类型为 {@link WxConsts.MenuButtonType} 的 MINIPROGRAM - */ + + @ApiModelProperty(value = "小程序的页面路径", example = "pages/index/index") + @NotEmpty(message = "小程序的页面路径不能为空", groups = MiniProgramButtonGroup.class) private String miniProgramPagePath; // ========== 消息内容 ========== - /** - * 消息类型 - * - * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG - * - * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS - */ + @ApiModelProperty(value = "消息类型", example = "text", + notes = "枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC") + @NotEmpty(message = "消息类型不能为空", groups = {ClickButtonGroup.class, ScanCodeWaitMsgButtonGroup.class}) private String replyMessageType; - /** - * 回复的消息内容 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT - */ + @ApiModelProperty(value = "回复的消息内容", example = "欢迎关注") + @NotEmpty(message = "回复的消息内容不能为空", groups = {TextMessageGroup.class}) private String replyContent; - /** - * 回复的媒体 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ + @ApiModelProperty(value = "回复的媒体 id", example = "123456") + @NotEmpty(message = "回复的消息 mediaId 不能为空", + groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) private String replyMediaId; - /** - * 回复的媒体 URL - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ + @ApiModelProperty(value = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg") + @NotEmpty(message = "回复的消息 mediaId 不能为空", + groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) private String replyMediaUrl; - /** - * 回复的标题 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ + @ApiModelProperty(value = "缩略图的媒体 id", example = "123456") + @NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) + private String replyThumbMediaId; + @ApiModelProperty(value = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg") + @NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class}) + private String replyThumbMediaUrl; + + @ApiModelProperty(value = "回复的标题", example = "视频标题") + @NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class) private String replyTitle; - /** - * 回复的描述 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ + @ApiModelProperty(value = "回复的描述", example = "视频描述") + @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) private String replyDescription; /** @@ -109,6 +96,17 @@ public class MpMenuBaseVO { * * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS */ + @NotNull(message = "回复的图文消息不能为空", groups = NewsMessageGroup.class) + @Valid private List replyArticles; + @ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/xxx.mp3") + @NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String replyMusicUrl; + @ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3") + @NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String replyHqMusicUrl; + } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/MpMessageSendReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/MpMessageSendReqVO.java index 6ca179201..8c417a247 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/MpMessageSendReqVO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/MpMessageSendReqVO.java @@ -26,28 +26,28 @@ public class MpMessageSendReqVO { public String type; @ApiModelProperty(value = "消息内容", required = true, example = "你好呀") - @NotEmpty(message = "消息内容不能为空", groups = TextGroup.class) + @NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class) private String content; @ApiModelProperty(value = "媒体 ID", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP") - @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class}) + @NotEmpty(message = "消息内容不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) private String mediaId; @ApiModelProperty(value = "标题", required = true, example = "没有标题") - @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class) + @NotEmpty(message = "消息内容不能为空", groups = VideoMessageGroup.class) private String title; @ApiModelProperty(value = "描述", required = true, example = "你猜") - @NotEmpty(message = "消息描述不能为空", groups = VideoGroup.class) + @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) private String description; @ApiModelProperty(value = "缩略图的媒体 id", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP") - @NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicGroup.class) + @NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicMessageGroup.class) private String thumbMediaId; @ApiModelProperty(value = "图文消息", required = true) @Valid - @NotNull(message = "图文消息不能为空", groups = NewsGroup.class) + @NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class) private List articles; @ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时") diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java index 38f4d23e1..b53e52af1 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java @@ -26,9 +26,12 @@ public interface MpMenuConvert { @Mapping(source = "menu.replyMessageType", target = "type"), @Mapping(source = "menu.replyContent", target = "content"), @Mapping(source = "menu.replyMediaId", target = "mediaId"), + @Mapping(source = "menu.replyThumbMediaId", target = "thumbMediaId"), @Mapping(source = "menu.replyTitle", target = "title"), @Mapping(source = "menu.replyDescription", target = "description"), @Mapping(source = "menu.replyArticles", target = "articles"), + @Mapping(source = "menu.replyMusicUrl", target = "musicUrl"), + @Mapping(source = "menu.replyHqMusicUrl", target = "hqMusicUrl"), }) MpMessageSendOutReqBO convert(String openid, MpMenuDO menu); diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java index be14fd9e6..c691c2bbd 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java @@ -17,7 +17,6 @@ public interface MpAutoReplyConvert { @Mapping(source = "reply.responseMessageType", target = "type"), @Mapping(source = "reply.responseContent", target = "content"), @Mapping(source = "reply.responseMediaId", target = "mediaId"), - @Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"), @Mapping(source = "reply.responseTitle", target = "title"), @Mapping(source = "reply.responseDescription", target = "description"), @Mapping(source = "reply.responseArticles", target = "articles"), 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 670b6c807..599a5ab77 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 @@ -57,10 +57,14 @@ public interface MpMessageConvert { break; case WxConsts.XmlMsgType.IMAGE: // 2. 图片 case WxConsts.XmlMsgType.VOICE: // 3. 语音 - message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl()); + message.setMediaId(sendReqBO.getMediaId()) +// .setMediaUrl(sendReqBO.getMediaUrl()) TODO 芋艿:去 url + ; break; case WxConsts.XmlMsgType.VIDEO: // 4. 视频 - message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl()) + message.setMediaId(sendReqBO.getMediaId()) +// .setMediaUrl(sendReqBO.getMediaUrl()) TODO 芋艿:去 url + .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()); break; case WxConsts.XmlMsgType.NEWS: // 5. 图文 @@ -69,7 +73,7 @@ public interface MpMessageConvert { message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()) .setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl()) .setThumbMediaId(sendReqBO.getThumbMediaId()); -// .setThumbMediaUrl(sendReqBO.getThumbMediaUrl()); TODO 芋艿:url 待确定 +// .setThumbMediaUrl(sendReqBO.getThumbMediaUrl()); TODO 芋艿:去 url break; default: throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java index 3fdcc61b1..eb04a1b38 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java @@ -103,7 +103,7 @@ public class MpMenuDO extends BaseDO { * * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG * - * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC */ private String replyMessageType; @@ -140,6 +140,19 @@ public class MpMenuDO extends BaseDO { */ private String replyDescription; + /** + * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String replyThumbMediaId; + /** + * 缩略图的媒体 URL + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String replyThumbMediaUrl; + /** * 回复的图文消息数组 * @@ -148,4 +161,19 @@ public class MpMenuDO extends BaseDO { @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class) private List replyArticles; + /** + * 回复的音乐链接 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String replyMusicUrl; + /** + * 回复的高质量音乐链接 + * + * WIFI 环境优先使用该链接播放音乐 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String replyHqMusicUrl; + } 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 4c0d8790e..343bed173 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 @@ -124,13 +124,13 @@ public class MpMessageDO extends BaseDO { /** * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO */ private String thumbMediaId; /** * 缩略图的媒体 URL * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO */ private String thumbMediaUrl; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java index 02d9ecb75..3a1c20b04 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.mp.framework.mp.core.util; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.api.WxConsts; @@ -14,36 +15,6 @@ import javax.validation.Validator; @Slf4j public class MpUtils { - /** - * Text 类型的消息,参数校验 Group - */ - public interface TextGroup {} - - /** - * Image 类型的消息,参数校验 Group - */ - public interface ImageGroup {} - - /** - * Voice 类型的消息,参数校验 Group - */ - public interface VoiceGroup {} - - /** - * Video 类型的消息,参数校验 Group - */ - public interface VideoGroup {} - - /** - * News 类型的消息,参数校验 Group - */ - public interface NewsGroup {} - - /** - * Music 类型的消息,参数校验 Group - */ - public interface MusicGroup {} - /** * 校验消息的格式是否符合要求 * @@ -55,22 +26,22 @@ public class MpUtils { Class group; switch (type) { case WxConsts.XmlMsgType.TEXT: - group = TextGroup.class; + group = TextMessageGroup.class; break; case WxConsts.XmlMsgType.IMAGE: - group = ImageGroup.class; + group = ImageMessageGroup.class; break; case WxConsts.XmlMsgType.VOICE: - group = VoiceGroup.class; + group = VoiceMessageGroup.class; break; case WxConsts.XmlMsgType.VIDEO: - group = VideoGroup.class; + group = VideoMessageGroup.class; break; case WxConsts.XmlMsgType.NEWS: - group = NewsGroup.class; + group = NewsMessageGroup.class; break; case WxConsts.XmlMsgType.MUSIC: - group = MusicGroup.class; + group = MusicMessageGroup.class; break; default: log.error("[validateMessage][未知的消息类型({})]", message); @@ -80,6 +51,35 @@ public class MpUtils { ValidationUtils.validate(validator, message, group); } + public static void validateButton(Validator validator, String type, String messageType, Object button) { + if (StrUtil.isBlank(type)) { + return; + } + // 获得对应的校验 group + Class group; + switch (type) { + case WxConsts.MenuButtonType.CLICK: + group = ClickButtonGroup.class; + validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式 + break; + case WxConsts.MenuButtonType.VIEW: + group = ViewButtonGroup.class; + break; + case WxConsts.MenuButtonType.MINIPROGRAM: + group = MiniProgramButtonGroup.class; + break; + case WxConsts.MenuButtonType.SCANCODE_WAITMSG: + group = ScanCodeWaitMsgButtonGroup.class; + validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式 + break; + default: + log.error("[validateButton][未知的按钮({})]", button); + throw new IllegalArgumentException("不支持的按钮类型:" + type); + } + // 执行校验 + ValidationUtils.validate(validator, button, group); + } + /** * 根据消息类型,获得对应的媒体文件类型 * @@ -101,4 +101,53 @@ public class MpUtils { } } + /** + * Text 类型的消息,参数校验 Group + */ + public interface TextMessageGroup {} + + /** + * Image 类型的消息,参数校验 Group + */ + public interface ImageMessageGroup {} + + /** + * Voice 类型的消息,参数校验 Group + */ + public interface VoiceMessageGroup {} + + /** + * Video 类型的消息,参数校验 Group + */ + public interface VideoMessageGroup {} + + /** + * News 类型的消息,参数校验 Group + */ + public interface NewsMessageGroup {} + + /** + * Music 类型的消息,参数校验 Group + */ + public interface MusicMessageGroup {} + + /** + * Click 类型的按钮,参数校验 Group + */ + public interface ClickButtonGroup {} + + /** + * View 类型的按钮,参数校验 Group + */ + public interface ViewButtonGroup {} + + /** + * MiniProgram 类型的按钮,参数校验 Group + */ + public interface MiniProgramButtonGroup {} + + /** + * SCANCODE_WAITMSG 类型的按钮,参数校验 Group + */ + public interface ScanCodeWaitMsgButtonGroup {} } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java index 15c420ed3..eb9ceba89 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; import cn.iocoder.yudao.module.mp.dal.mysql.menu.MpMenuMapper; import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; import cn.iocoder.yudao.module.mp.service.account.MpAccountService; import cn.iocoder.yudao.module.mp.service.message.MpMessageService; import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; @@ -22,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import javax.validation.Validator; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -48,6 +50,9 @@ public class MpMenuServiceImpl implements MpMenuService { @Lazy // 延迟加载,避免循环引用报错 private MpServiceFactory mpServiceFactory; + @Resource + private Validator validator; + @Resource private MpMenuMapper mpMenuMapper; @@ -57,6 +62,9 @@ public class MpMenuServiceImpl implements MpMenuService { MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId()); + // 参数校验 + createReqVO.getMenus().forEach(this::validateMenu); + // 第一步,同步公众号 WxMenu wxMenu = new WxMenu(); wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus())); @@ -79,6 +87,49 @@ public class MpMenuServiceImpl implements MpMenuService { }); } + /** + * 校验菜单的格式是否正确 + * + * @param menu 菜单 + */ + private void validateMenu(MpMenuSaveReqVO.Menu menu) { + MpUtils.validateButton(validator, menu.getType(), menu.getReplyMessageType(), menu); + // 子菜单 + if (CollUtil.isEmpty(menu.getChildren())) { + return; + } + menu.getChildren().forEach(this::validateMenu); + } + + /** + * 创建菜单,并存储到数据库 + * + * @param wxMenu 菜单信息 + * @param parentMenu 父菜单 + * @param account 公众号账号 + * @return 创建后的菜单 + */ + private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) { + // 创建菜单 + MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren()) + ? new MpMenuDO().setName(wxMenu.getName()) + : MpMenuConvert.INSTANCE.convert02(wxMenu); + // 设置菜单的公众号账号信息 + if (account != null) { + menu.setAccountId(account.getId()).setAppId(account.getAppId()); + } + // 设置父编号 + if (parentMenu != null) { + menu.setParentId(parentMenu.getId()); + } else { + menu.setParentId(MpMenuDO.ID_ROOT); + } + + // 插入到数据库 + mpMenuMapper.insert(menu); + return menu; + } + @Override public void deleteMenuByAccountId(Long accountId) { WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); @@ -93,25 +144,6 @@ public class MpMenuServiceImpl implements MpMenuService { mpMenuMapper.deleteByAccountId(accountId); } - private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) { - MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren()) - ? new MpMenuDO().setName(wxMenu.getName()) - : MpMenuConvert.INSTANCE.convert02(wxMenu); - if (account != null) { - menu.setAccountId(account.getId()).setAppId(account.getAppId()); - } - if (parentMenu != null) { - menu.setParentId(parentMenu.getId()); - } else { - menu.setParentId(MpMenuDO.ID_ROOT); - } - if (StrUtil.isNotEmpty(wxMenu.getReplyMediaId())) { - throw new IllegalArgumentException("未实现"); - } - mpMenuMapper.insert(menu); - return menu; - } - @Override public WxMpXmlOutMessage reply(String appId, String key, String openid) { // 第一步,获得菜单 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 526793439..46f59da25 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 @@ -76,8 +76,8 @@ public class MpMessageServiceImpl implements MpMessageService { Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser()); // 记录消息 - MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user); - message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom()); + MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user) + .setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom()); downloadMessageMedia(message); mpMessageMapper.insert(message); } @@ -94,9 +94,9 @@ public class MpMessageServiceImpl implements MpMessageService { Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid()); // 记录消息 - MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user); - message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); - // TODO 芋艿:downloadMessageMedia + MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user). + setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); + downloadMessageMedia(message); mpMessageMapper.insert(message); // 转换返回 WxMpXmlOutMessage 对象 @@ -122,8 +122,8 @@ public class MpMessageServiceImpl implements MpMessageService { } // 记录消息 - MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user); - message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); + MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user) + .setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); downloadMessageMedia(message); mpMessageMapper.insert(message); return message; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java index 04c209cc5..c29ce8fe8 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; import lombok.Data; import me.chanjar.weixin.common.api.WxConsts; +import org.hibernate.validator.constraints.URL; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; @@ -45,7 +46,7 @@ public class MpMessageSendOutReqBO { * * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT */ - @NotEmpty(message = "消息内容不能为空", groups = TextGroup.class) + @NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class) private String content; /** @@ -53,39 +54,38 @@ public class MpMessageSendOutReqBO { * * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO */ - @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class}) + @NotEmpty(message = "消息 mediaId 不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) private String mediaId; - // TODO 芋艿:考虑去掉 - /** - * 媒体 URL - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ - @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class}) - private String mediaUrl; +// // TODO 芋艿:考虑去掉 +// /** +// * 媒体 URL +// * +// * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO +// */ +// @NotEmpty(message = "消息内容不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) +// private String mediaUrl; /** * 缩略图的媒体 id * * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC */ - @NotEmpty(message = "消息内容不能为空", groups = {MusicGroup.class}) + @NotEmpty(message = "消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) private String thumbMediaId; - // TODO 芋艿:考虑去掉 /** * 标题 * * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO */ - @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class) + @NotEmpty(message = "消息标题不能为空", groups = VideoMessageGroup.class) private String title; /** * 描述 * * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO */ - @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class) + @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) private String description; /** @@ -94,7 +94,7 @@ public class MpMessageSendOutReqBO { * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS */ @Valid - @NotNull(message = "图文消息不能为空", groups = NewsGroup.class) + @NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class) private List articles; /** @@ -102,6 +102,8 @@ public class MpMessageSendOutReqBO { * * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC */ + @NotEmpty(message = "音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class) private String musicUrl; /** @@ -109,6 +111,8 @@ public class MpMessageSendOutReqBO { * * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC */ + @NotEmpty(message = "高质量音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class) private String hqMusicUrl; } diff --git a/yudao-ui-admin/src/views/mp/menu/index.vue b/yudao-ui-admin/src/views/mp/menu/index.vue index 69fa9655c..793945656 100644 --- a/yudao-ui-admin/src/views/mp/menu/index.vue +++ b/yudao-ui-admin/src/views/mp/menu/index.vue @@ -20,6 +20,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + 芋道源码: + ① less 切到 scss,减少对 less 和 less-loader 的依赖 + ② -->