微信公众平台开发,自定义菜单和消息处理及其工具类封装

如果你觉得内容对你有帮助的话,不如给个赞,鼓励一下更新😂。

今天是一篇实在的实战文章哦,最近做了一个公众号平台开发,里面碰到了自定义菜单开发、自动回复消息处理,水平有限,自己动了动小脑子做了一些简单封装,然后记录一下😘

消息处理

如果你正在看微信开放文档
那么你应该知道微信发送的消息有多种类型,那么我们对多种类型做封装呢,并且省去那些繁杂的if/else,这让我想起了以前看过的一篇文档《业务复杂=if else?刚来的大神竟然用策略+工厂彻底干掉了他们!

不扯那些没用的了,直接上代码啦,我们边看边解释 =>

封装微信消息实体类、常量类

/**
 * @description 回复微信消息的实体类
 * @author Linn-cn
 * @date 2020/8/23
 */
@Data
public class BaseMsgReply {
    /**
     * 接收方帐号(收到的OpenID)
     */
    @JacksonXmlProperty(localName = "ToUserName")
    private String toUserName;

    /**
     * 开发者微信号
     */
    @JacksonXmlProperty(localName = "FromUserName")
    private String fromUserName;

    /**
     * 消息创建时间 (整型)
     */
    @JacksonXmlProperty(localName = "CreateTime")
    private Long createTime;

    /**
     * 消息类型
     */
    @JacksonXmlProperty(localName = "MsgType")
    private String msgType;
}
/**
 * @description 微信发送消息的实体类
 * @author Linn-cn
 * @date 2020/8/23
 */
@Data
public class BaseMsgSend {
    /**
     * 开发者微信号
     */
    @JacksonXmlProperty(localName = "ToUserName")
    private String toUserName;

    /**
     * 发送方帐号(一个OpenID)
     */
    @JacksonXmlProperty(localName = "FromUserName")
    private String fromUserName;

    /**
     * 消息创建时间 (整型)
     */
    @JacksonXmlProperty(localName = "CreateTime")
    private Long createTime;

    /**
     * 消息类型
     */
    @JacksonXmlProperty(localName = "MsgType")
    private String msgType;

    /**
     * 消息id,64位整型
     */
    @JacksonXmlProperty(localName = "MsgId")
    private Long msgId;
}

这是两个基类,接下来我们针对微信不同的消息类型可以extend以上两个类,然后做扩展,这里我举例一个如下:

/**
 * @description 带事件的消息实体类
 * @author Linn-cn
 * @date 2020/8/23
 */
@Data
@ToString(callSuper = true)
@JacksonXmlRootElement(localName = "xml")
public class MsgEventSend extends BaseMsgSend {

    /**
     * 事件类型
     */
    @JacksonXmlProperty(localName = "Event")
    private String event;

    /**
     * 事件KEY值,与自定义菜单接口中KEY值对应
     */
    @JacksonXmlProperty(localName = "EventKey")
    private String eventKey;
}

可能会有小伙伴问 @JacksonXmlProperty一系列的注解是什么 ,为什么自己没有,这个主要是因为微信发送的消息和回复给微信的消息都得是xml格式的,这个注解是jackson中用来处理xml的方法,导入方式如下:

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.8.11</version>
        </dependency>

常量类如下,不做解释

/**
 * @description 微信公众号常量类
 * @author Linn-cn
 * @date 2020/8/23
 */
public class WxOaConstants {

    /**
     * 返回消息类型:文本
     */
    public static final String RESP_MESSAGE_TYPE_TEXT = "text";
    /**
     * 返回消息类型:音乐
     */
    public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
    /**
     * 返回消息类型:图文
     */
    public static final String RESP_MESSAGE_TYPE_NEWS = "news";
    /**
     * 返回消息类型:图片
     */
    public static final String RESP_MESSAGE_TYPE_Image = "image";
    /**
     * 返回消息类型:语音
     */
    public static final String RESP_MESSAGE_TYPE_Voice = "voice";
    /**
     * 返回消息类型:视频
     */
    public static final String RESP_MESSAGE_TYPE_Video = "video";
    /**
     * 请求消息类型:文本
     */
    public static final String REQ_MESSAGE_TYPE_TEXT = "text";
    /**
     * 请求消息类型:图片
     */
    public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
    /**
     * 请求消息类型:链接
     */
    public static final String REQ_MESSAGE_TYPE_LINK = "link";
    /**
     * 请求消息类型:地理位置
     */
    public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
    /**
     * 请求消息类型:音频
     */
    public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
    /**
     * 请求消息类型:视频
     */
    public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
    /**
     * 请求消息类型:推送
     */
    public static final String REQ_MESSAGE_TYPE_EVENT = "event";
    /**
     * 事件类型:subscribe(订阅)
     */
    public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
    /**
     * 事件类型:unsubscribe(取消订阅)
     */
    public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
    /**
     * 事件类型:CLICK(自定义菜单点击事件)
     */
    public static final String EVENT_TYPE_CLICK = "click";
    /**
     * 事件类型:VIEW(自定义菜单 URl 视图)
     */
    public static final String EVENT_TYPE_VIEW = "view";
    /**
     * 事件类型:LOCATION(上报地理位置事件)
     */
    public static final String EVENT_TYPE_LOCATION = "location";
    /**
     * 事件类型:LOCATION(上报地理位置事件)
     */
    public static final String EVENT_TYPE_SCAN = "scan";
    /**
     * 消息类型
     */
    public static final String MSG_TYPE = "MsgType";
    /**
     * oa的accessToken,key
     */
    public static final String OA_ACCESS_TOKEN = "wx:oa:access_token";
}

