Procházet zdrojové kódy

mp:完善微信公众号的消息

YunaiV před 2 roky
rodič
revize
68ef11ee87
52 změnil soubory, kde provedl 378 přidání a 494 odebrání
  1. 4 4
      yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java
  2. 2 2
      yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java
  3. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java
  4. 3 11
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java
  5. 4 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java
  6. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java
  7. 3 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java
  8. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java
  9. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java
  10. 0 193
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageBaseVO.java
  11. 17 9
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java
  12. 88 7
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java
  13. 2 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java
  14. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java
  15. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java
  16. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java
  17. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java
  18. 4 4
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java
  19. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java
  20. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java
  21. 4 4
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java
  22. 6 6
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java
  23. 30 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java
  24. 5 5
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java
  25. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java
  26. 3 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java
  27. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java
  28. 0 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java
  29. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java
  30. 1 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java
  31. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java
  32. 3 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java
  33. 3 5
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java
  34. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java
  35. 4 7
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java
  36. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java
  37. 2 2
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java
  38. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java
  39. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java
  40. 7 8
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java
  41. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
  42. 3 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java
  43. 4 4
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java
  44. 6 6
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java
  45. 3 3
      yudao-ui-admin/src/api/mp/draft.js
  46. 1 1
      yudao-ui-admin/src/api/mp/message.js
  47. 1 0
      yudao-ui-admin/src/utils/dict.js
  48. 52 74
      yudao-ui-admin/src/views/mp/autoReply/index.vue
  49. 36 25
      yudao-ui-admin/src/views/mp/components/wx-msg/main.vue
  50. 2 2
      yudao-ui-admin/src/views/mp/menu/index.vue
  51. 48 64
      yudao-ui-admin/src/views/mp/message/index.vue
  52. 1 1
      yudao-ui-admin/src/views/mp/statistics/index.vue

+ 4 - 4
yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java

@@ -15,8 +15,8 @@ public interface ErrorCodeConstants {
     ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}");
 
     // ========== 公众号统计 1006001000============
-    ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取用户增减数据失败,原因:{}");
-    ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1006001001, "获得用户累计数据失败,原因:{}");
+    ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取粉丝增减数据失败,原因:{}");
+    ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1006001001, "获得粉丝累计数据失败,原因:{}");
     ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1006001002, "获得消息发送概况数据失败,原因:{}");
     ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1006001003, "获得接口分析数据失败,原因:{}");
 
@@ -28,8 +28,8 @@ public interface ErrorCodeConstants {
     ErrorCode TAG_GET_FAIL = new ErrorCode(1006002001, "获得标签失败,原因:{}");
 
     // ========== 公众号粉丝 1006003000============
-    ErrorCode USER_NOT_EXISTS = new ErrorCode(1006003000, "用户不存在");
-    ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新用户标签失败,原因:{}");
+    ErrorCode USER_NOT_EXISTS = new ErrorCode(1006003000, "粉丝不存在");
+    ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新粉丝标签失败,原因:{}");
 
     // ========== 公众号素材 1006004000============
     ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1006004000, "素材不存在");

+ 2 - 2
yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java

