How To Use

Travis Maven Central License QQ群

本框架基于Spring Boot实现,使用注解完成快速开发,可以快速的完成一个微信公众号,重新定义公众号开发。

在使用本框架前建议对微信公众号开发文档有所了解,不过在不了解公众号文档的情况下使用本框架也能完成一个简单的微信公众号。

目前已兼容Spring Boot 1.4+以及Spring Boot 2.x,欢迎大家提issue和contribute,也欢迎加群627254793讨论,开源项目需要大家共同来共享,谢谢~

[toc]

一、简单示例:

1. 申请测试公众号

微信测试公众号申请链接

2. 准备完成,创建maven项目并加入maven依赖

<!-- 支持1.4.0.RELEASE及以上,包括2.x -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
</parent>

<dependencies>
    <!-- fastbootWeixin的核心依赖 -->
    <dependency>
        <groupId>com.mxixm</groupId>
        <artifactId>fastboot-weixin</artifactId>
        <version>0.6.2</version>
    </dependency>

    <!-- SpringBoot的web项目,必须 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 暂时只能使用apache的http,后续可加入其它http支持 -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpcore</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
</dependencies>

3. 编写示例代码

在resource目录下新建配置文件application.properties或者其他spring boot支持的配置文件类型,加入配置:

测试代码:

package com.mxixm.fastboot.weixin;

import com.mxixm.fastboot.weixin.annotation.WxApplication;
import com.mxixm.fastboot.weixin.annotation.WxAsyncMessage;
import com.mxixm.fastboot.weixin.annotation.WxButton;
import com.mxixm.fastboot.weixin.module.web.WxRequest;
import com.mxixm.fastboot.weixin.module.event.WxEvent;
import com.mxixm.fastboot.weixin.module.message.WxMessage;
import com.mxixm.fastboot.weixin.module.message.WxMessageBody;
import com.mxixm.fastboot.weixin.module.user.WxUser;
import com.mxixm.fastboot.weixin.mvc.annotation.WxController;
import com.mxixm.fastboot.weixin.mvc.annotation.WxEventMapping;
import com.mxixm.fastboot.weixin.mvc.annotation.WxMessageMapping;
import org.springframework.boot.SpringApplication;

@WxApplication
@WxController
public class WxApp {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(WxApp.class, args);
    }

    /**
     * 定义微信菜单
     */
    @WxButton(group = WxButton.Group.LEFT, main = true, name = "左")
    public void left() {
    }

    /**
     * 定义微信菜单
     */
    @WxButton(group = WxButton.Group.RIGHT, main = true, name = "右")
    public void right() {
    }

    /**
     * 定义微信菜单,并接受事件
     */
    @WxButton(type = WxButton.Type.CLICK,
            group = WxButton.Group.LEFT,
            order = WxButton.Order.FIRST,
            name = "文本消息")
    public String leftFirst(WxRequest wxRequest, WxUser wxUser) {
        return "测试文本消息";
    }

    /**
     * 定义微信菜单,并接受事件
     */
    @WxButton(type = WxButton.Type.VIEW,
            group = WxButton.Group.LEFT,
            order = WxButton.Order.SECOND,
            url = "http://baidu.com",
            name = "点击链接")
    @WxAsyncMessage
    public WxMessage link() {
        return WxMessage.newsBuilder().addItem("测试图文消息", "测试", "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/logo_white.png", "http://baidu.com").build();
    }

    /**
     * 接受微信事件
     * @param wxRequest
     * @param wxUser
     */
    @WxEventMapping(type = WxEvent.Type.UNSUBSCRIBE)
    public void unsubscribe(WxRequest wxRequest, WxUser wxUser) {
        System.out.println(wxUser.getNickName() + "退订了公众号");
    }

    /**
     * 接受用户文本消息,异步返回文本消息
     * @param content
     * @return the result
     */
    @WxMessageMapping(type = WxMessage.Type.TEXT)
    @WxAsyncMessage
    public String text(WxRequest wxRequest, String content) {
        WxSession wxSession = wxRequest.getWxSession();
        if (wxSession != null && wxSession.getAttribute("last") != null) {
            return "上次收到消息内容为" + wxSession.getAttribute("last");
        }
        return "收到消息内容为" + content;
    }

    /**
     * 接受用户文本消息,同步返回图文消息
     * @param content
     * @return the result
     */
    @WxMessageMapping(type = WxMessage.Type.TEXT, wildcard = "1*")
    public WxMessage message(WxSession wxSession, String content) {
        wxSession.setAttribute("last", content);
        return WxMessage.newsBuilder()
                .addItem(WxMessageBody.News.Item.builder().title(content).description("随便一点")
                        .picUrl("http://k2.jsqq.net/uploads/allimg/1702/7_170225142233_1.png")
                        .url("http://baidu.com").build())
                .addItem(WxMessageBody.News.Item.builder().title("第二条").description("随便二点")
                        .picUrl("http://k2.jsqq.net/uploads/allimg/1702/7_170225142233_1.png")
                        .url("http://baidu.com").build())
                .build();
    }

    /**
     * 接受用户文本消息,异步返回文本消息
     * @param content
     * @return the result
     */
    @WxMessageMapping(type = WxMessage.Type.TEXT, wildcard = "2*")
    @WxAsyncMessage
    public String text2(WxRequestBody.Text text, String content) {
        boolean match = text.getContent().equals(content);
        return "收到消息内容为" + content + "!结果匹配!" + match;
    }
}

4. 配置内网穿透

因为微信公众号需要配置自己的服务器接口,测试时可直接使用本地进行测试,使用内网穿透可以令微信公众平台访问到你自己本地的服务器。

软件可使用ngrok或者natapp,使用方式请参考两者官方文档。

启动后生成的域名url地址可以配置在wx.callback-url中,以便进行oauth2认证。授权回调页面域名中也需配置上上面生成的url中的域名。

5. 配置测试公众号

在测试公众号的接口配置信息中填写在第四步中生成的域名,token使用配置文件中的token,保存后,如果不出意外应该会验证成功。如有问题请及时反馈。

二、示例图解

菜单示例

菜单示例

消息示例

消息示例

三、示例说明

上面的示例在启动后,请关注自己的公众号,此时公众号的菜单应该是有两个主菜单:左、右,同时左有两个子菜单:文本消息、点击链接。

在点击文本消息菜单时,会收到文本消息,内容为:测试文本消息。

在点击第二个点击链接时,会跳转至百度,并收到一条图文消息,标题是测试图文消息。

给公众号发送文本消息,消息内容不是1开头时,会收到公众号回复内容:"收到消息内容为" + 发送的内容。

给公众号发送文本消息,消息内容是1开头时,会收到图文消息的回复。

当有用户退订公众号时,会在System.out中打印用户昵称 + "退订了公众号"

四、示例讲解

注解@WxApplication用于声明该应用为微信application,并使用SpringApplication启动。若已有SpringBoot环境,请在你的@SpringApplication类上加入注解@EnableWxMvc,效果一样。可以看源码。

注解@WxController用于声明该类为微信Controller,只有声明了这个注解,才会绑定在微信服务器的请求映射中,否则该类会被忽略。

注解@WxButton(group = WxButton.Group.LEFT, main = true, name = "左")用于声明一个按钮箱,group代表分组,有左中右三个分组,分别对应微信的三个一级菜单。main为boolean值,代表该菜单项是否为一级菜单。name就是菜单名。

注解@WxButton(type = WxButton.Type.CLICK, group = WxButton.Group.LEFT, order = WxButton.Order.FIRST, name = "文本消息") 用于声明左边分组的子菜单,order代表顺序,这里是第一个。

public String leftFirst(WxRequest wxRequest, WxUser wxUser) { return "测试文本消息"; } 这里有三个点:

@WxButton(type = WxButton.Type.VIEW, group = WxButton.Group.LEFT, order = WxButton.Order.SECOND, url = "http://baidu.com", name = "点击链接") 该注解同上面,类型变为View,具体内容可参考该枚举注释,或者公众号文档。注意每种菜单类型都有自己的限制,请参考文档,如不满足条件启动时就会发生异常。

注解@WxAsyncMessage表明异步回复消息,参考客服消息,暂时未对多客服进行支持。

WxMessage.News.builder(),在WxMessage类中,有不同的静态内部类,以及他们的builder,通过builder可以方便的构造微信不同类型的消息,请参考被动回复消息客服消息

注解@WxEventMapping(type = WxEvent.Type.UNSUBSCRIBE)绑定取消订阅事件,当有用户取消关注时,会进入这个注解下面的逻辑。还有一点要注解,WxEventMapping所有的回复内容都会被异步发送给用户。

注解@WxMessageMapping(type = WxMessage.Type.TEXT)表示绑定用户发送的文本消息到下面的方法逻辑,public String text(String content) { return "收到消息内容为" + content; }。content会自动被赋值为用户发送的文本内容。

注解@WxMessageMapping(type = WxMessage.Type.TEXT, wildcard = "1*")与上面相同,不同的是wildcard通配符,该通配符支持对消息内容做通配,覆盖该通配逻辑的会进入下面的执行逻辑。

五、功能支持

1. Spring Boot风格的启动方式

与Spring Boot完美整合,如果自己没有SpringBoot项目,想直接使用该框架搭建公众号,可直接使用@WxApplication标记启动类,该注解支持参数:menuAutoCreate,默认为true。表示自动创建微信菜单,可以设置为false关闭自动创建菜单的行为。若已有Spring Boot项目,想引入此框架,只需在你的任意配置类上标记注解@EnableWxMvc即可,参数同上。

2. Spring Mvc风格的Mapping

共支持三种Mapping:

注意:绑定所在类需声明为@WxController

3. Spring Mvc风格的参数绑定

支持以下类型参数:

参数绑定目前支持这几种,如果有更好的方案需要支持,也可以直接提出意见与建议,我会及时进行处理的。

4. Spring Mvc风格的返回值

返回值支持以下类型:

上面异步发送消息都是使用的WxMessageTemplate发送的,下面讲解。

5. Spring风格的消息发送

本框架提供WxMessageTemplate发送消息,同时在template中提供了WxMessageProcessor支持,作用是在消息发送前对消息做处理。

如同步返回消息时,需要写入fromUserName字段,而该字段是消息发送时的toUserName字段,没有必要让框架的使用者去处理这个字段,在WxCommonMessageProcessor处理器中就对该字段进行了处理,有兴趣的可以参考源码。

同时还支持以下转换:对于media类型的消息,可以直接使用mediaUrl或者mediaPath写入素材路径,消息转换器通过WxMediaManager自动管理素材获得必要的素材id。(关于WxMediaManager下面写)

注意:所有的消息处理文本消息,建议都使用WxMessage里的对应消息内容的builder来生成!

6. 素材自动管理

本框架提供WxMediaManager来管理素材,同时使用嵌入式数据库保存素材与本地文件的对应关系,目前这部分功能我虽然完成了,但是总感觉有很大的不妥,希望有人能帮我看看顺便提点意见。

上面消息发送中的媒体其实也是通过素材管理器来实现的。

0.2.0.alpha 版本优化存储,使用接口WxMediaStore来管理媒体存储,开发者可自行实现该接口并注册为Spring的Bean来替换默认的MapDbWxMediaStore。各接口具体使用可参考MapDbWxMediaStore。这里还可以提供一个基于内存的实现来替换MapDb。

7. 内置AccessToken管理

本框架提供WxTokenStore接口来存储token,并提供一个默认的基于内存的实现MemoryWxTokenStore,若有分布式需要可以自行实现该接口,并把实现类作为Bean注入Spring即可。

8. 微信接口调用与返回值自动处理

使用WxApiInvokeSpi接口与WxInvokerProxyFactory工厂类自动生成微信接口调用代理,只需要声明方法和注解即可,默认使用HttpComponent调用接口。有兴趣的小伙伴可以看看源码,我写的也不太好,有更好的建议欢迎提出。

同时对返回值做初步分析,如果接口返回内容的errorCode不为0,则会作为异常抛出。异常体系为WxException及其子类。

PS:你也可以使用这种方式任意生成自己的代理调用接口,后续我会加入文档,暂时懒。。。

9. 菜单自动生成与自动更新

可以通过开关开启与关闭,通过@WxButton注解生成菜单结构,并自动调用接口判断菜单是否改变,自动生成与更新菜单。

10. 内置微信接口认证

可以正确响应微信发送的Token验证,方便快捷。

11. 完全无侵入的MVC模式

使用本框架,不会对SpringMvc自己原生的Mapping产生任何影响,也不会占用任何独有的Mapping关系(除了认证请求)。在此框架存在的情况下,你可以使用任何SpringMvc的原生功能,包括根目录的请求,也不会被微信服务器独自占用。

若想使用单独的地址作为微信的api调用地址,请配置wx.path为路径信息,该路径与微信公众号后台管理里的接口配置信息里url的路径需要一致。

12. 微信Web认证拦截器与URL自动转换

提供微信OAUTH2认证拦截,通过配置wx.callback-domain填写OAUTH2授权回调页面域名,通过配置wx.mvc.interceptor.includePatterns和wx.mvc.interceptor.excludePatterns来配置拦截的目标地址,你可以提供一个WxOAuth2Callback接口的实现类作为Bean,在WxOAuth2Interceptor中会自动注入这个bean,并在微信Web认证通过后调用after(WxOAuth2Context context)方法把相关的context传递给该Bean的方法,你可以在该方法中获取到context了的WxWebUser,并通过WxUserManager把WxWebUser转换为WxUser。关系详细信息请参考:微信网页授权

自0.3.6版本后,提供wx.callback-domain的替代属性wx.callback-url,用于设置回调的url,包含回调域名以及协议类型。比wx.callback-domain多了协议类型,请使用该配置替换callback-domain配置。

附加功能1: view类型的WxButton,自动判断其中url是否属于授权回调域名下地址,根据需要自动处理为包含OAuth2的url。可结合默认的拦截器实现菜单url获取点击用户信息的功能。同时结合wx.callback-url功能,支持菜单中配置相对路径,无需携带域名。

附加功能2:自动判断消息中的url是否需要添加OAuth重定向,请参考WxRedirectUtils。

13. 内置WxJsConfig管理器

代码中可使用WxJsTicketManager来获取WxJsTicket

@Autowired
WxJsTicketManager wxJsTicketManager;

详细使用参考其中方法

14. 支持消息加解密

配置wx.encrypt=true,wx.encodingAesKey=公众号后台设置的aesKey,即开启消息加解密模式

注意开启消息加解密时,需要开启JCE无限制权限。

JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

JDK8的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt。如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件。如果安装了JDK,还要将两个jar文件也放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件。

六、相关链接

  1. JavaDocs
  2. 微信公众号开发文档
  3. Gitee
  4. GitHub

七、暂时不支持的功能

1. 自定义客服回复消息

之后可以加入支持,使用注解定义消息客服,类似于@RabbitListener的方式

2. 个性化菜单

稍微有点麻烦,用户分类

3. 高级用户管理

用户分组什么的是否有好的实现?暂时没有需求

4. 公众号其他高级功能

如支付等

八、更新日志

0.0.1-SNAPSHOT

初始版本

0.1.0-SNAPSHOT:

  1. 加入WxSession,类似HttpSession,默认实现中sessionId是fromUserName。如果自行提供WxSessionManager的实现类bean,则会使用你的Bean作为manager,默认实现是基于内存的,请自行扩展。
  2. 优化WxRequest,原始版本为直接把微信请求内容作为Request,更新为WxRequest为微信服务器请求的包装,内部的body属性为微信服务器请求体。
  3. 拆分不同的消息请求体

