mp:实现关键字,自动回复

This commit is contained in:
YunaiV 2023-01-04 21:10:54 +08:00
parent 48bea21a46
commit 44f0bcd182
9 changed files with 210 additions and 16 deletions

View File

@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
/** /**
* 微信消息自动回复的匹配模式 * 公众号消息自动回复的匹配模式
* *
* @author 芋道源码 * @author 芋道源码
*/ */

View File

@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
/** /**
* 微信消息自动回复的类型 * 公众号消息自动回复的类型
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -14,7 +14,7 @@ public enum MpAutoReplyTypeEnum {
SUBSCRIBE(1, "关注时回复"), SUBSCRIBE(1, "关注时回复"),
MESSAGE(2, "收到消息回复"), MESSAGE(2, "收到消息回复"),
KEYWORD(2, "关键词回复"), KEYWORD(3, "关键词回复"),
; ;
/** /**

View File

@ -1,18 +1,27 @@
package cn.iocoder.yudao.module.mp.convert.message; package cn.iocoder.yudao.module.mp.convert.message;
import java.util.*; 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.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageRespVO;
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.MpAutoReplyDO;
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 me.chanjar.weixin.common.api.WxConsts;
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.WxMpXmlOutNewsMessage;
import me.chanjar.weixin.mp.builder.outxml.BaseBuilder;
import me.chanjar.weixin.mp.builder.outxml.TextBuilder;
import me.chanjar.weixin.mp.builder.outxml.VideoBuilder;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.*;
import java.util.Collections;
import java.util.List;
@Mapper @Mapper
public interface MpMessageConvert { public interface MpMessageConvert {
@ -41,4 +50,64 @@ public interface MpMessageConvert {
} }
return message; return message;
} }
default MpMessageDO convert(MpAutoReplyDO reply, MpAccountDO account, MpUserDO user) {
// 构建消息
MpMessageDO message = new MpMessageDO();
message.setType(reply.getResponseMessageType());
// 1. 文本
if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.TEXT)) {
message.setContent(reply.getResponseContent());
} else if (ObjectUtils.equalsAny(reply.getResponseMessageType(), WxConsts.XmlMsgType.IMAGE, // 2. 图片
WxConsts.XmlMsgType.VOICE)) { // 3. 语音
message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl());
} else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.VIDEO)) { // 4. 视频
message.setMediaId(reply.getResponseMediaId()).setMediaUrl(reply.getResponseMediaUrl())
.setTitle(reply.getResponseTitle()).setDescription(reply.getResponseDescription());
} else if (StrUtil.equals(reply.getResponseMessageType(), WxConsts.XmlMsgType.NEWS)) { // 5. 图文
message.setArticles(Collections.singletonList(reply.getResponseArticle()));
} else {
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
}
// 其它字段
if (account != null) {
message.setAccountId(account.getId()).setAppId(account.getAppId());
}
if (user != null) {
message.setUserId(user.getId()).setOpenid(user.getOpenid());
}
return message;
}
default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) {
BaseBuilder<?, ? extends WxMpXmlOutMessage> messageBuilder;
// 个性化字段
switch (message.getType()) {
case WxConsts.XmlMsgType.TEXT:
messageBuilder = WxMpXmlOutMessage.TEXT().content(message.getContent());
break;
case WxConsts.XmlMsgType.IMAGE:
messageBuilder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());
break;
case WxConsts.XmlMsgType.VOICE:
messageBuilder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());
break;
case WxConsts.XmlMsgType.VIDEO:
messageBuilder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())
.title(message.getTitle()).description(message.getDescription());
break;
case WxConsts.XmlMsgType.NEWS:
messageBuilder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));
break;
default:
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
}
// 通用字段
messageBuilder.fromUser(account.getAccount());
messageBuilder.toUser(message.getOpenid());
return messageBuilder.build();
}
List<WxMpXmlOutNewsMessage.Item> convertList02(List<MpMessageDO.Article> list);
} }

View File

@ -6,18 +6,21 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; import me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
* 微信消息自动回复 DO * 公众号消息自动回复 DO
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -99,4 +102,35 @@ public class MpAutoReplyDO extends BaseDO {
*/ */
private String responseContent; private String responseContent;
/**
* 回复的媒体 id
*
* 消息类型为 {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/
private String responseMediaId;
/**
* 回复的媒体 URL
*/
private String responseMediaUrl;
/**
* 回复的标题
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/
private String responseTitle;
/**
* 回复的描述
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/
private String responseDescription;
/**
* 回复的图文消息
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private MpMessageDO.Article responseArticle;
} }

View File

@ -1,17 +1,23 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.message; package cn.iocoder.yudao.module.mp.dal.dataobject.message;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
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.user.MpUserDO; import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import lombok.*; import lombok.*;
import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.api.WxConsts;
import java.io.Serializable;
import java.util.List;
/** /**
* 微信消息 DO * 公众号消息 DO
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -160,6 +166,14 @@ public class MpMessageDO extends BaseDO {
*/ */
private String label; private String label;
/**
* 图文消息数组
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = ArticleTypeHandler.class)
private List<Article> articles;
// ========= 事件推送 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
/** /**
@ -176,4 +190,45 @@ public class MpMessageDO extends BaseDO {
*/ */
private String eventKey; private String eventKey;
/**
* 文章
*/
@Data
public static class Article implements Serializable {
/**
* 图文消息标题
*/
private String title;
/**
* 图文消息描述
*/
private String description;
/**
* 图片链接
*
* 支持JPGPNG格式较好的效果为大图 360*200小图 200*200
*/
private String picUrl;
/**
* 点击图文消息跳转链接
*/
private String url;
}
// TODO @芋艿可以找一些新的思路
public static class ArticleTypeHandler extends AbstractJsonTypeHandler<List<Article>> {
@Override
protected List<Article> parse(String json) {
return JsonUtils.parseArray(json, Article.class);
}
@Override
protected String toJson(List<Article> obj) {
return JsonUtils.toJsonString(obj);
}
}
} }

