当前位置: 首页 > 工具软件 > Wx-tools > 使用案例 >

微信开发(一)Wx-tools框架基础流程

宋凌龙
2023-12-01

前言

关于微信公众号开发,Wechat-Group/weixin-java-tools框架比较流行。写篇文章来分析下,该框架处理消息的基本流程。

以下源码均来自:
(weixin-java-tools框架源码)https://github.com/Wechat-Group/weixin-java-tools

(使用框架Demo)https://github.com/binarywang/weixin-java-mp-demo-springboot

一、微信消息发送、响应流程

从用户发送消息

-> 微信后台收到消息,再发送消息xml给开发者后台

-> 开发者后台收到消息xml,然后根据消息的路由操作,最后组装回复的消息xml

-> 发送给微信后台

-> 给用户

二、代码流程

以Wx-toos框架源码为例子,理清楚从微信客户端发送一条消息的整个代码流程。(主要解析消息路由器的过程)

文章中所有源码均省略get、set方法

2.1 加载配置信息、配置类

该过程主要依靠,WechatMpConfiguration、WechatMpProperties两个类完成。

WechatMpProperties负责读取配置信息

@ConfigurationProperties(prefix = "wechat.mp")
public class WechatMpProperties {
  /**
   * 设置微信公众号的appid
   */
  private String appId;

  /**
   * 设置微信公众号的app secret
   */
  private String secret;

  /**
   * 设置微信公众号的token
   */
  private String token;

  /**
   * 设置微信公众号的EncodingAESKey
   */
  private String aesKey;
  ...
}

WechatMpConfiguration配置类,比较重要。

@Configuration
@ConditionalOnClass(WxMpService.class)
@EnableConfigurationProperties(WechatMpProperties.class)
public class WechatMpConfiguration {
  ...

  @Bean
  @ConditionalOnMissingBean
  public WxMpConfigStorage configStorage() {
    WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage();
    configStorage.setAppId(this.properties.getAppId());
    configStorage.setSecret(this.properties.getSecret());
    configStorage.setToken(this.properties.getToken());
    configStorage.setAesKey(this.properties.getAesKey());
    return configStorage;
  }

  @Bean
  @ConditionalOnMissingBean
  public WxMpService wxMpService(WxMpConfigStorage configStorage) {
    WxMpService wxMpService = new me.chanjar.weixin.mp.api.impl.WxMpServiceImpl();
    wxMpService.setWxMpConfigStorage(configStorage);
    return wxMpService;
  }

  @Bean
  public WxMpMessageRouter router(WxMpService wxMpService) {
    final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);

    // 记录所有事件的日志 (异步执行)
    newRouter.rule().handler(this.logHandler).next();

    ......

    // 扫码事件
    newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
        .event(EventType.SCAN).handler(this.getScanHandler()).end();

    // 默认
    newRouter.rule().async(false).handler(this.getMsgHandler()).end();

    return newRouter;
  }
  ...
}

主要分析该类的三个方法。

首先在容器启动时,由于该类使用@Configuration注解,那么spring框架会按照由上至下的顺序自动调用configStorage()、wxMpService(…)、router(…)三个方法。之前的spring boot文章中讲过,@Bean注解在方法上,该方法返回的对象由spring容器管理。

2.1.1 WxMpMessageRouter、WxMpMessageRouterRule

主要分析WechatMpConfiguration类的router(…)方法。分析router(…)方法之前,先认识下WxMpMessageRouter、WxMpMessageRouterRule两个类。

  • WxMpMessageRouter(微信消息路由器)主要的属性和方法:
    • 属性
      • rules该路由器下的所有规则集合
    • 方法
      • route(...)处理微信消息的方法

WxMpMessageRouter路由器中有多个路由规则rules,在route方法中会遍历其中的规则对消息进行处理。(后面会详细讲到)

  • WxMpMessageRouterRule(微信消息路由规则)部分属性、方法如下:
    • 属性
      • routerBuilder该规则所属的路由器
      • msgType该规则所匹配的消息类型
      • event该规则所匹配的事件类型
      • async是否异步执行,默认异步
      • reEnter是否进入到下一个规则,默认false
      • handlers该消息规则,所拥有的所有的消息处理器集合
    • 方法
      • test(WxMpXmlMessage wxMessage)根据wxMessage消息的msgType和event匹配消息规则
      • handler(WxMpMessageHandler handler)设置微信消息处理器
      • end()规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则
      • next()规则结束,但是消息还会进入其他规则

