Commit 9cf88d1929abe9fc24124d41fdc3119d30e30da8

Authored by YunaiV
1 parent f8eed5dd

mp:后端增加发送客服消息接口

yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http
... ... @@ -3,3 +3,31 @@ GET {{baseUrl}}/mp/message/page?accountId=1&pageNo=1&pageSize=10
3 3 Content-Type: application/json
4 4 Authorization: Bearer {{token}}
5 5 tenant-id: {{adminTenentId}}
  6 +
  7 +### 请求 /mp/message/send 接口 => 成功(文本)
  8 +POST {{baseUrl}}/mp/message/send
  9 +Content-Type: application/json
  10 +Authorization: Bearer {{token}}
  11 +tenant-id: {{adminTenentId}}
  12 +
  13 +{
  14 + "userId": 3,
  15 + "type": "text",
  16 + "content": "测试消息"
  17 +}
  18 +
  19 +### 请求 /mp/message/send 接口 => 成功(音乐)
  20 +POST {{baseUrl}}/mp/message/send
  21 +Content-Type: application/json
  22 +Authorization: Bearer {{token}}
  23 +tenant-id: {{adminTenentId}}
  24 +
  25 +{
  26 + "userId": 3,
  27 + "type": "music",
  28 + "title": "测试音乐标题",
  29 + "description": "测试音乐内容",
  30 + "musicUrl": "https://www.iocoder.cn/xx.mp3",
  31 + "hqMusicUrl": "https://www.iocoder.cn/xx_high.mp3",
  32 + "thumbMediaId": "s98Iveeg9vDVFwa9q0u8-zSfdKe3xIzAm7wCrFE4WKGPIo4d9qAhtC-n6qvnyWyH"
  33 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
6 6 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
  7 +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
7 8 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
8 9 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
9 10 import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
... ... @@ -11,9 +12,7 @@ import io.swagger.annotations.Api;
11 12 import io.swagger.annotations.ApiOperation;
12 13 import org.springframework.security.access.prepost.PreAuthorize;
13 14 import org.springframework.validation.annotation.Validated;
14   -import org.springframework.web.bind.annotation.GetMapping;
15   -import org.springframework.web.bind.annotation.RequestMapping;
16   -import org.springframework.web.bind.annotation.RestController;
  15 +import org.springframework.web.bind.annotation.*;
17 16  
18 17 import javax.annotation.Resource;
19 18 import javax.validation.Valid;
... ... @@ -37,4 +36,11 @@ public class MpMessageController {
37 36 return success(MpMessageConvert.INSTANCE.convertPage(pageResult));
38 37 }
39 38  
  39 + @PostMapping("/send")
  40 +// @ApiOperation("获得粉丝消息表分页")
  41 +// @PreAuthorize("@ss.hasPermission('mp:message:query')")
  42 + public CommonResult<Long> sendMessage(@Valid @RequestBody MpMessageSendReqVO reqVO) {
  43 + return success(mpMessageService.sendKefuMessage(reqVO));
  44 + }
  45 +
40 46 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/MpMessageSendReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.controller.admin.message.vo;
  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 io.swagger.annotations.ApiModel;
  6 +import io.swagger.annotations.ApiModelProperty;
  7 +import lombok.Data;
  8 +
  9 +import javax.validation.Valid;
  10 +import javax.validation.constraints.NotEmpty;
  11 +import javax.validation.constraints.NotNull;
  12 +import java.util.List;
  13 +
  14 +@ApiModel("管理后台 - 公众号消息发送 Request VO")
  15 +@Data
  16 +public class MpMessageSendReqVO {
  17 +
  18 + @ApiModelProperty(value = "公众号粉丝的编号", required = true, example = "1024")
  19 + @NotNull(message = "公众号粉丝的编号不能为空")
  20 + private Long userId;
  21 +
  22 + // ========== 消息内容 ==========
  23 +
  24 + @ApiModelProperty(value = "消息类型", required = true, example = "text", notes = "TEXT/IMAGE/VOICE/VIDEO/NEWS")
  25 + @NotEmpty(message = "消息类型不能为空")
  26 + public String type;
  27 +
  28 + @ApiModelProperty(value = "消息内容", required = true, example = "你好呀")
  29 + @NotEmpty(message = "消息内容不能为空", groups = TextGroup.class)
  30 + private String content;
  31 +
  32 + @ApiModelProperty(value = "媒体 ID", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
  33 + @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
  34 + private String mediaId;
  35 +
  36 + @ApiModelProperty(value = "标题", required = true, example = "没有标题")
  37 + @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
  38 + private String title;
  39 +
  40 + @ApiModelProperty(value = "描述", required = true, example = "你猜")
  41 + @NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
  42 + private String description;
  43 +
  44 + @ApiModelProperty(value = "缩略图的媒体 id", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
  45 + @NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicGroup.class)
  46 + private String thumbMediaId;
  47 +
  48 + @ApiModelProperty(value = "图文消息", required = true)
  49 + @Valid
  50 + @NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
  51 + private List<MpMessageDO.Article> articles;
  52 +
  53 + @ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
  54 + private String musicUrl;
  55 +
  56 + @ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
  57 + private String hqMusicUrl;
  58 +
  59 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java
... ... @@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.convert.message;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
  5 +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
5 6 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
6 7 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
7 8 import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
8 9 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
9 10 import me.chanjar.weixin.common.api.WxConsts;
  11 +import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
10 12 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
11 13 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
12 14 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
... ... @@ -16,7 +18,6 @@ import org.mapstruct.Mapping;
16 18 import org.mapstruct.Mappings;
17 19 import org.mapstruct.factory.Mappers;
18 20  
19   -import java.util.Collections;
20 21 import java.util.List;
21 22  
22 23 @Mapper
... ... @@ -30,12 +31,6 @@ public interface MpMessageConvert {
30 31  
31 32 PageResult<MpMessageRespVO> convertPage(PageResult<MpMessageDO> page);
32 33  
33   - @Mappings(value = {
34   - @Mapping(source = "msgType", target = "type"),
35   - @Mapping(target = "createTime", ignore = true),
36   - })
37   - MpMessageDO convert(WxMpXmlMessage wxMessage);
38   -
39 34 default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) {
40 35 MpMessageDO message = convert(wxMessage);
41 36 if (account != null) {
... ... @@ -46,6 +41,11 @@ public interface MpMessageConvert {
46 41 }
47 42 return message;
48 43 }
  44 + @Mappings(value = {
  45 + @Mapping(source = "msgType", target = "type"),
  46 + @Mapping(target = "createTime", ignore = true),
  47 + })
  48 + MpMessageDO convert(WxMpXmlMessage bean);
49 49  
50 50 default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) {
51 51 // 构建消息
... ... @@ -65,6 +65,11 @@ public interface MpMessageConvert {
65 65 break;
66 66 case WxConsts.XmlMsgType.NEWS: // 5. 图文
67 67 message.setArticles(sendReqBO.getArticles());
  68 + case WxConsts.XmlMsgType.MUSIC: // 6. 音乐
  69 + message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription())
  70 + .setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl())
  71 + .setThumbMediaId(sendReqBO.getThumbMediaId());
  72 +// .setThumbMediaUrl(sendReqBO.getThumbMediaUrl()); TODO 芋艿:url 待确定
68 73 break;
69 74 default:
70 75 throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
... ... @@ -81,33 +86,88 @@ public interface MpMessageConvert {
81 86 }
82 87  
83 88 default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) {
84   - BaseBuilder<?, ? extends WxMpXmlOutMessage> messageBuilder;
  89 + BaseBuilder<?, ? extends WxMpXmlOutMessage> builder;
85 90 // 个性化字段
86 91 switch (message.getType()) {
87 92 case WxConsts.XmlMsgType.TEXT:
88   - messageBuilder = WxMpXmlOutMessage.TEXT().content(message.getContent());
  93 + builder = WxMpXmlOutMessage.TEXT().content(message.getContent());
89 94 break;
90 95 case WxConsts.XmlMsgType.IMAGE:
91   - messageBuilder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());
  96 + builder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());
92 97 break;
93 98 case WxConsts.XmlMsgType.VOICE:
94   - messageBuilder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());
  99 + builder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());