View File

@ -5,9 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@Mapper
public interface MpAutoReplyMapper extends BaseMapperX<MpAutoReplyDO> { public interface MpAutoReplyMapper extends BaseMapperX<MpAutoReplyDO> {
default List<MpAutoReplyDO> selectListByAppIdAndKeywordAll(String appId, String requestKeyword) { default List<MpAutoReplyDO> selectListByAppIdAndKeywordAll(String appId, String requestKeyword) {

View File

@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.mp.service.message; package cn.iocoder.yudao.module.mp.service.message;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.mp.builder.TextBuilder;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper; import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.api.WxConsts;
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.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -17,18 +19,21 @@ import java.util.List;
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@Resource @Service
@Validated @Validated
public class MpAutoReplyServiceImpl implements MpAutoReplyService { public class MpAutoReplyServiceImpl implements MpAutoReplyService {
@Resource @Resource
private MpAutoReplyService mpAutoReplyService; private MpMessageService mpMessageService;
@Resource @Resource
private MpAutoReplyMapper mpAutoReplyMapper; private MpAutoReplyMapper mpAutoReplyMapper;
@Override @Override
public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) { public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) {
// if (true) {
// return new TextBuilder().build("nihao", wxMessage, null);
// }
// 第一步匹配自动回复 // 第一步匹配自动回复
List<MpAutoReplyDO> replies = null; List<MpAutoReplyDO> replies = null;
// 1.1 关键字 // 1.1 关键字
@ -47,12 +52,10 @@ public class MpAutoReplyServiceImpl implements MpAutoReplyService {
if (CollUtil.isEmpty(replies)) { if (CollUtil.isEmpty(replies)) {
return null; return null;
} }
MpAutoReplyDO reply = CollUtil.getFirst(replies);
// 第二步基于自动回复创建消息 // 第二步基于自动回复创建消息
return mpMessageService.createFromAutoReply(wxMessage.getFromUser(), reply);
// 第三步将消息转换成 WxMpXmlOutMessage 返回
return null;
} }
} }

View File

@ -2,8 +2,10 @@ 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.dal.dataobject.message.MpAutoReplyDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
/** /**
* 粉丝消息表 Service 接口 * 粉丝消息表 Service 接口
@ -28,4 +30,14 @@ public interface MpMessageService {
* @param wxMessage 消息 * @param wxMessage 消息
*/ */
void createFromUser(String appId, WxMpXmlMessage wxMessage); void createFromUser(String appId, WxMpXmlMessage wxMessage);
/**
* 创建粉丝消息通过自动回复
*
* @param openid 公众号粉丝 openid
* @param reply 自动回复
* @return 微信回复消息 XML
*/
WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply);
} }

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.module.infra.api.file.FileApi; import cn.iocoder.yudao.module.infra.api.file.FileApi;
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.MpAutoReplyDO;
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.enums.message.MpMessageSendFromEnum; import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
@ -20,6 +21,7 @@ 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.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -74,7 +76,7 @@ public class MpMessageServiceImpl implements MpMessageService {
MpAccountDO account = mpAccountService.getAccountFromCache(appId); MpAccountDO account = mpAccountService.getAccountFromCache(appId);
Assert.notNull(account, "公众号账号({}) 不存在", appId); Assert.notNull(account, "公众号账号({}) 不存在", appId);
MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser()); MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser());
Assert.notNull(account, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser()); Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
// 记录消息 // 记录消息
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user); MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
@ -94,6 +96,23 @@ public class MpMessageServiceImpl implements MpMessageService {
// wxMessage.getEventKey() // wxMessage.getEventKey()
} }
@Override
public WxMpXmlOutMessage createFromAutoReply(String openid, MpAutoReplyDO reply) {
// 获得关联信息
MpAccountDO account = mpAccountService.getAccountFromCache(reply.getAppId());
Assert.notNull(account, "公众号账号({}) 不存在", reply.getAppId());
MpUserDO user = mpUserService.getUser(reply.getAppId(), openid);
Assert.notNull(user, "公众号粉丝({}/{}) 不存在", reply.getAppId(), openid);
// 记录消息
MpMessageDO message = MpMessageConvert.INSTANCE.convert(reply, account, user);
message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
mpMessageMapper.insert(message);
// 转换返回 WxMpXmlOutMessage 对象
return MpMessageConvert.INSTANCE.convert02(message, account);
}
/** /**
* 下载微信媒体文件的内容并上传到文件服务 * 下载微信媒体文件的内容并上传到文件服务
* *