在使用本框架前建議對微信公眾號開發文檔有所了解,不過在不了解公眾號文檔的情況下使用本框架也能完成一個簡單的微信公眾號。
目前已兼容Spring Boot 1.4+以及Spring Boot 2.x,歡迎大家提issue和contribute,也歡迎加群627254793討論,開源項目需要大家共同來共享,謝謝~
微信測試公眾號申請鏈接
<!-- 支持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>
在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;
}
}
因為微信公眾號需要配置自己的服務器接口,測試時可直接使用本地進行測試,使用內網穿透可以令微信公眾平台訪問到你自己本地的服務器。
軟件可使用ngrok或者natapp,使用方式請參考兩者官方文檔。
啟動後生成的域名url地址可以配置在wx.callback-url中,以便進行oauth2認證。授權回調頁面域名中也需配置上上面生成的url中的域名。
在測試公眾號的接口配置信息中填寫在第四步中生成的域名,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通配符,該通配符支持對消息內容做通配,覆蓋該通配邏輯的會進入下面的執行邏輯。
與Spring Boot完美整合,如果自己沒有SpringBoot項目,想直接使用該框架搭建公眾號,可直接使用@WxApplication標記啟動類,該註解支持參數:menuAutoCreate,默認為true。表示自動創建微信菜單,可以設置為false關閉自動創建菜單的行為。若已有Spring Boot項目,想引入此框架,只需在你的任意配置類上標記註解@EnableWxMvc即可,參數同上。
共支持三種Mapping:
注意:綁定所在類需聲明為@WxController
支持以下類型參數:
參數綁定目前支持這幾種,如果有更好的方案需要支持,也可以直接提出意見與建議,我會及時進行處理的。
返回值支持以下類型:
上面異步發送消息都是使用的WxMessageTemplate發送的,下面講解。
本框架提供WxMessageTemplate發送消息,同時在template中提供了WxMessageProcessor支持,作用是在消息發送前對消息做處理。
如同步返回消息時,需要寫入fromUserName字段,而該字段是消息發送時的toUserName字段,沒有必要讓框架的使用者去處理這個字段,在WxCommonMessageProcessor處理器中就對該字段進行了處理,有興趣的可以參考源碼。
同時還支持以下轉換:對於media類型的消息,可以直接使用mediaUrl或者mediaPath寫入素材路徑,消息轉換器通過WxMediaManager自動管理素材獲得必要的素材id。 (關於WxMediaManager下面寫)
注意:所有的消息處理文本消息,建議都使用WxMessage裡的對應消息內容的builder來生成!
本框架提供WxMediaManager來管理素材,同時使用嵌入式數據庫保存素材與本地文件的對應關係,目前這部分功能我雖然完成了,但是總感覺有很大的不妥,希望有人能幫我看看順便提點意見。
上面消息發送中的媒體其實也是通過素材管理器來實現的。
0.2.0.alpha 版本優化存儲,使用接口WxMediaStore來管理媒體存儲,開發者可自行實現該接口並註冊為Spring的Bean來替換默認的MapDbWxMediaStore。各接口具體使用可參考MapDbWxMediaStore。這裡還可以提供一個基於內存的實現來替換MapDb。
本框架提供WxTokenStore接口來存儲token,並提供一個默認的基於內存的實現MemoryWxTokenStore,若有分佈式需要可以自行實現該接口,並把實現類作為Bean注入Spring即可。
使用WxApiInvokeSpi接口與WxInvokerProxyFactory工廠類自動生成微信接口調用代理,只需要聲明方法和註解即可,默認使用HttpComponent調用接口。有興趣的小伙伴可以看看源碼,我寫的也不太好,有更好的建議歡迎提出。
同時對返回值做初步分析,如果接口返回內容的errorCode不為0,則會作為異常拋出。異常體係為WxException及其子類。
PS:你也可以使用這種方式任意生成自己的代理調用接口,後續我會加入文檔,暫時懶。 。 。
可以通過開關開啟與關閉,通過@WxButton註解生成菜單結構,並自動調用接口判斷菜單是否改變,自動生成與更新菜單。
可以正確響應微信發送的Token驗證,方便快捷。
使用本框架,不會對SpringMvc自己原生的Mapping產生任何影響,也不會佔用任何獨有的Mapping關係(除了認證請求)。在此框架存在的情況下,你可以使用任何SpringMvc的原生功能,包括根目錄的請求,也不會被微信服務器獨自佔用。
若想使用單獨的地址作為微信的api調用地址,請配置wx.path為路徑信息,該路徑與微信公眾號後台管理裡的接口配置信息裡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。
代碼中可使用WxJsTicketManager來獲取WxJsTicket
@Autowired
WxJsTicketManager wxJsTicketManager;
詳細使用參考其中方法
配置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%libsecurity目錄下覆蓋原來的文件。如果安裝了JDK,還要將兩個jar文件也放到%JDK_HOME%jrelibsecurity目錄下覆蓋原來文件。
之後可以加入支持,使用註解定義消息客服,類似於@RabbitListener的方式
稍微有點麻煩,用戶分類
用戶分組什麼的是否有好的實現?暫時沒有需求
如支付等
初始版本
優化消息參數綁定,新增指定消息體綁定,參考WxRequestBody
上傳maven中央倉庫,生成javadoc,以及一系列規範化
加入apache copyright,全部delombok
上次加入copyright時不小心把所有文件的頭註釋刪掉了,目前補回一部分,等全部補回後加入微信卡券功能,發布release版本