mp:完成 menu 点击时,自动回复消息的逻辑

pull/2/head
YunaiV 2023-01-07 11:07:02 +08:00
parent 665f4b2b09
commit 71beeabe9c
11 changed files with 150 additions and 160 deletions

View File

@ -16,5 +16,19 @@ tenant-id: {{adminTenentId}}
"name":"搜索", "name":"搜索",
"type":"view", "type":"view",
"url":"http://www.soso.com/" "url":"http://www.soso.com/"
},
{
"name": "父按钮",
"subButtons": [
{
"type":"click",
"name":"归去来兮",
"key":"MUSIC"
},
{
"name":"不说",
"type":"view",
"url":"https://www.soso.com/"
}]
}] }]
} }

View File

@ -18,7 +18,4 @@ public class MpMenuBaseVO {
@NotNull(message = "公众号账号的编号不能为空") @NotNull(message = "公众号账号的编号不能为空")
private Long accountId; private Long accountId;
@NotNull(message = "按钮不能为空")
private List<WxMenuButton> buttons;
} }

View File

@ -2,6 +2,10 @@ package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
import lombok.*; import lombok.*;
import io.swagger.annotations.*; import io.swagger.annotations.*;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 微信菜单保存 Request VO") @ApiModel("管理后台 - 微信菜单保存 Request VO")
@Data @Data
@ -9,4 +13,7 @@ import io.swagger.annotations.*;
@ToString(callSuper = true) @ToString(callSuper = true)
public class MpMenuSaveReqVO extends MpMenuBaseVO { public class MpMenuSaveReqVO extends MpMenuBaseVO {
@NotNull(message = "按钮不能为空")
private List<WxMenuButton> buttons;
} }

View File

@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.mp.convert.menu;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*; import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.*;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
@ -16,4 +19,16 @@ public interface MpMenuConvert {
MpMenuRespVO convert(MpMenuDO bean); MpMenuRespVO convert(MpMenuDO bean);
@Mappings({
@Mapping(source = "menu.appId", target = "appId"),
@Mapping(source = "menu.replyMessageType", target = "type"),
@Mapping(source = "menu.replyContent", target = "content"),
@Mapping(source = "menu.replyMediaId", target = "mediaId"),
@Mapping(source = "menu.replyMediaUrl", target = "mediaUrl"),
@Mapping(source = "menu.replyTitle", target = "title"),
@Mapping(source = "menu.replyDescription", target = "description"),
@Mapping(source = "menu.replyArticles", target = "articles"),
})
MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);
} }

View File

@ -20,7 +20,7 @@ public interface MpAutoReplyConvert {
@Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"), @Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"),
@Mapping(source = "reply.responseTitle", target = "title"), @Mapping(source = "reply.responseTitle", target = "title"),
@Mapping(source = "reply.responseDescription", target = "description"), @Mapping(source = "reply.responseDescription", target = "description"),
@Mapping(source = "reply.responseArticle", target = "article"), @Mapping(source = "reply.responseArticles", target = "articles"),
}) })
MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply); MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply);

View File

