Эх сурвалжийг харах

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

YunaiV 2 жил өмнө
parent
commit
71beeabe9c
11 өөрчлөгдсөн 166 нэмэгдсэн , 176 устгасан
  1. 14 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http
  2. 0 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java
  3. 7 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java
  4. 15 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java
  5. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java
  6. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java
  7. 107 128
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java
  8. 3 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java
  9. 3 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java
  10. 13 38
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java
  11. 2 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java

+ 14 - 0
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}}
       "name":"搜索",
       "type":"view",
       "url":"http://www.soso.com/"
+    },
+    {
+      "name": "父按钮",
+      "subButtons": [
+        {
+          "type":"click",
+          "name":"归去来兮",
+          "key":"MUSIC"
+        },
+        {
+          "name":"不说",
+          "type":"view",
+          "url":"https://www.soso.com/"
+        }]
     }]
 }

+ 0 - 3
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 {
     @NotNull(message = "公众号账号的编号不能为空")
     private Long accountId;
 
-    @NotNull(message = "按钮不能为空")
-    private List<WxMenuButton> buttons;
-
 }

+ 7 - 0
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;
 
 import lombok.*;
 import io.swagger.annotations.*;
+import me.chanjar.weixin.common.bean.menu.WxMenuButton;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
 
 @ApiModel("管理后台 - 微信菜单保存 Request VO")
 @Data
@@ -9,4 +13,7 @@ import io.swagger.annotations.*;
 @ToString(callSuper = true)
 public class MpMenuSaveReqVO extends MpMenuBaseVO {
 
+    @NotNull(message = "按钮不能为空")
+    private List<WxMenuButton> buttons;
+
 }

+ 15 - 0
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;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
+import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
 import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*;
 import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
@@ -16,4 +19,16 @@ public interface MpMenuConvert {
 
     MpMenuRespVO convert(MpMenuDO bean);
 
+    @Mappings({
+            @Mapping(source = "menu.appId", target = "appId"),
+            @Mapping(source = "menu.replyMessageType", target = "type"),
+            @Mapping(source = "menu.replyContent", target = "content"),
+            @Mapping(source = "menu.replyMediaId", target = "mediaId"),
+            @Mapping(source = "menu.replyMediaUrl", target = "mediaUrl"),
+            @Mapping(source = "menu.replyTitle", target = "title"),
+            @Mapping(source = "menu.replyDescription", target = "description"),
+            @Mapping(source = "menu.replyArticles", target = "articles"),
+    })
+    MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);
+
 }

+ 1 - 1
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 {
             @Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"),
             @Mapping(source = "reply.responseTitle", target = "title"),
             @Mapping(source = "reply.responseDescription", target = "description"),
-            @Mapping(source = "reply.responseArticle", target = "article"),
+            @Mapping(source = "reply.responseArticles", target = "articles"),
     })
     MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply);
 

+ 1 - 1
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 {
                         .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());
                 break;
             case WxConsts.XmlMsgType.NEWS: // 5. 图文
-                message.setArticles(Collections.singletonList(sendReqBO.getArticle()));
+                message.setArticles(sendReqBO.getArticles());
                 break;
             default:
                 throw new IllegalArgumentException("不支持的消息类型:" + message.getType());

+ 107 - 128
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.mp.dal.dataobject.menu;
 
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
@@ -8,7 +7,6 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
@@ -20,8 +18,6 @@ import java.util.List;
 /**
  * 微信菜单 DO
  *
- * 一个公众号,只有一个 MpMenuDO 记录。一个公众号的多个菜单,对应到就是 {@link #buttons} 多个按钮
- *
  * @author 芋道源码
  */
 @TableName(value = "mp_menu", autoResultMap = true)