95 100 break;
96 101 case WxConsts.XmlMsgType.VIDEO:
97   - messageBuilder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())
  102 + builder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())
98 103 .title(message.getTitle()).description(message.getDescription());
99 104 break;
100 105 case WxConsts.XmlMsgType.NEWS:
101   - messageBuilder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));
  106 + builder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));
  107 + break;
  108 + case WxConsts.XmlMsgType.MUSIC:
  109 + builder = WxMpXmlOutMessage.MUSIC().title(message.getTitle()).description(message.getDescription())
  110 + .musicUrl(message.getMusicUrl()).hqMusicUrl(message.getHqMusicUrl())
  111 + .thumbMediaId(message.getThumbMediaId());
102 112 break;
103 113 default:
104 114 throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
105 115 }
106 116 // 通用字段
107   - messageBuilder.fromUser(account.getAccount());
108   - messageBuilder.toUser(message.getOpenid());
109   - return messageBuilder.build();
  117 + builder.fromUser(account.getAccount());
  118 + builder.toUser(message.getOpenid());
  119 + return builder.build();
110 120 }
111 121 List<WxMpXmlOutNewsMessage.Item> convertList02(List<MpMessageDO.Article> list);
112 122  
  123 + default WxMpKefuMessage convert(MpMessageSendReqVO sendReqVO, MpUserDO user) {
  124 + me.chanjar.weixin.mp.builder.kefu.BaseBuilder<?> builder;
  125 + // 个性化字段
  126 + switch (sendReqVO.getType()) {
  127 + case WxConsts.KefuMsgType.TEXT:
  128 + builder = WxMpKefuMessage.TEXT().content(sendReqVO.getContent());
  129 + break;
  130 + case WxConsts.KefuMsgType.IMAGE:
  131 + builder = WxMpKefuMessage.IMAGE().mediaId(sendReqVO.getMediaId());
  132 + break;
  133 + case WxConsts.KefuMsgType.VOICE:
  134 + builder = WxMpKefuMessage.VOICE().mediaId(sendReqVO.getMediaId());
  135 + break;
  136 + case WxConsts.KefuMsgType.VIDEO:
  137 + builder = WxMpKefuMessage.VIDEO().mediaId(sendReqVO.getMediaId())
  138 + .title(sendReqVO.getTitle()).description(sendReqVO.getDescription());
  139 + break;
  140 + case WxConsts.KefuMsgType.NEWS:
  141 + builder = WxMpKefuMessage.NEWS().articles(convertList03(sendReqVO.getArticles()));
  142 + break;
  143 + case WxConsts.KefuMsgType.MUSIC:
  144 + builder = WxMpKefuMessage.MUSIC().title(sendReqVO.getTitle()).description(sendReqVO.getDescription())
  145 + .thumbMediaId(sendReqVO.getThumbMediaId())
  146 + .musicUrl(sendReqVO.getMusicUrl()).hqMusicUrl(sendReqVO.getHqMusicUrl());
  147 + break;
  148 + default:
  149 + throw new IllegalArgumentException("不支持的消息类型:" + sendReqVO.getType());
  150 + }
  151 + // 通用字段
  152 + builder.toUser(user.getOpenid());
  153 + return builder.build();
  154 + }
  155 + List<WxMpKefuMessage.WxArticle> convertList03(List<MpMessageDO.Article> list);
  156 +
  157 + default MpMessageDO convert(WxMpKefuMessage wxMessage, MpAccountDO account, MpUserDO user) {
  158 + MpMessageDO message = convert(wxMessage);
  159 + if (account != null) {
  160 + message.setAccountId(account.getId()).setAppId(account.getAppId());
  161 + }
  162 + if (user != null) {
  163 + message.setUserId(user.getId()).setOpenid(user.getOpenid());
  164 + }
  165 + return message;
  166 + }
  167 + @Mappings(value = {
  168 + @Mapping(source = "msgType", target = "type"),
  169 + @Mapping(target = "createTime", ignore = true),
  170 + })
  171 + MpMessageDO convert(WxMpKefuMessage bean);
  172 +