用策略模式封装不同的消息处理

策略模式的定义:属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。

简单来说:我们定义一个专门用来处理微信消息的接口,然后不同的处理方式就各自去实现这个接口即可

/**
 * @description 公众号请求处理器
 * @author Linn-cn
 * @date 2020/8/23
 */
public interface HandlerService {

    /**
     * 处理微信公众号发送的请求
     * @param requestBody
     * @author Linn-cn
     */
    String handler(String requestBody) throws Exception;

    /**
     * 消息流转换
     *
     * @param msgSendModel
     * @param msgReplyModel
     * @param MsgType
     * @author Linn-cn
     */
    default void sendToReply(BaseMsgSend msgSendModel, BaseMsgReply msgReplyModel,
                             String MsgType) {
        msgReplyModel.setToUserName(msgSendModel.getFromUserName());
        msgReplyModel.setFromUserName(msgSendModel.getToUserName());
        msgReplyModel.setCreateTime(Instant.now().getEpochSecond());
        msgReplyModel.setMsgType(MsgType);
    }

}

我们可以看到handle方法是专门用来处理微信消息的,现在我举两个例子去实现它,小伙伴们看一下就懂了

/**
 * @description 回复文本消息类型处理
 * @author Linn-cn
 * @date 2020/8/23
 */
@Component
@WxOaHandler(msgType = WxOaConstants.REQ_MESSAGE_TYPE_TEXT)
public class ReplyTextHandlerServiceImpl implements HandlerService {


    @Override
    public String handler(String requestBody) throws IOException, IllegalAccessException {
        XmlMapper xmlMapper = new XmlMapper();
        MsgTextSend sendTextModel = xmlMapper.readValue(requestBody, MsgTextSend.class);
        MsgReplyTextSend replyTextModel = new MsgReplyTextSend();
        sendToReply(sendTextModel, replyTextModel, WxOaConstants.REQ_MESSAGE_TYPE_TEXT);
        replyTextModel.setContent("谢谢你的关注,更多功能还未开放...");
        return xmlMapper.writeValueAsString(replyTextModel);
    }
}

这个是用来回复普通的文本消息的

/**
 * @description 订阅处理
 * @author Linn-cn
 * @date 2020/8/23
 */
@Component
@WxOaHandler(msgType = WxOaConstants.REQ_MESSAGE_TYPE_EVENT, event = WxOaConstants.EVENT_TYPE_SUBSCRIBE)
public class SubscribeHandlerServiceImpl implements HandlerService {

    @Override
    public String handler(String requestBody) throws IOException {
        XmlMapper xmlMapper = new XmlMapper();
        MsgEventSend sendEventModel = xmlMapper.readValue(requestBody, MsgEventSend.class);
        MsgReplyTextSend replyTextModel = new MsgReplyTextSend();
        sendToReply(sendEventModel, replyTextModel, WxOaConstants.REQ_MESSAGE_TYPE_TEXT);
        String content = "客服在线时间工作日9:00-12:00,13:30-18:00,非工作日请留言,我们会尽快回复您!";
        replyTextModel.setContent(content);
        return xmlMapper.writeValueAsString(replyTextModel);
    }
}

这个用来处理当用户关注公众号的时候自动回复的事件,这样是不是就很简单了?那么接下来我们需要做的就是怎么才能用上这些实现类呢,我们继续往下看,细心的小伙伴应该看到了 @WxOaHandler 注解了,我们就来说说它的妙用。

自定义注解 + 工厂模式

首先 @WxOaHandler 是一个自定义注解,主要用来定义在工厂中获取对应实现类的逻辑

/**
 * @description 微信oa处理器注解
 * @author Linn-cn
 * @date 2020/8/23
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WxOaHandler {

    /**
     * 消息类型
     * @see WxOaConstants
     * @return
     */
    String msgType() default "";

    /**
     * 如果MsgType为Event,就需要填写
     * @see WxOaConstants
     * @return
     */
    String event() default "";
}

然后我们结合上面两个例子上面注解的使用来看看工厂类是怎么实现的

/**
 * @description 微信消息处理服务 工厂
 * @author Linn-cn
 * @date 2020/8/23
 */