@@ -12,8 +12,8 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum MpMessageSendFromEnum {
 
-    USER_TO_MP(1, "用户发送给公众号"),
-    MP_TO_USER(2, "公众号发给用户"),
+    USER_TO_MP(1, "粉丝发送给公众号"),
+    MP_TO_USER(2, "公众号发给粉丝"),
     ;
 
     /**

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java

@@ -14,7 +14,7 @@ import java.util.List;
 import static cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
 
 /**
- * 微信菜单 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 公众号菜单 Base VO,提供给添加、修改、详细的子 VO 使用
  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  */
 @Data

+ 3 - 11
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java

@@ -1,34 +1,26 @@
 package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
 
-import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
-import javax.validation.constraints.NotNull;
 import java.util.Date;
 
-// TODO swagger 文档
-@ApiModel("管理后台 - 微信菜单 Response VO")
+@ApiModel("管理后台 - 公众号菜单 Response VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 public class MpMenuRespVO extends MpMenuBaseVO {
 
-    @ApiModelProperty(value = "主键", required = true)
+    @ApiModelProperty(value = "主键", required = true, example = "1024")
     private Long id;
 
     @ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
-    @NotNull(message = "公众号账号的编号不能为空")
     private Long accountId;
 
-    /**
-     * 微信公众号 appid
-     *
-     * 冗余 {@link MpAccountDO#getAppId()}
-     */
+    @ApiModelProperty(value = "公众号 appId", required = true, example = "wx1234567890ox")
     private String appId;
 
     @ApiModelProperty(value = "创建时间", required = true)

+ 4 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java

@@ -9,7 +9,6 @@ import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.util.List;
 
-// TODO 芋艿:swagger 文档
 @ApiModel("管理后台 - 公众号菜单保存 Request VO")
 @Data
 public class MpMenuSaveReqVO {
@@ -22,9 +21,13 @@ public class MpMenuSaveReqVO {
     @Valid
     private List<Menu> menus;
 
+    @ApiModel("管理后台 - 公众号菜单保存时的每个菜单")
     @Data
     public static class Menu extends MpMenuBaseVO {
 
+        /**
+         * 子菜单数组
+         */
         private List<Menu> children;
 
     }

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java

@@ -40,7 +40,7 @@ public class MpAutoReplyController {
 
     @GetMapping("/get")
     @ApiOperation("获得公众号自动回复")
-    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
     @PreAuthorize("@ss.hasPermission('mp:auto-reply:query')")
     public CommonResult<MpAutoReplyRespVO> getAutoReply(@RequestParam("id") Long id) {
         MpAutoReplyDO autoReply = mpAutoReplyService.getAutoReply(id);

+ 3 - 3
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java

@@ -29,10 +29,10 @@ public class MpMessageController {
     private MpMessageService mpMessageService;
 
     @GetMapping("/page")
-    @ApiOperation("获得粉丝消息分页")
+    @ApiOperation("获得公众号消息分页")
     @PreAuthorize("@ss.hasPermission('mp:message:query')")
-    public CommonResult<PageResult<MpMessageRespVO>> getWxFansMsgPage(@Valid MpMessagePageReqVO pageVO) {
-        PageResult<MpMessageDO> pageResult = mpMessageService.getWxFansMsgPage(pageVO);
+    public CommonResult<PageResult<MpMessageRespVO>> getMessagePage(@Valid MpMessagePageReqVO pageVO) {
+        PageResult<MpMessageDO> pageResult = mpMessageService.getMessagePage(pageVO);
         return success(MpMessageConvert.INSTANCE.convertPage(pageResult));
     }
 

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java

@@ -14,8 +14,8 @@ import javax.validation.constraints.NotNull;
 @ToString(callSuper = true)
 public class MpAutoReplyCreateReqVO extends MpAutoReplyBaseVO {
 
-    @ApiModelProperty(value = "微信公众号 ID", required = true, example = "1024")
-    @NotNull(message = "微信公众号 ID不能为空")
+    @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
+    @NotNull(message = "公众号账号的编号不能为空")
     private Long accountId;
 
 }

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java

@@ -17,9 +17,9 @@ public class MpAutoReplyRespVO extends MpAutoReplyBaseVO {
     @ApiModelProperty(value = "主键", required = true, example = "1024")
     private Long id;
 
-    @ApiModelProperty(value = "微信公众号 ID", required = true, example = "1024")
+    @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
     private Long accountId;
-    @ApiModelProperty(value = "微信公众号 appid", required = true, example = "wx1234567890")
+    @ApiModelProperty(value = "公众号 appId", required = true, example = "wx1234567890")
     private String appId;
 
     @ApiModelProperty(value = "创建时间", required = true)

+ 0 - 193
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageBaseVO.java

@@ -1,193 +0,0 @@
-package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
-
-import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
-import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
-import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
-import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
-import com.baomidou.mybatisplus.annotation.TableField;
-import lombok.Data;
-import me.chanjar.weixin.common.api.WxConsts;
-
-import java.util.List;
-
-// TODO 芋艿:VO 的注解
-/**
- * 粉丝消息  Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
-@Data
-public class MpMessageBaseVO {
-
-    /**
-     * 微信公众号消息 id
-     */
-    private Long msgId;
-    /**
-     * 微信公众号 ID
-     *
-     * 关联 {@link MpAccountDO#getId()}
-     */
-    private Long accountId;
-    /**
-     * 微信公众号 appid
-     *
-     * 冗余 {@link MpAccountDO#getAppId()}
-     */
-    private String appId;
-    /**
-     * 微信用户编号
-     *
-     * 关联 {@link MpUserDO#getId()}
-     */
-    private Long userId;
-    /**
-     * 用户标识
-     *
-     * 冗余 {@link MpUserDO#getOpenid()}
-     */
-    private String openid;
-
-    /**
-     * 消息类型
-     *
-     * 枚举 {@link WxConsts.XmlMsgType}
-     */
-    private String type;
-    /**
-     * 消息来源
-     *
-     * 枚举 {@link MpMessageSendFromEnum}
-     */
-    private Integer sendFrom;
-
-    // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
-
-    /**
-     * 消息内容
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
-     */
-    private String content;
-
-    /**
-     * 通过素材管理中的接口上传多媒体文件,得到的 id
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
-     */
-    private String mediaId;
-    /**
-     * 媒体文件的 URL
-     */
-    private String mediaUrl;
-    /**
-     * 语音识别后文本
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE
-     */
-    private String recognition;
-    /**
-     * 语音格式,如 amr,speex 等
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE
-     */
-    private String format;
-    /**
-     * 标题
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC、LINK
-     */
-    private String title;
-    /**
-     * 描述
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC
-     */
-    private String description;
-
-    /**
-     * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
-     */
-    private String thumbMediaId;
-    /**
-     * 缩略图的媒体 URL
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
-     */
-    private String thumbMediaUrl;
-
-    /**
-     * 点击图文消息跳转链接
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LINK
-     */
-    private String url;
-
-    /**
-     * 地理位置维度
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
-     */
-    private Double locationX;
-    /**
-     * 地理位置经度
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
-     */
-    private Double locationY;
-    /**
-     * 地图缩放大小
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
-     */
-    private Double scale;
-    /**
-     * 详细地址
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
-     *
-     * 例如说杨浦区黄兴路 221-4 号临
-     */
-    private String label;
-
-    /**
-     * 图文消息数组
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
-     */
-    @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
-    private List<MpMessageDO.Article> articles;
-
-    /**
-     * 音乐链接
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
-     */
-    private String musicUrl;
-    /**
-     * 高质量音乐链接
-     *
-     * WIFI 环境优先使用该链接播放音乐
-     *
-     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
-     */
-    private String hqMusicUrl;
-
-    // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
-
-    /**
-     * 事件类型
-     *
-     * 枚举 {@link WxConsts.EventType}
-     */
-    private String event;
-    /**
-     * 事件 Key
-     *
-     * 1. {@link WxConsts.EventType} 的 SCAN:qrscene_ 为前缀,后面为二维码的参数值
-     * 2. {@link WxConsts.EventType} 的 CLICK:与自定义菜单接口中 KEY 值对应
-     */
-    private String eventKey;
-
-}

+ 17 - 9
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java

@@ -4,23 +4,31 @@ import lombok.*;
 
 import io.swagger.annotations.*;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
 
-@ApiModel("管理后台 - 粉丝消息分页 Request VO")
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 公众号消息分页 Request VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 public class MpMessagePageReqVO extends PageParam {
 
-    @ApiModelProperty(value = "用户标识")
-    private String openId;
-
-    @ApiModelProperty(value = "昵称")
-    private String nickname;
-
-    @ApiModelProperty(value = "微信账号ID")
+    @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
+    @NotNull(message = "公众号账号的编号不能为空")
     private Long accountId;
 
-    @ApiModelProperty(value = "消息类型")
+    @ApiModelProperty(value = "消息类型", example = "text", notes = "参见 WxConsts.XmlMsgType 枚举")
     private String type;
 
+    @ApiModelProperty(value = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
+    private String openid;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "创建时间")
+    private LocalDateTime[] createTime;
+
 }

+ 88 - 7
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java

@@ -1,22 +1,103 @@
 package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
 
+import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
+import com.baomidou.mybatisplus.annotation.TableField;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
+import me.chanjar.weixin.common.api.WxConsts;
 
 import java.util.Date;
+import java.util.List;
 
-@ApiModel("管理后台 - 粉丝消息 Response VO")
+@ApiModel("管理后台 - 公众号消息 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class MpMessageRespVO extends MpMessageBaseVO {
+public class MpMessageRespVO {
 
-    @ApiModelProperty(value = "主键", required = true)
+    @ApiModelProperty(value = "主键", required = true, example = "1024")
     private Integer id;
 
+    @ApiModelProperty(value = "微信公众号消息 id", required = true, example = "23953173569869169")
+    private Long msgId;
+
+    @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1")
+    private Long accountId;
+    @ApiModelProperty(value = "公众号账号的 appid", required = true, example = "wx1234567890")
+    private String appId;
+
+    @ApiModelProperty(value = "公众号粉丝编号", required = true, example = "2048")
+    private Long userId;
+    @ApiModelProperty(value = "公众号粉丝标志", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
+    private String openid;
+
+    @ApiModelProperty(value = "消息类型", required = true, example = "text", notes = "参见 WxConsts.XmlMsgType 枚举")
+    private String type;
+    @ApiModelProperty(value = "消息来源", required = true, example = "1", notes = "参见 MpMessageSendFromEnum 枚举")
+    private Integer sendFrom;
+
+    // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
+
+    @ApiModelProperty(value = "消息内容", example = "你好呀", notes = "消息类型为 text 时,才有值")
+    private String content;
+
+    @ApiModelProperty(value = "媒体素材的编号", example = "1234567890", notes = "消息类型为 image、voice、video 时,才有值")
+    private String mediaId;
+    @ApiModelProperty(value = "媒体文件的 URL", example = "https://www.iocoder.cn/xxx.png",
+            notes = "消息类型为 image、voice、video 时,才有值")
+    private String mediaUrl;
+
+    @ApiModelProperty(value = "语音识别后文本", example = "语音识别后文本", notes = "消息类型为 voice 时,才有值")
+    private String recognition;
+    @ApiModelProperty(value = "语音格式", example = "amr", notes = "消息类型为 voice 时,才有值")
+    private String format;
+
+    @ApiModelProperty(value = "标题", example = "我是标题", notes = "消息类型为 video、music、link 时,才有值")
+    private String title;
+
+    @ApiModelProperty(value = "描述", example = "我是描述", notes = "消息类型为 video、music 时,才有值")
+    private String description;
+
+    @ApiModelProperty(value = "缩略图的媒体 id", example = "1234567890", notes = "消息类型为 video、music 时,才有值")
+    private String thumbMediaId;
+    @ApiModelProperty(value = "缩略图的媒体 URL", example = "https://www.iocoder.cn/xxx.png",
+            notes = "消息类型为 video、music 时,才有值")
+    private String thumbMediaUrl;
+
+    @ApiModelProperty(value = "点击图文消息跳转链接", example = "https://www.iocoder.cn", notes = "消息类型为 link 时,才有值")
+    private String url;
+
+    @ApiModelProperty(value = "地理位置维度", example = "23.137466", notes = "消息类型为 location 时,才有值")
+    private Double locationX;
+
+    @ApiModelProperty(value = "地理位置经度", example = "113.352425", notes = "消息类型为 location 时,才有值")
+    private Double locationY;
+
+    @ApiModelProperty(value = "地图缩放大小", example = "13", notes = "消息类型为 location 时,才有值")
+    private Double scale;
+
+    @ApiModelProperty(value = "详细地址", example = "杨浦区黄兴路 221-4 号临", notes = "消息类型为 location 时,才有值")
+    private String label;
+
+    /**
+     * 图文消息数组
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
+     */
+    @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
+    private List<MpMessageDO.Article> articles;
+
+    @ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/xxx.mp3", notes = "消息类型为 music 时,才有值")
+    private String musicUrl;
+    @ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3", notes = "消息类型为 music 时,才有值")
+    private String hqMusicUrl;
+
+    // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
+
+    @ApiModelProperty(value = "事件类型", example = "subscribe", notes = "参见 WxConsts.EventType 枚举")
+    private String event;
+    @ApiModelProperty(value = "事件 Key", example = "qrscene_123456", notes = "参见 WxConsts.EventType 枚举")
+    private String eventKey;
+
     @ApiModelProperty(value = "创建时间", required = true)
     private Date createTime;
 

+ 2 - 3
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java

@@ -4,7 +4,6 @@ package cn.iocoder.yudao.module.mp.controller.admin.open.vo;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
-import org.springframework.web.bind.annotation.RequestParam;
 
 import javax.validation.constraints.NotEmpty;
 
@@ -26,8 +25,8 @@ public class MpOpenHandleMessageReqVO {
     @NotEmpty(message = "随机数不能为空")
     private String nonce;
 
-    @ApiModelProperty(value = "用户 openid", required = true, example = "oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY")
-    @NotEmpty(message = "用户 openid 不能为空")
+    @ApiModelProperty(value = "粉丝 openid", required = true, example = "oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY")
+    @NotEmpty(message = "粉丝 openid 不能为空")
     private String openid;
 
     @ApiModelProperty(value = "消息加密类型", example = "aes")

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java

@@ -31,7 +31,7 @@ public class MpStatisticsController {
     private MpStatisticsService mpStatisticsService;
 
     @GetMapping("/user-summary")
-    @ApiOperation("获得用户增减数据")
+    @ApiOperation("获得粉丝增减数据")
     @PreAuthorize("@ss.hasPermission('mp:statistics:query')")
     public CommonResult<List<MpStatisticsUserSummaryRespVO>> getUserSummary(MpStatisticsGetReqVO getReqVO) {
         List<WxDataCubeUserSummary> list = mpStatisticsService.getUserSummary(
@@ -40,7 +40,7 @@ public class MpStatisticsController {
     }
 
     @GetMapping("/user-cumulate")
-    @ApiOperation("获得用户累计数据")
+    @ApiOperation("获得粉丝累计数据")
     @PreAuthorize("@ss.hasPermission('mp:statistics:query')")
     public CommonResult<List<MpStatisticsUserCumulateRespVO>> getUserCumulate(MpStatisticsGetReqVO getReqVO) {
         List<WxDataCubeUserCumulate> list = mpStatisticsService.getUserCumulate(

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java

@@ -13,7 +13,7 @@ public class MpStatisticsInterfaceSummaryRespVO {
     @ApiModelProperty(value = "日期", required = true)
     private Date refDate;
 
-    @ApiModelProperty(value = "通过服务器配置地址获得消息后,被动回复用户消息的次数", required = true, example = "10")
+    @ApiModelProperty(value = "通过服务器配置地址获得消息后,被动回复粉丝消息的次数", required = true, example = "10")
     private Integer callbackCount;
 
     @ApiModelProperty(value = "上述动作的失败次数", required = true, example = "20")

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java

@@ -6,14 +6,14 @@ import lombok.Data;
 
 import java.util.Date;
 
-@ApiModel("管理后台 - 某一天的用户增减数据 Response VO")
+@ApiModel("管理后台 - 某一天的粉丝增减数据 Response VO")
 @Data
 public class MpStatisticsUpstreamMessageRespVO {
 
     @ApiModelProperty(value = "日期", required = true)
     private Date refDate;
 
-    @ApiModelProperty(value = "上行发送了(向公众号发送了)消息的用户数", required = true, example = "10")
+    @ApiModelProperty(value = "上行发送了(向公众号发送了)消息的粉丝数", required = true, example = "10")
     private Integer messageUser;
 
     @ApiModelProperty(value = "上行发送了消息的消息总数", required = true, example = "20")

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java

@@ -13,7 +13,7 @@ public class MpStatisticsUserCumulateRespVO {
     @ApiModelProperty(value = "日期", required = true)
     private Date refDate;
 
-    @ApiModelProperty(value = "累计用户量", required = true, example = "10")
+    @ApiModelProperty(value = "累计粉丝量", required = true, example = "10")
     private Integer cumulateUser;
 
 }

+ 4 - 4
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java

@@ -6,20 +6,20 @@ import lombok.Data;
 
 import java.util.Date;
 
-@ApiModel("管理后台 - 某一天的用户增减数据 Response VO")
+@ApiModel("管理后台 - 某一天的粉丝增减数据 Response VO")
 @Data
 public class MpStatisticsUserSummaryRespVO {
 
     @ApiModelProperty(value = "日期", required = true)
     private Date refDate;
 
-    @ApiModelProperty(value = "用户来源", required = true, example = "0")
+    @ApiModelProperty(value = "粉丝来源", required = true, example = "0")
     private Integer userSource;
 
-    @ApiModelProperty(value = "新关注的用户数量", required = true, example = "10")
+    @ApiModelProperty(value = "新关注的粉丝数量", required = true, example = "10")
     private Integer newUser;
 
-    @ApiModelProperty(value = "取消关注的用户数量", required = true, example = "20")
+    @ApiModelProperty(value = "取消关注的粉丝数量", required = true, example = "20")
     private Integer cancelUser;
 
 }

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java

@@ -19,10 +19,10 @@ public class MpUserPageReqVO extends PageParam {
     @NotNull(message = "公众号账号的编号不能为空")
     private Long accountId;
 
-    @ApiModelProperty(value = "公众号用户标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", notes = "模糊匹配")
+    @ApiModelProperty(value = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", notes = "模糊匹配")
     private String openid;
 
-    @ApiModelProperty(value = "公众号用户昵称", example = "芋艿", notes = "模糊匹配")
+    @ApiModelProperty(value = "公众号粉丝昵称", example = "芋艿", notes = "模糊匹配")
     private String nickname;
 
 }

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java

@@ -15,7 +15,7 @@ public class MpUserRespVO  {
     @ApiModelProperty(value = "编号", required = true, example = "1024")
     private Long id;
 
-    @ApiModelProperty(value = "公众号用户标识", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
+    @ApiModelProperty(value = "公众号粉丝标识", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
     private String openid;
 
     @ApiModelProperty(value = "关注状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")

+ 4 - 4
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java

@@ -32,13 +32,13 @@ public class MpMaterialDO extends BaseDO {
     @TableId
     private Long id;
     /**
-     * 微信公众号 ID
+     * 公众号账号的编号
      *
      * 关联 {@link MpAccountDO#getId()}
      */
     private Long accountId;
     /**
-     * 微信公众号 appid
+     * 公众号 appId
      *
      * 冗余 {@link MpAccountDO#getAppId()}
      */
@@ -71,8 +71,8 @@ public class MpMaterialDO extends BaseDO {
      *
      * 永久素材:非空
      * 临时素材:可能为空。
-     *      1. 为空的情况:用户主动发送的图片、语音等
-     *      2. 非空的情况:主动发送给用户的图片、语音等
+     *      1. 为空的情况:粉丝主动发送的图片、语音等
+     *      2. 非空的情况:主动发送给粉丝的图片、语音等
      */
     private String name;
 

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

@@ -16,7 +16,7 @@ import me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
 import java.util.List;
 
 /**
- * 微信菜单 DO
+ * 公众号菜单 DO
  *
  * @author 芋道源码
  */
@@ -38,13 +38,13 @@ public class MpMenuDO extends BaseDO {
     @TableId
     private Long id;
     /**
-     * 微信公众号 ID
+     * 公众号账号的编号
      *
      * 关联 {@link MpAccountDO#getId()}
      */
     private Long accountId;
     /**
-     * 微信公众号 appid
+     * 公众号 appId
      *
      * 冗余 {@link MpAccountDO#getAppId()}
      */
@@ -77,7 +77,7 @@ public class MpMenuDO extends BaseDO {
     /**
      * 网页链接
      *
-     * 用户点击菜单可打开链接,不超过 1024 字节
+     * 粉丝点击菜单可打开链接,不超过 1024 字节
      *
      * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM
      */
@@ -146,13 +146,13 @@ public class MpMenuDO extends BaseDO {
     private String replyDescription;
 
     /**
-     * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
+     * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
      *
      * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
      */
     private String replyThumbMediaId;
     /**
-     * 缩略图的媒体 URL
+     * 回复的缩略图的媒体 URL
      *
      * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
      */

+ 30 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java

@@ -40,13 +40,13 @@ public class MpAutoReplyDO extends BaseDO {
     @TableId
     private Long id;
     /**
-     * 微信公众号 ID
+     * 公众号账号的编号
      *
      * 关联 {@link MpAccountDO#getId()}
      */
     private Long accountId;
     /**
-     * 微信公众号 appid
+     * 公众号 appId
      *
      * 冗余 {@link MpAccountDO#getAppId()}
      */
@@ -126,6 +126,19 @@ public class MpAutoReplyDO extends BaseDO {
     private String responseDescription;
 
     /**
+     * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
+     */
+    private String responseThumbMediaId;
+    /**
+     * 回复的缩略图的媒体 URL
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
+     */
+    private String responseThumbMediaUrl;
+
+    /**
      * 回复的图文消息
      *
      * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
@@ -133,4 +146,19 @@ public class MpAutoReplyDO extends BaseDO {
     @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
     private List<MpMessageDO.Article> responseArticles;
 
+    /**
+     * 回复的音乐链接
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
+     */
+    private String responseMusicUrl;
+    /**
+     * 回复的高质量音乐链接
+     *
+     * WIFI 环境优先使用该链接播放音乐
+     *
+     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
+     */
+    private String responseHqMusicUrl;
+
 }

+ 5 - 5
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java

@@ -40,25 +40,25 @@ public class MpMessageDO extends BaseDO {
      */
     private Long msgId;
     /**
-     * 微信公众号 ID
+     * 公众号账号的 ID
      *
      * 关联 {@link MpAccountDO#getId()}
      */
     private Long accountId;
     /**
-     * 微信公众号 appid
+     * 公众号 appid
      *
      * 冗余 {@link MpAccountDO#getAppId()}
      */
     private String appId;
     /**
-     * 微信用户编号
+     * 公众号粉丝的编号
      *
      * 关联 {@link MpUserDO#getId()}
      */
     private Long userId;
     /**
-     * 用户标识
+     * 公众号粉丝标志
      *
      * 冗余 {@link MpUserDO#getOpenid()}
      */
@@ -87,7 +87,7 @@ public class MpMessageDO extends BaseDO {
     private String content;
 
     /**
-     * 通过素材管理中的接口上传多媒体文件,得到的 id
+     * 媒体文件的编号
      *
      * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
      */

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java

@@ -43,13 +43,13 @@ public class MpTagDO extends BaseDO {
     private Integer count;
 
     /**
-     * 微信公众号 ID
+     * 公众号账号的编号
      *
      * 关联 {@link MpAccountDO#getId()}
      */
     private Long accountId;
     /**
-     * 微信公众号 appid
+     * 公众号 appId
      *
      * 冗余 {@link MpAccountDO#getAppId()}
      */

+ 3 - 3
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java

@@ -35,7 +35,7 @@ public class MpUserDO extends BaseDO {
     @TableId
     private Long id;
     /**
-     * 用户标识
+     * 粉丝标识
      */
     private String openid;
     /**
@@ -95,13 +95,13 @@ public class MpUserDO extends BaseDO {
     private List<Long> tagIds;
 
     /**
-     * 微信公众号 ID
+     * 公众号账号的编号
      *
      * 关联 {@link MpAccountDO#getId()}
      */
     private Long accountId;
     /**
-     * 微信公众号 appid
+     * 公众号 appId
      *
      * 冗余 {@link MpAccountDO#getAppId()}
      */

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java

@@ -13,9 +13,9 @@ public interface MpMessageMapper extends BaseMapperX<MpMessageDO> {
     default PageResult<MpMessageDO> selectPage(MpMessagePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<MpMessageDO>()
                 .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
-                .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenId())
-//                .likeIfPresent(MpMessageDO::getNickname, reqVO.getNickname())
                 .eqIfPresent(MpMessageDO::getType, reqVO.getType())
+                .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid())
+                .betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(MpMessageDO::getId));
     }
 

+ 0 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java

@@ -103,7 +103,6 @@ public class DefaultMpServiceFactory implements MpServiceFactory {
 
     private WxMpService buildMpService(MpAccountDO account) {
         // 第一步,创建 WxMpRedisConfigImpl 对象
-        // TODO 芋艿:需要确认下,redis key 的存储结构
         WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(
                 redisTemplateWxRedisOps, mpProperties.getConfigStorage().getKeyPrefix());
         configStorage.setAppId(account.getAppId());

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java

@@ -18,7 +18,7 @@ import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
 /**
  * 自定义菜单的事件处理器
  *
- * 逻辑:用户点击菜单时,触发对应的回复
+ * 逻辑:粉丝点击菜单时,触发对应的回复
  *
  * @author 芋道源码
  */

+ 1 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java

@@ -20,8 +20,7 @@ public class KfSessionHandler implements WxMpMessageHandler {
     @Override
     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
                                     WxMpService wxMpService, WxSessionManager sessionManager) {
-        // TODO 对会话做处理
-        return null;
+        throw new UnsupportedOperationException("未实现该处理,请自行重写");
     }
 
 }

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java

@@ -18,7 +18,7 @@ public class NullHandler implements WxMpMessageHandler {
     @Override
     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
                                     WxMpService wxMpService, WxSessionManager sessionManager) {
-        return null;
+        throw new UnsupportedOperationException("未实现该处理,请自行重写");
     }
 
 }

+ 3 - 3
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java

@@ -17,9 +17,9 @@ import java.util.Map;
 public class ScanHandler implements WxMpMessageHandler {
 
     @Override
-    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> map,
+    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> context,
                                     WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
-        // 扫码事件处理
-        return null;
+        throw new UnsupportedOperationException("未实现该处理,请自行重写");
     }
+
 }

+ 3 - 5
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java

@@ -16,11 +16,9 @@ import java.util.Map;
 public class StoreCheckNotifyHandler implements WxMpMessageHandler {
 
     @Override
-    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
-                                    Map<String, Object> context, WxMpService wxMpService,
-                                    WxSessionManager sessionManager) {
-        // TODO 处理门店审核事件
-        return null;
+    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
+                                    WxMpService wxMpService, WxSessionManager sessionManager) {
+        throw new UnsupportedOperationException("未实现该处理,请自行重写");
     }
 
 }

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java

@@ -21,7 +21,7 @@ import java.util.Map;
  *
  * 触发操作:打开微信公众号 -> 点击 + 号 -> 选择「语音」
  *
- * 逻辑:用户上传地理位置时,也可以触发自动回复
+ * 逻辑:粉丝上传地理位置时,也可以触发自动回复
  *
  * @author 芋道源码
  */

+ 4 - 7
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java

@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.mp.service.handler.user;
 
-import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
 import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
-import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
 import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService;
 import cn.iocoder.yudao.module.mp.service.user.MpUserService;
 import lombok.extern.slf4j.Slf4j;
@@ -13,7 +11,6 @@ import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
 import me.chanjar.weixin.mp.bean.result.WxMpUser;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
@@ -36,16 +33,16 @@ public class SubscribeHandler implements WxMpMessageHandler {
     @Override
     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
                                     WxMpService weixinService, WxSessionManager sessionManager) throws WxErrorException {
-        // 第一步,从公众号平台,获取用户信息
-        log.info("[handle][用户({}) 关注]", wxMessage.getFromUser());
+        // 第一步,从公众号平台,获取粉丝信息
+        log.info("[handle][粉丝({}) 关注]", wxMessage.getFromUser());
         WxMpUser wxMpUser = null;
         try {
             wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
         } catch (WxErrorException e) {
-            log.error("[handle][用户({})] 获取用户信息失败!", wxMessage.getFromUser(), e);
+            log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e);
         }
 
-        // 第二步,保存用户信息
+        // 第二步,保存粉丝信息
         mpUserService.saveUser(MpContextHolder.getAppId(), wxMpUser);
 
         // 第三步,回复关注的欢迎语

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java

@@ -31,7 +31,7 @@ public class UnsubscribeHandler implements WxMpMessageHandler {
     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
                                     Map<String, Object> context, WxMpService wxMpService,
                                     WxSessionManager sessionManager) {
-        log.info("[handle][用户({}) 取消关注]", wxMessage.getFromUser());
+        log.info("[handle][粉丝({}) 取消关注]", wxMessage.getFromUser());
         mpUserService.updateUserUnsubscribe(MpContextHolder.getAppId(), wxMessage.getFromUser());
         return null;
     }

+ 2 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java

@@ -29,11 +29,11 @@ public interface MpMenuService {
     void deleteMenuByAccountId(Long accountId);
 
     /**
-     * 用户点击菜单按钮时,回复对应的消息
+     * 粉丝点击菜单按钮时,回复对应的消息
      *
      * @param appId 公众号 AppId
      * @param key 菜单按钮的标识
-     * @param openid 用户的 openid
+     * @param openid 粉丝的 openid
      * @return 消息
      */
     WxMpXmlOutMessage reply(String appId, String key, String openid);

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java

@@ -64,7 +64,7 @@ public interface MpAutoReplyService {
     WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage);
 
     /**
-     * 当用户关注时,自动回复
+     * 当粉丝关注时,自动回复
      *
      * @param appId 微信公众号 appId
      * @param wxMessage 消息

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java

@@ -136,7 +136,7 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService {
 
     @Override
     public void deleteAutoReply(Long id) {
-        // 校验用户存在
+        // 校验粉丝存在
         validateAutoReplyExists(id);
 
         // 删除自动回复

+ 7 - 8
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java

@@ -11,23 +11,22 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
 import javax.validation.Valid;
 
 /**
- * 粉丝消息 Service 接口
+ * 公众号消息 Service 接口
  *
  * @author 芋道源码
  */
 public interface MpMessageService {
 
-    // TODO 芋艿:方法名要优化下
     /**
-     * 获得粉丝消息分页
+     * 获得公众号消息分页
      *
      * @param pageReqVO 分页查询
-     * @return 粉丝消息分页
+     * @return 公众号消息分页
      */
-    PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO);
+    PageResult<MpMessageDO> getMessagePage(MpMessagePageReqVO pageReqVO);
 
     /**
-     * 从公众号,接收到用户消息
+     * 从公众号,接收到粉丝消息
      *
      * @param appId 微信公众号 appId
      * @param wxMessage 消息
@@ -35,7 +34,7 @@ public interface MpMessageService {
     void receiveMessage(String appId, WxMpXmlMessage wxMessage);
 
     /**
-     * 使用公众号,给用户回复消息
+     * 使用公众号,给粉丝回复消息
      *
      * 例如说:自动回复、客服消息、菜单回复消息等场景
      *
@@ -47,7 +46,7 @@ public interface MpMessageService {
     WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO);
 
     /**
-     * 使用公众号,给用户发送【客服】消息
+     * 使用公众号,给粉丝发送【客服】消息
      *
      * 注意,该方法会真实发送消息
      *

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java

@@ -63,7 +63,7 @@ public class MpMessageServiceImpl implements MpMessageService {
     private Validator validator;
 
     @Override
-    public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) {
+    public PageResult<MpMessageDO> getMessagePage(MpMessagePageReqVO pageReqVO) {
         return mpMessageMapper.selectPage(pageReqVO);
     }
 

+ 3 - 3
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java

@@ -14,7 +14,7 @@ import java.util.List;
 /**
  * 公众号消息发送 Request BO
  *
- * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给用户发送消息,所以使用该 BO 统一承接
+ * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给粉丝发送消息,所以使用该 BO 统一承接
  *
  * @author 芋道源码
  */
@@ -27,9 +27,9 @@ public class MpMessageSendOutReqBO {
     @NotEmpty(message = "公众号 appId 不能为空")
     private String appId;
     /**
-     * 公众号用户 openid
+     * 公众号粉丝 openid
      */
-    @NotEmpty(message = "公众号用户 openid 不能为空")
+    @NotEmpty(message = "公众号粉丝 openid 不能为空")
     private String openid;
 
     // ========== 消息内容 ==========

+ 4 - 4
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java

@@ -16,20 +16,20 @@ import java.util.List;
 public interface MpStatisticsService {
 
     /**
-     * 获取用户增减数据
+     * 获取粉丝增减数据
      *
      * @param accountId 公众号账号编号
      * @param date 时间区间
-     * @return 用户增减数据
+     * @return 粉丝增减数据
      */
     List<WxDataCubeUserSummary> getUserSummary(Long accountId, LocalDateTime[] date);
 
     /**
-     * 获取用户累计数据
+     * 获取粉丝累计数据
      *
      * @param accountId 公众号账号编号
      * @param date 时间区间
-     * @return 用户累计数据
+     * @return 粉丝累计数据
      */
     List<WxDataCubeUserCumulate> getUserCumulate(Long accountId, LocalDateTime[] date);
 

+ 6 - 6
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java

@@ -99,11 +99,11 @@ public class MpUserServiceImpl implements MpUserService {
         // for 循环,避免递归出意外问题,导致死循环
         String nextOpenid = null;
         for (int i = 0; i < Short.MAX_VALUE; i++) {
-            log.info("[syncUser][第({}) 次加载公众号用户列表,nextOpenid({})]", i, nextOpenid);
+            log.info("[syncUser][第({}) 次加载公众号粉丝列表,nextOpenid({})]", i, nextOpenid);
             try {
                 nextOpenid = syncUser0(account, nextOpenid);
             } catch (WxErrorException e) {
-                log.error("[syncUser][第({}) 次同步用户异常]", i, e);
+                log.error("[syncUser][第({}) 次同步粉丝异常]", i, e);
                 break;
             }
             // 如果 nextOpenid 为空,表示已经同步完毕
@@ -114,17 +114,17 @@ public class MpUserServiceImpl implements MpUserService {
     }
 
     private String syncUser0(MpAccountDO account, String nextOpenid) throws WxErrorException {
-        // 第一步,从公众号流式加载用户
+        // 第一步,从公众号流式加载粉丝
         WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getId());
         WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid);
         if (CollUtil.isEmpty(wxUserList.getOpenids())) {
             return null;
         }
 
-        // 第二步,分批加载用户信息
+        // 第二步,分批加载粉丝信息
         List<List<String>> openidsList = CollUtil.split(wxUserList.getOpenids(), 100);
         for (List<String> openids : openidsList) {
-            log.info("[syncUser][批量加载用户信息,openids({})]", openids);
+            log.info("[syncUser][批量加载粉丝信息,openids({})]", openids);
             List<WxMpUser> wxUsers = mpService.getUserService().userInfoList(openids);
             batchSaveUser(account, wxUsers);
         }
@@ -137,7 +137,7 @@ public class MpUserServiceImpl implements MpUserService {
         if (CollUtil.isEmpty(wxUsers)) {
             return;
         }
-        // 1. 获得数据库已保存的用户列表
+        // 1. 获得数据库已保存的粉丝列表
         List<MpUserDO> dbUsers = mpUserMapper.selectListByAppIdAndOpenid(account.getAppId(),
                 CollectionUtils.convertList(wxUsers, WxMpUser::getOpenId));
         Map<String, MpUserDO> openId2Users = CollectionUtils.convertMap(dbUsers, MpUserDO::getOpenid);

+ 3 - 3
yudao-ui-admin/src/api/mp/draft.js

@@ -9,7 +9,7 @@ export function getDraftPage(query) {
   })
 }
 
-// 创建草稿
+// 创建公众号草稿
 export function createDraft(accountId, articles) {
   return request({
     url: '/mp/draft/create?accountId=' + accountId,
@@ -20,7 +20,7 @@ export function createDraft(accountId, articles) {
   })
 }
 
-// 更新草稿
+// 更新公众号草稿
 export function updateDraft(accountId, mediaId, articles) {
   return request({
     url: '/mp/draft/update?accountId=' + accountId + '&mediaId=' + mediaId,
@@ -29,7 +29,7 @@ export function updateDraft(accountId, mediaId, articles) {
   })
 }
 
-// 删除草稿
+// 删除公众号草稿
 export function deleteDraft(accountId, mediaId) {
   return request({
     url: '/mp/draft/delete?accountId=' + accountId + '&mediaId=' + mediaId,

+ 1 - 1
yudao-ui-admin/src/api/mp/message.js

@@ -1,6 +1,6 @@
 import request from '@/utils/request'
 
-// 获得粉丝消息分页
+// 获得公众号消息分页
 export function getMessagePage(query) {
   return request({
     url: '/mp/message/page',

+ 1 - 0
yudao-ui-admin/src/utils/dict.js

@@ -60,6 +60,7 @@ export const DICT_TYPE = {
 
   // ========== MP 模块 ==========
   MP_AUTO_REPLY_REQUEST_MATCH: 'mp_auto_reply_request_match', // 自动回复请求匹配类型
+  MP_MESSAGE_TYPE: 'mp_message_type', // 消息类型
 
   // ========== MALL - PRODUCT 模块 ==========
   PRODUCT_SPU_STATUS: 'product_spu_status', // 商品 SPU 状态

+ 52 - 74
yudao-ui-admin/src/views/mp/autoReply/index.vue

@@ -50,49 +50,12 @@ SOFTWARE.
         </el-col>
         <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
       </el-row>
-      <!-- 列表 -->
+      <!-- tab 项 -->
       <el-tab-pane name="1">
         <span slot="label"><i class="el-icon-star-off"></i> 关注时回复</span>
-        <el-table v-loading="loading" :data="list">
-          <el-table-column label="回复消息类型" align="center" prop="responseMessageType"/>
-          <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template slot-scope="scope">
-              <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-              <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
-                         v-hasPermi="['mp:auto-reply:update']">修改
-              </el-button>
-              <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
-                         v-hasPermi="['mp:auto-reply:delete']">删除
-              </el-button>
-            </template>
-          </el-table-column>
-        </el-table>
       </el-tab-pane>
       <el-tab-pane name="2">
         <span slot="label"><i class="el-icon-chat-line-round"></i> 消息回复</span>
-        <el-table v-loading="loading" :data="list">
-          <el-table-column label="请求消息类型" align="center" prop="requestMessageType"/>
-          <el-table-column label="回复消息类型" align="center" prop="responseMessageType"/>
-          <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template slot-scope="scope">
-              <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-              <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
-                         v-hasPermi="['mp:auto-reply:update']">修改
-              </el-button>
-              <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
-                         v-hasPermi="['mp:auto-reply:delete']">删除
-              </el-button>
-            </template>
-          </el-table-column>
-        </el-table>
       </el-tab-pane>
       <el-tab-pane name="3">
         <span slot="label"><i class="el-icon-news"></i> 关键词回复</span>
@@ -101,13 +64,42 @@ SOFTWARE.
 
     <!-- 列表 -->
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="关键词" align="center" prop="requestKeyword"/>
-      <el-table-column label="匹配类型" align="center" prop="requestMatch">
+      <el-table-column label="请求消息类型" align="center" prop="requestMessageType" v-if="type === '2'" />
+      <el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
+      <el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
         <template v-slot="scope">
           <dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch"/>
         </template>
       </el-table-column>
-      <el-table-column label="回复消息类型" align="center" prop="responseMessageType"/>
+      <el-table-column label="回复消息类型" align="center">
+        <template v-slot="scope">
+          <dict-tag :type="DICT_TYPE.MP_MESSAGE_TYPE" :value="scope.row.responseMessageType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="回复内容" align="center">
+        <template slot-scope="scope">
+          <div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
+          <div v-else-if="scope.row.responseMessageType === 'voice'">
+            <wx-voice-player :url="scope.row.responseMediaUrl" />
+          </div>
+          <div v-else-if="scope.row.responseMessageType === 'image'">
+            <a target="_blank" :href="scope.row.responseMediaUrl">
+              <img :src="scope.row.responseMediaUrl" style="width: 100px">
+            </a>
+          </div>
+          <div v-else-if="scope.row.responseMessageType === 'video' || scope.row.responseMessageType === 'shortvideo'">
+            <wx-video-player :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
+          </div>
+          <div v-else-if="scope.row.responseMessageType === 'news'">
+            <wx-news :articles="scope.row.responseArticles" />
+          </div>
+          <div v-else-if="scope.row.responseMessageType === 'music'">
+            <wx-music :title="scope.row.responseTitle" :description="scope.row.responseDescription"
+                      :thumb-media-url="scope.row.responseThumbMediaUrl"
+                      :music-url="scope.row.responseMusicUrl" :hq-music-url="scope.row.responseHqMusicUrl" />
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column label="创建时间" align="center" prop="createTime" width="180">
         <template slot-scope="scope">
           <span>{{ parseTime(scope.row.createTime) }}</span>
@@ -130,9 +122,9 @@ SOFTWARE.
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
           <el-select v-model="form.requestMessageType" placeholder="请选择">
-            <el-option v-for="item in dictData.get('wx_req_type')" :key="item.value" :label="item.label"
-              :value="item.value" :disabled="item.disabled" v-if="item.value !== 'event'">
-            </el-option>
+            <el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
+                       :key="dict.value" :label="dict.label" :value="dict.value"
+                       v-if="requestMessageTypes.includes(dict.value)"/>
           </el-select>
         </el-form-item>
         <el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
@@ -157,6 +149,12 @@ SOFTWARE.
 </template>
 
 <script>
+import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
+import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
+import WxMsg from '@/views/mp/components/wx-msg/main.vue';
+import WxLocation from '@/views/mp/components/wx-location/main.vue';
+import WxMusic from '@/views/mp/components/wx-music/main.vue';
+import WxNews from '@/views/mp/components/wx-news/main.vue';
 import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
 import { getSimpleAccounts } from "@/api/mp/account";
 import { createAutoReply, deleteAutoReply, getAutoReply, getAutoReplyPage, updateAutoReply } from "@/api/mp/autoReply";
@@ -164,12 +162,20 @@ import { createAutoReply, deleteAutoReply, getAutoReply, getAutoReplyPage, updat
 export default {
   name: 'mpAutoReply',
   components: {
+    WxVideoPlayer,
+    WxVoicePlayer,
+    WxMsg,
+    WxLocation,
+    WxMusic,
+    WxNews,
     WxReplySelect
   },
   data() {
     return {
       // tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
       type: '3',
+      // 允许选择的请求消息类型
+      requestMessageTypes: ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'],
       // 遮罩层
       loading: true,
       // 显示搜索条件
@@ -202,8 +208,6 @@ export default {
       },
       hackResetWxReplySelect: false, // 重置 WxReplySelect 组件,解决无法清除的问题
 
-      dictData: new Map(),
-
       // 公众号账号列表
       accounts: []
     }
@@ -218,33 +222,6 @@ export default {
       // 加载数据
       this.getList();
     })
-
-    // TODO 芋艿:字典数据,一起搞
-    this.dictData.set('wx_req_type',[{
-      value: 'text',
-      label: '文本'
-    },{
-      value: 'image',
-      label: '图片'
-    },{
-      value: 'voice',
-      label: '语音'
-    },{
-      value: 'video',
-      label: '视频'
-    },{
-      value: 'shortvideo',
-      label: '小视频'
-    },{
-      value: 'location',
-      label: '地理位置'
-    },{
-      value: 'link',
-      label: '链接消息'
-    },{
-      value: 'event',
-      label: '事件推送'
-    }])
   },
   methods: {
     /** 查询列表 */
@@ -295,7 +272,8 @@ export default {
       this.open = true
       this.title = '新增自动回复';
       this.objData = {
-        type : 'text'
+        type : 'text',
+        accountId: this.queryParams.accountId,
       }
     },
     /** 修改按钮操作 */

+ 36 - 25
yudao-ui-admin/src/views/mp/components/wx-msg/main.vue

@@ -9,13 +9,13 @@
   <div class="msg-main">
     <div class="msg-div" :id="'msg-div' + nowStr">
       <!-- 加载更多 -->
-      <div v-loading="tableLoading"></div>
-      <div v-if="!tableLoading">
+      <div v-loading="loading"></div>
+      <div v-if="!loading">
         <div class="el-table__empty-block" v-if="loadMore" @click="loadingMore"><span class="el-table__empty-text">点击加载更多</span></div>
         <div class="el-table__empty-block" v-if="!loadMore"><span class="el-table__empty-text">没有更多了</span></div>
       </div>
       <!-- 消息列表 -->
-      <div class="execution" v-for="item in tableData" :key='item.id'>
+      <div class="execution" v-for="item in list" :key='item.id'>
         <div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''">
           <div class="avatar-div">
             <img :src="item.sendFrom === 1 ? user.avatar : mp.avatar" class="avue-comment__avatar">
@@ -124,20 +124,20 @@ import { getUser } from "@/api/mp/user";
     },
     props: {
       userId: {
-        type: String,
+        type: Number,
         required: true
       },
     },
     data() {
       return {
         nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
-        sendLoading: false, // 发送消息是否加载中
-        tableLoading: false, // 消息列表是否正在加载中
+        loading: false, // 消息列表是否正在加载中
         loadMore: true, // 是否可以加载更多
-        tableData: [], // 消息列表
-        page: {
+        list: [], // 消息列表
+        queryParams: {
           pageNo: 1, // 当前页数
           pageSize: 14, // 每页显示多少条
+          accountId: undefined,
         },
         user: { // 由于微信不再提供昵称,直接使用“用户”展示
           nickname: '用户',
@@ -148,6 +148,9 @@ import { getUser } from "@/api/mp/user";
           nickname: '公众号',
           avatar: require("@/assets/images/wechat.png"),
         },
+
+        // ========= 消息发送 =========
+        sendLoading: false, // 发送消息是否加载中
         objData: { // 微信发送消息
           type: 'text',
         },
@@ -156,14 +159,19 @@ import { getUser } from "@/api/mp/user";
     created() {
       // 获得用户信息
       getUser(this.userId).then(response => {
-        this.user.nickname = response.data.nickname | this.user.nickname;
-        this.user.avatar = response.data.avatar | this.user.avatar;
+        this.user.nickname = response.data.nickname && response.data.nickname.length > 0 ?
+            response.data.nickname : this.user.nickname;
+        this.user.avatar = response.data.avatar && this.user.avatar.length > 0 ?
+            response.data.avatar : this.user.avatar;
+        this.user.accountId = response.data.accountId;
         // 设置公众号账号编号
+        this.queryParams.accountId = response.data.accountId;
         this.objData.accountId = response.data.accountId;
-      })
 
-      // 加载消息
-      this.refreshChange()
+        // 加载消息
+        console.log(this.queryParams)
+        this.refreshChange()
+      })
     },
     methods:{
       sendMsg(){
@@ -190,7 +198,7 @@ import { getUser } from "@/api/mp/user";
         })).then(response => {
           this.sendLoading = false
           // 添加到消息列表,并滚动
-          this.tableData = [...this.tableData , ...[response.data] ]
+          this.list = [...this.list , ...[response.data] ]
           this.scrollToBottom()
           // 重置 objData 状态
           this.$refs['replySelect'].deleteObj(); // 重置,避免 tab 的数据未清理
@@ -199,15 +207,16 @@ import { getUser } from "@/api/mp/user";
         })
       },
       loadingMore() {
-        this.page.pageNo++
-        this.getPage(this.page)
+        this.queryParams.pageNo++
+        this.getPage(this.queryParams)
       },
       getPage(page, params) {
-        this.tableLoading = true
+        this.loading = true
         getMessagePage(Object.assign({
           pageNo: page.pageNo,
           pageSize: page.pageSize,
-          userId: this.userId
+          userId: this.userId,
+          accountId: page.accountId,
         }, params)).then(response => {
           // 计算当前的滚动高度
           const msgDiv = document.getElementById('msg-div' + this.nowStr);
@@ -218,16 +227,16 @@ import { getUser } from "@/api/mp/user";
 
           // 处理数据
           const data = response.data.list.reverse();
-          this.tableData = [...data, ...this.tableData]
-          this.tableLoading = false
-          if (data.length < this.page.pageSize || data.length === 0){
+          this.list = [...data, ...this.list]
+          this.loading = false
+          if (data.length < this.queryParams.pageSize || data.length === 0){
             this.loadMore = false
           }
-          this.page.pageNo = page.pageNo
-          this.page.pageSize = page.pageSize
+          this.queryParams.pageNo = page.pageNo
+          this.queryParams.pageSize = page.pageSize
 
           // 滚动到原来的位置
-          if(this.page.pageNo === 1) { // 定位到消息底部
+          if(this.queryParams.pageNo === 1) { // 定位到消息底部
             this.scrollToBottom()
           } else if (data.length !== 0) { // 定位滚动条
             this.$nextTick(() => {
@@ -242,7 +251,7 @@ import { getUser } from "@/api/mp/user";
        * 刷新回调
        */
       refreshChange() {
-        this.getPage(this.page)
+        this.getPage(this.queryParams)
       },
       /** 定位到消息底部 */
       scrollToBottom: function () {
@@ -267,6 +276,8 @@ import { getUser } from "@/api/mp/user";
   height: 50vh;
   overflow: auto;
   background-color: #eaeaea;
+  margin-left: 10px;
+  margin-right: 10px;
 }
 .msg-send {
   padding: 10px;

+ 2 - 2
yudao-ui-admin/src/views/mp/menu/index.vue

@@ -66,8 +66,8 @@ SOFTWARE.
           <div class="menu_bottom menu_addicon" v-if="this.menuList.length < 3" @click="addMenu"><i class="el-icon-plus"></i></div>
         </div>
         <div class="save_div">
-            <el-button class="save_btn" type="success" size="small" @click="handleSave">保存并发布菜单</el-button>
-            <el-button class="save_btn" type="danger" size="small" @click="handleDelete">清空菜单</el-button>
+            <el-button class="save_btn" type="success" size="small" @click="handleSave" v-hasPermi="['mp:menu:save']">保存并发布菜单</el-button>
+            <el-button class="save_btn" type="danger" size="small" @click="handleDelete" v-hasPermi="['mp:menu:delete']">清空菜单</el-button>
         </div>
       </div>
       <!--右边配置-->

+ 48 - 64
yudao-ui-admin/src/views/mp/message/index.vue

@@ -8,18 +8,19 @@
           <el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
         </el-select>
       </el-form-item>
-      <!-- 等待处理 -->
-      <el-form-item label="消息类型" prop="msgType">
-        <el-select v-model="queryParams.msgType" placeholder="请选择消息类型" clearable size="small">
-          <el-option label="请选择字典生成" value=""/>
+      <!-- TODO 等待处理 -->
+      <el-form-item label="消息类型" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small">
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
+                     :key="dict.value" :label="dict.label" :value="dict.value"/>
         </el-select>
       </el-form-item>
       <el-form-item label="用户标识" prop="openid">
         <el-input v-model="queryParams.openid" placeholder="请输入用户标识" clearable @keyup.enter.native="handleQuery"/>
       </el-form-item>
-      <el-form-item label="创建时间">
-        <el-date-picker v-model="dateRangeCreateTime" style="width: 240px" value-format="yyyy-MM-dd"
-                        type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"/>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
+                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
@@ -42,12 +43,11 @@
       <el-table-column label="消息类型" align="center" prop="type" width="80"/>
       <el-table-column label="发送方" align="center" prop="sendFrom" width="80">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.sendFrom === 1" type="success">用户</el-tag>
+          <el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
           <el-tag v-else type="info">公众号</el-tag>
         </template>
       </el-table-column>
       <el-table-column label="用户标识" align="center" prop="openid" width="300" />
-      <!-- TODO 芋艿:发送/接收 -->
       <el-table-column label="内容" prop="content">
         <template slot-scope="scope">
           <!-- 【事件】区域 -->
@@ -118,10 +118,9 @@
       </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
-          <!-- TODO 芋艿:增加消息按钮 -->
-<!--          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"-->
-<!--                     v-hasPermi="['mp:message:update']">修改-->
-<!--          </el-button>-->
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleSend(scope.row)"
+                     v-hasPermi="['mp:message:send']">消息
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -129,24 +128,26 @@
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getList"/>
 
-    <el-dialog title="用户消息" :visible.sync="open" width="40%">
-      <wx-msg user-id="3" v-if="true" />
+    <!-- 发送消息的弹窗 -->
+    <el-dialog title="粉丝消息列表" :visible.sync="open" width="50%">
+      <wx-msg :user-id="userId" v-if="open" />
     </el-dialog>
 
   </div>
 </template>
 
 <script>
-import { getMessagePage } from "@/api/mp/message";
 import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
 import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
 import WxMsg from '@/views/mp/components/wx-msg/main.vue';
 import WxLocation from '@/views/mp/components/wx-location/main.vue';
 import WxMusic from '@/views/mp/components/wx-music/main.vue';
 import WxNews from '@/views/mp/components/wx-news/main.vue';
+import { getMessagePage } from "@/api/mp/message";
+import { getSimpleAccounts } from "@/api/mp/account";
 
 export default {
-  name: "WxFansMsg",
+  name: "MpMessage",
   components: {
     WxVideoPlayer,
     WxVoicePlayer,
@@ -167,76 +168,52 @@ export default {
       total: 0,
       // 粉丝消息列表
       list: [],
-      // 弹出层标题
-      title: "",
       // 是否显示弹出层
-      open: true,
+      open: false,
       // 查询参数
-      dateRangeCreateTime: [],
       queryParams: {
         pageNo: 1,
         pageSize: 10,
         openid: null,
-        nickname: null,
-        headimgUrl: null,
-        wxAccountId: null,
-        msgType: null,
-        content: null,
-        resContent: null,
-        isRes: null,
-        mediaId: null,
-        picUrl: null,
-        picPath: null,
+        accountId: null,
+        type: null,
+        createTime: []
       },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {},
+      // 操作的用户编号
+      userId: 0,
 
       // 公众号账号列表
       accounts: []
     };
   },
   created() {
-    this.getList();
+    getSimpleAccounts().then(response => {
+      this.accounts = response.data;
+      // 默认选中第一个
+      if (this.accounts.length > 0) {
+        this.queryParams.accountId = this.accounts[0].id;
+      }
+      // 加载数据
+      this.getList();
+    })
   },
   methods: {
     /** 查询列表 */
     getList() {
+      // 如果没有选中公众号账号,则进行提示。
+      if (!this.queryParams.accountId) {
+        this.$message.error('未选中公众号,无法查询消息')
+        return false
+      }
+
       this.loading = true;
-      // 处理查询参数
-      let params = {...this.queryParams};
-      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
       // 执行查询
-      getMessagePage(params).then(response => {
+      getMessagePage(this.queryParams).then(response => {
         this.list = response.data.list;
         this.total = response.data.total;
         this.loading = false;
       });
     },
-    /** 取消按钮 */
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    /** 表单重置 */
-    reset() {
-      this.form = {
-        id: undefined,
-        openid: undefined,
-        nickname: undefined,
-        headimgUrl: undefined,
-        wxAccountId: undefined,
-        msgType: undefined,
-        content: undefined,
-        resContent: undefined,
-        isRes: undefined,
-        mediaId: undefined,
-        picUrl: undefined,
-        picPath: undefined,
-      };
-      this.resetForm("form");
-    },
     /** 搜索按钮操作 */
     handleQuery() {
       this.queryParams.pageNo = 1;
@@ -244,10 +221,17 @@ export default {
     },
     /** 重置按钮操作 */
     resetQuery() {
-      this.dateRangeCreateTime = [];
       this.resetForm("queryForm");
+      // 默认选中第一个
+      if (this.accounts.length > 0) {
+        this.queryParams.accountId = this.accounts[0].id;
+      }
       this.handleQuery();
     },
+    handleSend(row) {
+      this.userId = row.userId;
+      this.open = true;
+    },
   }
 };
 </script>

+ 1 - 1
yudao-ui-admin/src/views/mp/statistics/index.vue

@@ -77,7 +77,7 @@ require('echarts/lib/component/legend')
 import { getInterfaceSummary, getUserSummary, getUserCumulate, getUpstreamMessage} from '@/api/mp/statistics'
 import { datePickerOptions } from "@/utils/constants";
 import {addTime, beginOfDay, betweenDay, endOfDay, formatDate} from "@/utils/dateUtils";
-import {getSimpleAccounts} from "@/api/mp/account";
+import { getSimpleAccounts } from "@/api/mp/account";
 
 export default {
   name: 'mpStatistics',