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

This commit is contained in:
YunaiV
2023-01-17 23:20:56 +08:00
parent 6fc0b3fc54
commit 68ef11ee87
52 changed files with 378 additions and 494 deletions

View File

@ -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

View File

@ -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)

View File

@ -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;
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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;
/**
* 语音格式,如 amrspeex 等
*
* 消息类型为 {@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} 的 SCANqrscene_ 为前缀,后面为二维码的参数值
* 2. {@link WxConsts.EventType} 的 CLICK与自定义菜单接口中 KEY 值对应
*/
private String eventKey;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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")

View File

@ -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(

View File

@ -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")

View File

@ -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")

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 枚举")

View File

@ -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;

View File

@ -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
*/

View File

@ -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()}
*/
@ -125,6 +125,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;
/**
* 回复的图文消息
*
@ -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;
}

View File

@ -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
*/

View File

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

View File

@ -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()}
*/

View File

@ -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));
}

View File

@ -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());

View File

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

View File

@ -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("未实现该处理,请自行重写");
}
}

View File

@ -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("未实现该处理,请自行重写");
}
}

View File

@ -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("未实现该处理,请自行重写");
}
}

View File

@ -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("未实现该处理,请自行重写");
}
}

View File

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

View File

@ -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);
// 第三步,回复关注的欢迎语

View File

@ -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;
}

View File

@ -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);

View File

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

View File

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

View File

@ -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);
/**
* 使用公众号,给用户发送【客服】消息
* 使用公众号,给粉丝发送【客服】消息
*
* 注意,该方法会真实发送消息
*

View File

@ -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);
}

View File

@ -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;
// ========== 消息内容 ==========

View File

@ -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);

View File

@ -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);