mp:后端增加发送客服消息接口
parent
f8eed5dd3f
commit
9cf88d1929
|
@ -3,3 +3,31 @@ GET {{baseUrl}}/mp/message/page?accountId=1&pageNo=1&pageSize=10
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
tenant-id: {{adminTenentId}}
|
tenant-id: {{adminTenentId}}
|
||||||
|
|
||||||
|
### 请求 /mp/message/send 接口 => 成功(文本)
|
||||||
|
POST {{baseUrl}}/mp/message/send
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
tenant-id: {{adminTenentId}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"userId": 3,
|
||||||
|
"type": "text",
|
||||||
|
"content": "测试消息"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 请求 /mp/message/send 接口 => 成功(音乐)
|
||||||
|
POST {{baseUrl}}/mp/message/send
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
tenant-id: {{adminTenentId}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"userId": 3,
|
||||||
|
"type": "music",
|
||||||
|
"title": "测试音乐标题",
|
||||||
|
"description": "测试音乐内容",
|
||||||
|
"musicUrl": "https://www.iocoder.cn/xx.mp3",
|
||||||
|
"hqMusicUrl": "https://www.iocoder.cn/xx_high.mp3",
|
||||||
|
"thumbMediaId": "s98Iveeg9vDVFwa9q0u8-zSfdKe3xIzAm7wCrFE4WKGPIo4d9qAhtC-n6qvnyWyH"
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
|
||||||
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
|
||||||
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
|
||||||
import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
|
import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
||||||
import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
|
import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
|
||||||
|
@ -11,9 +12,7 @@ import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
@ -37,4 +36,11 @@ public class MpMessageController {
|
||||||
return success(MpMessageConvert.INSTANCE.convertPage(pageResult));
|
return success(MpMessageConvert.INSTANCE.convertPage(pageResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/send")
|
||||||
|
// @ApiOperation("获得粉丝消息表分页")
|
||||||
|
// @PreAuthorize("@ss.hasPermission('mp:message:query')")
|
||||||
|
public CommonResult<Long> sendMessage(@Valid @RequestBody MpMessageSendReqVO reqVO) {
|
||||||
|
return success(mpMessageService.sendKefuMessage(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package cn.iocoder.yudao.module.mp.controller.admin.message.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
||||||
|
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ApiModel("管理后台 - 公众号消息发送 Request VO")
|
||||||
|
@Data
|
||||||
|
public class MpMessageSendReqVO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "公众号粉丝的编号", required = true, example = "1024")
|
||||||
|
@NotNull(message = "公众号粉丝的编号不能为空")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
// ========== 消息内容 ==========
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "消息类型", required = true, example = "text", notes = "TEXT/IMAGE/VOICE/VIDEO/NEWS")
|
||||||
|
@NotEmpty(message = "消息类型不能为空")
|
||||||
|
public String type;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "消息内容", required = true, example = "你好呀")
|
||||||
|
@NotEmpty(message = "消息内容不能为空", groups = TextGroup.class)
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "媒体 ID", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
|
||||||
|
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
||||||
|
private String mediaId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "标题", required = true, example = "没有标题")
|
||||||
|
@NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "描述", required = true, example = "你猜")
|
||||||
|
@NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "缩略图的媒体 id", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
|
||||||
|
@NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicGroup.class)
|
||||||
|
private String thumbMediaId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "图文消息", required = true)
|
||||||
|
@Valid
|
||||||
|
@NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
|
||||||
|
private List<MpMessageDO.Article> articles;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
|
||||||
|
private String musicUrl;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
|
||||||
|
private String hqMusicUrl;
|
||||||
|
|
||||||
|
}
|
|
@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.mp.convert.message;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
|
||||||
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
|
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.message.MpMessageDO;
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
|
||||||
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
|
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
|
||||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
||||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
|
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
|
||||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
|
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
|
||||||
|
@ -16,7 +18,6 @@ import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.Mappings;
|
import org.mapstruct.Mappings;
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
|
@ -30,12 +31,6 @@ public interface MpMessageConvert {
|
||||||
|
|
||||||
PageResult<MpMessageRespVO> convertPage(PageResult<MpMessageDO> page);
|
PageResult<MpMessageRespVO> convertPage(PageResult<MpMessageDO> page);
|
||||||
|
|
||||||
@Mappings(value = {
|
|
||||||
@Mapping(source = "msgType", target = "type"),
|
|
||||||
@Mapping(target = "createTime", ignore = true),
|
|
||||||
})
|
|
||||||
MpMessageDO convert(WxMpXmlMessage wxMessage);
|
|
||||||
|
|
||||||
default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) {
|
default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) {
|
||||||
MpMessageDO message = convert(wxMessage);
|
MpMessageDO message = convert(wxMessage);
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
|
@ -46,6 +41,11 @@ public interface MpMessageConvert {
|
||||||
}
|
}
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
@Mappings(value = {
|
||||||
|
@Mapping(source = "msgType", target = "type"),
|
||||||
|
@Mapping(target = "createTime", ignore = true),
|
||||||
|
})
|
||||||
|
MpMessageDO convert(WxMpXmlMessage bean);
|
||||||
|
|
||||||
default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) {
|
default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) {
|
||||||
// 构建消息
|
// 构建消息
|
||||||
|
@ -65,6 +65,11 @@ public interface MpMessageConvert {
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.NEWS: // 5. 图文
|
case WxConsts.XmlMsgType.NEWS: // 5. 图文
|
||||||
message.setArticles(sendReqBO.getArticles());
|
message.setArticles(sendReqBO.getArticles());
|
||||||
|
case WxConsts.XmlMsgType.MUSIC: // 6. 音乐
|
||||||
|
message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription())
|
||||||
|
.setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl())
|
||||||
|
.setThumbMediaId(sendReqBO.getThumbMediaId());
|
||||||
|
// .setThumbMediaUrl(sendReqBO.getThumbMediaUrl()); TODO 芋艿:url 待确定
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
|
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
|
||||||
|
@ -81,33 +86,88 @@ public interface MpMessageConvert {
|
||||||
}
|
}
|
||||||
|
|
||||||
default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) {
|
default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) {
|
||||||
BaseBuilder<?, ? extends WxMpXmlOutMessage> messageBuilder;
|
BaseBuilder<?, ? extends WxMpXmlOutMessage> builder;
|
||||||
// 个性化字段
|
// 个性化字段
|
||||||
switch (message.getType()) {
|
switch (message.getType()) {
|
||||||
case WxConsts.XmlMsgType.TEXT:
|
case WxConsts.XmlMsgType.TEXT:
|
||||||
messageBuilder = WxMpXmlOutMessage.TEXT().content(message.getContent());
|
builder = WxMpXmlOutMessage.TEXT().content(message.getContent());
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.IMAGE:
|
case WxConsts.XmlMsgType.IMAGE:
|
||||||
messageBuilder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());
|
builder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.VOICE:
|
case WxConsts.XmlMsgType.VOICE:
|
||||||
messageBuilder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());
|
builder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.VIDEO:
|
case WxConsts.XmlMsgType.VIDEO:
|
||||||
messageBuilder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())
|
builder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())
|
||||||
.title(message.getTitle()).description(message.getDescription());
|
.title(message.getTitle()).description(message.getDescription());
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.NEWS:
|
case WxConsts.XmlMsgType.NEWS:
|
||||||
messageBuilder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));
|
builder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));
|
||||||
|
break;
|
||||||
|
case WxConsts.XmlMsgType.MUSIC:
|
||||||
|
builder = WxMpXmlOutMessage.MUSIC().title(message.getTitle()).description(message.getDescription())
|
||||||
|
.musicUrl(message.getMusicUrl()).hqMusicUrl(message.getHqMusicUrl())
|
||||||
|
.thumbMediaId(message.getThumbMediaId());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
|
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
|
||||||
}
|
}
|
||||||
// 通用字段
|
// 通用字段
|
||||||
messageBuilder.fromUser(account.getAccount());
|
builder.fromUser(account.getAccount());
|
||||||
messageBuilder.toUser(message.getOpenid());
|
builder.toUser(message.getOpenid());
|
||||||
return messageBuilder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
List<WxMpXmlOutNewsMessage.Item> convertList02(List<MpMessageDO.Article> list);
|
List<WxMpXmlOutNewsMessage.Item> convertList02(List<MpMessageDO.Article> list);
|
||||||
|
|
||||||
|
default WxMpKefuMessage convert(MpMessageSendReqVO sendReqVO, MpUserDO user) {
|
||||||
|
me.chanjar.weixin.mp.builder.kefu.BaseBuilder<?> builder;
|
||||||
|
// 个性化字段
|
||||||
|
switch (sendReqVO.getType()) {
|
||||||
|
case WxConsts.KefuMsgType.TEXT:
|
||||||
|
builder = WxMpKefuMessage.TEXT().content(sendReqVO.getContent());
|
||||||
|
break;
|
||||||
|
case WxConsts.KefuMsgType.IMAGE:
|
||||||
|
builder = WxMpKefuMessage.IMAGE().mediaId(sendReqVO.getMediaId());
|
||||||
|
break;
|
||||||
|
case WxConsts.KefuMsgType.VOICE:
|
||||||
|
builder = WxMpKefuMessage.VOICE().mediaId(sendReqVO.getMediaId());
|
||||||
|
break;
|
||||||
|
case WxConsts.KefuMsgType.VIDEO:
|
||||||
|
builder = WxMpKefuMessage.VIDEO().mediaId(sendReqVO.getMediaId())
|
||||||
|
.title(sendReqVO.getTitle()).description(sendReqVO.getDescription());
|
||||||
|
break;
|
||||||
|
case WxConsts.KefuMsgType.NEWS:
|
||||||
|
builder = WxMpKefuMessage.NEWS().articles(convertList03(sendReqVO.getArticles()));
|
||||||
|
break;
|
||||||
|
case WxConsts.KefuMsgType.MUSIC:
|
||||||
|
builder = WxMpKefuMessage.MUSIC().title(sendReqVO.getTitle()).description(sendReqVO.getDescription())
|
||||||
|
.thumbMediaId(sendReqVO.getThumbMediaId())
|
||||||
|
.musicUrl(sendReqVO.getMusicUrl()).hqMusicUrl(sendReqVO.getHqMusicUrl());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("不支持的消息类型:" + sendReqVO.getType());
|
||||||
|
}
|
||||||
|
// 通用字段
|
||||||
|
builder.toUser(user.getOpenid());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
List<WxMpKefuMessage.WxArticle> convertList03(List<MpMessageDO.Article> list);
|
||||||
|
|
||||||
|
default MpMessageDO convert(WxMpKefuMessage wxMessage, MpAccountDO account, MpUserDO user) {
|
||||||
|
MpMessageDO message = convert(wxMessage);
|
||||||
|
if (account != null) {
|
||||||
|
message.setAccountId(account.getId()).setAppId(account.getAppId());
|
||||||
|
}
|
||||||
|
if (user != null) {
|
||||||
|
message.setUserId(user.getId()).setOpenid(user.getOpenid());
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
@Mappings(value = {
|
||||||
|
@Mapping(source = "msgType", target = "type"),
|
||||||
|
@Mapping(target = "createTime", ignore = true),
|
||||||
|
})
|
||||||
|
MpMessageDO convert(WxMpKefuMessage bean);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ public class MpMessageDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
|
* 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
||||||
*/
|
*/
|
||||||
private String thumbMediaId;
|
private String thumbMediaId;
|
||||||
/**
|
/**
|
||||||
|
@ -176,6 +176,21 @@ public class MpMessageDO extends BaseDO {
|
||||||
@TableField(typeHandler = ArticleTypeHandler.class)
|
@TableField(typeHandler = ArticleTypeHandler.class)
|
||||||
private List<Article> articles;
|
private List<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
|
// ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -39,6 +39,11 @@ public class MpUtils {
|
||||||
*/
|
*/
|
||||||
public interface NewsGroup {}
|
public interface NewsGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Music 类型的消息,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface MusicGroup {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验消息的格式是否符合要求
|
* 校验消息的格式是否符合要求
|
||||||
*
|
*
|
||||||
|
@ -64,6 +69,9 @@ public class MpUtils {
|
||||||
case WxConsts.XmlMsgType.NEWS:
|
case WxConsts.XmlMsgType.NEWS:
|
||||||
group = NewsGroup.class;
|
group = NewsGroup.class;
|
||||||
break;
|
break;
|
||||||
|
case WxConsts.XmlMsgType.MUSIC:
|
||||||
|
group = MusicGroup.class;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("[validateMessage][未知的消息类型({})]", message);
|
log.error("[validateMessage][未知的消息类型({})]", message);
|
||||||
throw new IllegalArgumentException("不支持的消息类型:" + type);
|
throw new IllegalArgumentException("不支持的消息类型:" + type);
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mp.service.message;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
|
||||||
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
||||||
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
|
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
|
||||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
||||||
|
@ -45,4 +46,14 @@ public interface MpMessageService {
|
||||||
*/
|
*/
|
||||||
WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO);
|
WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用公众号,给用户发送【客服】消息
|
||||||
|
*
|
||||||
|
* 注意,该方法会真实发送消息
|
||||||
|
*
|
||||||
|
* @param sendReqVO 消息内容
|
||||||
|
* @return 消息编号
|
||||||
|
*/
|
||||||
|
Long sendKefuMessage(MpMessageSendReqVO sendReqVO);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||||
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
|
||||||
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
|
||||||
import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
|
import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
|
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.message.MpMessageDO;
|
||||||
|
@ -21,6 +22,7 @@ import cn.iocoder.yudao.module.mp.service.user.MpUserService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.chanjar.weixin.common.error.WxErrorException;
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
import me.chanjar.weixin.mp.api.WxMpService;
|
import me.chanjar.weixin.mp.api.WxMpService;
|
||||||
|
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
|
||||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
|
||||||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
|
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
@ -77,12 +79,7 @@ public class MpMessageServiceImpl implements MpMessageService {
|
||||||
// 记录消息
|
// 记录消息
|
||||||
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
||||||
message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
||||||
if (StrUtil.isNotEmpty(message.getMediaId())) {
|
downloadMessageMedia(mpService, message);
|
||||||
message.setMediaUrl(mediaDownload(mpService, message.getMediaId()));
|
|
||||||
}
|
|
||||||
if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
|
|
||||||
message.setThumbMediaUrl(mediaDownload(mpService, message.getThumbMediaId()));
|
|
||||||
}
|
|
||||||
mpMessageMapper.insert(message);
|
mpMessageMapper.insert(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +103,48 @@ public class MpMessageServiceImpl implements MpMessageService {
|
||||||
return MpMessageConvert.INSTANCE.convert02(message, account);
|
return MpMessageConvert.INSTANCE.convert02(message, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long sendKefuMessage(MpMessageSendReqVO sendReqVO) {
|
||||||
|
// 校验消息格式
|
||||||
|
MpUtils.validateMessage(validator, sendReqVO.getType(), sendReqVO);
|
||||||
|
|
||||||
|
// 获得关联信息
|
||||||
|
MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId());
|
||||||
|
MpAccountDO account = mpAccountService.getRequiredAccount(user.getAccountId());
|
||||||
|
|
||||||
|
// 发送客服消息
|
||||||
|
WxMpKefuMessage wxMessage = MpMessageConvert.INSTANCE.convert(sendReqVO, user);
|
||||||
|
WxMpService mpService = mpServiceFactory.getRequiredMpService(user.getAppId());
|
||||||
|
try {
|
||||||
|
boolean result = mpService.getKefuService().sendKefuMessage(wxMessage);
|
||||||
|
System.out.println(result);
|
||||||
|
} catch (WxErrorException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录消息
|
||||||
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
||||||
|
message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
||||||
|
downloadMessageMedia(mpService, message);
|
||||||
|
mpMessageMapper.insert(message);
|
||||||
|
return message.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载消息使用到的媒体文件,并上传到文件服务
|
||||||
|
*
|
||||||
|
* @param mpService 公众号 Service
|
||||||
|
* @param message 消息
|
||||||
|
*/
|
||||||
|
private void downloadMessageMedia(WxMpService mpService, MpMessageDO message) {
|
||||||
|
if (StrUtil.isNotEmpty(message.getMediaId())) {
|
||||||
|
message.setMediaUrl(downloadMedia(mpService, message.getMediaId()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
|
||||||
|
message.setThumbMediaUrl(downloadMedia(mpService, message.getThumbMediaId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载微信媒体文件的内容,并上传到文件服务
|
* 下载微信媒体文件的内容,并上传到文件服务
|
||||||
*
|
*
|
||||||
|
@ -115,7 +154,7 @@ public class MpMessageServiceImpl implements MpMessageService {
|
||||||
* @param mediaId 媒体文件编号
|
* @param mediaId 媒体文件编号
|
||||||
* @return 上传后的 URL
|
* @return 上传后的 URL
|
||||||
*/
|
*/
|
||||||
private String mediaDownload(WxMpService mpService, String mediaId) {
|
private String downloadMedia(WxMpService mpService, String mediaId) {
|
||||||
try {
|
try {
|
||||||
// 第一步,从公众号下载媒体文件
|
// 第一步,从公众号下载媒体文件
|
||||||
File file = mpService.getMaterialService().mediaDownload(mediaId);
|
File file = mpService.getMaterialService().mediaDownload(mediaId);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cn.iocoder.yudao.module.mp.service.message.bo;
|
package cn.iocoder.yudao.module.mp.service.message.bo;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
||||||
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
|
|
||||||
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
|
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
@ -36,7 +35,7 @@ public class MpMessageSendOutReqBO {
|
||||||
/**
|
/**
|
||||||
* 消息类型
|
* 消息类型
|
||||||
*
|
*
|
||||||
* 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
|
* 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "消息类型不能为空")
|
@NotEmpty(message = "消息类型不能为空")
|
||||||
public String type;
|
public String type;
|
||||||
|
@ -56,6 +55,7 @@ public class MpMessageSendOutReqBO {
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
||||||
private String mediaId;
|
private String mediaId;
|
||||||
|
// TODO 芋艿:考虑去掉
|
||||||
/**
|
/**
|
||||||
* 媒体 URL
|
* 媒体 URL
|
||||||
*
|
*
|
||||||
|
@ -64,6 +64,15 @@ public class MpMessageSendOutReqBO {
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
||||||
private String mediaUrl;
|
private String mediaUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩略图的媒体 id
|
||||||
|
*
|
||||||
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "消息内容不能为空", groups = {MusicGroup.class})
|
||||||
|
private String thumbMediaId;
|
||||||
|
// TODO 芋艿:考虑去掉
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标题
|
* 标题
|
||||||
*
|
*
|
||||||
|
@ -88,4 +97,18 @@ public class MpMessageSendOutReqBO {
|
||||||
@NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
|
@NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
|
||||||
private List<MpMessageDO.Article> articles;
|
private List<MpMessageDO.Article> articles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音乐链接
|
||||||
|
*
|
||||||
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
||||||
|
*/
|
||||||
|
private String musicUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高质量音乐链接
|
||||||
|
*
|
||||||
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
||||||
|
*/
|
||||||
|
private String hqMusicUrl;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import me.chanjar.weixin.mp.bean.result.WxMpUser;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公众号粉丝 Service 接口
|
* 公众号粉丝 Service 接口
|
||||||
*
|
*
|
||||||
|
@ -33,6 +36,20 @@ public interface MpUserService {
|
||||||
*/
|
*/
|
||||||
MpUserDO getUser(String appId, String openId);
|
MpUserDO getUser(String appId, String openId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得公众号粉丝
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
* @return 公众号粉丝
|
||||||
|
*/
|
||||||
|
default MpUserDO getRequiredUser(Long id) {
|
||||||
|
MpUserDO user = getUser(id);
|
||||||
|
if (user == null) {
|
||||||
|
throw exception(USER_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得公众号粉丝列表
|
* 获得公众号粉丝列表
|
||||||
*
|
*
|
||||||
|
|
|
@ -140,7 +140,6 @@
|
||||||
nickname: '公众号',
|
nickname: '公众号',
|
||||||
avatar: require("@/assets/images/wechat.png"),
|
avatar: require("@/assets/images/wechat.png"),
|
||||||
},
|
},
|
||||||
qqMapKey: '' //
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
Loading…
Reference in New Issue