2.1.2 WechatMpConfiguration配置类的router()方法

@Bean
public WxMpMessageRouter router(WxMpService wxMpService) {
    final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
    ...
    // 点击菜单链接事件
    newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
        .event(MenuButtonType.VIEW).handler(this.nullHandler).end();
    ...
}

直接分析上面点击菜单链接事件代码:
-> newRouter.rule()返回一个该路由器下的新的WxMpMessageRouterRule规则类。
-> .async(false)设置该规则为同步
-> .msgType(XmlMsgType.EVENT)设置该规则匹配的消息类型为event
-> .event(MenuButtonType.VIEW)设置该规则匹配的事件类型为VIEW
-> .handler(this.nullHandler)设置该规则的消息处理器
-> .end()匹配该规则后,不会匹配其他规则

router(…)方法中其他部分代码,同上。

2.2 消息处理过程

spring boot配置加载完成,启动后。消息到达controller,会调用WxMpMessageRouter类中的router(...)方法处理微信消息。

  /**
   * 处理微信消息
   */
  public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage, final Map<String, Object> context) {
    if (isMsgDuplicated(wxMessage)) {
      // 如果是重复消息,那么就不做处理
      return null;
    }

    //满足匹配条件的规则
    final List<WxMpMessageRouterRule> matchRules = new ArrayList<>();

    // 收集匹配的规则
    for (final WxMpMessageRouterRule rule : this.rules) {
      if (rule.test(wxMessage)) {//test方法用于判断消息类型是否与该规则匹配,并排除大小写
        matchRules.add(rule);
        if (!rule.isReEnter()) {//判断是否需要匹配下一个规则
          break;
        }
      }
    }

    if (matchRules.size() == 0) {
      return null;
    }

    WxMpXmlOutMessage res = null;


    final List<Future<?>> futures = new ArrayList<>();

    for (final WxMpMessageRouterRule rule : matchRules) {

      if (rule.isAsync()) {
        futures.add(
            //submit()方法接收Runable,会把线程执行的结果返回出来
          this.executorService.submit(new Runnable() {
            @Override
            public void run() {
              rule.service(wxMessage, context, WxMpMessageRouter.this.wxMpService, WxMpMessageRouter.this.sessionManager, WxMpMessageRouter.this.exceptionHandler);
            }
          })
        );
      } else {
        // 返回最后一个非异步的rule的执行结果
        res = rule.service(wxMessage, context, this.wxMpService, this.sessionManager, this.exceptionHandler);
        // 在同步操作结束,session访问结束
        this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser());
        sessionEndAccess(wxMessage);
      }
    }

    if (futures.size() > 0) {
      this.executorService.submit(new Runnable() {
        @Override
        public void run() {
          for (Future<?> future : futures) {
            try {
              future.get();
              WxMpMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser());
              // 异步操作结束,session访问结束
              sessionEndAccess(wxMessage);
            } catch (InterruptedException | ExecutionException e) {
              WxMpMessageRouter.this.log.error("Error happened when wait task finish", e);
            }
          }
        }
      });
    }
    return res;
  }
  /**
   * 处理微信推送过来的消息
   *
   * @param wxMessage
   * @return true 代表继续执行别的router,false 代表停止执行别的router
   */
  protected WxMpXmlOutMessage service(WxMpXmlMessage wxMessage,
                                      Map<String, Object> context,
                                      WxMpService wxMpService,
                                      WxSessionManager sessionManager,
                                      WxErrorExceptionHandler exceptionHandler) {

    if (context == null) {
      context = new HashMap<>();
    }

    try {
      // 如果拦截器不通过
      for (WxMpMessageInterceptor interceptor : this.interceptors) {
        if (!interceptor.intercept(wxMessage, context, wxMpService, sessionManager)) {
          return null;
        }
      }

      // 交给handler处理
      WxMpXmlOutMessage res = null;
      for (WxMpMessageHandler handler : this.handlers) {
        // 返回最后handler的结果
        if (handler == null) {
          continue;
        }
        res = handler.handle(wxMessage, context, wxMpService, sessionManager);
      }
      return res;
    } catch (WxErrorException e) {
      exceptionHandler.handle(e);
    }
    return null;

  }

方法执行流程如下:

WxMpMessageRouter类中的router(...)方法

-> 会调用WxMpMessageRouterRule类中的service(...)方法处理消息

-> 最终会调用XxxHandler类中的handler(...)方法

因此最终,如何处理微信消息,就在自定义的XxxHandler类中的handler(…)方法中去实现

 类似资料: