1. 程式人生 > >java微信訂閱號(公眾號)開發案例

java微信訂閱號(公眾號)開發案例

微信公眾號開發一般是針對企業和組織的,個人一般只能申請訂閱號,並且呼叫的介面有限,下面我們就來簡單的描述下接入公眾號的步驟:

1、首先你需要一個郵箱在微信公眾號平臺進行註冊;

     註冊的方式有訂閱號、公眾號、小程式和企業號,個人我們這裡只能選擇訂閱號

2、註冊完後,我們登入到公眾號平臺--->開發--->基本配置,這裡需要填寫URL和token,URL就是我們使用伺服器的介面;

3、java web伺服器程式編譯好且在伺服器上部署可以執行的話,可在微信公眾號進行線上介面除錯:

    1)、選擇合適的介面
    2)、系統會生成該介面的引數表,您可以直接在文字框內填入對應的引數值(紅色星號表示該欄位必填)
    3)、點選檢查問題按鈕,即可得到相應的除錯資訊

   eg:獲取access_token的步驟:

   1)、介面型別:基礎支援

   2)、介面列表:獲取access_token介面/token

   3)、填寫相應的引數:grant_type、appid、secret

   4)、點選檢查問題

   5)、驗證成功會返回結果和提示,結果為:200 ok,提示:Request successful即表示驗證成功

   我們這裡驗證比較多的是訊息介面除錯:文字訊息、圖片訊息、語音訊息、視訊訊息、etc

4、介面有不理解的地方,可進入開發-->開發者工具-->開發者文件進行查詢

5、介面許可權:訂閱號主要有基礎支援、接收訊息及網頁服務裡面的一些介面

下面我們主要講訂閱號怎麼樣接收訊息的案例:

1、需要申請一個個人微信訂閱號

2、url伺服器和伺服器端程式碼部署(可以用騰訊雲or阿里雲伺服器)

1)、AccountsServlet.java類,驗證來自微信伺服器和微信伺服器的訊息處理

package cn.jon.wechat.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.jon.wechat.service.AccountsService;
import cn.jon.wechat.utils.SignUtil;




public class AccountsServlet extends HttpServlet {

	public AccountsServlet() {
		super();
	}


	public void destroy() {
		super.destroy(); 
		// Put your code here
	}
	/**
	 * 確認請求來自於微信伺服器
	 */

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			System.out.println("介面測試開始!!!");
			//微信加密簽名
			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);
			}
			out.close();
			out = null;
//			response.encodeRedirectURL("success.jsp");
			
		
	}
	/**
	 * 處理微信伺服器發來的訊息
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//訊息的接受、處理、響應
		request.setCharacterEncoding("utf-8");
		response.setCharacterEncoding("utf-8");
		//呼叫核心業務型別接受訊息、處理訊息
		String respMessage = AccountsService.processRequest(request);
		
		//響應訊息
		PrintWriter out = response.getWriter();
		out.print(respMessage);
		out.close();
		
		
	}

	public void init() throws ServletException {
		// Put your code here
	}
	
}

2)、SignUtil.java類,請求校驗工具類,token需要和微信中填寫的token一致
package cn.jon.wechat.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 請求校驗工具類
 * @author jon
 */
public class SignUtil {
	//與微信配置中的的Token一致
	private static String token  = "";
	
	public static boolean checkSignature(String signature, String timestamp,
			String nonce) {
		String[] arra = new String[]{token,timestamp,nonce};
		//將signature,timestamp,nonce組成陣列進行字典排序
		Arrays.sort(arra);
		StringBuilder sb = new StringBuilder();
		for(int i=0;i<arra.length;i++){
			sb.append(arra[i]);
		}
		MessageDigest md = null;
		String stnStr = null;
		try {
			md = MessageDigest.getInstance("SHA-1");
			byte[] digest = md.digest(sb.toString().getBytes());
			stnStr = byteToStr(digest);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//釋放記憶體
		sb = null;
		//將sha1加密後的字串與signature對比,標識該請求來源於微信
		return stnStr!=null?stnStr.equals(signature.toUpperCase()):false;
	}
	/**
	 * 將位元組陣列轉換成十六進位制字串
	 * @param digestArra
	 * @return
	 */
	private static String byteToStr(byte[] digestArra) {
		// TODO Auto-generated method stub
		String digestStr = "";
		for(int i=0;i<digestArra.length;i++){
			digestStr += byteToHexStr(digestArra[i]);
		}
		return digestStr;
	}
	/**
	 * 將位元組轉換成為十六進位制字串
	 */
	private static String byteToHexStr(byte dByte) {
		// TODO Auto-generated method stub
		char[] Digit = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
		char[] tmpArr = new char[2];
		tmpArr[0] = Digit[(dByte>>>4)&0X0F];
		tmpArr[1] = Digit[dByte&0X0F];
		String s = new String(tmpArr);
		return s;
	}
	
	public static void main(String[] args) {
		/*byte dByte = 'A';
		System.out.println(byteToHexStr(dByte));*/
		Map<String,String> map = new ConcurrentHashMap<String, String>();
		map.put("4", "zhangsan");
		map.put("100", "lisi");
		Set set = map.keySet();
		Iterator iter = set.iterator();
		while(iter.hasNext()){
//			String keyV = (String) iter.next();
			String key =(String)iter.next();
			System.out.println(map.get(key));
//			System.out.println(map.get(iter.next()));
		}
		/*for(int i=0;i<map.size();i++){
			
		}*/
	}
}

3)、AccountsService.java服務類,主要是訊息的請求和響應處理,並且當用戶關注你的公眾號的時候,可以設定預設推送訊息
package cn.jon.wechat.service;

import java.util.Date;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import cn.jon.wechat.message.req.ImageMessage;
import cn.jon.wechat.message.req.LinkMessage;
import cn.jon.wechat.message.req.LocationMessage;
import cn.jon.wechat.message.req.VideoMessage;
import cn.jon.wechat.message.req.VoiceMessage;
import cn.jon.wechat.message.resp.TextMessage;
import cn.jon.wechat.utils.MessageUtil;

/**
 * 解耦,使控制層與業務邏輯層分離開來,主要處理請求,響應
 * @author jon
 */
public class AccountsService {
	
	public static String processRequest(HttpServletRequest request) {
		String respMessage = null;
		//預設返回的文字訊息內容
		String respContent = "請求處理異常,請稍後嘗試!";
		try {
			//xml請求解析
			Map<String,String> requestMap = MessageUtil.pareXml(request);
			
			//傳送方賬號(open_id)
			String fromUserName = requestMap.get("FromUserName");
			//公眾賬號
			String toUserName = requestMap.get("ToUserName");
			//訊息型別
			String msgType = requestMap.get("MsgType");
			
			//預設回覆此文字訊息  
            TextMessage defaultTextMessage = new TextMessage();  
            defaultTextMessage.setToUserName(fromUserName);  
            defaultTextMessage.setFromUserName(toUserName);  
            defaultTextMessage.setCreateTime(new Date().getTime());  
            defaultTextMessage.setMsgType(MessageUtil.MESSSAGE_TYPE_TEXT);  
            defaultTextMessage.setFuncFlag(0);  
            // 由於href屬性值必須用雙引號引起,這與字串本身的雙引號衝突,所以要轉義  
            defaultTextMessage.setContent("歡迎訪問<a href=\"http://blog.csdn.net/j086924\">jon的部落格</a>!");  
//            defaultTextMessage.setContent(getMainMenu());
            // 將文字訊息物件轉換成xml字串  
            respMessage = MessageUtil.textMessageToXml(defaultTextMessage);  
  
			
			//文字訊息
			if(msgType.equals(MessageUtil.MESSSAGE_TYPE_TEXT)){
				//respContent = "Hi,您傳送的是文字訊息!";
				//回覆文字訊息
				TextMessage textMessage = new TextMessage();
//				textMessage.setToUserName(toUserName);
//				textMessage.setFromUserName(fromUserName);
				//這裡需要注意,否則無法回覆訊息給使用者了
				textMessage.setToUserName(fromUserName);
				textMessage.setFromUserName(toUserName);
				textMessage.setCreateTime(new Date().getTime());
				textMessage.setMsgType(MessageUtil.MESSSAGE_TYPE_TEXT);
				textMessage.setFuncFlag(0);
				respContent = "Hi,你發的訊息是:"+requestMap.get("Content");
				textMessage.setContent(respContent);
				respMessage = MessageUtil.textMessageToXml(textMessage);
			}
			//圖片訊息
			else if(msgType.equals(MessageUtil.MESSSAGE_TYPE_IMAGE)){
				
				ImageMessage imageMessage=new ImageMessage();
				imageMessage.setToUserName(fromUserName);
				imageMessage.setFromUserName(toUserName);
				imageMessage.setCreateTime(new Date().getTime());
				imageMessage.setMsgType(MessageUtil.MESSSAGE_TYPE_IMAGE);
				//respContent=requestMap.get("PicUrl");
				imageMessage.setPicUrl("http://img24.pplive.cn//2013//07//24//12103112092_230X306.jpg");
				respMessage = MessageUtil.imageMessageToXml(imageMessage);
			}
			//地理位置
			else if(msgType.equals(MessageUtil.MESSSAGE_TYPE_LOCATION)){
				LocationMessage locationMessage=new LocationMessage();
				locationMessage.setToUserName(fromUserName);
				locationMessage.setFromUserName(toUserName);
				locationMessage.setCreateTime(new Date().getTime());
				locationMessage.setMsgType(MessageUtil.MESSSAGE_TYPE_LOCATION);
				locationMessage.setLocation_X(requestMap.get("Location_X"));
				locationMessage.setLocation_Y(requestMap.get("Location_Y"));
				locationMessage.setScale(requestMap.get("Scale"));
				locationMessage.setLabel(requestMap.get("Label"));
				respMessage = MessageUtil.locationMessageToXml(locationMessage);
				
			}
			
			//視訊訊息
			else if(msgType.equals(MessageUtil.MESSSAGE_TYPE_VIDEO)){
				VideoMessage videoMessage=new VideoMessage();
				videoMessage.setToUserName(fromUserName);
				videoMessage.setFromUserName(toUserName);
				videoMessage.setCreateTime(new Date().getTime());
				videoMessage.setMsgType(MessageUtil.MESSSAGE_TYPE_VIDEO);
				videoMessage.setMediaId(requestMap.get("MediaId"));
				videoMessage.setThumbMediaId(requestMap.get("ThumbMediaId"));
				respMessage = MessageUtil.videoMessageToXml(videoMessage);
				
			}
			//連結訊息
			else if(msgType.equals(MessageUtil.MESSSAGE_TYPE_LINK)){
				LinkMessage linkMessage=new LinkMessage();
				linkMessage.setToUserName(fromUserName);
				linkMessage.setFromUserName(toUserName);
				linkMessage.setCreateTime(new Date().getTime());
				linkMessage.setMsgType(MessageUtil.MESSSAGE_TYPE_LINK);
				linkMessage.setTitle(requestMap.get("Title"));
				linkMessage.setDescription(requestMap.get("Description"));
				linkMessage.setUrl(requestMap.get("Url"));
				respMessage = MessageUtil.linkMessageToXml(linkMessage);
			}
			//語音訊息
			else if(msgType.equals(MessageUtil.MESSSAGE_TYPE_VOICE)){
				VoiceMessage voiceMessage=new VoiceMessage();
				voiceMessage.setToUserName(fromUserName);
				voiceMessage.setFromUserName(toUserName);
				voiceMessage.setCreateTime(new Date().getTime());
				voiceMessage.setMsgType(MessageUtil.MESSSAGE_TYPE_VOICE);
				voiceMessage.setMediaId(requestMap.get("MediaId"));
				voiceMessage.setFormat(requestMap.get("Format"));
				respMessage = MessageUtil.voiceMessageToXml(voiceMessage);
			}
			//事件推送
			else if(msgType.equals(MessageUtil.MESSSAGE_TYPE_EVENT)){
				//事件型別
				String eventType = requestMap.get("Event");
				//訂閱
				if(eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)){
					respContent = "謝謝關注!";
				}
				//取消訂閱
				else if(eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)){
					//
					System.out.println("取消訂閱");
					
				}
				else if(eventType.equals(MessageUtil.EVENT_TYPE_CLICK)){
					//自定義選單訊息處理
					System.out.println("自定義選單訊息處理");
				}
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return respMessage;
	}
	
	public static String getMainMenu()
	{
		StringBuffer buffer =new StringBuffer();
		buffer.append("您好,我是jon,請回複數字選擇服務:").append("\n");
		buffer.append("1、我的部落格").append("\n");
		buffer.append("2、 歌曲點播").append("\n");
		buffer.append("3、 經典遊戲").append("\n");
		buffer.append("4 、聊天打牌").append("\n\n");
		buffer.append("回覆"+"0"+"顯示幫助選單");
		return buffer.toString();
		
	}
}

4)、MessageUtil.java幫助類,包括常量的定義和xml訊息轉換和處理
package cn.jon.wechat.utils;

import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import cn.jon.wechat.message.req.ImageMessage;
import cn.jon.wechat.message.req.LinkMessage;
import cn.jon.wechat.message.req.LocationMessage;
import cn.jon.wechat.message.req.VideoMessage;
import cn.jon.wechat.message.req.VoiceMessage;
import cn.jon.wechat.message.resp.Article;
import cn.jon.wechat.message.resp.MusicMessage;
import cn.jon.wechat.message.resp.NewsMessage;
import cn.jon.wechat.message.resp.TextMessage;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;

/**
 * 各種訊息的處理類 
 * @author jon
 */

public class MessageUtil {
	/**
	 * 文字型別
	 */
	public static final String  MESSSAGE_TYPE_TEXT = "text";
	/**
	 * 音樂型別
	 */
	public static final String  MESSSAGE_TYPE_MUSIC = "music";
	/**
	 * 圖文型別
	 */
	public static final String  MESSSAGE_TYPE_NEWS = "news";
	
	/**
	 * 視訊型別
	 */
	public static final String  MESSSAGE_TYPE_VIDEO = "video";
	/**
	 * 圖片型別
	 */
	public static final String  MESSSAGE_TYPE_IMAGE = "image";
	/**
	 * 連結型別
	 */
	public static final String  MESSSAGE_TYPE_LINK = "link";
	/**
	 * 地理位置型別
	 */
	public static final String  MESSSAGE_TYPE_LOCATION = "location";
	/**
	 * 音訊型別
	 */
	public static final String  MESSSAGE_TYPE_VOICE = "voice";
	/**
	 * 推送型別
	 */
	public static final String  MESSSAGE_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";
	
	/**
	 * 解析微信發來的請求 XML 
	 */
	@SuppressWarnings("unchecked")
	public static Map<String,String> pareXml(HttpServletRequest request) throws Exception {
		
		//將解析的結果儲存在HashMap中
		Map<String,String> reqMap = new HashMap<String, String>();
		
		//從request中取得輸入流
		InputStream inputStream = request.getInputStream();
		//讀取輸入流
		SAXReader reader = new SAXReader();
		Document document = reader.read(inputStream);
		//得到xml根元素
		Element root = document.getRootElement();
		//得到根元素的所有子節點
		List<Element> elementList = root.elements();
		//遍歷所有的子節點取得資訊類容
		for(Element elem:elementList){
			reqMap.put(elem.getName(),elem.getText());
		}
		//釋放資源
		inputStream.close();
		inputStream = null;
		
		return reqMap;		
	}
	/**
	 * 響應訊息轉換成xml返回
	 * 文字物件轉換成xml
	 */
	public  static String textMessageToXml(TextMessage textMessage) {
		xstream.alias("xml", textMessage.getClass());
		return xstream.toXML(textMessage);
	}
	/**
	 * 語音物件的轉換成xml
	 * 
	 */
	public  static String voiceMessageToXml(VoiceMessage voiceMessage) {
		xstream.alias("xml", voiceMessage.getClass());
		return xstream.toXML(voiceMessage);
	}
	
	/**
	 * 視訊物件的轉換成xml
	 * 
	 */
	public  static String videoMessageToXml(VideoMessage videoMessage) {
		xstream.alias("xml", videoMessage.getClass());
		return xstream.toXML(videoMessage);
	}
	
	/**
	 * 音樂物件的轉換成xml
	 * 
	 */
	public  static String musicMessageToXml(MusicMessage musicMessage) {
		xstream.alias("xml", musicMessage.getClass());
		return xstream.toXML(musicMessage);
	}
	/**
	 * 圖文物件轉換成xml
	 * 
	 */
	public  static String newsMessageToXml(NewsMessage newsMessage) {
		xstream.alias("xml", newsMessage.getClass());
		xstream.alias("item", new Article().getClass());
		return xstream.toXML(newsMessage);
	}
	
	/**
	 * 圖片物件轉換成xml
	 *
	 */
	
	public static String imageMessageToXml(ImageMessage imageMessage)
	{
		xstream.alias("xml",imageMessage.getClass());
		return xstream.toXML(imageMessage);
		
	}
	
	/**
	 * 連結物件轉換成xml
	 *
	 */
	
	public static String linkMessageToXml(LinkMessage linkMessage)
	{
		xstream.alias("xml",linkMessage.getClass());
		return xstream.toXML(linkMessage);
		
	}
	
	/**
	 * 地理位置物件轉換成xml
	 *
	 */
	
	public static String locationMessageToXml(LocationMessage locationMessage)
	{
		xstream.alias("xml",locationMessage.getClass());
		return xstream.toXML(locationMessage);
		
	}
	
	/**
	 * 拓展xstream,使得支援CDATA塊
	 * 
	 */
	private static XStream xstream = new XStream(new XppDriver(){
		public HierarchicalStreamWriter createWriter(Writer out){
			return new PrettyPrintWriter(out){
				//對所有的xml節點的轉換都增加CDATA標記
				boolean cdata = true;
				
				@SuppressWarnings("unchecked")
				public void startNode(String name,Class clazz){
					super.startNode(name,clazz);
				}
				
				protected void writeText(QuickWriter writer,String text){
					if(cdata){
						writer.write("<![CDATA[");
						writer.write(text);
						writer.write("]]>");
					}else{
						writer.write(text);
					}
				}
			};
		}
	});
	
}

5)、BaseMessage.java訊息基類(包括:開發者微訊號、使用者賬戶、建立時間、訊息型別、訊息ID變數),文字、視訊、圖片訊息都會繼承此基類,在此基礎上擴充套件自己的變數,可根據開發者文件中的各種訊息屬性進行定義
package cn.jon.wechat.message.req;
/**
 * 訊息基類 (普通使用者-公眾號)
 * @author jon
 */
public class BaseMessage {
	
	//開發者微訊號
	private String ToUserName;
	//傳送方賬號(一個openId)
	private String FromUserName;
	//訊息建立時間(整型)
	private long CreateTime;
	//訊息型別(text/image/location/link...)
	private String MsgType;
	//訊息id 64位整型
	private String MsgId;
	
	public BaseMessage() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	public BaseMessage(String toUserName, String fromUserName, long createTime,
			String msgType, String msgId) {
		super();
		ToUserName = toUserName;
		FromUserName = fromUserName;
		CreateTime = createTime;
		MsgType = msgType;
		MsgId = msgId;
	}
	
	public String getToUserName() {
		return ToUserName;
	}
	
	public void setToUserName(String toUserName) {
		ToUserName = toUserName;
	}
	
	public String getFromUserName() {
		return FromUserName;
	}
	
	public void setFromUserName(String fromUserName) {
		FromUserName = fromUserName;
	}
	public long getCreateTime() {
		return CreateTime;
	}
	
	public void setCreateTime(long createTime) {
		CreateTime = createTime;
	}
	public String getMsgType() {
		return MsgType;
	}
	
	public void setMsgType(String msgType) {
		MsgType = msgType;
	}
	public String getMsgId() {
		return MsgId;
	}
	
	public void setMsgId(String msgId) {
		MsgId = msgId;
	}
}
6)、TextMessage.java文字訊息,繼承自5中基類,擴充套件內容屬性
package cn.jon.wechat.message.req;
/**
 * 文字訊息
 * @author jon
 */
public class TextMessage extends BaseMessage{
	//訊息內容
	private String content;

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}
	
}
7)、ImageMessage.java圖片訊息
package cn.jon.wechat.message.req;
/**
 * 圖片訊息
 * @author jon
 */
public class ImageMessage extends BaseMessage{
	//圖片連結
	private String PicUrl;

	public String getPicUrl() {
		return PicUrl;
	}

	public void setPicUrl(String picUrl) {
		PicUrl = picUrl;
	}	
}

8)、VideoMessage.java視訊訊息
package cn.jon.wechat.message.req;

public class VideoMessage extends BaseMessage {
	
	private String mediaId;
	private String thumbMediaId;
	public String getMediaId() {
		return mediaId;
	}
	public void setMediaId(String mediaId) {
		this.mediaId = mediaId;
	}
	public String getThumbMediaId() {
		return thumbMediaId;
	}
	public void setThumbMediaId(String thumbMediaId) {
		this.thumbMediaId = thumbMediaId;
	}	

}
其他訊息類可根據開發者文件自行進行完成,另外,開發者也可以申請公眾平臺測試賬號,對開發的相關內容進行測試