@ -64,7 +64,7 @@ public interface MpMessageConvert {
.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()); .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());
break; break;
case WxConsts.XmlMsgType.NEWS: // 5. 图文 case WxConsts.XmlMsgType.NEWS: // 5. 图文
message.setArticles(Collections.singletonList(sendReqBO.getArticle())); message.setArticles(sendReqBO.getArticles());
break; break;
default: default:
throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); throw new IllegalArgumentException("不支持的消息类型:" + message.getType());

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.menu; package cn.iocoder.yudao.module.mp.dal.dataobject.menu;
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.message.MpMessageDO; import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
@ -8,7 +7,6 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField; 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.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
@ -20,8 +18,6 @@ import java.util.List;
/** /**
* DO * DO
* *
* MpMenuDO {@link #buttons}
*
* @author * @author
*/ */
@TableName(value = "mp_menu", autoResultMap = true) @TableName(value = "mp_menu", autoResultMap = true)
@ -32,7 +28,12 @@ import java.util.List;
public class MpMenuDO extends BaseDO { public class MpMenuDO extends BaseDO {
/** /**
* * -
*/
public static final Long ID_ROOT = 0L;
/**
*
*/ */
@TableId @TableId
private Long id; private Long id;
@ -50,127 +51,105 @@ public class MpMenuDO extends BaseDO {
private String appId; private String appId;
/** /**
* *
*/ */
@TableField(typeHandler = ButtonTypeHandler.class) private String name;
private List<Button> buttons;
/** /**
* *
* *
* true - * DB 使 key + @TableField("menuKey") "menuKey" AS key
* false -
*/ */
private Boolean syncStatus; private String menuKey;
/**
*
*/
private Long parentId;
/**
*
*/
private Integer sort;
// ========== 按钮操作 ==========
/** /**
* *
*
* {@link MenuButtonType}
*/ */
@Data private String type;
public static class Button {
/** /**
* *
* *
* {@link MenuButtonType} * 1024
*/ *
private String type; * {@link WxConsts.XmlMsgType} VIEWMINIPROGRAM
/** */
* private String url;
*
* {@link #type} CLICKSCANCODE_WAITMSG
*
* {@link WxConsts.XmlMsgType} TEXTIMAGEVOICEVIDEONEWS
*/
private String messageType;
/**
*
*/
private String name;
/**
*
*/
private String key;
/**
*
*/
private List<Button> subButtons;
/**
*
*
* 1024
*
* {@link WxConsts.XmlMsgType} VIEWMINIPROGRAM
*/
private String url;
/** /**
* appId * appId
* *
* {@link WxConsts.XmlMsgType} MINIPROGRAM * {@link MenuButtonType} MINIPROGRAM
*/ */
private String appId; private String miniProgramAppId;
/**
*
*
* {@link MenuButtonType} MINIPROGRAM
*/
private String miniProgramPagePath;
/** // ========== 消息内容 ==========
*
*
* {@link WxConsts.XmlMsgType} MINIPROGRAM
*/
private String pagePath;
/** /**
* *
* *
* {@link WxConsts.XmlMsgType} TEXT * {@link #type} CLICKSCANCODE_WAITMSG
*/ *
private String content; * {@link WxConsts.XmlMsgType} TEXTIMAGEVOICEVIDEONEWS
*/
private String replyMessageType;
/** /**
* id *
* *
* {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO * {@link WxConsts.XmlMsgType} TEXT
*/ */
private String mediaId; private String replyContent;
/**
* URL
*
* {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/
private String mediaUrl;
/** /**
* * id
* *
* {@link WxConsts.XmlMsgType} VIDEO * {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/ */
private String title; private String replyMediaId;
/** /**
* * URL
* *
* {@link WxConsts.XmlMsgType} VIDEO * {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/ */
private String description; private String replyMediaUrl;
/** /**
* *
* *
* {@link WxConsts.XmlMsgType} NEWS * {@link WxConsts.XmlMsgType} VIDEO
*/ */
private MpMessageDO.Article article; private String replyTitle;
/**
*
*
* {@link WxConsts.XmlMsgType} VIDEO
*/
private String replyDescription;
} /**
*
*
* {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
private List<MpMessageDO.Article> replyArticles;
// TODO @芋艿:可以找一些新的思路
public static class ButtonTypeHandler extends AbstractJsonTypeHandler<List<Button>> {
@Override
protected List<Button> parse(String json) {
return JsonUtils.parseArray(json, Button.class);
}
@Override
protected String toJson(List<Button> obj) {
return JsonUtils.toJsonString(obj);
}
}
} }

View File

@ -16,6 +16,7 @@ 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;
/** /**
@ -130,6 +131,6 @@ public class MpAutoReplyDO extends BaseDO {
* *
* {@link WxConsts.XmlMsgType} NEWS * {@link WxConsts.XmlMsgType} NEWS
*/ */
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
private MpMessageDO.Article responseArticle; private List<MpMessageDO.Article> responseArticles;
} }

View File

@ -7,8 +7,9 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface MpMenuMapper extends BaseMapperX<MpMenuDO> { public interface MpMenuMapper extends BaseMapperX<MpMenuDO> {
default MpMenuDO selectByAppId(String appId) { default MpMenuDO selectByAppIdAndMenuKey(String appId, String menuKey) {
return selectOne(MpMenuDO::getAppId, appId); return selectOne(MpMenuDO::getAppId, appId,
MpMenuDO::getMenuKey, menuKey);
} }
} }

View File

@ -1,13 +1,11 @@
package cn.iocoder.yudao.module.mp.service.menu; package cn.iocoder.yudao.module.mp.service.menu;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService; import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.menu.WxMenu; import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.error.WxErrorException;
@ -37,6 +35,9 @@ import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
@Slf4j @Slf4j
public class MpMenuServiceImpl implements MpMenuService { public class MpMenuServiceImpl implements MpMenuService {
@Resource
private MpMessageService mpMessageService;
@Resource @Resource
@Lazy // 延迟加载,避免循环引用报错 @Lazy // 延迟加载,避免循环引用报错
private MpServiceFactory mpServiceFactory; private MpServiceFactory mpServiceFactory;
@ -87,47 +88,21 @@ public class MpMenuServiceImpl implements MpMenuService {
@Override @Override
public WxMpXmlOutMessage reply(String appId, String key, String openid) { public WxMpXmlOutMessage reply(String appId, String key, String openid) {
// 获得菜单 // 第一步,获得菜单
MpMenuDO menu = mpMenuMapper.selectByAppId(appId); MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key);
if (menu == null) { if (menu == null) {
log.error("[reply][appId({}) 找不到对应的菜单]", appId); log.error("[reply][appId({}) key({}) 找不到对应的菜单]", appId, key);
return null;
}
// 匹配对应的按钮
MpMenuDO.Button button = getMenuButton(menu, key);
if (button == null) {
log.error("[reply][appId({}) key({}) 找不到对应的菜单按钮]", appId, key);
return null; return null;
} }
// 按钮必须要有消息类型,不然后续无法回复消息 // 按钮必须要有消息类型,不然后续无法回复消息
if (StrUtil.isEmpty(button.getMessageType())) { if (StrUtil.isEmpty(menu.getReplyMessageType())) {
log.error("[reply][appId({}) key({}) 不存在消息类型({})]", appId, key, button); log.error("[reply][menu({}) 不存在对应的消息类型]", menu);
return null; return null;
} }
// 回复消息 // 第二步,回复消息
return null; MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu);
} return mpMessageService.sendOutMessage(sendReqBO);
private MpMenuDO.Button getMenuButton(MpMenuDO menu, String key) {
// 先查询子按钮
for (MpMenuDO.Button button : menu.getButtons()) {
if (CollUtil.isEmpty(button.getSubButtons())) {
continue;
}
for (MpMenuDO.Button subButton : button.getSubButtons()) {
if (StrUtil.equals(subButton.getKey(), key)) {
return subButton;
}
}
}
// 再查询父按钮
for (MpMenuDO.Button button : menu.getButtons()) {
if (StrUtil.equals(button.getKey(), key)) {
return button;
}
}
return null;
} }
} }

View File

@ -9,6 +9,7 @@ import me.chanjar.weixin.common.api.WxConsts;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List;
/** /**
* Request BO * Request BO
@ -85,6 +86,6 @@ public class MpMessageSendOutReqBO {
*/ */
@Valid @Valid
@NotNull(message = "图文消息不能为空", groups = NewsGroup.class) @NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
private MpMessageDO.Article article; private List<MpMessageDO.Article> articles;
} }