@Component
public class HandlerServiceFactory {

    private static final Map<String, HandlerService> MAP = new ConcurrentHashMap<>();

    @Autowired
    private void setServices(HandlerService[] services) {
        for (HandlerService service : services) {
            WxOaHandler handler = service.getClass().getAnnotation(WxOaHandler.class);
            if (handler != null) {
                String key = handler.msgType();
                if (StringUtils.isNotBlank(handler.event())) {
                    key += handler.event();
                }
                MAP.put(key, service);
            }
        }
    }

    /**
     * 插入微信消息处理handler
     *
     * @param key
     * @param handlerService
     * @author Linn-cn
     */
    public static void setService(String key, HandlerService handlerService) {
        MAP.put(key, handlerService);
    }

    /**
     * 获得对应的微信消息处理handler
     *
     * @param simpleName
     */
    public static HandlerService getService(String simpleName) {
        return MAP.get(simpleName);
    }

}

我们可以看到 setService 方法,这个方法利用里Spring的 @Autowired 支持数组注入的规则,将实现了 HandlerService 接口的实现类按

            if (handler != null) {
                String key = handler.msgType();
                if (StringUtils.isNotBlank(handler.event())) {
                    key += handler.event();
                }
                MAP.put(key, service);
            }

首先判断是什么消息类型,并且是否支持事件,然后按规则 put 进 Map中。

写了这么多,如何使用呢?

    /**
     * 微信公众号相关请求操作
     *
     * @param requestBody 微信发送的xml数据
     * @author Linn-cn
     */
    @ApiOperation(value = "微信公众号相关请求操作")
    @PostMapping(value = "/inspect", produces = "application/xml;charset=UTF-8")
    public String autoReply(
            @RequestBody String requestBody) throws Exception {
        String key = WxOaParseUtils.getElement(requestBody, WxOaConstants.MSG_TYPE);
        if (StringUtils.isNotBlank(key)) {
            if (WxOaConstants.REQ_MESSAGE_TYPE_EVENT.equals(key)) {
                key += WxOaParseUtils.getElement(requestBody, StringUtils.capitalize(WxOaConstants.REQ_MESSAGE_TYPE_EVENT));
            }
            HandlerService handlerService = HandlerServiceFactory
                    .getService(key);
            System.out.println("得到的类为:" + handlerService);
            if (handlerService != null) {
                return handlerService.handler(requestBody);
            }
        }
        return SUCCESS;
    }


上面的工具类方法:
    /**
     * 获得节点值
     * @param requestBody 微信发送的xml信息
     * @author Linn-cn
     */
    public static String getElement(String requestBody,String elementKey) {
        SAXReader reader = new SAXReader();
        try {
            // 读取输入流,获得文档对象
            Document document = reader.read(new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8)));
            // 根据文档对象获取根节点
            Element root = document.getRootElement();
            Element element = root.element(elementKey);
            return element.getStringValue();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return "success";
    }

这样我们就可以得到我们想要的service类去处理微信消息啦,是不是很简单?

自定义菜单

这个其实很简单,就是怎么设计实体类,这里主要用了组合模式,不懂的自行百度一下吧,就不赘述了,直接上代码

/**
 * @description 自定义菜单
 * @author Linn-cn
 * @date 2020/8/23
 */
public class Button {

    private List<AbstractButton> button = new ArrayList<>();

    public List<AbstractButton> getButton() {
        return button;
    }

    public void setButton(List<AbstractButton> button) {
        this.button = button;
    }

    public void add(AbstractButton button){
        this.button.add(button);
    }
}
/**
 * @description 抽象按钮
 * @author Linn-cn
 * @date 2020/8/23
 */
public class AbstractButton {

    private String name;

    private String key;

    private String url;

    public AbstractButton() {
    }

    public AbstractButton(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void add(AbstractButton button){
        // 默认实现
        throw new UnsupportedOperationException();
    }
}
/**
 * @description 子菜单
 * @author Linn-cn
 * @date 2020/8/23
 */
public class SubButton extends AbstractButton{

    private List<AbstractButton> sub_button;

    public SubButton(String name) {
        super(name);
        this.sub_button = new ArrayList<> ();
    }

    public List<AbstractButton> getSub_button() {
        return sub_button;
    }

    public void setSub_button(List<AbstractButton> sub_button) {
        this.sub_button = sub_button;
    }

    @Override
    public void add(AbstractButton button) {
        this.sub_button.add(button);
    }
}
/**
 * @description 视图按钮
 * @author Linn-cn
 * @date 2020/8/23
 */
public class ViewButton extends AbstractButton{

    public final String type = WxOaConstants.EVENT_TYPE_VIEW;

    public ViewButton() {
    }

    public ViewButton(String name,String url) {
        setName(name);
        setUrl(url);
    }
}

这篇文章到这就结束啦,喜欢的话就给个赞 + 收藏 + 关注吧!😋

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linn-cn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值