java編寫微信公眾賬號收發訊息
這兩天無聊就申請了一個微信的訂閱號,然後從網上搜集了些材料,通過蒐集的材料使我的公眾號可以接收文字訊息並返回簡單的文字資訊。這裡做下記錄,日後如果要深入研究一下可以從這裡撿起。
拿到訂閱號後我把頭像設定了一下與微訊號設定了一番。對於我們來說,最好奇的莫過於它能幹嘛,所以就點選了自動回覆,自定義了回覆內容,然後用自己微訊號關注了我的訂閱號。關注後,立馬給我發了我自己設定的資訊。頓時覺得好新奇,感覺不錯,然後就是看到自定義選單了,這東西好啊,可以自己定義選單,說幹就幹,我立馬設定了三個選單(一級選單最多有三個,這些東西騰訊都說的很明白),設定好後我就取消關注重新加了一遍,但是自定義選單沒有出來!!有點懊惱,這是怎麼回事,沒有放棄的我重複了幾次還是無功而返。原來是這裡有一個稽核的過程,等一天左右稽核完後,我再次訪問我的公眾號時就了選單了,哈哈。
好了,上面說了這麼多,是我在玩的過程中的細節,作為一個程式設計師來說,最想了解的肯定是微信可以讓我們開發者來做什麼。微信為我們提供了兩種方式:(1)編輯模式
(2)開發者模式 上文中所做的都是使用了編輯模式,也就是說剛申請完的訂閱號是以編輯模式供申請者使用的。
為了可以利用開發者模式,我們需要通過開發者認證,在頁面最下面有一個開發者中心,點選後通過申請,那麼就是作為開發者的第一步。
1.開發者認證相關
首先,你註冊完訂閱號後(我是個人玩,所以申請了個人訂閱號,不過現在才發現在開發者模式下沒有獲得自定義選單的許可權),點選開發者中心,然後可以申請成為開發者
然後,就可以看到我們的開發者ID 了,其中包含應用ID與應用祕鑰這兩個,通過這兩個東西我們可以獲取我們公眾號的唯一票據access_token,也就是唯一辨識你公眾號的東西。下面是去取得這個標示的方法。
將你的應用id與祕鑰填上就可以獲取到你的access_token 了,在這裡只是說一下這個東西,對於我在接收與訊息的試驗中沒有用到它,但是在自定義選單(可惜我的是個人訂閱號,沒有許可權!)等功能時就會用到了,不然微信伺服器怎麼知道你是哪個公眾號,然後給你定義選單呢!O(∩_∩)O
2.看了看我可憐的許可權,我打算稍微瞭解一下收發訊息的介面。(在這裡我也只是實驗了簡單的文字收發,圖片與語音等也大同小異,等用到再研究也可以)
要想可以收到使用者發的訊息,並做處理後返回訊息,那肯定是作為第三方平臺來支援了(相對於微信伺服器,我們當然是第三方了),收發訊息按我自己的理解就是使用者發訊息到公眾號,微信伺服器將使用者發的資訊通過特定格式發給第三方平臺,然後第三方平臺解析微信伺服器發來的訊息,處理並以特定的格式返回給伺服器,伺服器再通過公眾號呈現給使用者。
那作為第三方平臺的我們當然要是一個可以訪問的地址了。不然微信伺服器如何將資訊發給你呢,你說是不?好了那麼可以上圖了
這個地方就是要配置我們伺服器的地方,當然你肯定會問如何配置自己的伺服器了。那麼下面慢慢告訴你:
(1)首先,這個URL是部署在伺服器上的一個應用,而這個應用就是供微信伺服器訪問用的,而且在微信伺服器訪問這個伺服器地址時是要簽名驗證的,微信官網也給了php的demo。不過我對php不熟悉,所以我就用了java,那麼用java怎麼做呢?
由於只是最簡單的訪問驗證,那麼java的話最方便的就是用一個servlet了,我們只需要將官網給的demo用java來實現一下就可以了。從網上可以搜到好多現成的程式碼(我也是從網上找的)。那麼這裡帖一下:
這段程式碼可以說是一個驗證工具類
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class SignUtil {
/**
* 與介面配置資訊中的 token 要一致,這裡賦予什麼值,在介面配置資訊中的Token就要填寫什麼值,
* 兩邊保持一致即可,建議用專案名稱、公司名稱縮寫等,我在這裡用的是專案名稱weixinface
*/
private static String token = "你自己的token(這裡的token與你上圖中配置伺服器token相同)";
/**
* 驗證簽名
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce){
String[] arr = new String[]{token, timestamp, nonce};
// 將 token, timestamp, nonce 三個引數進行字典排序
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for(int i = 0; i < arr.length; i++){
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 將三個引數字串拼接成一個字串進行 shal 加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
content = null;
// 將sha1加密後的字串可與signature對比,標識該請求來源於微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()): false;
}
/**
* 將位元組陣列轉換為十六進位制字串
* @param digest
* @return
*/
private static String byteToStr(byte[] digest) {
// TODO Auto-generated method stub
String strDigest = "";
for(int i = 0; i < digest.length; i++){
strDigest += byteToHexStr(digest[i]);
}
return strDigest;
}
/**
* 將位元組轉換為十六進位制字串
* @param b
* @return
*/
private static String byteToHexStr(byte b) {
// TODO Auto-generated method stub
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] tempArr = new char[2];
tempArr[0] = Digit[(b >>> 4) & 0X0F];
tempArr[1] = Digit[b & 0X0F];
String s = new String(tempArr);
return s;
}
}
(2)再需要的當然就是我們最重要的servlet了。上程式碼:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 微信加密簽名
String signature = request.getParameter("signature");
// 時間戮
String timestamp = request.getParameter("timestamp");
// 隨機數
String nonce = request.getParameter("nonce");
// 隨機字串
String echostr = request.getParameter("echostr");
PrintWriter out = response.getWriter();
// 通過檢驗 signature 對請求進行校驗,若校驗成功則原樣返回 echostr,表示接入成功,否則接入失敗
if(SignUtil.checkSignature(signature, timestamp, nonce)){
out.print(echostr);
}
}
這段程式碼是在servlet中get方法中的,因為在驗證伺服器的時候微信伺服器是通過GET方式訪問的。
完整程式碼我們將會在下面講到收發訊息的時候一起給附上。(ps:如何建立servlet以及在web裡配置servlet這裡不講了,這些東西可以百度)
現在好了,執行你伺服器上的tomcat將寫好的servlet執行起來,在URL中寫上你你servlet的訪問地址,在Token那寫上與程式碼裡設定的token值(仔細看程式碼,有個token),訊息加解祕鑰可以隨機生成,因為是測試,訊息加解密方式我就選擇了明文,你也可以選擇其他的。
這樣提交後,如果沒問題就會出現提示提交成功,那麼恭喜你!你的伺服器驗證就通過了。
提示:相信很多人在這一步會有問題,如果你的問題是servlet等方面的問題,那麼請你去百度瞭解一下,然後我會在下面將整個原始碼附上。對於像我來說,最大的問題是去哪裡弄個伺服器呀,沒有關係,可以去新浪申請一個sae賬號,然後在這裡建立自己的應用,將程式碼釋出到上面,具體的不細說了,可以百度。由於我在學校裡,是nat對映的內網,以前用花生殼工具是不能對映到公網上的,不過現在查了一下,現在的新花生殼竟然強大到可以無需公網ip,無需埠對映就可以對映公網的功能,好奇心驅動下的我下載了一個,垃圾捆綁不少,最重要的是要我付1塊錢,我就沒有去實驗,有興趣的可以試試,看看這個新花生殼怎麼樣。
(3)有了自己的伺服器可以訪問了,那麼真正的開發才開始。上文提到了,我這個訂閱號由於是個人的,所以沒有獲得開發模式下的自定義選單,那麼就拿微信給的收發訊息的介面玩起來。從網上搜集了部分資料,然後放到程式碼裡整合了一下,經過幾次嘗試,終於可以通過使用者發文字或者簡單常用表情,然後將處理內容返回給使用者。有了上面說的訊息收發的過程,然後去看一下微信給出的訊息格式,相信你看到下面的程式碼也會明白的。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
@SuppressWarnings("serial")
public class wechatservlet extends HttpServlet
{
public wechatservlet() {
super();
}
public void destroy() {
super.destroy();
}
/**
* 驗證url和token
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 微信加密簽名
String signature = request.getParameter("signature");
// 時間戮
String timestamp = request.getParameter("timestamp");
// 隨機數
String nonce = request.getParameter("nonce");
// 隨機字串
String echostr = request.getParameter("echostr");
PrintWriter out = response.getWriter();
if(echostr!=null&&!echostr.isEmpty())
{ // 通過檢驗 signature 對請求進行校驗,若校驗成功則原樣返回 echostr,表示接入成功,否則接入失敗
if(SignUtil.checkSignature(signature, timestamp, nonce)){
out.print(echostr);
}
}
responseMsg( request, response);
out.close();
out = null;
}
public void responseMsg(HttpServletRequest request,HttpServletResponse response){
String postStr=null;
try{
postStr=this.readStreamParameter(request.getInputStream());
}catch(Exception e){
e.printStackTrace();
}
if (null!=postStr&&!postStr.isEmpty()){
Document document=null;
try{
document = DocumentHelper.parseText(postStr);
}catch(Exception e){
e.printStackTrace();
}
if(null==document){
try {
response.getWriter().print("");
} catch (IOException e) {
e.printStackTrace();
}
return;
}
Element root=document.getRootElement();
String fromUsername = root.elementText("FromUserName");
String toUsername = root.elementText("ToUserName");
String keyword = root.elementTextTrim("Content");
String time = new Date().getTime()+"";
String textTpl = "<xml>"+
"<ToUserName><![CDATA[%1$s]]></ToUserName>"+
"<FromUserName><![CDATA[%2$s]]></FromUserName>"+
"<CreateTime>%3$s</CreateTime>"+
"<MsgType><![CDATA[%4$s]]></MsgType>"+
"<Content><![CDATA[%5$s]]></Content>"+
"<FuncFlag>0</FuncFlag>"+
"</xml>";
if(null!=keyword&&!keyword.equals(""))
{
String msgType = "text";
String contentStr = "您傳送的訊息是: "+keyword;
String resultStr = textTpl.format(textTpl, fromUsername, toUsername, time, msgType, contentStr);
try {
response.getWriter().print(resultStr);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
try {
response.getWriter().print("Input something…");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}else {
try {
response.getWriter().print("");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public String readStreamParameter(ServletInputStream in){
StringBuilder buffer = new StringBuilder();
BufferedReader reader=null;
try{
reader = new BufferedReader(new InputStreamReader(in));
String line=null;
while((line = reader.readLine())!=null){
buffer.append(line);
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(null!=reader){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return buffer.toString();
}
/**
*使用者向公眾平臺發信息並自動返回資訊
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet( request, response);
}
public void init() throws ServletException {
}
}
這裡在解析xml檔案的時候用到了dom4j,所以你在使用這段程式碼的時候將需要的dom4j的jar包匯入。
現在,一切都準備妥當了,我們已經有了這個servlet完整的程式碼了,還有最上面的驗證簽名的工具類,那麼我們所需要的程式碼已經全了,如果你跟我一樣用的是sae,那麼抓緊將執行起來的程式碼打包成war包放到你的應用裡吧(sae要求你的war包名字跟你的應用名相同)
開啟你的微信,向你的公眾號傳送一條文字測試一下吧!O(∩_∩)O~(也可以先用微信給你的測試工具去測試一下)