0.1.1-SNAPSHOT:

优化消息参数绑定,新增指定消息体绑定,参考WxRequestBody

0.1.2.SNAPSHOT & 0.1.2.alpha

上传maven中央仓库,生成javadoc,以及一系列规范化

0.1.2.beta

加入apache copyright,全部delombok

0.1.2.rc

上次加入copyright时不小心把所有文件的头注释删掉了,目前补回一部分,等全部补回后加入微信卡券功能,发布release版本

0.2.0.alpha

  1. 新增卡券相关接口、推广(二维码与短链接)相关接口
  2. 重构消息功能,消息推送现支持群发和单发
  3. 重构媒体存储模块,现在更容易替换为自己的实现,详情参考Wiki

0.2.1.alpha

  1. 重构WxAsyncMessage,之前是错误实现,因为只实现了发送是Async的,方法调用不是Async的。现在重构为方法的调用都是Async的,这才是真正的WxAsyncMessage
  2. 增加微信模板消息功能
  3. 修复其他一些bug

0.2.1.beta

  1. 增加部分说明,修复部分bug,包括上个版本WxAsyncMessage中返回消息提示服务器故障的bug。
  2. 增加菜单url自动重写为微信web认证url。

0.3.0

  1. 优化部分包名。
  2. 修复一个大bug:当没有配置消息处理器时提示服务器故障。默认提供一个返回空的处理器。
  3. 发布正式版。

0.3.1

  1. 修复上次拦截器返回修改为HttpEntity.EMPTY导致原方法声明与返回类不一致的bug。
  2. 修复忘了加getter的问题。

0.3.2

  1. 修复图文消息的bug(Request异步使用时,getRequestURL等会错误)。
  2. 修复url处理会处理为小写的bug。

0.3.3

  1. 修复无后缀文件名上传素材时提示无效的媒体类型错误。
  2. 修复未关注用户放缓存导致关注后取不到信息的bug。

0.3.4

  1. 新增手动调用WxMessageTemplate发送消息时的参数处理。

0.3.5

  1. 再次优化消息和菜单中url的处理逻辑。

0.3.6

  1. 新增wx.callback-url配置以替换wx.callback-domain,基于此可在菜单配置中使用url相对路径。
  2. 修复群发消息的bug,新增预览群发消息

0.3.7

  1. 仓库问题,上传失败,重新发布为0.3.8。

0.3.8

  1. 修复未配置wx.callback-xxx时报空指针的bug。
  2. 修复返回值类型为WxMessage的Controller方法,在非微信请求的情况下报空指针的bug。
  3. 修复群发消息中,群发全部时报invalid group id错误的bug。
  4. 修复异步发送消息时,异常无法被捕获的bug。

0.5.0

  1. 加入Spring Boot 2.x支持

0.5.1

  1. 修复缺失MessageConverter的bug
  2. 修复Spring 5的FormHttpMessageConverter中对ContentType头包装了charset导致微信服务器不兼容的bug
  3. 优化WxRequest.Body中的scene

0.6.0

  1. 添加WxJsTicket相关的支持
  2. 修复群发消息回调报错的bug
  3. 修复可能引起code被盗用的安全性bug

0.6.1

  1. 重构消息处理器
  2. 为@WxButton新增${}表达式解析
  3. 微信菜单新增小程序类型
  4. 修复在没有匹配的wildcard的情况下,返回内容错误的情况

0.6.2

  1. 完成消息加解密支持
  2. 添加长链接转短连接接口
  3. 添加调用接口的access_token容错处理,access_token失效时先强制刷新再重新调用接口
  4. 重构微信请求转换器和返回值处理器,优化原逻辑
  5. 在WxMessageUtils中添加微信文本消息中的a标签构造器