Commit 68ef11ee8723013380a79c3c2c41e4007caddd1a

Authored by YunaiV
1 parent 6fc0b3fc

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

Showing 52 changed files with 378 additions and 494 deletions
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 {
15 15 ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}");
16 16  
17 17 // ========== 公众号统计 1006001000============
18   - ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取用户增减数据失败,原因:{}");
19   - ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1006001001, "获得用户累计数据失败,原因:{}");
  18 + ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取粉丝增减数据失败,原因:{}");
  19 + ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1006001001, "获得粉丝累计数据失败,原因:{}");
20 20 ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1006001002, "获得消息发送概况数据失败,原因:{}");
21 21 ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1006001003, "获得接口分析数据失败,原因:{}");
22 22  
... ... @@ -28,8 +28,8 @@ public interface ErrorCodeConstants {
28 28 ErrorCode TAG_GET_FAIL = new ErrorCode(1006002001, "获得标签失败,原因:{}");
29 29  
30 30 // ========== 公众号粉丝 1006003000============
31   - ErrorCode USER_NOT_EXISTS = new ErrorCode(1006003000, "用户不存在");
32   - ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新用户标签失败,原因:{}");
  31 + ErrorCode USER_NOT_EXISTS = new ErrorCode(1006003000, "粉丝不存在");
  32 + ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新粉丝标签失败,原因:{}");
33 33  
34 34 // ========== 公众号素材 1006004000============
35 35 ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1006004000, "素材不存在");
... ...
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;
12 12 @AllArgsConstructor
13 13 public enum MpMessageSendFromEnum {
14 14  
15   - USER_TO_MP(1, "用户发送给公众号"),
16   - MP_TO_USER(2, "公众号发给用户"),
  15 + USER_TO_MP(1, "粉丝发送给公众号"),
  16 + MP_TO_USER(2, "公众号发给粉丝"),
17 17 ;
18 18  
19 19 /**
... ...
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;
14 14 import static cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
15 15  
16 16 /**
17   - * 微信菜单 Base VO,提供给添加、修改、详细的子 VO 使用
  17 + * 公众号菜单 Base VO,提供给添加、修改、详细的子 VO 使用
18 18 * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
19 19 */
20 20 @Data
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java
1 1 package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
2 2  
3   -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
4 3 import io.swagger.annotations.ApiModel;
5 4 import io.swagger.annotations.ApiModelProperty;
6 5 import lombok.Data;
7 6 import lombok.EqualsAndHashCode;
8 7 import lombok.ToString;
9 8  
10   -import javax.validation.constraints.NotNull;
11 9 import java.util.Date;
12 10  
13   -// TODO swagger 文档
14   -@ApiModel("管理后台 - 微信菜单 Response VO")
  11 +@ApiModel("管理后台 - 公众号菜单 Response VO")
15 12 @Data
16 13 @EqualsAndHashCode(callSuper = true)
17 14 @ToString(callSuper = true)
18 15 public class MpMenuRespVO extends MpMenuBaseVO {
19 16  
20   - @ApiModelProperty(value = "主键", required = true)
  17 + @ApiModelProperty(value = "主键", required = true, example = "1024")
21 18 private Long id;
22 19  
23 20 @ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
24   - @NotNull(message = "公众号账号的编号不能为空")
25 21 private Long accountId;
26 22  
27   - /**
28   - * 微信公众号 appid
29   - *
30   - * 冗余 {@link MpAccountDO#getAppId()}
31   - */
  23 + @ApiModelProperty(value = "公众号 appId", required = true, example = "wx1234567890ox")
32 24 private String appId;
33 25  
34 26 @ApiModelProperty(value = "创建时间", required = true)
... ...
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;
9 9 import javax.validation.constraints.NotNull;
10 10 import java.util.List;
11 11  
12   -// TODO 芋艿:swagger 文档
13 12 @ApiModel("管理后台 - 公众号菜单保存 Request VO")
14 13 @Data
15 14 public class MpMenuSaveReqVO {
... ... @@ -22,9 +21,13 @@ public class MpMenuSaveReqVO {
22 21 @Valid
23 22 private List<Menu> menus;
24 23  
  24 + @ApiModel("管理后台 - 公众号菜单保存时的每个菜单")
25 25 @Data
26 26 public static class Menu extends MpMenuBaseVO {
27 27  
  28 + /**
  29 + * 子菜单数组
  30 + */
28 31 private List<Menu> children;
29 32  
30 33 }
... ...
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 {
40 40  
41 41 @GetMapping("/get")
42 42 @ApiOperation("获得公众号自动回复")
43   - @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024")
  43 + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
44 44 @PreAuthorize("@ss.hasPermission('mp:auto-reply:query')")
45 45 public CommonResult<MpAutoReplyRespVO> getAutoReply(@RequestParam("id") Long id) {
46 46 MpAutoReplyDO autoReply = mpAutoReplyService.getAutoReply(id);
... ...
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 {
29 29 private MpMessageService mpMessageService;
30 30  
31 31 @GetMapping("/page")
32   - @ApiOperation("获得粉丝消息分页")
  32 + @ApiOperation("获得公众号消息分页")
33 33 @PreAuthorize("@ss.hasPermission('mp:message:query')")
34   - public CommonResult<PageResult<MpMessageRespVO>> getWxFansMsgPage(@Valid MpMessagePageReqVO pageVO) {
35   - PageResult<MpMessageDO> pageResult = mpMessageService.getWxFansMsgPage(pageVO);
  34 + public CommonResult<PageResult<MpMessageRespVO>> getMessagePage(@Valid MpMessagePageReqVO pageVO) {
  35 + PageResult<MpMessageDO> pageResult = mpMessageService.getMessagePage(pageVO);
36 36 return success(MpMessageConvert.INSTANCE.convertPage(pageResult));
37 37 }
38 38  
... ...
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;
14 14 @ToString(callSuper = true)
15 15 public class MpAutoReplyCreateReqVO extends MpAutoReplyBaseVO {
16 16  
17   - @ApiModelProperty(value = "微信公众号 ID", required = true, example = "1024")
18   - @NotNull(message = "微信公众号 ID不能为空")
  17 + @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
  18 + @NotNull(message = "公众号账号的编号不能为空")
19 19 private Long accountId;
20 20  
21 21 }
... ...
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 {
17 17 @ApiModelProperty(value = "主键", required = true, example = "1024")
18 18 private Long id;
19 19  
20   - @ApiModelProperty(value = "微信公众号 ID", required = true, example = "1024")
  20 + @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
21 21 private Long accountId;
22   - @ApiModelProperty(value = "微信公众号 appid", required = true, example = "wx1234567890")
  22 + @ApiModelProperty(value = "公众号 appId", required = true, example = "wx1234567890")
23 23 private String appId;
24 24  
25 25 @ApiModelProperty(value = "创建时间", required = true)
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageBaseVO.java deleted
1   -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
2   -
3   -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
4   -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
5   -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
6   -import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
7   -import com.baomidou.mybatisplus.annotation.TableField;
8   -import lombok.Data;
9   -import me.chanjar.weixin.common.api.WxConsts;
10   -
11   -import java.util.List;
12   -
13   -// TODO 芋艿:VO 的注解
14   -/**
15   - * 粉丝消息 Base VO,提供给添加、修改、详细的子 VO 使用
16   - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
17   - */
18   -@Data
19   -public class MpMessageBaseVO {
20   -
21   - /**
22   - * 微信公众号消息 id
23   - */
24   - private Long msgId;
25   - /**
26   - * 微信公众号 ID
27   - *
28   - * 关联 {@link MpAccountDO#getId()}
29   - */
30   - private Long accountId;
31   - /**
32   - * 微信公众号 appid
33   - *
34   - * 冗余 {@link MpAccountDO#getAppId()}
35   - */
36   - private String appId;
37   - /**
38   - * 微信用户编号
39   - *
40   - * 关联 {@link MpUserDO#getId()}
41   - */
42   - private Long userId;
43   - /**
44   - * 用户标识
45   - *
46   - * 冗余 {@link MpUserDO#getOpenid()}
47   - */
48   - private String openid;
49   -
50   - /**
51   - * 消息类型
52   - *
53   - * 枚举 {@link WxConsts.XmlMsgType}
54   - */
55   - private String type;
56   - /**
57   - * 消息来源
58   - *
59   - * 枚举 {@link MpMessageSendFromEnum}
60   - */
61   - private Integer sendFrom;
62   -
63   - // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
64   -
65   - /**
66   - * 消息内容
67   - *
68   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
69   - */
70   - private String content;
71   -
72   - /**
73   - * 通过素材管理中的接口上传多媒体文件,得到的 id
74   - *
75   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
76   - */
77   - private String mediaId;
78   - /**
79   - * 媒体文件的 URL
80   - */
81   - private String mediaUrl;
82   - /**
83   - * 语音识别后文本
84   - *
85   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE
86   - */
87   - private String recognition;
88   - /**
89   - * 语音格式,如 amr,speex 等
90   - *
91   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE
92   - */
93   - private String format;
94   - /**
95   - * 标题
96   - *
97   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC、LINK
98   - */
99   - private String title;
100   - /**
101   - * 描述
102   - *
103   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC
104   - */
105   - private String description;
106   -
107   - /**
108   - * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
109   - *
110   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
111   - */
112   - private String thumbMediaId;
113   - /**
114   - * 缩略图的媒体 URL
115   - *
116   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
117   - */
118   - private String thumbMediaUrl;
119   -
120   - /**
121   - * 点击图文消息跳转链接
122   - *
123   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LINK
124   - */
125   - private String url;
126   -
127   - /**
128   - * 地理位置维度
129   - *
130   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
131   - */
132   - private Double locationX;
133   - /**
134   - * 地理位置经度
135   - *
136   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
137   - */
138   - private Double locationY;
139   - /**
140   - * 地图缩放大小
141   - *
142   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
143   - */
144   - private Double scale;
145   - /**
146   - * 详细地址
147   - *
148   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION
149   - *
150   - * 例如说杨浦区黄兴路 221-4 号临
151   - */
152   - private String label;
153   -
154   - /**
155   - * 图文消息数组
156   - *
157   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
158   - */
159   - @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
160   - private List<MpMessageDO.Article> articles;
161   -
162   - /**
163   - * 音乐链接
164   - *
165   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
166   - */
167   - private String musicUrl;
168   - /**
169   - * 高质量音乐链接
170   - *
171   - * WIFI 环境优先使用该链接播放音乐
172   - *
173   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
174   - */
175   - private String hqMusicUrl;
176   -
177   - // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
178   -
179   - /**
180   - * 事件类型
181   - *
182   - * 枚举 {@link WxConsts.EventType}
183   - */
184   - private String event;
185   - /**
186   - * 事件 Key
187   - *
188   - * 1. {@link WxConsts.EventType} 的 SCAN:qrscene_ 为前缀,后面为二维码的参数值
189   - * 2. {@link WxConsts.EventType} 的 CLICK:与自定义菜单接口中 KEY 值对应
190   - */
191   - private String eventKey;
192   -
193   -}
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.*;
4 4  
5 5 import io.swagger.annotations.*;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageParam;
  7 +import org.springframework.format.annotation.DateTimeFormat;
7 8  
8   -@ApiModel("管理后台 - 粉丝消息分页 Request VO")
  9 +import javax.validation.constraints.NotNull;
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@ApiModel("管理后台 - 公众号消息分页 Request VO")
9 15 @Data
10 16 @EqualsAndHashCode(callSuper = true)
11 17 @ToString(callSuper = true)
12 18 public class MpMessagePageReqVO extends PageParam {
13 19  
14   - @ApiModelProperty(value = "用户标识")
15   - private String openId;
16   -
17   - @ApiModelProperty(value = "昵称")
18   - private String nickname;
19   -
20   - @ApiModelProperty(value = "微信账号ID")
  20 + @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
  21 + @NotNull(message = "公众号账号的编号不能为空")
21 22 private Long accountId;
22 23  
23   - @ApiModelProperty(value = "消息类型")
  24 + @ApiModelProperty(value = "消息类型", example = "text", notes = "参见 WxConsts.XmlMsgType 枚举")
24 25 private String type;
25 26  
  27 + @ApiModelProperty(value = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
  28 + private String openid;
  29 +
  30 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  31 + @ApiModelProperty(value = "创建时间")
  32 + private LocalDateTime[] createTime;
  33 +
26 34 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java
1 1 package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
2 2  
  3 +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
  4 +import com.baomidou.mybatisplus.annotation.TableField;
3 5 import io.swagger.annotations.ApiModel;
4 6 import io.swagger.annotations.ApiModelProperty;
5 7 import lombok.Data;
6   -import lombok.EqualsAndHashCode;
7   -import lombok.ToString;
  8 +import me.chanjar.weixin.common.api.WxConsts;
8 9  
9 10 import java.util.Date;
  11 +import java.util.List;
10 12  
11   -@ApiModel("管理后台 - 粉丝消息 Response VO")
  13 +@ApiModel("管理后台 - 公众号消息 Response VO")
12 14 @Data
13   -@EqualsAndHashCode(callSuper = true)
14   -@ToString(callSuper = true)
15   -public class MpMessageRespVO extends MpMessageBaseVO {
  15 +public class MpMessageRespVO {
16 16  
17   - @ApiModelProperty(value = "主键", required = true)
  17 + @ApiModelProperty(value = "主键", required = true, example = "1024")
18 18 private Integer id;
19 19  
  20 + @ApiModelProperty(value = "微信公众号消息 id", required = true, example = "23953173569869169")
  21 + private Long msgId;
  22 +
  23 + @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1")
  24 + private Long accountId;
  25 + @ApiModelProperty(value = "公众号账号的 appid", required = true, example = "wx1234567890")
  26 + private String appId;
  27 +
  28 + @ApiModelProperty(value = "公众号粉丝编号", required = true, example = "2048")
  29 + private Long userId;
  30 + @ApiModelProperty(value = "公众号粉丝标志", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
  31 + private String openid;
  32 +
  33 + @ApiModelProperty(value = "消息类型", required = true, example = "text", notes = "参见 WxConsts.XmlMsgType 枚举")
  34 + private String type;
  35 + @ApiModelProperty(value = "消息来源", required = true, example = "1", notes = "参见 MpMessageSendFromEnum 枚举")
  36 + private Integer sendFrom;
  37 +
  38 + // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
  39 +
  40 + @ApiModelProperty(value = "消息内容", example = "你好呀", notes = "消息类型为 text 时,才有值")
  41 + private String content;
  42 +
  43 + @ApiModelProperty(value = "媒体素材的编号", example = "1234567890", notes = "消息类型为 image、voice、video 时,才有值")
  44 + private String mediaId;
  45 + @ApiModelProperty(value = "媒体文件的 URL", example = "https://www.iocoder.cn/xxx.png",
  46 + notes = "消息类型为 image、voice、video 时,才有值")
  47 + private String mediaUrl;
  48 +
  49 + @ApiModelProperty(value = "语音识别后文本", example = "语音识别后文本", notes = "消息类型为 voice 时,才有值")
  50 + private String recognition;
  51 + @ApiModelProperty(value = "语音格式", example = "amr", notes = "消息类型为 voice 时,才有值")
  52 + private String format;
  53 +
  54 + @ApiModelProperty(value = "标题", example = "我是标题", notes = "消息类型为 video、music、link 时,才有值")
  55 + private String title;
  56 +
  57 + @ApiModelProperty(value = "描述", example = "我是描述", notes = "消息类型为 video、music 时,才有值")
  58 + private String description;
  59 +
  60 + @ApiModelProperty(value = "缩略图的媒体 id", example = "1234567890", notes = "消息类型为 video、music 时,才有值")
  61 + private String thumbMediaId;
  62 + @ApiModelProperty(value = "缩略图的媒体 URL", example = "https://www.iocoder.cn/xxx.png",
  63 + notes = "消息类型为 video、music 时,才有值")
  64 + private String thumbMediaUrl;
  65 +
  66 + @ApiModelProperty(value = "点击图文消息跳转链接", example = "https://www.iocoder.cn", notes = "消息类型为 link 时,才有值")
  67 + private String url;
  68 +
  69 + @ApiModelProperty(value = "地理位置维度", example = "23.137466", notes = "消息类型为 location 时,才有值")
  70 + private Double locationX;
  71 +
  72 + @ApiModelProperty(value = "地理位置经度", example = "113.352425", notes = "消息类型为 location 时,才有值")
  73 + private Double locationY;
  74 +
  75 + @ApiModelProperty(value = "地图缩放大小", example = "13", notes = "消息类型为 location 时,才有值")
  76 + private Double scale;
  77 +
  78 + @ApiModelProperty(value = "详细地址", example = "杨浦区黄兴路 221-4 号临", notes = "消息类型为 location 时,才有值")
  79 + private String label;
  80 +
  81 + /**
  82 + * 图文消息数组
  83 + *
  84 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
  85 + */
  86 + @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
  87 + private List<MpMessageDO.Article> articles;
  88 +
  89 + @ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/xxx.mp3", notes = "消息类型为 music 时,才有值")
  90 + private String musicUrl;
  91 + @ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3", notes = "消息类型为 music 时,才有值")
  92 + private String hqMusicUrl;
  93 +
  94 + // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
  95 +
  96 + @ApiModelProperty(value = "事件类型", example = "subscribe", notes = "参见 WxConsts.EventType 枚举")
  97 + private String event;
  98 + @ApiModelProperty(value = "事件 Key", example = "qrscene_123456", notes = "参见 WxConsts.EventType 枚举")
  99 + private String eventKey;
  100 +
20 101 @ApiModelProperty(value = "创建时间", required = true)
21 102 private Date createTime;
22 103  
... ...
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;
4 4 import io.swagger.annotations.ApiModel;
5 5 import io.swagger.annotations.ApiModelProperty;
6 6 import lombok.Data;
7   -import org.springframework.web.bind.annotation.RequestParam;
8 7  
9 8 import javax.validation.constraints.NotEmpty;
10 9  
... ... @@ -26,8 +25,8 @@ public class MpOpenHandleMessageReqVO {
26 25 @NotEmpty(message = "随机数不能为空")
27 26 private String nonce;
28 27  
29   - @ApiModelProperty(value = "用户 openid", required = true, example = "oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY")
30   - @NotEmpty(message = "用户 openid 不能为空")
  28 + @ApiModelProperty(value = "粉丝 openid", required = true, example = "oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY")
  29 + @NotEmpty(message = "粉丝 openid 不能为空")
31 30 private String openid;
32 31  
33 32 @ApiModelProperty(value = "消息加密类型", example = "aes")
... ...
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 {
31 31 private MpStatisticsService mpStatisticsService;
32 32  
33 33 @GetMapping("/user-summary")
34   - @ApiOperation("获得用户增减数据")
  34 + @ApiOperation("获得粉丝增减数据")
35 35 @PreAuthorize("@ss.hasPermission('mp:statistics:query')")
36 36 public CommonResult<List<MpStatisticsUserSummaryRespVO>> getUserSummary(MpStatisticsGetReqVO getReqVO) {
37 37 List<WxDataCubeUserSummary> list = mpStatisticsService.getUserSummary(
... ... @@ -40,7 +40,7 @@ public class MpStatisticsController {
40 40 }
41 41  
42 42 @GetMapping("/user-cumulate")
43   - @ApiOperation("获得用户累计数据")
  43 + @ApiOperation("获得粉丝累计数据")
44 44 @PreAuthorize("@ss.hasPermission('mp:statistics:query')")
45 45 public CommonResult<List<MpStatisticsUserCumulateRespVO>> getUserCumulate(MpStatisticsGetReqVO getReqVO) {
46 46 List<WxDataCubeUserCumulate> list = mpStatisticsService.getUserCumulate(
... ...
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 {
13 13 @ApiModelProperty(value = "日期", required = true)
14 14 private Date refDate;
15 15  
16   - @ApiModelProperty(value = "通过服务器配置地址获得消息后,被动回复用户消息的次数", required = true, example = "10")
  16 + @ApiModelProperty(value = "通过服务器配置地址获得消息后,被动回复粉丝消息的次数", required = true, example = "10")
17 17 private Integer callbackCount;
18 18  
19 19 @ApiModelProperty(value = "上述动作的失败次数", required = true, example = "20")
... ...
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;
6 6  
7 7 import java.util.Date;
8 8  
9   -@ApiModel("管理后台 - 某一天的用户增减数据 Response VO")
  9 +@ApiModel("管理后台 - 某一天的粉丝增减数据 Response VO")
10 10 @Data
11 11 public class MpStatisticsUpstreamMessageRespVO {
12 12  
13 13 @ApiModelProperty(value = "日期", required = true)
14 14 private Date refDate;
15 15  
16   - @ApiModelProperty(value = "上行发送了(向公众号发送了)消息的用户数", required = true, example = "10")
  16 + @ApiModelProperty(value = "上行发送了(向公众号发送了)消息的粉丝数", required = true, example = "10")
17 17 private Integer messageUser;
18 18  
19 19 @ApiModelProperty(value = "上行发送了消息的消息总数", required = true, example = "20")
... ...
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 {
13 13 @ApiModelProperty(value = "日期", required = true)
14 14 private Date refDate;
15 15  
16   - @ApiModelProperty(value = "累计用户量", required = true, example = "10")
  16 + @ApiModelProperty(value = "累计粉丝量", required = true, example = "10")
17 17 private Integer cumulateUser;
18 18  
19 19 }
... ...
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;
6 6  
7 7 import java.util.Date;
8 8  
9   -@ApiModel("管理后台 - 某一天的用户增减数据 Response VO")
  9 +@ApiModel("管理后台 - 某一天的粉丝增减数据 Response VO")
10 10 @Data
11 11 public class MpStatisticsUserSummaryRespVO {
12 12  
13 13 @ApiModelProperty(value = "日期", required = true)
14 14 private Date refDate;
15 15  
16   - @ApiModelProperty(value = "用户来源", required = true, example = "0")
  16 + @ApiModelProperty(value = "粉丝来源", required = true, example = "0")
17 17 private Integer userSource;
18 18  
19   - @ApiModelProperty(value = "新关注的用户数量", required = true, example = "10")
  19 + @ApiModelProperty(value = "新关注的粉丝数量", required = true, example = "10")
20 20 private Integer newUser;
21 21  
22   - @ApiModelProperty(value = "取消关注的用户数量", required = true, example = "20")
  22 + @ApiModelProperty(value = "取消关注的粉丝数量", required = true, example = "20")
23 23 private Integer cancelUser;
24 24  
25 25 }
... ...
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 {
19 19 @NotNull(message = "公众号账号的编号不能为空")
20 20 private Long accountId;
21 21  
22   - @ApiModelProperty(value = "公众号用户标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", notes = "模糊匹配")
  22 + @ApiModelProperty(value = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", notes = "模糊匹配")
23 23 private String openid;
24 24  
25   - @ApiModelProperty(value = "公众号用户昵称", example = "芋艿", notes = "模糊匹配")
  25 + @ApiModelProperty(value = "公众号粉丝昵称", example = "芋艿", notes = "模糊匹配")
26 26 private String nickname;
27 27  
28 28 }
... ...
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 {
15 15 @ApiModelProperty(value = "编号", required = true, example = "1024")
16 16 private Long id;
17 17  
18   - @ApiModelProperty(value = "公众号用户标识", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
  18 + @ApiModelProperty(value = "公众号粉丝标识", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
19 19 private String openid;
20 20  
21 21 @ApiModelProperty(value = "关注状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")
... ...
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 {
32 32 @TableId
33 33 private Long id;
34 34 /**
35   - * 微信公众号 ID
  35 + * 公众号账号的编号
36 36 *
37 37 * 关联 {@link MpAccountDO#getId()}
38 38 */
39 39 private Long accountId;
40 40 /**
41   - * 微信公众号 appid
  41 + * 公众号 appId
42 42 *
43 43 * 冗余 {@link MpAccountDO#getAppId()}
44 44 */
... ... @@ -71,8 +71,8 @@ public class MpMaterialDO extends BaseDO {
71 71 *
72 72 * 永久素材:非空
73 73 * 临时素材:可能为空。
74   - * 1. 为空的情况:用户主动发送的图片、语音等
75   - * 2. 非空的情况:主动发送给用户的图片、语音等
  74 + * 1. 为空的情况:粉丝主动发送的图片、语音等
  75 + * 2. 非空的情况:主动发送给粉丝的图片、语音等
76 76 */
77 77 private String name;
78 78  
... ...
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;
16 16 import java.util.List;
17 17  
18 18 /**
19   - * 微信菜单 DO
  19 + * 公众号菜单 DO
20 20 *
21 21 * @author 芋道源码
22 22 */
... ... @@ -38,13 +38,13 @@ public class MpMenuDO extends BaseDO {
38 38 @TableId
39 39 private Long id;
40 40 /**
41   - * 微信公众号 ID
  41 + * 公众号账号的编号
42 42 *
43 43 * 关联 {@link MpAccountDO#getId()}
44 44 */
45 45 private Long accountId;
46 46 /**
47   - * 微信公众号 appid
  47 + * 公众号 appId
48 48 *
49 49 * 冗余 {@link MpAccountDO#getAppId()}
50 50 */
... ... @@ -77,7 +77,7 @@ public class MpMenuDO extends BaseDO {
77 77 /**
78 78 * 网页链接
79 79 *
80   - * 用户点击菜单可打开链接,不超过 1024 字节
  80 + * 粉丝点击菜单可打开链接,不超过 1024 字节
81 81 *
82 82 * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM
83 83 */
... ... @@ -146,13 +146,13 @@ public class MpMenuDO extends BaseDO {
146 146 private String replyDescription;
147 147  
148 148 /**
149   - * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
  149 + * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
150 150 *
151 151 * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
152 152 */
153 153 private String replyThumbMediaId;
154 154 /**
155   - * 缩略图的媒体 URL
  155 + * 回复的缩略图的媒体 URL
156 156 *
157 157 * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
158 158 */
... ...
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 {
40 40 @TableId
41 41 private Long id;
42 42 /**
43   - * 微信公众号 ID
  43 + * 公众号账号的编号
44 44 *
45 45 * 关联 {@link MpAccountDO#getId()}
46 46 */
47 47 private Long accountId;
48 48 /**
49   - * 微信公众号 appid
  49 + * 公众号 appId
50 50 *
51 51 * 冗余 {@link MpAccountDO#getAppId()}
52 52 */
... ... @@ -126,6 +126,19 @@ public class MpAutoReplyDO extends BaseDO {
126 126 private String responseDescription;
127 127  
128 128 /**
  129 + * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
  130 + *
  131 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
  132 + */
  133 + private String responseThumbMediaId;
  134 + /**
  135 + * 回复的缩略图的媒体 URL
  136 + *
  137 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
  138 + */
  139 + private String responseThumbMediaUrl;
  140 +
  141 + /**
129 142 * 回复的图文消息
130 143 *
131 144 * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
... ... @@ -133,4 +146,19 @@ public class MpAutoReplyDO extends BaseDO {
133 146 @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
134 147 private List<MpMessageDO.Article> responseArticles;
135 148  
  149 + /**
  150 + * 回复的音乐链接
  151 + *
  152 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
  153 + */
  154 + private String responseMusicUrl;
  155 + /**
  156 + * 回复的高质量音乐链接
  157 + *
  158 + * WIFI 环境优先使用该链接播放音乐
  159 + *
  160 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
  161 + */
  162 + private String responseHqMusicUrl;
  163 +
136 164 }
... ...
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 {
40 40 */
41 41 private Long msgId;
42 42 /**
43   - * 微信公众号 ID
  43 + * 公众号账号的 ID
44 44 *
45 45 * 关联 {@link MpAccountDO#getId()}
46 46 */
47 47 private Long accountId;
48 48 /**
49   - * 微信公众号 appid
  49 + * 公众号 appid
50 50 *
51 51 * 冗余 {@link MpAccountDO#getAppId()}
52 52 */
53 53 private String appId;
54 54 /**
55   - * 微信用户编号
  55 + * 公众号粉丝的编号
56 56 *
57 57 * 关联 {@link MpUserDO#getId()}
58 58 */
59 59 private Long userId;
60 60 /**
61   - * 用户标识
  61 + * 公众号粉丝标志
62 62 *
63 63 * 冗余 {@link MpUserDO#getOpenid()}
64 64 */
... ... @@ -87,7 +87,7 @@ public class MpMessageDO extends BaseDO {
87 87 private String content;
88 88  
89 89 /**
90   - * 通过素材管理中的接口上传多媒体文件,得到的 id
  90 + * 媒体文件的编号
91 91 *
92 92 * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
93 93 */
... ...
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 {
43 43 private Integer count;
44 44  
45 45 /**
46   - * 微信公众号 ID
  46 + * 公众号账号的编号
47 47 *
48 48 * 关联 {@link MpAccountDO#getId()}
49 49 */
50 50 private Long accountId;
51 51 /**
52   - * 微信公众号 appid
  52 + * 公众号 appId
53 53 *
54 54 * 冗余 {@link MpAccountDO#getAppId()}
55 55 */
... ...
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 {
35 35 @TableId
36 36 private Long id;
37 37 /**
38   - * 用户标识
  38 + * 粉丝标识
39 39 */
40 40 private String openid;
41 41 /**
... ... @@ -95,13 +95,13 @@ public class MpUserDO extends BaseDO {
95 95 private List<Long> tagIds;
96 96  
97 97 /**
98   - * 微信公众号 ID
  98 + * 公众号账号的编号
99 99 *
100 100 * 关联 {@link MpAccountDO#getId()}
101 101 */
102 102 private Long accountId;
103 103 /**
104   - * 微信公众号 appid
  104 + * 公众号 appId
105 105 *
106 106 * 冗余 {@link MpAccountDO#getAppId()}
107 107 */
... ...
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&lt;MpMessageDO&gt; {
13 13 default PageResult<MpMessageDO> selectPage(MpMessagePageReqVO reqVO) {
14 14 return selectPage(reqVO, new LambdaQueryWrapperX<MpMessageDO>()
15 15 .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
16   - .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenId())
17   -// .likeIfPresent(MpMessageDO::getNickname, reqVO.getNickname())
18 16 .eqIfPresent(MpMessageDO::getType, reqVO.getType())
  17 + .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid())
  18 + .betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime())
19 19 .orderByDesc(MpMessageDO::getId));
20 20 }
21 21  
... ...
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 {
103 103  
104 104 private WxMpService buildMpService(MpAccountDO account) {
105 105 // 第一步,创建 WxMpRedisConfigImpl 对象
106   - // TODO 芋艿:需要确认下,redis key 的存储结构
107 106 WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(
108 107 redisTemplateWxRedisOps, mpProperties.getConfigStorage().getKeyPrefix());
109 108 configStorage.setAppId(account.getAppId());
... ...
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;
18 18 /**
19 19 * 自定义菜单的事件处理器
20 20 *
21   - * 逻辑:用户点击菜单时,触发对应的回复
  21 + * 逻辑:粉丝点击菜单时,触发对应的回复
22 22 *
23 23 * @author 芋道源码
24 24 */
... ...
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 {
20 20 @Override
21 21 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
22 22 WxMpService wxMpService, WxSessionManager sessionManager) {
23   - // TODO 对会话做处理
24   - return null;
  23 + throw new UnsupportedOperationException("未实现该处理,请自行重写");
25 24 }
26 25  
27 26 }
... ...
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 {
18 18 @Override
19 19 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
20 20 WxMpService wxMpService, WxSessionManager sessionManager) {
21   - return null;
  21 + throw new UnsupportedOperationException("未实现该处理,请自行重写");
22 22 }
23 23  
24 24 }
... ...
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;
17 17 public class ScanHandler implements WxMpMessageHandler {
18 18  
19 19 @Override
20   - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> map,
  20 + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> context,
21 21 WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
22   - // 扫码事件处理
23   - return null;
  22 + throw new UnsupportedOperationException("未实现该处理,请自行重写");
24 23 }
  24 +
25 25 }
... ...
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;
16 16 public class StoreCheckNotifyHandler implements WxMpMessageHandler {
17 17  
18 18 @Override
19   - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
20   - Map<String, Object> context, WxMpService wxMpService,
21   - WxSessionManager sessionManager) {
22   - // TODO 处理门店审核事件
23   - return null;
  19 + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
  20 + WxMpService wxMpService, WxSessionManager sessionManager) {
  21 + throw new UnsupportedOperationException("未实现该处理,请自行重写");
24 22 }
25 23  
26 24 }
... ...
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;
21 21 *
22 22 * 触发操作:打开微信公众号 -> 点击 + 号 -> 选择「语音」
23 23 *
24   - * 逻辑:用户上传地理位置时,也可以触发自动回复
  24 + * 逻辑:粉丝上传地理位置时,也可以触发自动回复
25 25 *
26 26 * @author 芋道源码
27 27 */
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java
1 1 package cn.iocoder.yudao.module.mp.service.handler.user;
2 2  
3   -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
4 3 import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
5   -import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
6 4 import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService;
7 5 import cn.iocoder.yudao.module.mp.service.user.MpUserService;
8 6 import lombok.extern.slf4j.Slf4j;
... ... @@ -13,7 +11,6 @@ import me.chanjar.weixin.mp.api.WxMpService;
13 11 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
14 12 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
15 13 import me.chanjar.weixin.mp.bean.result.WxMpUser;
16   -import org.springframework.context.annotation.Lazy;
17 14 import org.springframework.stereotype.Component;
18 15  
19 16 import javax.annotation.Resource;
... ... @@ -36,16 +33,16 @@ public class SubscribeHandler implements WxMpMessageHandler {
36 33 @Override
37 34 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
38 35 WxMpService weixinService, WxSessionManager sessionManager) throws WxErrorException {
39   - // 第一步,从公众号平台,获取用户信息
40   - log.info("[handle][用户({}) 关注]", wxMessage.getFromUser());
  36 + // 第一步,从公众号平台,获取粉丝信息
  37 + log.info("[handle][粉丝({}) 关注]", wxMessage.getFromUser());
41 38 WxMpUser wxMpUser = null;
42 39 try {
43 40 wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
44 41 } catch (WxErrorException e) {
45   - log.error("[handle][用户({})] 获取用户信息失败!", wxMessage.getFromUser(), e);
  42 + log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e);
46 43 }
47 44  
48   - // 第二步,保存用户信息
  45 + // 第二步,保存粉丝信息
49 46 mpUserService.saveUser(MpContextHolder.getAppId(), wxMpUser);
50 47  
51 48 // 第三步,回复关注的欢迎语
... ...
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 {
31 31 public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
32 32 Map<String, Object> context, WxMpService wxMpService,
33 33 WxSessionManager sessionManager) {
34   - log.info("[handle][用户({}) 取消关注]", wxMessage.getFromUser());
  34 + log.info("[handle][粉丝({}) 取消关注]", wxMessage.getFromUser());
35 35 mpUserService.updateUserUnsubscribe(MpContextHolder.getAppId(), wxMessage.getFromUser());
36 36 return null;
37 37 }
... ...
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 {
29 29 void deleteMenuByAccountId(Long accountId);
30 30  
31 31 /**
32   - * 用户点击菜单按钮时,回复对应的消息
  32 + * 粉丝点击菜单按钮时,回复对应的消息
33 33 *
34 34 * @param appId 公众号 AppId
35 35 * @param key 菜单按钮的标识
36   - * @param openid 用户的 openid
  36 + * @param openid 粉丝的 openid
37 37 * @return 消息
38 38 */
39 39 WxMpXmlOutMessage reply(String appId, String key, String openid);
... ...
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 {
64 64 WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage);
65 65  
66 66 /**
67   - * 当用户关注时,自动回复
  67 + * 当粉丝关注时,自动回复
68 68 *
69 69 * @param appId 微信公众号 appId
70 70 * @param wxMessage 消息
... ...
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 {
136 136  
137 137 @Override
138 138 public void deleteAutoReply(Long id) {
139   - // 校验用户存在
  139 + // 校验粉丝存在
140 140 validateAutoReplyExists(id);
141 141  
142 142 // 删除自动回复
... ...
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;
11 11 import javax.validation.Valid;
12 12  
13 13 /**
14   - * 粉丝消息 Service 接口
  14 + * 公众号消息 Service 接口
15 15 *
16 16 * @author 芋道源码
17 17 */
18 18 public interface MpMessageService {
19 19  
20   - // TODO 芋艿:方法名要优化下
21 20 /**
22   - * 获得粉丝消息分页
  21 + * 获得公众号消息分页
23 22 *
24 23 * @param pageReqVO 分页查询
25   - * @return 粉丝消息分页
  24 + * @return 公众号消息分页
26 25 */
27   - PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO);
  26 + PageResult<MpMessageDO> getMessagePage(MpMessagePageReqVO pageReqVO);
28 27  
29 28 /**
30   - * 从公众号,接收到用户消息
  29 + * 从公众号,接收到粉丝消息
31 30 *
32 31 * @param appId 微信公众号 appId
33 32 * @param wxMessage 消息
... ... @@ -35,7 +34,7 @@ public interface MpMessageService {
35 34 void receiveMessage(String appId, WxMpXmlMessage wxMessage);
36 35  
37 36 /**
38   - * 使用公众号,给用户回复消息
  37 + * 使用公众号,给粉丝回复消息
39 38 *
40 39 * 例如说:自动回复、客服消息、菜单回复消息等场景
41 40 *
... ... @@ -47,7 +46,7 @@ public interface MpMessageService {
47 46 WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO);
48 47  
49 48 /**
50   - * 使用公众号,给用户发送【客服】消息
  49 + * 使用公众号,给粉丝发送【客服】消息
51 50 *
52 51 * 注意,该方法会真实发送消息
53 52 *
... ...
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 {
63 63 private Validator validator;
64 64  
65 65 @Override
66   - public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) {
  66 + public PageResult<MpMessageDO> getMessagePage(MpMessagePageReqVO pageReqVO) {
67 67 return mpMessageMapper.selectPage(pageReqVO);
68 68 }
69 69  
... ...
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;
14 14 /**
15 15 * 公众号消息发送 Request BO
16 16 *
17   - * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给用户发送消息,所以使用该 BO 统一承接
  17 + * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给粉丝发送消息,所以使用该 BO 统一承接
18 18 *
19 19 * @author 芋道源码
20 20 */
... ... @@ -27,9 +27,9 @@ public class MpMessageSendOutReqBO {
27 27 @NotEmpty(message = "公众号 appId 不能为空")
28 28 private String appId;
29 29 /**
30   - * 公众号用户 openid
  30 + * 公众号粉丝 openid
31 31 */
32   - @NotEmpty(message = "公众号用户 openid 不能为空")
  32 + @NotEmpty(message = "公众号粉丝 openid 不能为空")
33 33 private String openid;
34 34  
35 35 // ========== 消息内容 ==========
... ...
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;
16 16 public interface MpStatisticsService {
17 17  
18 18 /**
19   - * 获取用户增减数据
  19 + * 获取粉丝增减数据
20 20 *
21 21 * @param accountId 公众号账号编号
22 22 * @param date 时间区间
23   - * @return 用户增减数据
  23 + * @return 粉丝增减数据
24 24 */
25 25 List<WxDataCubeUserSummary> getUserSummary(Long accountId, LocalDateTime[] date);
26 26  
27 27 /**
28   - * 获取用户累计数据
  28 + * 获取粉丝累计数据
29 29 *
30 30 * @param accountId 公众号账号编号
31 31 * @param date 时间区间
32   - * @return 用户累计数据
  32 + * @return 粉丝累计数据
33 33 */
34 34 List<WxDataCubeUserCumulate> getUserCumulate(Long accountId, LocalDateTime[] date);
35 35  
... ...
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 {
99 99 // for 循环,避免递归出意外问题,导致死循环
100 100 String nextOpenid = null;
101 101 for (int i = 0; i < Short.MAX_VALUE; i++) {
102   - log.info("[syncUser][第({}) 次加载公众号用户列表,nextOpenid({})]", i, nextOpenid);
  102 + log.info("[syncUser][第({}) 次加载公众号粉丝列表,nextOpenid({})]", i, nextOpenid);
103 103 try {
104 104 nextOpenid = syncUser0(account, nextOpenid);
105 105 } catch (WxErrorException e) {
106   - log.error("[syncUser][第({}) 次同步用户异常]", i, e);
  106 + log.error("[syncUser][第({}) 次同步粉丝异常]", i, e);
107 107 break;
108 108 }
109 109 // 如果 nextOpenid 为空,表示已经同步完毕
... ... @@ -114,17 +114,17 @@ public class MpUserServiceImpl implements MpUserService {
114 114 }
115 115  
116 116 private String syncUser0(MpAccountDO account, String nextOpenid) throws WxErrorException {
117   - // 第一步,从公众号流式加载用户
  117 + // 第一步,从公众号流式加载粉丝
118 118 WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getId());
119 119 WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid);
120 120 if (CollUtil.isEmpty(wxUserList.getOpenids())) {
121 121 return null;
122 122 }
123 123  
124   - // 第二步,分批加载用户信息
  124 + // 第二步,分批加载粉丝信息
125 125 List<List<String>> openidsList = CollUtil.split(wxUserList.getOpenids(), 100);
126 126 for (List<String> openids : openidsList) {
127   - log.info("[syncUser][批量加载用户信息,openids({})]", openids);
  127 + log.info("[syncUser][批量加载粉丝信息,openids({})]", openids);
128 128 List<WxMpUser> wxUsers = mpService.getUserService().userInfoList(openids);
129 129 batchSaveUser(account, wxUsers);
130 130 }
... ... @@ -137,7 +137,7 @@ public class MpUserServiceImpl implements MpUserService {
137 137 if (CollUtil.isEmpty(wxUsers)) {
138 138 return;
139 139 }
140   - // 1. 获得数据库已保存的用户列表
  140 + // 1. 获得数据库已保存的粉丝列表
141 141 List<MpUserDO> dbUsers = mpUserMapper.selectListByAppIdAndOpenid(account.getAppId(),
142 142 CollectionUtils.convertList(wxUsers, WxMpUser::getOpenId));
143 143 Map<String, MpUserDO> openId2Users = CollectionUtils.convertMap(dbUsers, MpUserDO::getOpenid);
... ...
yudao-ui-admin/src/api/mp/draft.js
... ... @@ -9,7 +9,7 @@ export function getDraftPage(query) {
9 9 })
10 10 }
11 11  
12   -// 创建草稿
  12 +// 创建公众号草稿
13 13 export function createDraft(accountId, articles) {
14 14 return request({
15 15 url: '/mp/draft/create?accountId=' + accountId,
... ... @@ -20,7 +20,7 @@ export function createDraft(accountId, articles) {
20 20 })
21 21 }
22 22  
23   -// 更新草稿
  23 +// 更新公众号草稿
24 24 export function updateDraft(accountId, mediaId, articles) {
25 25 return request({
26 26 url: '/mp/draft/update?accountId=' + accountId + '&mediaId=' + mediaId,
... ... @@ -29,7 +29,7 @@ export function updateDraft(accountId, mediaId, articles) {
29 29 })
30 30 }
31 31  
32   -// 删除草稿
  32 +// 删除公众号草稿
33 33 export function deleteDraft(accountId, mediaId) {
34 34 return request({
35 35 url: '/mp/draft/delete?accountId=' + accountId + '&mediaId=' + mediaId,
... ...
yudao-ui-admin/src/api/mp/message.js
1 1 import request from '@/utils/request'
2 2  
3   -// 获得粉丝消息分页
  3 +// 获得公众号消息分页
4 4 export function getMessagePage(query) {
5 5 return request({
6 6 url: '/mp/message/page',
... ...
yudao-ui-admin/src/utils/dict.js
... ... @@ -60,6 +60,7 @@ export const DICT_TYPE = {
60 60  
61 61 // ========== MP 模块 ==========
62 62 MP_AUTO_REPLY_REQUEST_MATCH: 'mp_auto_reply_request_match', // 自动回复请求匹配类型
  63 + MP_MESSAGE_TYPE: 'mp_message_type', // 消息类型
63 64  
64 65 // ========== MALL - PRODUCT 模块 ==========
65 66 PRODUCT_SPU_STATUS: 'product_spu_status', // 商品 SPU 状态
... ...
yudao-ui-admin/src/views/mp/autoReply/index.vue
... ... @@ -50,49 +50,12 @@ SOFTWARE.
50 50 </el-col>
51 51 <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
52 52 </el-row>
53   - <!-- 列表 -->
  53 + <!-- tab 项 -->
54 54 <el-tab-pane name="1">
55 55 <span slot="label"><i class="el-icon-star-off"></i> 关注时回复</span>
56   - <el-table v-loading="loading" :data="list">
57   - <el-table-column label="回复消息类型" align="center" prop="responseMessageType"/>
58   - <el-table-column label="创建时间" align="center" prop="createTime" width="180">
59   - <template slot-scope="scope">
60   - <span>{{ parseTime(scope.row.createTime) }}</span>
61   - </template>
62   - </el-table-column>
63   - <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
64   - <template slot-scope="scope">
65   - <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
66   - v-hasPermi="['mp:auto-reply:update']">修改
67   - </el-button>
68   - <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
69   - v-hasPermi="['mp:auto-reply:delete']">删除
70   - </el-button>
71   - </template>
72   - </el-table-column>
73   - </el-table>
74 56 </el-tab-pane>
75 57 <el-tab-pane name="2">
76 58 <span slot="label"><i class="el-icon-chat-line-round"></i> 消息回复</span>
77   - <el-table v-loading="loading" :data="list">
78   - <el-table-column label="请求消息类型" align="center" prop="requestMessageType"/>
79   - <el-table-column label="回复消息类型" align="center" prop="responseMessageType"/>
80   - <el-table-column label="创建时间" align="center" prop="createTime" width="180">
81   - <template slot-scope="scope">
82   - <span>{{ parseTime(scope.row.createTime) }}</span>
83   - </template>
84   - </el-table-column>
85   - <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
86   - <template slot-scope="scope">
87   - <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
88   - v-hasPermi="['mp:auto-reply:update']">修改
89   - </el-button>
90   - <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
91   - v-hasPermi="['mp:auto-reply:delete']">删除
92   - </el-button>
93   - </template>
94   - </el-table-column>
95   - </el-table>
96 59 </el-tab-pane>
97 60 <el-tab-pane name="3">
98 61 <span slot="label"><i class="el-icon-news"></i> 关键词回复</span>
... ... @@ -101,13 +64,42 @@ SOFTWARE.
101 64  
102 65 <!-- 列表 -->
103 66 <el-table v-loading="loading" :data="list">
104   - <el-table-column label="关键词" align="center" prop="requestKeyword"/>
105   - <el-table-column label="匹配类型" align="center" prop="requestMatch">
  67 + <el-table-column label="请求消息类型" align="center" prop="requestMessageType" v-if="type === '2'" />
  68 + <el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
  69 + <el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
106 70 <template v-slot="scope">
107 71 <dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch"/>
108 72 </template>
109 73 </el-table-column>
110   - <el-table-column label="回复消息类型" align="center" prop="responseMessageType"/>
  74 + <el-table-column label="回复消息类型" align="center">
  75 + <template v-slot="scope">
  76 + <dict-tag :type="DICT_TYPE.MP_MESSAGE_TYPE" :value="scope.row.responseMessageType"/>
  77 + </template>
  78 + </el-table-column>
  79 + <el-table-column label="回复内容" align="center">
  80 + <template slot-scope="scope">
  81 + <div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
  82 + <div v-else-if="scope.row.responseMessageType === 'voice'">
  83 + <wx-voice-player :url="scope.row.responseMediaUrl" />
  84 + </div>
  85 + <div v-else-if="scope.row.responseMessageType === 'image'">
  86 + <a target="_blank" :href="scope.row.responseMediaUrl">
  87 + <img :src="scope.row.responseMediaUrl" style="width: 100px">
  88 + </a>
  89 + </div>
  90 + <div v-else-if="scope.row.responseMessageType === 'video' || scope.row.responseMessageType === 'shortvideo'">
  91 + <wx-video-player :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
  92 + </div>
  93 + <div v-else-if="scope.row.responseMessageType === 'news'">
  94 + <wx-news :articles="scope.row.responseArticles" />
  95 + </div>
  96 + <div v-else-if="scope.row.responseMessageType === 'music'">
  97 + <wx-music :title="scope.row.responseTitle" :description="scope.row.responseDescription"
  98 + :thumb-media-url="scope.row.responseThumbMediaUrl"
  99 + :music-url="scope.row.responseMusicUrl" :hq-music-url="scope.row.responseHqMusicUrl" />
  100 + </div>
  101 + </template>
  102 + </el-table-column>
111 103 <el-table-column label="创建时间" align="center" prop="createTime" width="180">
112 104 <template slot-scope="scope">
113 105 <span>{{ parseTime(scope.row.createTime) }}</span>
... ... @@ -130,9 +122,9 @@ SOFTWARE.
130 122 <el-form ref="form" :model="form" :rules="rules" label-width="80px">
131 123 <el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
132 124 <el-select v-model="form.requestMessageType" placeholder="请选择">
133   - <el-option v-for="item in dictData.get('wx_req_type')" :key="item.value" :label="item.label"
134   - :value="item.value" :disabled="item.disabled" v-if="item.value !== 'event'">
135   - </el-option>
  125 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
  126 + :key="dict.value" :label="dict.label" :value="dict.value"
  127 + v-if="requestMessageTypes.includes(dict.value)"/>
136 128 </el-select>
137 129 </el-form-item>
138 130 <el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
... ... @@ -157,6 +149,12 @@ SOFTWARE.
157 149 </template>
158 150  
159 151 <script>
  152 +import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
  153 +import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
  154 +import WxMsg from '@/views/mp/components/wx-msg/main.vue';
  155 +import WxLocation from '@/views/mp/components/wx-location/main.vue';
  156 +import WxMusic from '@/views/mp/components/wx-music/main.vue';
  157 +import WxNews from '@/views/mp/components/wx-news/main.vue';
160 158 import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
161 159 import { getSimpleAccounts } from "@/api/mp/account";
162 160 import { createAutoReply, deleteAutoReply, getAutoReply, getAutoReplyPage, updateAutoReply } from "@/api/mp/autoReply";
... ... @@ -164,12 +162,20 @@ import { createAutoReply, deleteAutoReply, getAutoReply, getAutoReplyPage, updat
164 162 export default {
165 163 name: 'mpAutoReply',
166 164 components: {
  165 + WxVideoPlayer,
  166 + WxVoicePlayer,
  167 + WxMsg,
  168 + WxLocation,
  169 + WxMusic,
  170 + WxNews,
167 171 WxReplySelect
168 172 },
169 173 data() {
170 174 return {
171 175 // tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
172 176 type: '3',
  177 + // 允许选择的请求消息类型
  178 + requestMessageTypes: ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'],
173 179 // 遮罩层
174 180 loading: true,
175 181 // 显示搜索条件
... ... @@ -202,8 +208,6 @@ export default {
202 208 },
203 209 hackResetWxReplySelect: false, // 重置 WxReplySelect 组件,解决无法清除的问题
204 210  
205   - dictData: new Map(),
206   -
207 211 // 公众号账号列表
208 212 accounts: []
209 213 }
... ... @@ -218,33 +222,6 @@ export default {
218 222 // 加载数据
219 223 this.getList();
220 224 })
221   -
222   - // TODO 芋艿:字典数据,一起搞
223   - this.dictData.set('wx_req_type',[{
224   - value: 'text',
225   - label: '文本'
226   - },{
227   - value: 'image',
228   - label: '图片'
229   - },{
230   - value: 'voice',
231   - label: '语音'
232   - },{
233   - value: 'video',
234   - label: '视频'
235   - },{
236   - value: 'shortvideo',
237   - label: '小视频'
238   - },{
239   - value: 'location',
240   - label: '地理位置'
241   - },{
242   - value: 'link',
243   - label: '链接消息'
244   - },{
245   - value: 'event',
246   - label: '事件推送'
247   - }])
248 225 },
249 226 methods: {
250 227 /** 查询列表 */
... ... @@ -295,7 +272,8 @@ export default {
295 272 this.open = true
296 273 this.title = '新增自动回复';
297 274 this.objData = {
298   - type : 'text'
  275 + type : 'text',
  276 + accountId: this.queryParams.accountId,
299 277 }
300 278 },
301 279 /** 修改按钮操作 */
... ...
yudao-ui-admin/src/views/mp/components/wx-msg/main.vue
... ... @@ -9,13 +9,13 @@
9 9 <div class="msg-main">
10 10 <div class="msg-div" :id="'msg-div' + nowStr">
11 11 <!-- 加载更多 -->
12   - <div v-loading="tableLoading"></div>
13   - <div v-if="!tableLoading">
  12 + <div v-loading="loading"></div>
  13 + <div v-if="!loading">
14 14 <div class="el-table__empty-block" v-if="loadMore" @click="loadingMore"><span class="el-table__empty-text">点击加载更多</span></div>
15 15 <div class="el-table__empty-block" v-if="!loadMore"><span class="el-table__empty-text">没有更多了</span></div>
16 16 </div>
17 17 <!-- 消息列表 -->
18   - <div class="execution" v-for="item in tableData" :key='item.id'>
  18 + <div class="execution" v-for="item in list" :key='item.id'>
19 19 <div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''">
20 20 <div class="avatar-div">
21 21 <img :src="item.sendFrom === 1 ? user.avatar : mp.avatar" class="avue-comment__avatar">
... ... @@ -124,20 +124,20 @@ import { getUser } from &quot;@/api/mp/user&quot;;
124 124 },
125 125 props: {
126 126 userId: {
127   - type: String,
  127 + type: Number,
128 128 required: true
129 129 },
130 130 },
131 131 data() {
132 132 return {
133 133 nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
134   - sendLoading: false, // 发送消息是否加载中
135   - tableLoading: false, // 消息列表是否正在加载中
  134 + loading: false, // 消息列表是否正在加载中
136 135 loadMore: true, // 是否可以加载更多
137   - tableData: [], // 消息列表
138   - page: {
  136 + list: [], // 消息列表
  137 + queryParams: {
139 138 pageNo: 1, // 当前页数
140 139 pageSize: 14, // 每页显示多少条
  140 + accountId: undefined,
141 141 },
142 142 user: { // 由于微信不再提供昵称,直接使用“用户”展示
143 143 nickname: '用户',
... ... @@ -148,6 +148,9 @@ import { getUser } from &quot;@/api/mp/user&quot;;
148 148 nickname: '公众号',
149 149 avatar: require("@/assets/images/wechat.png"),
150 150 },
  151 +
  152 + // ========= 消息发送 =========
  153 + sendLoading: false, // 发送消息是否加载中
151 154 objData: { // 微信发送消息
152 155 type: 'text',
153 156 },
... ... @@ -156,14 +159,19 @@ import { getUser } from &quot;@/api/mp/user&quot;;
156 159 created() {
157 160 // 获得用户信息
158 161 getUser(this.userId).then(response => {
159   - this.user.nickname = response.data.nickname | this.user.nickname;
160   - this.user.avatar = response.data.avatar | this.user.avatar;
  162 + this.user.nickname = response.data.nickname && response.data.nickname.length > 0 ?
  163 + response.data.nickname : this.user.nickname;
  164 + this.user.avatar = response.data.avatar && this.user.avatar.length > 0 ?
  165 + response.data.avatar : this.user.avatar;
  166 + this.user.accountId = response.data.accountId;
161 167 // 设置公众号账号编号
  168 + this.queryParams.accountId = response.data.accountId;
162 169 this.objData.accountId = response.data.accountId;
163   - })
164 170  
165   - // 加载消息
166   - this.refreshChange()
  171 + // 加载消息
  172 + console.log(this.queryParams)
  173 + this.refreshChange()
  174 + })
167 175 },
168 176 methods:{
169 177 sendMsg(){
... ... @@ -190,7 +198,7 @@ import { getUser } from &quot;@/api/mp/user&quot;;
190 198 })).then(response => {
191 199 this.sendLoading = false
192 200 // 添加到消息列表,并滚动
193   - this.tableData = [...this.tableData , ...[response.data] ]
  201 + this.list = [...this.list , ...[response.data] ]
194 202 this.scrollToBottom()
195 203 // 重置 objData 状态
196 204 this.$refs['replySelect'].deleteObj(); // 重置,避免 tab 的数据未清理
... ... @@ -199,15 +207,16 @@ import { getUser } from &quot;@/api/mp/user&quot;;
199 207 })
200 208 },
201 209 loadingMore() {
202   - this.page.pageNo++
203   - this.getPage(this.page)
  210 + this.queryParams.pageNo++
  211 + this.getPage(this.queryParams)
204 212 },
205 213 getPage(page, params) {
206   - this.tableLoading = true
  214 + this.loading = true
207 215 getMessagePage(Object.assign({
208 216 pageNo: page.pageNo,
209 217 pageSize: page.pageSize,
210   - userId: this.userId
  218 + userId: this.userId,
  219 + accountId: page.accountId,
211 220 }, params)).then(response => {
212 221 // 计算当前的滚动高度
213 222 const msgDiv = document.getElementById('msg-div' + this.nowStr);
... ... @@ -218,16 +227,16 @@ import { getUser } from &quot;@/api/mp/user&quot;;
218 227  
219 228 // 处理数据
220 229 const data = response.data.list.reverse();
221   - this.tableData = [...data, ...this.tableData]
222   - this.tableLoading = false
223   - if (data.length < this.page.pageSize || data.length === 0){
  230 + this.list = [...data, ...this.list]
  231 + this.loading = false
  232 + if (data.length < this.queryParams.pageSize || data.length === 0){
224 233 this.loadMore = false
225 234 }
226   - this.page.pageNo = page.pageNo
227   - this.page.pageSize = page.pageSize
  235 + this.queryParams.pageNo = page.pageNo
  236 + this.queryParams.pageSize = page.pageSize
228 237  
229 238 // 滚动到原来的位置
230   - if(this.page.pageNo === 1) { // 定位到消息底部
  239 + if(this.queryParams.pageNo === 1) { // 定位到消息底部
231 240 this.scrollToBottom()
232 241 } else if (data.length !== 0) { // 定位滚动条
233 242 this.$nextTick(() => {
... ... @@ -242,7 +251,7 @@ import { getUser } from &quot;@/api/mp/user&quot;;
242 251 * 刷新回调
243 252 */
244 253 refreshChange() {
245   - this.getPage(this.page)
  254 + this.getPage(this.queryParams)
246 255 },
247 256 /** 定位到消息底部 */
248 257 scrollToBottom: function () {
... ... @@ -267,6 +276,8 @@ import { getUser } from &quot;@/api/mp/user&quot;;
267 276 height: 50vh;
268 277 overflow: auto;
269 278 background-color: #eaeaea;
  279 + margin-left: 10px;
  280 + margin-right: 10px;
270 281 }
271 282 .msg-send {
272 283 padding: 10px;
... ...
yudao-ui-admin/src/views/mp/menu/index.vue
... ... @@ -66,8 +66,8 @@ SOFTWARE.
66 66 <div class="menu_bottom menu_addicon" v-if="this.menuList.length < 3" @click="addMenu"><i class="el-icon-plus"></i></div>
67 67 </div>
68 68 <div class="save_div">
69   - <el-button class="save_btn" type="success" size="small" @click="handleSave">保存并发布菜单</el-button>
70   - <el-button class="save_btn" type="danger" size="small" @click="handleDelete">清空菜单</el-button>
  69 + <el-button class="save_btn" type="success" size="small" @click="handleSave" v-hasPermi="['mp:menu:save']">保存并发布菜单</el-button>
  70 + <el-button class="save_btn" type="danger" size="small" @click="handleDelete" v-hasPermi="['mp:menu:delete']">清空菜单</el-button>
71 71 </div>
72 72 </div>
73 73 <!--右边配置-->
... ...
yudao-ui-admin/src/views/mp/message/index.vue
... ... @@ -8,18 +8,19 @@
8 8 <el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
9 9 </el-select>
10 10 </el-form-item>
11   - <!-- 等待处理 -->
12   - <el-form-item label="消息类型" prop="msgType">
13   - <el-select v-model="queryParams.msgType" placeholder="请选择消息类型" clearable size="small">
14   - <el-option label="请选择字典生成" value=""/>
  11 + <!-- TODO 等待处理 -->
  12 + <el-form-item label="消息类型" prop="type">
  13 + <el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small">
  14 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
  15 + :key="dict.value" :label="dict.label" :value="dict.value"/>
15 16 </el-select>
16 17 </el-form-item>
17 18 <el-form-item label="用户标识" prop="openid">
18 19 <el-input v-model="queryParams.openid" placeholder="请输入用户标识" clearable @keyup.enter.native="handleQuery"/>
19 20 </el-form-item>
20   - <el-form-item label="创建时间">
21   - <el-date-picker v-model="dateRangeCreateTime" style="width: 240px" value-format="yyyy-MM-dd"
22   - type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"/>
  21 + <el-form-item label="创建时间" prop="createTime">
  22 + <el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
  23 + range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
23 24 </el-form-item>
24 25 <el-form-item>
25 26 <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
... ... @@ -42,12 +43,11 @@
42 43 <el-table-column label="消息类型" align="center" prop="type" width="80"/>
43 44 <el-table-column label="发送方" align="center" prop="sendFrom" width="80">
44 45 <template slot-scope="scope">
45   - <el-tag v-if="scope.row.sendFrom === 1" type="success">用户</el-tag>
  46 + <el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
46 47 <el-tag v-else type="info">公众号</el-tag>
47 48 </template>
48 49 </el-table-column>
49 50 <el-table-column label="用户标识" align="center" prop="openid" width="300" />
50   - <!-- TODO 芋艿:发送/接收 -->
51 51 <el-table-column label="内容" prop="content">
52 52 <template slot-scope="scope">
53 53 <!-- 【事件】区域 -->
... ... @@ -118,10 +118,9 @@
118 118 </el-table-column>
119 119 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
120 120 <template slot-scope="scope">
121   - <!-- TODO 芋艿:增加消息按钮 -->
122   -<!-- <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"-->
123   -<!-- v-hasPermi="['mp:message:update']">修改-->
124   -<!-- </el-button>-->
  121 + <el-button size="mini" type="text" icon="el-icon-edit" @click="handleSend(scope.row)"
  122 + v-hasPermi="['mp:message:send']">消息
  123 + </el-button>
125 124 </template>
126 125 </el-table-column>
127 126 </el-table>
... ... @@ -129,24 +128,26 @@
129 128 <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
130 129 @pagination="getList"/>
131 130  
132   - <el-dialog title="用户消息" :visible.sync="open" width="40%">
133   - <wx-msg user-id="3" v-if="true" />
  131 + <!-- 发送消息的弹窗 -->
  132 + <el-dialog title="粉丝消息列表" :visible.sync="open" width="50%">
  133 + <wx-msg :user-id="userId" v-if="open" />
134 134 </el-dialog>
135 135  
136 136 </div>
137 137 </template>
138 138  
139 139 <script>
140   -import { getMessagePage } from "@/api/mp/message";
141 140 import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
142 141 import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
143 142 import WxMsg from '@/views/mp/components/wx-msg/main.vue';
144 143 import WxLocation from '@/views/mp/components/wx-location/main.vue';
145 144 import WxMusic from '@/views/mp/components/wx-music/main.vue';
146 145 import WxNews from '@/views/mp/components/wx-news/main.vue';
  146 +import { getMessagePage } from "@/api/mp/message";
  147 +import { getSimpleAccounts } from "@/api/mp/account";
147 148  
148 149 export default {
149   - name: "WxFansMsg",
  150 + name: "MpMessage",
150 151 components: {
151 152 WxVideoPlayer,
152 153 WxVoicePlayer,
... ... @@ -167,76 +168,52 @@ export default {
167 168 total: 0,
168 169 // 粉丝消息列表
169 170 list: [],
170   - // 弹出层标题
171   - title: "",
172 171 // 是否显示弹出层
173   - open: true,
  172 + open: false,
174 173 // 查询参数
175   - dateRangeCreateTime: [],
176 174 queryParams: {
177 175 pageNo: 1,
178 176 pageSize: 10,
179 177 openid: null,
180   - nickname: null,
181   - headimgUrl: null,
182   - wxAccountId: null,
183   - msgType: null,
184   - content: null,
185   - resContent: null,
186   - isRes: null,
187   - mediaId: null,
188   - picUrl: null,
189   - picPath: null,
  178 + accountId: null,
  179 + type: null,
  180 + createTime: []
190 181 },
191   - // 表单参数
192   - form: {},
193   - // 表单校验
194   - rules: {},
  182 + // 操作的用户编号
  183 + userId: 0,
195 184  
196 185 // 公众号账号列表
197 186 accounts: []
198 187 };
199 188 },
200 189 created() {
201   - this.getList();
  190 + getSimpleAccounts().then(response => {
  191 + this.accounts = response.data;
  192 + // 默认选中第一个
  193 + if (this.accounts.length > 0) {
  194 + this.queryParams.accountId = this.accounts[0].id;
  195 + }
  196 + // 加载数据
  197 + this.getList();
  198 + })
202 199 },
203 200 methods: {
204 201 /** 查询列表 */
205 202 getList() {
  203 + // 如果没有选中公众号账号,则进行提示。
  204 + if (!this.queryParams.accountId) {
  205 + this.$message.error('未选中公众号,无法查询消息')
  206 + return false
  207 + }
  208 +
206 209 this.loading = true;
207   - // 处理查询参数
208   - let params = {...this.queryParams};
209   - this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
210 210 // 执行查询
211   - getMessagePage(params).then(response => {
  211 + getMessagePage(this.queryParams).then(response => {
212 212 this.list = response.data.list;
213 213 this.total = response.data.total;
214 214 this.loading = false;
215 215 });
216 216 },
217   - /** 取消按钮 */
218   - cancel() {
219   - this.open = false;
220   - this.reset();
221   - },
222   - /** 表单重置 */
223   - reset() {
224   - this.form = {
225   - id: undefined,
226   - openid: undefined,
227   - nickname: undefined,
228   - headimgUrl: undefined,
229   - wxAccountId: undefined,
230   - msgType: undefined,
231   - content: undefined,
232   - resContent: undefined,
233   - isRes: undefined,
234   - mediaId: undefined,
235   - picUrl: undefined,
236   - picPath: undefined,
237   - };
238   - this.resetForm("form");
239   - },
240 217 /** 搜索按钮操作 */
241 218 handleQuery() {
242 219 this.queryParams.pageNo = 1;
... ... @@ -244,10 +221,17 @@ export default {
244 221 },
245 222 /** 重置按钮操作 */
246 223 resetQuery() {
247   - this.dateRangeCreateTime = [];
248 224 this.resetForm("queryForm");
  225 + // 默认选中第一个
  226 + if (this.accounts.length > 0) {
  227 + this.queryParams.accountId = this.accounts[0].id;
  228 + }
249 229 this.handleQuery();
250 230 },
  231 + handleSend(row) {
  232 + this.userId = row.userId;
  233 + this.open = true;
  234 + },
251 235 }
252 236 };
253 237 </script>
... ...
yudao-ui-admin/src/views/mp/statistics/index.vue
... ... @@ -77,7 +77,7 @@ require(&#39;echarts/lib/component/legend&#39;)
77 77 import { getInterfaceSummary, getUserSummary, getUserCumulate, getUpstreamMessage} from '@/api/mp/statistics'
78 78 import { datePickerOptions } from "@/utils/constants";
79 79 import {addTime, beginOfDay, betweenDay, endOfDay, formatDate} from "@/utils/dateUtils";
80   -import {getSimpleAccounts} from "@/api/mp/account";
  80 +import { getSimpleAccounts } from "@/api/mp/account";
81 81  
82 82 export default {
83 83 name: 'mpStatistics',
... ...