Commit 665f4b2b0926f4cf10b295724ffa06be04064a92
1 parent
47f07156
mp:重构 message 发送消息的方法
Showing
15 changed files
with
453 additions
and
84 deletions
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.convert.message; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; | ||
| 4 | +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; | ||
| 5 | +import org.mapstruct.Mapper; | ||
| 6 | +import org.mapstruct.Mapping; | ||
| 7 | +import org.mapstruct.Mappings; | ||
| 8 | +import org.mapstruct.factory.Mappers; | ||
| 9 | + | ||
| 10 | +@Mapper | ||
| 11 | +public interface MpAutoReplyConvert { | ||
| 12 | + | ||
| 13 | + MpAutoReplyConvert INSTANCE = Mappers.getMapper(MpAutoReplyConvert.class); | ||
| 14 | + | ||
| 15 | + @Mappings({ | ||
| 16 | + @Mapping(source = "reply.appId", target = "appId"), | ||
| 17 | + @Mapping(source = "reply.responseMessageType", target = "type"), | ||
| 18 | + @Mapping(source = "reply.responseContent", target = "content"), | ||
| 19 | + @Mapping(source = "reply.responseMediaId", target = "mediaId"), | ||
| 20 | + @Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"), | ||
| 21 | + @Mapping(source = "reply.responseTitle", target = "title"), | ||
| 22 | + @Mapping(source = "reply.responseDescription", target = "description"), | ||
| 23 | + @Mapping(source = "reply.responseArticle", target = "article"), | ||
| 24 | + }) | ||
| 25 | + MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply); | ||
| 26 | + | ||
| 27 | +} |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java
| 1 | package cn.iocoder.yudao.module.mp.convert.message; | 1 | package cn.iocoder.yudao.module.mp.convert.message; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||
| 6 | import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO; | 4 | import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO; |
| 7 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | 5 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; |
| 8 | -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; | ||
| 9 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; | 6 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; |
| 10 | import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; | 7 | import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; |
| 8 | +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; | ||
| 11 | import me.chanjar.weixin.common.api.WxConsts; | 9 | import me.chanjar.weixin.common.api.WxConsts; |
| 12 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | 10 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; |
| 13 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | 11 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; |
| 14 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; | 12 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; |
| 15 | import me.chanjar.weixin.mp.builder.outxml.BaseBuilder; | 13 | import me.chanjar.weixin.mp.builder.outxml.BaseBuilder; |
| 16 | -import me.chanjar.weixin.mp.builder.outxml.TextBuilder; | ||
| 17 | -import me.chanjar.weixin.mp.builder.outxml.VideoBuilder; | ||
| 18 | import org.mapstruct.Mapper; | 14 | import org.mapstruct.Mapper; |
| 19 | import org.mapstruct.Mapping; | 15 | import org.mapstruct.Mapping; |
| 20 | import org.mapstruct.Mappings; | 16 | import org.mapstruct.Mappings; |
| @@ -51,23 +47,27 @@ public interface MpMessageConvert { | @@ -51,23 +47,27 @@ public interface MpMessageConvert { | ||
| 51 | return message; | 47 | return message; |
| 52 | } | 48 | } |
| 53 | 49 | ||
| 54 | - default MpMessageDO convert(MpAutoReplyDO reply, MpAccountDO account, MpUserDO user) { | 50 | + default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) { |
| 55 | // 构建消息 | 51 | // 构建消息 |
| 56 | MpMessageDO message = new MpMessageDO(); | 52 | MpMessageDO message = new MpMessageDO(); |
| 57 | - message.setType(reply.getResponseMessageType()); | ||
| 58 | - // 1. 文本 | ||
| 59 | - if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.TEXT)) { | ||
| 60 | - message.setContent(reply.getResponseContent()); | ||
| 61 | - } else if (ObjectUtils.equalsAny(reply.getResponseMessageType(), WxConsts.XmlMsgType.IMAGE, // 2. 图片 | ||
| 62 | - WxConsts.XmlMsgType.VOICE)) { // 3. 语音 | ||
| 63 | - message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl()); | ||
| 64 | - } else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.VIDEO)) { // 4. 视频 | ||
| 65 | - message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl()) | ||
| 66 | - .setTitle(reply.getResponseTitle()).setDescription(reply.getResponseDescription()); | ||
| 67 | - } else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.NEWS)) { // 5. 图文 | ||
| 68 | - message.setArticles(Collections.singletonList(reply.getResponseArticle())); | ||
| 69 | - } else { | ||
| 70 | - throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); | 53 | + message.setType(sendReqBO.getType()); |
| 54 | + switch (sendReqBO.getType()) { | ||
| 55 | + case WxConsts.XmlMsgType.TEXT: // 1. 文本 | ||
| 56 | + message.setContent(sendReqBO.getContent()); | ||
| 57 | + break; | ||
| 58 | + case WxConsts.XmlMsgType.IMAGE: // 2. 图片 | ||
| 59 | + case WxConsts.XmlMsgType.VOICE: // 3. 语音 | ||
| 60 | + message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl()); | ||
| 61 | + break; | ||
| 62 | + case WxConsts.XmlMsgType.VIDEO: // 4. 视频 | ||
| 63 | + message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl()) | ||
| 64 | + .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()); | ||
| 65 | + break; | ||
| 66 | + case WxConsts.XmlMsgType.NEWS: // 5. 图文 | ||
| 67 | + message.setArticles(Collections.singletonList(sendReqBO.getArticle())); | ||
| 68 | + break; | ||
| 69 | + default: | ||
| 70 | + throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); | ||
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | // 其它字段 | 73 | // 其它字段 |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java
| 1 | package cn.iocoder.yudao.module.mp.dal.dataobject.menu; | 1 | package cn.iocoder.yudao.module.mp.dal.dataobject.menu; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 3 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 4 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | 5 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; |
| 5 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; | 6 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; |
| 7 | +import com.baomidou.mybatisplus.annotation.KeySequence; | ||
| 8 | +import com.baomidou.mybatisplus.annotation.TableField; | ||
| 9 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 10 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 6 | import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; | 11 | import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; |
| 7 | -import lombok.*; | ||
| 8 | - | ||
| 9 | -import com.baomidou.mybatisplus.annotation.*; | ||
| 10 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 11 | -import me.chanjar.weixin.common.bean.menu.WxMenu; | ||
| 12 | -import me.chanjar.weixin.common.bean.menu.WxMenuButton; | 12 | +import lombok.Data; |
| 13 | +import lombok.EqualsAndHashCode; | ||
| 14 | +import lombok.ToString; | ||
| 15 | +import me.chanjar.weixin.common.api.WxConsts; | ||
| 16 | +import me.chanjar.weixin.common.api.WxConsts.MenuButtonType; | ||
| 13 | 17 | ||
| 14 | import java.util.List; | 18 | import java.util.List; |
| 15 | 19 | ||
| @@ -48,8 +52,8 @@ public class MpMenuDO extends BaseDO { | @@ -48,8 +52,8 @@ public class MpMenuDO extends BaseDO { | ||
| 48 | /** | 52 | /** |
| 49 | * 按钮列表 | 53 | * 按钮列表 |
| 50 | */ | 54 | */ |
| 51 | - @TableField(typeHandler = WxMenuButtonTypeHandler.class) | ||
| 52 | - private List<WxMenuButton> buttons; | 55 | + @TableField(typeHandler = ButtonTypeHandler.class) |
| 56 | + private List<Button> buttons; | ||
| 53 | /** | 57 | /** |
| 54 | * 同步状态 | 58 | * 同步状态 |
| 55 | * | 59 | * |
| @@ -58,16 +62,113 @@ public class MpMenuDO extends BaseDO { | @@ -58,16 +62,113 @@ public class MpMenuDO extends BaseDO { | ||
| 58 | */ | 62 | */ |
| 59 | private Boolean syncStatus; | 63 | private Boolean syncStatus; |
| 60 | 64 | ||
| 65 | + /** | ||
| 66 | + * 按钮 | ||
| 67 | + */ | ||
| 68 | + @Data | ||
| 69 | + public static class Button { | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 类型 | ||
| 73 | + * | ||
| 74 | + * 枚举 {@link MenuButtonType} | ||
| 75 | + */ | ||
| 76 | + private String type; | ||
| 77 | + /** | ||
| 78 | + * 消息类型 | ||
| 79 | + * | ||
| 80 | + * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG | ||
| 81 | + * | ||
| 82 | + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS | ||
| 83 | + */ | ||
| 84 | + private String messageType; | ||
| 85 | + /** | ||
| 86 | + * 名称 | ||
| 87 | + */ | ||
| 88 | + private String name; | ||
| 89 | + /** | ||
| 90 | + * 标识 | ||
| 91 | + */ | ||
| 92 | + private String key; | ||
| 93 | + /** | ||
| 94 | + * 二级菜单列表 | ||
| 95 | + */ | ||
| 96 | + private List<Button> subButtons; | ||
| 97 | + /** | ||
| 98 | + * 网页链接 | ||
| 99 | + * | ||
| 100 | + * 用户点击菜单可打开链接,不超过 1024 字节 | ||
| 101 | + * | ||
| 102 | + * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM | ||
| 103 | + */ | ||
| 104 | + private String url; | ||
| 105 | + | ||
| 106 | + /** | ||
| 107 | + * 小程序的 appId | ||
| 108 | + * | ||
| 109 | + * 类型为 {@link WxConsts.XmlMsgType} 的 MINIPROGRAM | ||
| 110 | + */ | ||
| 111 | + private String appId; | ||
| 112 | + | ||
| 113 | + /** | ||
| 114 | + * 小程序的页面路径 | ||
| 115 | + * | ||
| 116 | + * 类型为 {@link WxConsts.XmlMsgType} 的 MINIPROGRAM | ||
| 117 | + */ | ||
| 118 | + private String pagePath; | ||
| 119 | + | ||
| 120 | + /** | ||
| 121 | + * 消息内容 | ||
| 122 | + * | ||
| 123 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT | ||
| 124 | + */ | ||
| 125 | + private String content; | ||
| 126 | + | ||
| 127 | + /** | ||
| 128 | + * 媒体 id | ||
| 129 | + * | ||
| 130 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO | ||
| 131 | + */ | ||
| 132 | + private String mediaId; | ||
| 133 | + /** | ||
| 134 | + * 媒体 URL | ||
| 135 | + * | ||
| 136 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO | ||
| 137 | + */ | ||
| 138 | + private String mediaUrl; | ||
| 139 | + | ||
| 140 | + /** | ||
| 141 | + * 回复的标题 | ||
| 142 | + * | ||
| 143 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO | ||
| 144 | + */ | ||
| 145 | + private String title; | ||
| 146 | + /** | ||
| 147 | + * 回复的描述 | ||
| 148 | + * | ||
| 149 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO | ||
| 150 | + */ | ||
| 151 | + private String description; | ||
| 152 | + | ||
| 153 | + /** | ||
| 154 | + * 图文消息 | ||
| 155 | + * | ||
| 156 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS | ||
| 157 | + */ | ||
| 158 | + private MpMessageDO.Article article; | ||
| 159 | + | ||
| 160 | + } | ||
| 161 | + | ||
| 61 | // TODO @芋艿:可以找一些新的思路 | 162 | // TODO @芋艿:可以找一些新的思路 |
| 62 | - public static class WxMenuButtonTypeHandler extends AbstractJsonTypeHandler<List<WxMenuButton>> { | 163 | + public static class ButtonTypeHandler extends AbstractJsonTypeHandler<List<Button>> { |
| 63 | 164 | ||
| 64 | @Override | 165 | @Override |
| 65 | - protected List<WxMenuButton> parse(String json) { | ||
| 66 | - return JsonUtils.parseArray(json, WxMenuButton.class); | 166 | + protected List<Button> parse(String json) { |
| 167 | + return JsonUtils.parseArray(json, Button.class); | ||
| 67 | } | 168 | } |
| 68 | 169 | ||
| 69 | @Override | 170 | @Override |
| 70 | - protected String toJson(List<WxMenuButton> obj) { | 171 | + protected String toJson(List<Button> obj) { |
| 71 | return JsonUtils.toJsonString(obj); | 172 | return JsonUtils.toJsonString(obj); |
| 72 | } | 173 | } |
| 73 | 174 |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java
| @@ -16,7 +16,6 @@ import lombok.ToString; | @@ -16,7 +16,6 @@ import lombok.ToString; | ||
| 16 | import me.chanjar.weixin.common.api.WxConsts; | 16 | import me.chanjar.weixin.common.api.WxConsts; |
| 17 | import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; | 17 | import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; |
| 18 | 18 | ||
| 19 | -import java.util.List; | ||
| 20 | import java.util.Set; | 19 | import java.util.Set; |
| 21 | 20 | ||
| 22 | /** | 21 | /** |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java
| @@ -12,7 +12,9 @@ import com.baomidou.mybatisplus.annotation.TableName; | @@ -12,7 +12,9 @@ import com.baomidou.mybatisplus.annotation.TableName; | ||
| 12 | import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; | 12 | import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; |
| 13 | import lombok.*; | 13 | import lombok.*; |
| 14 | import me.chanjar.weixin.common.api.WxConsts; | 14 | import me.chanjar.weixin.common.api.WxConsts; |
| 15 | +import me.chanjar.weixin.mp.builder.kefu.NewsBuilder; | ||
| 15 | 16 | ||
| 17 | +import javax.validation.constraints.NotEmpty; | ||
| 16 | import java.io.Serializable; | 18 | import java.io.Serializable; |
| 17 | import java.util.List; | 19 | import java.util.List; |
| 18 | 20 | ||
| @@ -199,20 +201,24 @@ public class MpMessageDO extends BaseDO { | @@ -199,20 +201,24 @@ public class MpMessageDO extends BaseDO { | ||
| 199 | /** | 201 | /** |
| 200 | * 图文消息标题 | 202 | * 图文消息标题 |
| 201 | */ | 203 | */ |
| 204 | + @NotEmpty(message = "图文消息标题不能为空", groups = NewsBuilder.class) | ||
| 202 | private String title; | 205 | private String title; |
| 203 | /** | 206 | /** |
| 204 | * 图文消息描述 | 207 | * 图文消息描述 |
| 205 | */ | 208 | */ |
| 209 | + @NotEmpty(message = "图文消息描述不能为空", groups = NewsBuilder.class) | ||
| 206 | private String description; | 210 | private String description; |
| 207 | /** | 211 | /** |
| 208 | * 图片链接 | 212 | * 图片链接 |
| 209 | * | 213 | * |
| 210 | - * 支持JPG、PNG格式,较好的效果为大图 360*200,小图 200*200 | 214 | + * 支持 JPG、PNG 格式,较好的效果为大图 360*200,小图 200*200 |
| 211 | */ | 215 | */ |
| 216 | + @NotEmpty(message = "图片链接不能为空", groups = NewsBuilder.class) | ||
| 212 | private String picUrl; | 217 | private String picUrl; |
| 213 | /** | 218 | /** |
| 214 | * 点击图文消息跳转链接 | 219 | * 点击图文消息跳转链接 |
| 215 | */ | 220 | */ |
| 221 | + @NotEmpty(message = "点击图文消息跳转链接不能为空", groups = NewsBuilder.class) | ||
| 216 | private String url; | 222 | private String url; |
| 217 | 223 | ||
| 218 | } | 224 | } |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java
| @@ -7,4 +7,8 @@ import org.apache.ibatis.annotations.Mapper; | @@ -7,4 +7,8 @@ import org.apache.ibatis.annotations.Mapper; | ||
| 7 | @Mapper | 7 | @Mapper |
| 8 | public interface MpMenuMapper extends BaseMapperX<MpMenuDO> { | 8 | public interface MpMenuMapper extends BaseMapperX<MpMenuDO> { |
| 9 | 9 | ||
| 10 | + default MpMenuDO selectByAppId(String appId) { | ||
| 11 | + return selectOne(MpMenuDO::getAppId, appId); | ||
| 12 | + } | ||
| 13 | + | ||
| 10 | } | 14 | } |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.framework.mp.core.util; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import me.chanjar.weixin.common.api.WxConsts; | ||
| 6 | + | ||
| 7 | +import javax.validation.Validator; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 公众号工具类 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +@Slf4j | ||
| 15 | +public class MpUtils { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * Text 类型的消息,参数校验 Group | ||
| 19 | + */ | ||
| 20 | + public interface TextGroup {} | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * Image 类型的消息,参数校验 Group | ||
| 24 | + */ | ||
| 25 | + public interface ImageGroup {} | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * Voice 类型的消息,参数校验 Group | ||
| 29 | + */ | ||
| 30 | + public interface VoiceGroup {} | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * Video 类型的消息,参数校验 Group | ||
| 34 | + */ | ||
| 35 | + public interface VideoGroup {} | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * News 类型的消息,参数校验 Group | ||
| 39 | + */ | ||
| 40 | + public interface NewsGroup {} | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 校验消息的格式是否符合要求 | ||
| 44 | + * | ||
| 45 | + * @param type 类型 | ||
| 46 | + * @param message 消息 | ||
| 47 | + */ | ||
| 48 | + public static void validateMessage(Validator validator, String type, Object message) { | ||
| 49 | + // 获得对应的校验 group | ||
| 50 | + Class<?> group; | ||
| 51 | + switch (type) { | ||
| 52 | + case WxConsts.XmlMsgType.TEXT: | ||
| 53 | + group = TextGroup.class; | ||
| 54 | + break; | ||
| 55 | + case WxConsts.XmlMsgType.IMAGE: | ||
| 56 | + group = ImageGroup.class; | ||
| 57 | + break; | ||
| 58 | + case WxConsts.XmlMsgType.VOICE: | ||
| 59 | + group = VoiceGroup.class; | ||
| 60 | + break; | ||
| 61 | + case WxConsts.XmlMsgType.VIDEO: | ||
| 62 | + group = VideoGroup.class; | ||
| 63 | + break; | ||
| 64 | + case WxConsts.XmlMsgType.NEWS: | ||
| 65 | + group = NewsGroup.class; | ||
| 66 | + break; | ||
| 67 | + default: | ||
| 68 | + log.error("[validateMessage][未知的消息类型({})]", message); | ||
| 69 | + throw new IllegalArgumentException("不支持的消息类型:" + type); | ||
| 70 | + } | ||
| 71 | + // 执行校验 | ||
| 72 | + ValidationUtils.validate(validator, message, group); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | +} |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java
| 1 | package cn.iocoder.yudao.module.mp.service.handler.menu; | 1 | package cn.iocoder.yudao.module.mp.service.handler.menu; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; | ||
| 4 | +import cn.iocoder.yudao.module.mp.service.menu.MpMenuService; | ||
| 3 | import me.chanjar.weixin.common.session.WxSessionManager; | 5 | import me.chanjar.weixin.common.session.WxSessionManager; |
| 6 | +import me.chanjar.weixin.mp.api.WxMpMenuService; | ||
| 4 | import me.chanjar.weixin.mp.api.WxMpMessageHandler; | 7 | import me.chanjar.weixin.mp.api.WxMpMessageHandler; |
| 5 | import me.chanjar.weixin.mp.api.WxMpService; | 8 | import me.chanjar.weixin.mp.api.WxMpService; |
| 6 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | 9 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; |
| 7 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | 10 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; |
| 8 | import org.springframework.stereotype.Component; | 11 | import org.springframework.stereotype.Component; |
| 9 | 12 | ||
| 13 | +import javax.annotation.Resource; | ||
| 10 | import java.util.Map; | 14 | import java.util.Map; |
| 11 | 15 | ||
| 12 | import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; | 16 | import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; |
| @@ -14,24 +18,20 @@ import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; | @@ -14,24 +18,20 @@ import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; | ||
| 14 | /** | 18 | /** |
| 15 | * 自定义菜单的事件处理器 | 19 | * 自定义菜单的事件处理器 |
| 16 | * | 20 | * |
| 17 | - * // TODO 芋艿:待实现 | 21 | + * 逻辑:用户点击菜单时,触发对应的回复 |
| 22 | + * | ||
| 23 | + * @author 芋道源码 | ||
| 18 | */ | 24 | */ |
| 19 | @Component | 25 | @Component |
| 20 | public class MenuHandler implements WxMpMessageHandler { | 26 | public class MenuHandler implements WxMpMessageHandler { |
| 21 | 27 | ||
| 22 | - @Override | ||
| 23 | - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | ||
| 24 | - Map<String, Object> context, WxMpService weixinService, | ||
| 25 | - WxSessionManager sessionManager) { | ||
| 26 | - | ||
| 27 | - String msg = wxMessage.getEventKey(); | ||
| 28 | - if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) { | ||
| 29 | - return null; | ||
| 30 | - } | 28 | + @Resource |
| 29 | + private MpMenuService mpMenuService; | ||
| 31 | 30 | ||
| 32 | - return WxMpXmlOutMessage.TEXT().content(msg) | ||
| 33 | - .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) | ||
| 34 | - .build(); | 31 | + @Override |
| 32 | + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, | ||
| 33 | + WxMpService weixinService, WxSessionManager sessionManager) { | ||
| 34 | + return mpMenuService.reply(MpContextHolder.getAppId(), wxMessage.getEventKey(), wxMessage.getFromUser()); | ||
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | } | 37 | } |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java
| @@ -29,7 +29,7 @@ public class MessageReceiveHandler implements WxMpMessageHandler { | @@ -29,7 +29,7 @@ public class MessageReceiveHandler implements WxMpMessageHandler { | ||
| 29 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, | 29 | public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, |
| 30 | WxMpService wxMpService, WxSessionManager sessionManager) { | 30 | WxMpService wxMpService, WxSessionManager sessionManager) { |
| 31 | log.info("[handle][接收到请求消息,内容:{}]", wxMessage); | 31 | log.info("[handle][接收到请求消息,内容:{}]", wxMessage); |
| 32 | - mpMessageService.createFromUser(MpContextHolder.getAppId(), wxMessage); | 32 | + mpMessageService.receiveMessage(MpContextHolder.getAppId(), wxMessage); |
| 33 | return null; | 33 | return null; |
| 34 | } | 34 | } |
| 35 | 35 |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java
| @@ -4,6 +4,8 @@ import javax.validation.*; | @@ -4,6 +4,8 @@ import javax.validation.*; | ||
| 4 | 4 | ||
| 5 | import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*; | 5 | import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*; |
| 6 | import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; | 6 | import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; |
| 7 | +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | ||
| 8 | +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | ||
| 7 | 9 | ||
| 8 | /** | 10 | /** |
| 9 | * 微信菜单 Service 接口 | 11 | * 微信菜单 Service 接口 |
| @@ -35,4 +37,14 @@ public interface MpMenuService { | @@ -35,4 +37,14 @@ public interface MpMenuService { | ||
| 35 | */ | 37 | */ |
| 36 | MpMenuDO getMenu(Long id); | 38 | MpMenuDO getMenu(Long id); |
| 37 | 39 | ||
| 40 | + /** | ||
| 41 | + * 用户点击菜单按钮时,回复对应的消息 | ||
| 42 | + * | ||
| 43 | + * @param appId 公众号 AppId | ||
| 44 | + * @param key 菜单按钮的标识 | ||
| 45 | + * @param openid 用户的 openid | ||
| 46 | + * @return 消息 | ||
| 47 | + */ | ||
| 48 | + WxMpXmlOutMessage reply(String appId, String key, String openid); | ||
| 49 | + | ||
| 38 | } | 50 | } |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java
| 1 | package cn.iocoder.yudao.module.mp.service.menu; | 1 | package cn.iocoder.yudao.module.mp.service.menu; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.lang.Assert; | ||
| 5 | +import cn.hutool.core.util.StrUtil; | ||
| 3 | import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; | 6 | import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; |
| 7 | +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | ||
| 4 | import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; | 8 | import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; |
| 5 | import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; | 9 | import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; |
| 10 | +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; | ||
| 11 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | import me.chanjar.weixin.common.bean.menu.WxMenu; | 12 | import me.chanjar.weixin.common.bean.menu.WxMenu; |
| 7 | import me.chanjar.weixin.common.error.WxErrorException; | 13 | import me.chanjar.weixin.common.error.WxErrorException; |
| 8 | import me.chanjar.weixin.mp.api.WxMpService; | 14 | import me.chanjar.weixin.mp.api.WxMpService; |
| 15 | +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | ||
| 9 | import org.springframework.context.annotation.Lazy; | 16 | import org.springframework.context.annotation.Lazy; |
| 10 | import org.springframework.stereotype.Service; | 17 | import org.springframework.stereotype.Service; |
| 11 | 18 | ||
| @@ -27,6 +34,7 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; | @@ -27,6 +34,7 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; | ||
| 27 | */ | 34 | */ |
| 28 | @Service | 35 | @Service |
| 29 | @Validated | 36 | @Validated |
| 37 | +@Slf4j | ||
| 30 | public class MpMenuServiceImpl implements MpMenuService { | 38 | public class MpMenuServiceImpl implements MpMenuService { |
| 31 | 39 | ||
| 32 | @Resource | 40 | @Resource |
| @@ -77,4 +85,49 @@ public class MpMenuServiceImpl implements MpMenuService { | @@ -77,4 +85,49 @@ public class MpMenuServiceImpl implements MpMenuService { | ||
| 77 | return mpMenuMapper.selectById(id); | 85 | return mpMenuMapper.selectById(id); |
| 78 | } | 86 | } |
| 79 | 87 | ||
| 88 | + @Override | ||
| 89 | + public WxMpXmlOutMessage reply(String appId, String key, String openid) { | ||
| 90 | + // 获得菜单 | ||
| 91 | + MpMenuDO menu = mpMenuMapper.selectByAppId(appId); | ||
| 92 | + if (menu == null) { | ||
| 93 | + log.error("[reply][appId({}) 找不到对应的菜单]", appId); | ||
| 94 | + return null; | ||
| 95 | + } | ||
| 96 | + // 匹配对应的按钮 | ||
| 97 | + MpMenuDO.Button button = getMenuButton(menu, key); | ||
| 98 | + if (button == null) { | ||
| 99 | + log.error("[reply][appId({}) key({}) 找不到对应的菜单按钮]", appId, key); | ||
| 100 | + return null; | ||
| 101 | + } | ||
| 102 | + // 按钮必须要有消息类型,不然后续无法回复消息 | ||
| 103 | + if (StrUtil.isEmpty(button.getMessageType())) { | ||
| 104 | + log.error("[reply][appId({}) key({}) 不存在消息类型({})]", appId, key, button); | ||
| 105 | + return null; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + // 回复消息 | ||
| 109 | + return null; | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + private MpMenuDO.Button getMenuButton(MpMenuDO menu, String key) { | ||
| 113 | + // 先查询子按钮 | ||
| 114 | + for (MpMenuDO.Button button : menu.getButtons()) { | ||
| 115 | + if (CollUtil.isEmpty(button.getSubButtons())) { | ||
| 116 | + continue; | ||
| 117 | + } | ||
| 118 | + for (MpMenuDO.Button subButton : button.getSubButtons()) { | ||
| 119 | + if (StrUtil.equals(subButton.getKey(), key)) { | ||
| 120 | + return subButton; | ||
| 121 | + } | ||
| 122 | + } | ||
| 123 | + } | ||
| 124 | + // 再查询父按钮 | ||
| 125 | + for (MpMenuDO.Button button : menu.getButtons()) { | ||
| 126 | + if (StrUtil.equals(button.getKey(), key)) { | ||
| 127 | + return button; | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + return null; | ||
| 131 | + } | ||
| 132 | + | ||
| 80 | } | 133 | } |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java
| @@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message; | @@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.lang.Assert; | 4 | import cn.hutool.core.lang.Assert; |
| 5 | +import cn.iocoder.yudao.module.mp.convert.message.MpAutoReplyConvert; | ||
| 5 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | 6 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; |
| 6 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; | 7 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; |
| 7 | import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper; | 8 | import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper; |
| 8 | import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; | 9 | import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; |
| 9 | import cn.iocoder.yudao.module.mp.service.account.MpAccountService; | 10 | import cn.iocoder.yudao.module.mp.service.account.MpAccountService; |
| 11 | +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; | ||
| 10 | import me.chanjar.weixin.common.api.WxConsts; | 12 | import me.chanjar.weixin.common.api.WxConsts; |
| 11 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | 13 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; |
| 12 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | 14 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; |
| @@ -58,7 +60,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService { | @@ -58,7 +60,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService { | ||
| 58 | MpAutoReplyDO reply = CollUtil.getFirst(replies); | 60 | MpAutoReplyDO reply = CollUtil.getFirst(replies); |
| 59 | 61 | ||
| 60 | // 第二步,基于自动回复,创建消息 | 62 | // 第二步,基于自动回复,创建消息 |
| 61 | - return mpMessageService.createFromAutoReply(wxMessage.getFromUser(), reply); | 63 | + MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply); |
| 64 | + return mpMessageService.sendOutMessage(sendReqBO); | ||
| 62 | } | 65 | } |
| 63 | 66 | ||
| 64 | @Override | 67 | @Override |
| @@ -69,7 +72,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService { | @@ -69,7 +72,8 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService { | ||
| 69 | : buildDefaultSubscribeAutoReply(appId); // 如果不存在,提供一个默认末班 | 72 | : buildDefaultSubscribeAutoReply(appId); // 如果不存在,提供一个默认末班 |
| 70 | 73 | ||
| 71 | // 第二步,基于自动回复,创建消息 | 74 | // 第二步,基于自动回复,创建消息 |
| 72 | - return mpMessageService.createFromAutoReply(wxMessage.getFromUser(), reply); | 75 | + MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply); |
| 76 | + return mpMessageService.sendOutMessage(sendReqBO); | ||
| 73 | } | 77 | } |
| 74 | 78 | ||
| 75 | private MpAutoReplyDO buildDefaultSubscribeAutoReply(String appId) { | 79 | private MpAutoReplyDO buildDefaultSubscribeAutoReply(String appId) { |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java
| @@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message; | @@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.service.message; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; | 4 | import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; |
| 5 | -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; | ||
| 6 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; | 5 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; |
| 6 | +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; | ||
| 7 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | 7 | import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; |
| 8 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | 8 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; |
| 9 | 9 | ||
| 10 | +import javax.validation.Valid; | ||
| 11 | + | ||
| 10 | /** | 12 | /** |
| 11 | * 粉丝消息表 Service 接口 | 13 | * 粉丝消息表 Service 接口 |
| 12 | * | 14 | * |
| @@ -24,20 +26,23 @@ public interface MpMessageService { | @@ -24,20 +26,23 @@ public interface MpMessageService { | ||
| 24 | PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO); | 26 | PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO); |
| 25 | 27 | ||
| 26 | /** | 28 | /** |
| 27 | - * 保存粉丝消息,来自用户发送 | 29 | + * 从公众号,接收到用户消息 |
| 28 | * | 30 | * |
| 29 | * @param appId 微信公众号 appId | 31 | * @param appId 微信公众号 appId |
| 30 | * @param wxMessage 消息 | 32 | * @param wxMessage 消息 |
| 31 | */ | 33 | */ |
| 32 | - void createFromUser(String appId, WxMpXmlMessage wxMessage); | 34 | + void receiveMessage(String appId, WxMpXmlMessage wxMessage); |
| 33 | 35 | ||
| 34 | /** | 36 | /** |
| 35 | - * 创建粉丝消息,通过自动回复 | 37 | + * 使用公众号,给用户回复消息 |
| 38 | + * | ||
| 39 | + * 例如说:自动回复、客服消息、菜单回复消息等场景 | ||
| 40 | + * | ||
| 41 | + * 注意,该方法只是返回 WxMpXmlOutMessage 对象,不会真的发送消息 | ||
| 36 | * | 42 | * |
| 37 | - * @param openid 公众号粉丝 openid | ||
| 38 | - * @param reply 自动回复 | 43 | + * @param sendReqBO 消息内容 |
| 39 | * @return 微信回复消息 XML | 44 | * @return 微信回复消息 XML |
| 40 | */ | 45 | */ |
| 41 | - WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply); | 46 | + WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO); |
| 42 | 47 | ||
| 43 | } | 48 | } |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
| @@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.mp.service.message; | @@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.mp.service.message; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.io.FileTypeUtil; | 3 | import cn.hutool.core.io.FileTypeUtil; |
| 4 | import cn.hutool.core.io.FileUtil; | 4 | import cn.hutool.core.io.FileUtil; |
| 5 | -import cn.hutool.core.io.IoUtil; | ||
| 6 | import cn.hutool.core.lang.Assert; | 5 | import cn.hutool.core.lang.Assert; |
| 7 | import cn.hutool.core.util.StrUtil; | 6 | import cn.hutool.core.util.StrUtil; |
| 8 | -import cn.iocoder.yudao.framework.common.util.io.FileUtils; | 7 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | import cn.iocoder.yudao.module.infra.api.file.FileApi; | 8 | import cn.iocoder.yudao.module.infra.api.file.FileApi; |
| 9 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; | ||
| 10 | import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; | 10 | import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; |
| 11 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | 11 | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; |
| 12 | -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; | ||
| 13 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; | 12 | import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; |
| 14 | import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; | 13 | import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; |
| 14 | +import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper; | ||
| 15 | import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; | 15 | import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; |
| 16 | import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; | 16 | import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; |
| 17 | +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; | ||
| 17 | import cn.iocoder.yudao.module.mp.service.account.MpAccountService; | 18 | import cn.iocoder.yudao.module.mp.service.account.MpAccountService; |
| 19 | +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; | ||
| 18 | import cn.iocoder.yudao.module.mp.service.user.MpUserService; | 20 | import cn.iocoder.yudao.module.mp.service.user.MpUserService; |
| 19 | -import lombok.SneakyThrows; | ||
| 20 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
| 21 | import me.chanjar.weixin.common.error.WxErrorException; | 22 | import me.chanjar.weixin.common.error.WxErrorException; |
| 22 | import me.chanjar.weixin.mp.api.WxMpService; | 23 | import me.chanjar.weixin.mp.api.WxMpService; |
| @@ -24,20 +25,12 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | @@ -24,20 +25,12 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | ||
| 24 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | 25 | import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; |
| 25 | import org.springframework.context.annotation.Lazy; | 26 | import org.springframework.context.annotation.Lazy; |
| 26 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
| 27 | - | ||
| 28 | -import javax.annotation.Resource; | ||
| 29 | - | ||
| 30 | import org.springframework.validation.annotation.Validated; | 28 | import org.springframework.validation.annotation.Validated; |
| 31 | 29 | ||
| 32 | -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*; | ||
| 33 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 34 | - | ||
| 35 | -import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper; | ||
| 36 | - | 30 | +import javax.annotation.Resource; |
| 31 | +import javax.validation.Validator; | ||
| 37 | import java.io.File; | 32 | import java.io.File; |
| 38 | 33 | ||
| 39 | -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 40 | - | ||
| 41 | /** | 34 | /** |
| 42 | * 粉丝消息表 Service 实现类 | 35 | * 粉丝消息表 Service 实现类 |
| 43 | * | 36 | * |
| @@ -64,13 +57,16 @@ public class MpMessageServiceImpl implements MpMessageService { | @@ -64,13 +57,16 @@ public class MpMessageServiceImpl implements MpMessageService { | ||
| 64 | @Resource | 57 | @Resource |
| 65 | private FileApi fileApi; | 58 | private FileApi fileApi; |
| 66 | 59 | ||
| 60 | + @Resource | ||
| 61 | + private Validator validator; | ||
| 62 | + | ||
| 67 | @Override | 63 | @Override |
| 68 | public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) { | 64 | public PageResult<MpMessageDO> getWxFansMsgPage(MpMessagePageReqVO pageReqVO) { |
| 69 | return mpMessageMapper.selectPage(pageReqVO); | 65 | return mpMessageMapper.selectPage(pageReqVO); |
| 70 | } | 66 | } |
| 71 | 67 | ||
| 72 | @Override | 68 | @Override |
| 73 | - public void createFromUser(String appId, WxMpXmlMessage wxMessage) { | 69 | + public void receiveMessage(String appId, WxMpXmlMessage wxMessage) { |
| 74 | WxMpService mpService = mpServiceFactory.getRequiredMpService(appId); | 70 | WxMpService mpService = mpServiceFactory.getRequiredMpService(appId); |
| 75 | // 获得关联信息 | 71 | // 获得关联信息 |
| 76 | MpAccountDO account = mpAccountService.getAccountFromCache(appId); | 72 | MpAccountDO account = mpAccountService.getAccountFromCache(appId); |
| @@ -88,24 +84,21 @@ public class MpMessageServiceImpl implements MpMessageService { | @@ -88,24 +84,21 @@ public class MpMessageServiceImpl implements MpMessageService { | ||
| 88 | message.setThumbMediaUrl(mediaDownload(mpService, message.getThumbMediaId())); | 84 | message.setThumbMediaUrl(mediaDownload(mpService, message.getThumbMediaId())); |
| 89 | } | 85 | } |
| 90 | mpMessageMapper.insert(message); | 86 | mpMessageMapper.insert(message); |
| 91 | - | ||
| 92 | -// WxConsts.MenuButtonType.VIEW TODO 芋艿:待测试 | ||
| 93 | -// wxMessage.getEventKey() | ||
| 94 | - | ||
| 95 | -// WxConsts.MenuButtonType.CLICK | ||
| 96 | -// wxMessage.getEventKey() | ||
| 97 | } | 87 | } |
| 98 | 88 | ||
| 99 | @Override | 89 | @Override |
| 100 | - public WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply) { | 90 | + public WxMpXmlOutMessage sendOutMessage(MpMessageSendOutReqBO sendReqBO) { |
| 91 | + // 校验消息格式 | ||
| 92 | + MpUtils.validateMessage(validator, sendReqBO.getType(), sendReqBO); | ||
| 93 | + | ||
| 101 | // 获得关联信息 | 94 | // 获得关联信息 |
| 102 | - MpAccountDO account = mpAccountService.getAccountFromCache(reply.getAppId()); | ||
| 103 | - Assert.notNull(account, "公众号账号({}) 不存在", reply.getAppId()); | ||
| 104 | - MpUserDO user = mpUserService.getUser(reply.getAppId(), openid); | ||
| 105 | - Assert.notNull(user, "公众号粉丝({}/{}) 不存在", reply.getAppId(), openid); | 95 | + MpAccountDO account = mpAccountService.getAccountFromCache(sendReqBO.getAppId()); |
| 96 | + Assert.notNull(account, "公众号账号({}) 不存在", sendReqBO.getAppId()); | ||
| 97 | + MpUserDO user = mpUserService.getUser(sendReqBO.getAppId(), sendReqBO.getOpenid()); | ||
| 98 | + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid()); | ||
| 106 | 99 | ||
| 107 | // 记录消息 | 100 | // 记录消息 |
| 108 | - MpMessageDO message = MpMessageConvert.INSTANCE.convert(reply, account, user); | 101 | + MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user); |
| 109 | message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); | 102 | message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); |
| 110 | mpMessageMapper.insert(message); | 103 | mpMessageMapper.insert(message); |
| 111 | 104 |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.service.message.bo; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; | ||
| 4 | +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; | ||
| 5 | +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; | ||
| 6 | +import lombok.Data; | ||
| 7 | +import me.chanjar.weixin.common.api.WxConsts; | ||
| 8 | + | ||
| 9 | +import javax.validation.Valid; | ||
| 10 | +import javax.validation.constraints.NotEmpty; | ||
| 11 | +import javax.validation.constraints.NotNull; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * 公众号消息发送 Request BO | ||
| 15 | + * | ||
| 16 | + * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给用户发送消息,所以使用该 BO 统一承接 | ||
| 17 | + * | ||
| 18 | + * @author 芋道源码 | ||
| 19 | + */ | ||
| 20 | +@Data | ||
| 21 | +public class MpMessageSendOutReqBO { | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 公众号 appId | ||
| 25 | + */ | ||
| 26 | + @NotEmpty(message = "公众号 appId 不能为空") | ||
| 27 | + private String appId; | ||
| 28 | + /** | ||
| 29 | + * 公众号用户 openid | ||
| 30 | + */ | ||
| 31 | + @NotEmpty(message = "公众号用户 openid 不能为空") | ||
| 32 | + private String openid; | ||
| 33 | + | ||
| 34 | + // ========== 消息内容 ========== | ||
| 35 | + /** | ||
| 36 | + * 消息类型 | ||
| 37 | + * | ||
| 38 | + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS | ||
| 39 | + */ | ||
| 40 | + @NotEmpty(message = "消息类型不能为空") | ||
| 41 | + public String type; | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 消息内容 | ||
| 45 | + * | ||
| 46 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT | ||
| 47 | + */ | ||
| 48 | + @NotEmpty(message = "消息内容不能为空", groups = TextGroup.class) | ||
| 49 | + private String content; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 媒体 id | ||
| 53 | + * | ||
| 54 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO | ||
| 55 | + */ | ||
| 56 | + @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class}) | ||
| 57 | + private String mediaId; | ||
| 58 | + /** | ||
| 59 | + * 媒体 URL | ||
| 60 | + * | ||
| 61 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO | ||
| 62 | + */ | ||
| 63 | + @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class}) | ||
| 64 | + private String mediaUrl; | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * 标题 | ||
| 68 | + * | ||
| 69 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO | ||
| 70 | + */ | ||
| 71 | + @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class) | ||
| 72 | + private String title; | ||
| 73 | + /** | ||
| 74 | + * 描述 | ||
| 75 | + * | ||
| 76 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO | ||
| 77 | + */ | ||
| 78 | + @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class) | ||
| 79 | + private String description; | ||
| 80 | + | ||
| 81 | + /** | ||
| 82 | + * 图文消息 | ||
| 83 | + * | ||
| 84 | + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS | ||
| 85 | + */ | ||
| 86 | + @Valid | ||
| 87 | + @NotNull(message = "图文消息不能为空", groups = NewsGroup.class) | ||
| 88 | + private MpMessageDO.Article article; | ||
| 89 | + | ||
| 90 | +} |