113 173 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java
... ... @@ -124,7 +124,7 @@ public class MpMessageDO extends BaseDO {
124 124 /**
125 125 * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
126 126 *
127   - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
  127 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
128 128 */
129 129 private String thumbMediaId;
130 130 /**
... ... @@ -176,6 +176,21 @@ public class MpMessageDO extends BaseDO {
176 176 @TableField(typeHandler = ArticleTypeHandler.class)
177 177 private List<Article> articles;
178 178  
  179 + /**
  180 + * 音乐链接
  181 + *
  182 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
  183 + */
  184 + private String musicUrl;
  185 + /**
  186 + * 高质量音乐链接
  187 + *
  188 + * WIFI 环境优先使用该链接播放音乐
  189 + *
  190 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
  191 + */
  192 + private String hqMusicUrl;
  193 +
179 194 // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
180 195  
181 196 /**
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java
... ... @@ -40,6 +40,11 @@ public class MpUtils {
40 40 public interface NewsGroup {}
41 41  
42 42 /**
  43 + * Music 类型的消息,参数校验 Group
  44 + */
  45 + public interface MusicGroup {}
  46 +
  47 + /**
43 48 * 校验消息的格式是否符合要求
44 49 *
45 50 * @param type 类型
... ... @@ -64,6 +69,9 @@ public class MpUtils {
64 69 case WxConsts.XmlMsgType.NEWS:
65 70 group = NewsGroup.class;
66 71 break;
  72 + case WxConsts.XmlMsgType.MUSIC:
  73 + group = MusicGroup.class;
  74 + break;
67 75 default:
68 76 log.error("[validateMessage][未知的消息类型({})]", message);
69 77 throw new IllegalArgumentException("不支持的消息类型:" + type);
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mp.service.message;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
  5 +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
5 6 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
6 7 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
7 8 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
... ... @@ -45,4 +46,14 @@ public interface MpMessageService {
45 46 */
46 47 WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO);
47 48  
  49 + /**
  50 + * 使用公众号,给用户发送【客服】消息
  51 + *
  52 + * 注意,该方法会真实发送消息
  53 + *
  54 + * @param sendReqVO 消息内容
  55 + * @return 消息编号
  56 + */
  57 + Long sendKefuMessage(MpMessageSendReqVO sendReqVO);
  58 +