@@ -32,7 +28,12 @@ import java.util.List;
 public class MpMenuDO extends BaseDO {
 
     /**
-     * 主键
+     * 编号 - 顶级菜单
+     */
+    public static final Long ID_ROOT = 0L;
+
+    /**
+     * 编号
      */
     @TableId
     private Long id;
@@ -50,127 +51,105 @@ public class MpMenuDO extends BaseDO {
     private String appId;
 
     /**
-     * 按钮列表
-     */
-    @TableField(typeHandler = ButtonTypeHandler.class)
-    private List<Button> buttons;
-    /**
-     * 同步状态
-     *
-     * true - 已同步
-     * false - 未同步
-     */
-    private Boolean syncStatus;
-
-    /**
-     * 按钮
-     */
-    @Data
-    public static class Button {
-
-        /**
-         * 类型
-         *
-         * 枚举 {@link MenuButtonType}
-         */
-        private String type;
-        /**
-         * 消息类型
-         *
-         * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG
-         *
-         * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
-         */
-        private String messageType;
-        /**
-         * 名称
-         */
-        private String name;
-        /**
-         * 标识
-         */
-        private String key;
-        /**
-         * 二级菜单列表
-         */
-        private List<Button> subButtons;
-        /**
-         * 网页链接
-         *
-         * 用户点击菜单可打开链接,不超过 1024 字节
-         *
-         * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM
-         */
-        private String url;
-
-        /**
-         * 小程序的 appId
-         *
-         * 类型为 {@link WxConsts.XmlMsgType} 的 MINIPROGRAM
-         */
-        private String appId;
-
-        /**
-         * 小程序的页面路径
-         *
-         * 类型为 {@link WxConsts.XmlMsgType} 的 MINIPROGRAM
-         */
-        private String pagePath;
-
-        /**
-         * 消息内容
-         *
-         * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
-         */
-        private String content;
-
-        /**
-         * 媒体 id
-         *
-         * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
-         */
-        private String mediaId;
-        /**
-         * 媒体 URL
-         *
-         * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
-         */
-        private String mediaUrl;
-
-        /**
-         * 回复的标题
-         *
-         * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
-         */
-        private String title;
-        /**
-         * 回复的描述
-         *
-         * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
-         */
-        private String description;
-
-        /**
-         * 图文消息
-         *
-         * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
-         */
-        private MpMessageDO.Article article;
-
-    }
-
-    // TODO @芋艿:可以找一些新的思路
-    public static class ButtonTypeHandler extends AbstractJsonTypeHandler<List<Button>> {
-
-        @Override
-        protected List<Button> parse(String json) {
-            return JsonUtils.parseArray(json, Button.class);
-        }
-
-        @Override
-        protected String toJson(List<Button> obj) {
-            return JsonUtils.toJsonString(obj);
-        }
-
-    }
+     * 菜单名称
+     */
+    private String name;
+    /**
+     * 菜单标识
+     *
+     * 支持多 DB 类型时,无法直接使用 key + @TableField("menuKey") 来实现转换,原因是 "menuKey" AS key 而存在报错
+     */
+    private String menuKey;
+    /**
+     * 父菜单编号
+     */
+    private Long parentId;
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    // ========== 按钮操作 ==========
+
+    /**
+     * 按钮类型
+     *
+     * 枚举 {@link MenuButtonType}
+     */
+    private String type;
+
+    /**
+     * 网页链接
+     *
+     * 用户点击菜单可打开链接,不超过 1024 字节
+     *
+     * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM
+     */
+    private String url;
+
+    /**
+     * 小程序的 appId
+     *
+     * 类型为 {@link MenuButtonType} 的 MINIPROGRAM
+     */
+    private String miniProgramAppId;
+    /**
+     * 小程序的页面路径
+     *
+     * 类型为 {@link MenuButtonType} 的 MINIPROGRAM
+     */
+    private String miniProgramPagePath;
+
+    // ========== 消息内容 ==========
+
+    /**
+     * 消息类型
+     *
+     * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG
+     *
+     * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
+     */
+    private String replyMessageType;
+
+    /**
+     * 回复的消息内容
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
+     */
+    private String replyContent;
+
+    /**
+     * 回复的媒体 id
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
+     */
+    private String replyMediaId;
+    /**
+     * 回复的媒体 URL
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
+     */
+    private String replyMediaUrl;
+
+    /**
+     * 回复的标题
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
+     */
+    private String replyTitle;
+    /**
+     * 回复的描述
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
+     */
+    private String replyDescription;
+
+    /**
+     * 回复的图文消息数组
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
+     */
+    @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
+    private List<MpMessageDO.Article> replyArticles;
+
 }

+ 3 - 2
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;
 import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -130,6 +131,6 @@ public class MpAutoReplyDO extends BaseDO {
      *
      * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
      */
-    @TableField(typeHandler = JacksonTypeHandler.class)
-    private MpMessageDO.Article responseArticle;
+    @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
+    private List<MpMessageDO.Article> responseArticles;
 }

+ 3 - 2
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;
 @Mapper
 public interface MpMenuMapper extends BaseMapperX<MpMenuDO> {
 
-    default MpMenuDO selectByAppId(String appId) {
-        return selectOne(MpMenuDO::getAppId, appId);
+    default MpMenuDO selectByAppIdAndMenuKey(String appId, String menuKey) {
+        return selectOne(MpMenuDO::getAppId, appId,
+                MpMenuDO::getMenuKey, menuKey);
     }
 
 }

+ 13 - 38
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java

@@ -1,13 +1,11 @@
 package cn.iocoder.yudao.module.mp.service.menu;
 
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert;
-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.framework.mp.core.MpServiceFactory;
-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;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.bean.menu.WxMenu;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -37,6 +35,9 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
 @Slf4j
 public class MpMenuServiceImpl implements MpMenuService {
 
+    @Resource
+    private MpMessageService mpMessageService;
+
     @Resource
     @Lazy // 延迟加载,避免循环引用报错
     private MpServiceFactory mpServiceFactory;
@@ -87,47 +88,21 @@ public class MpMenuServiceImpl implements MpMenuService {
 
     @Override
     public WxMpXmlOutMessage reply(String appId, String key, String openid) {
-        // 获得菜单
-        MpMenuDO menu = mpMenuMapper.selectByAppId(appId);
+        // 第一步,获得菜单
+        MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key);
         if (menu == null) {
-            log.error("[reply][appId({}) 找不到对应的菜单]", appId);
-            return null;
-        }
-        // 匹配对应的按钮
-        MpMenuDO.Button button = getMenuButton(menu, key);
-        if (button == null) {
-            log.error("[reply][appId({}) key({}) 找不到对应的菜单按钮]", appId, key);
+            log.error("[reply][appId({}) key({}) 找不到对应的菜单]", appId, key);
             return null;
         }
         // 按钮必须要有消息类型,不然后续无法回复消息
-        if (StrUtil.isEmpty(button.getMessageType())) {
-            log.error("[reply][appId({}) key({}) 不存在消息类型({})]", appId, key, button);
+        if (StrUtil.isEmpty(menu.getReplyMessageType())) {
+            log.error("[reply][menu({}) 不存在对应的消息类型]", menu);
             return null;
         }
 
-        // 回复消息
-        return null;
-    }
-
-    private MpMenuDO.Button getMenuButton(MpMenuDO menu, String key) {
-        // 先查询子按钮
-        for (MpMenuDO.Button button : menu.getButtons()) {
-            if (CollUtil.isEmpty(button.getSubButtons())) {
-                continue;
-            }
-            for (MpMenuDO.Button subButton : button.getSubButtons()) {
-                if (StrUtil.equals(subButton.getKey(), key)) {
-                    return subButton;
-                }
-            }
-        }
-        // 再查询父按钮
-        for (MpMenuDO.Button button : menu.getButtons()) {
-            if (StrUtil.equals(button.getKey(), key)) {
-                return button;
-            }
-        }
-        return null;
+        // 第二步,回复消息
+        MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu);
+        return mpMessageService.sendOutMessage(sendReqBO);
     }
 
 }

+ 2 - 1
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;
 import javax.validation.Valid;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
+import java.util.List;
 
 /**
  * 公众号消息发送 Request BO
@@ -85,6 +86,6 @@ public class MpMessageSendOutReqBO {
      */
     @Valid
     @NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
-    private MpMessageDO.Article article;
+    private List<MpMessageDO.Article> articles;
 
 }