48 59 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
... ... @@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil;
7 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 8 import cn.iocoder.yudao.module.infra.api.file.FileApi;
9 9 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
  10 +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
10 11 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
11 12 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
12 13 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
... ... @@ -21,6 +22,7 @@ import cn.iocoder.yudao.module.mp.service.user.MpUserService;
21 22 import lombok.extern.slf4j.Slf4j;
22 23 import me.chanjar.weixin.common.error.WxErrorException;
23 24 import me.chanjar.weixin.mp.api.WxMpService;
  25 +import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
24 26 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
25 27 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
26 28 import org.springframework.context.annotation.Lazy;
... ... @@ -77,12 +79,7 @@ public class MpMessageServiceImpl implements MpMessageService {
77 79 // 记录消息
78 80 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
79 81 message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
80   - if (StrUtil.isNotEmpty(message.getMediaId())) {
81   - message.setMediaUrl(mediaDownload(mpService, message.getMediaId()));
82   - }
83   - if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
84   - message.setThumbMediaUrl(mediaDownload(mpService, message.getThumbMediaId()));
85   - }
  82 + downloadMessageMedia(mpService, message);
86 83 mpMessageMapper.insert(message);
87 84 }
88 85  
... ... @@ -106,6 +103,48 @@ public class MpMessageServiceImpl implements MpMessageService {
106 103 return MpMessageConvert.INSTANCE.convert02(message, account);
107 104 }
108 105  
  106 + @Override
  107 + public Long sendKefuMessage(MpMessageSendReqVO sendReqVO) {
  108 + // 校验消息格式
  109 + MpUtils.validateMessage(validator, sendReqVO.getType(), sendReqVO);
  110 +
  111 + // 获得关联信息
  112 + MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId());
  113 + MpAccountDO account = mpAccountService.getRequiredAccount(user.getAccountId());
  114 +
  115 + // 发送客服消息
  116 + WxMpKefuMessage wxMessage = MpMessageConvert.INSTANCE.convert(sendReqVO, user);
  117 + WxMpService mpService = mpServiceFactory.getRequiredMpService(user.getAppId());
  118 + try {
  119 + boolean result = mpService.getKefuService().sendKefuMessage(wxMessage);
  120 + System.out.println(result);
  121 + } catch (WxErrorException e) {
  122 + throw new RuntimeException(e);
  123 + }
  124 +
  125 + // 记录消息
  126 + MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
  127 + message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
  128 + downloadMessageMedia(mpService, message);
  129 + mpMessageMapper.insert(message);
  130 + return message.getId();
  131 + }
  132 +
  133 + /**
  134 + * 下载消息使用到的媒体文件,并上传到文件服务
  135 + *
  136 + * @param mpService 公众号 Service
  137 + * @param message 消息
  138 + */
  139 + private void downloadMessageMedia(WxMpService mpService, MpMessageDO message) {
  140 + if (StrUtil.isNotEmpty(message.getMediaId())) {
  141 + message.setMediaUrl(downloadMedia(mpService, message.getMediaId()));
  142 + }
  143 + if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
  144 + message.setThumbMediaUrl(downloadMedia(mpService, message.getThumbMediaId()));
  145 + }
  146 + }
  147 +
109 148 /**
110 149 * 下载微信媒体文件的内容,并上传到文件服务
111 150 *
... ... @@ -115,7 +154,7 @@ public class MpMessageServiceImpl implements MpMessageService {
115 154 * @param mediaId 媒体文件编号
116 155 * @return 上传后的 URL
117 156 */
118   - private String mediaDownload(WxMpService mpService, String mediaId) {
  157 + private String downloadMedia(WxMpService mpService, String mediaId) {
119 158 try {
120 159 // 第一步,从公众号下载媒体文件
121 160 File file = mpService.getMaterialService().mediaDownload(mediaId);
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java
1 1 package cn.iocoder.yudao.module.mp.service.message.bo;
2 2  
3 3 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
4   -import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
5 4 import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
6 5 import lombok.Data;
7 6 import me.chanjar.weixin.common.api.WxConsts;
... ... @@ -36,7 +35,7 @@ public class MpMessageSendOutReqBO {
36 35 /**
37 36 * 消息类型
38 37 *
39   - * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
  38 + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC
40 39 */
41 40 @NotEmpty(message = "消息类型不能为空")
42 41 public String type;
... ... @@ -56,6 +55,7 @@ public class MpMessageSendOutReqBO {
56 55 */
57 56 @NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
58 57 private String mediaId;
  58 + // TODO 芋艿:考虑去掉
59 59 /**
60 60 * 媒体 URL
61 61 *
... ... @@ -65,6 +65,15 @@ public class MpMessageSendOutReqBO {
65 65 private String mediaUrl;
66 66  
67 67 /**
  68 + * 缩略图的媒体 id
  69 + *
  70 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC
  71 + */
  72 + @NotEmpty(message = "消息内容不能为空", groups = {MusicGroup.class})
  73 + private String thumbMediaId;
  74 + // TODO 芋艿:考虑去掉
  75 +
  76 + /**
68 77 * 标题
69 78 *
70 79 * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
... ... @@ -88,4 +97,18 @@ public class MpMessageSendOutReqBO {
88 97 @NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
89 98 private List<MpMessageDO.Article> articles;
90 99  
  100 + /**
  101 + * 音乐链接
  102 + *
  103 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
  104 + */
  105 + private String musicUrl;
  106 +
  107 + /**
  108 + * 高质量音乐链接
  109 + *
  110 + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
  111 + */
  112 + private String hqMusicUrl;
  113 +
91 114 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java
... ... @@ -9,6 +9,9 @@ import me.chanjar.weixin.mp.bean.result.WxMpUser;
9 9 import java.util.Collection;
10 10 import java.util.List;
11 11  
  12 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  13 +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS;
  14 +
12 15 /**
13 16 * 公众号粉丝 Service 接口
14 17 *
... ... @@ -34,6 +37,20 @@ public interface MpUserService {
34 37 MpUserDO getUser(String appId, String openId);
35 38  
36 39 /**
  40 + * 获得公众号粉丝
  41 + *
  42 + * @param id 编号
  43 + * @return 公众号粉丝
  44 + */
  45 + default MpUserDO getRequiredUser(Long id) {
  46 + MpUserDO user = getUser(id);
  47 + if (user == null) {
  48 + throw exception(USER_NOT_EXISTS);
  49 + }
  50 + return user;
  51 + }
  52 +
  53 + /**
37 54 * 获得公众号粉丝列表
38 55 *
39 56 * @param ids 编号
... ...
yudao-ui-admin/src/views/mp/components/wx-msg/main.vue
... ... @@ -140,7 +140,6 @@
140 140 nickname: '公众号',
141 141 avatar: require("@/assets/images/wechat.png"),
142 142 },
143   - qqMapKey: '' //
144 143 }
145 144 },
146 145 created() {
... ...