1. 程式人生 > >微信公眾平臺 java示例 接收訊息並回復

微信公眾平臺 java示例 接收訊息並回復

最近公司在開發微信專案,所以自己也試著申請了個人的訂閱服務號,實現了通過微信接收資訊轉發至java後臺解析並回復的訊息的簡單功能,在還沒忘記的時候記錄一下,以便日後查閱,並且貢獻出程式碼希望能給大家一個參考。

好首先你要看下面的示例,要事先申請微信公眾平臺的訂閱服務號(個人只能申請這個),地址https://mp.weixin.qq.com ,申請的範例我這裡就不講了,一般根據提示可以自行完成,如果這都完成不了,那隻能去度娘翻翻了。

要想讓使用者傳送給公眾帳號的訊息轉發給java後臺伺服器,首先要 在開發者中心 進行 伺服器配置

下圖為認證啟動後小效果:


你要先進入到 修改配置裡面,如下圖:


你要填寫這幾個文字框內的內容,

1.URL 不用解釋了,就是微信將使用者發來的訊息轉發到你伺服器的請求的地址,我讓微信把請求傳送到本地服務這樣方便除錯。

推薦一款反向代理的工具 pagekite.net ,感興趣的朋友可以去搜索一下。使用相當方便,就是需要python2.7.x環境支援,然後執行下載的一個指令碼,輸入你的郵箱,然後在輸入你要設定的域名字首,就搞定,下次執行就不用在輸入,它影射的是本地80埠,所以你啟動服務的時候記得改成80埠就對了,還有這個貌似對於一個郵箱只有31天和5個連線的限制,PS:郵箱這東西都是免費的,你懂的。

2.Token:這個長度符合就行 自己隨意

3.EncodingAESKey

:點選隨機生成 就OK

下面介紹我的程式碼:

我的開發環境是eclipse+springMvc

"/chat" 是我最終專案的請求Controller URL路徑

下面是homecontroller

@Controller
@RequestMapping("/*")
public class HomeController {

	private String Token = "123456789abcdef";

	@RequestMapping(value = "chat", method = { RequestMethod.GET, RequestMethod.POST })
	@ResponseBody
	public void liaotian(Model model, HttpServletRequest request, HttpServletResponse response) {
		System.out.println("進入chat");
		boolean isGet = request.getMethod().toLowerCase().equals("get");
		if (isGet) {
			String signature = request.getParameter("signature");
			String timestamp = request.getParameter("timestamp");
			String nonce = request.getParameter("nonce");
			String echostr = request.getParameter("echostr");
			System.out.println(signature);
			System.out.println(timestamp);
			System.out.println(nonce);
			System.out.println(echostr);
			access(request, response);
		} else {
			// 進入POST聊天處理
			System.out.println("enter post");
			try {
				// 接收訊息並返回訊息
				acceptMessage(request, response);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 驗證URL真實性
	 * 
	 * @author morning
	 * @date 2015年2月17日 上午10:53:07
	 * @param request
	 * @param response
	 * @return String
	 */
	private String access(HttpServletRequest request, HttpServletResponse response) {
		// 驗證URL真實性
		System.out.println("進入驗證access");
		String signature = request.getParameter("signature");// 微信加密簽名
		String timestamp = request.getParameter("timestamp");// 時間戳
		String nonce = request.getParameter("nonce");// 隨機數
		String echostr = request.getParameter("echostr");// 隨機字串
		List<String> params = new ArrayList<String>();
		params.add(Token);
		params.add(timestamp);
		params.add(nonce);
		// 1. 將token、timestamp、nonce三個引數進行字典序排序
		Collections.sort(params, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.compareTo(o2);
			}
		});
		// 2. 將三個引數字串拼接成一個字串進行sha1加密
		String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
		if (temp.equals(signature)) {
			try {
				response.getWriter().write(echostr);
				System.out.println("成功返回 echostr:" + echostr);
				return echostr;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		System.out.println("失敗 認證");
		return null;
	}

	private void acceptMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// 處理接收訊息
		ServletInputStream in = request.getInputStream();
		// 將POST流轉換為XStream物件
		XStream xs = SerializeXmlUtil.createXstream();
		xs.processAnnotations(InputMessage.class);
		xs.processAnnotations(OutputMessage.class);
		// 將指定節點下的xml節點資料對映為物件
		xs.alias("xml", InputMessage.class);
		// 將流轉換為字串
		StringBuilder xmlMsg = new StringBuilder();
		byte[] b = new byte[4096];
		for (int n; (n = in.read(b)) != -1;) {
			xmlMsg.append(new String(b, 0, n, "UTF-8"));
		}
		// 將xml內容轉換為InputMessage物件
		InputMessage inputMsg = (InputMessage) xs.fromXML(xmlMsg.toString());

		String servername = inputMsg.getToUserName();// 服務端
		String custermname = inputMsg.getFromUserName();// 客戶端
		long createTime = inputMsg.getCreateTime();// 接收時間
		Long returnTime = Calendar.getInstance().getTimeInMillis() / 1000;// 返回時間

		// 取得訊息型別
		String msgType = inputMsg.getMsgType();
		// 根據訊息型別獲取對應的訊息內容
		if (msgType.equals(MsgType.Text.toString())) {
			// 文字訊息
			System.out.println("開發者微訊號:" + inputMsg.getToUserName());
			System.out.println("傳送方帳號:" + inputMsg.getFromUserName());
			System.out.println("訊息建立時間:" + inputMsg.getCreateTime() + new Date(createTime * 1000l));
			System.out.println("訊息內容:" + inputMsg.getContent());
			System.out.println("訊息Id:" + inputMsg.getMsgId());

			StringBuffer str = new StringBuffer();
			str.append("<xml>");
			str.append("<ToUserName><![CDATA[" + custermname + "]]></ToUserName>");
			str.append("<FromUserName><![CDATA[" + servername + "]]></FromUserName>");
			str.append("<CreateTime>" + returnTime + "</CreateTime>");
			str.append("<MsgType><![CDATA[" + msgType + "]]></MsgType>");
			str.append("<Content><![CDATA[你說的是:" + inputMsg.getContent() + ",嗎?]]></Content>");
			str.append("</xml>");
			System.out.println(str.toString());
			response.getWriter().write(str.toString());
		}
		// 獲取並返回多圖片訊息
		if (msgType.equals(MsgType.Image.toString())) {
			System.out.println("獲取多媒體資訊");
			System.out.println("多媒體檔案id:" + inputMsg.getMediaId());
			System.out.println("圖片連結:" + inputMsg.getPicUrl());
			System.out.println("訊息id,64位整型:" + inputMsg.getMsgId());

			OutputMessage outputMsg = new OutputMessage();
			outputMsg.setFromUserName(servername);
			outputMsg.setToUserName(custermname);
			outputMsg.setCreateTime(returnTime);
			outputMsg.setMsgType(msgType);
			ImageMessage images = new ImageMessage();
			images.setMediaId(inputMsg.getMediaId());
			outputMsg.setImage(images);
			System.out.println("xml轉換:/n" + xs.toXML(outputMsg));
			response.getWriter().write(xs.toXML(outputMsg));

		}
	}

}

加密SHA1,此程式碼是來自網上
/*
 * 微信公眾平臺(JAVA) SDK
 *
 * Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved.
 * http://www.ansitech.com/weixin/sdk/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mor.util;

import java.security.MessageDigest;

/**
 * <p>
 * Title: SHA1演算法
 * </p>
 * 
 */
public final class SHA1 {

	private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

	/**
	 * Takes the raw bytes from the digest and formats them correct.
	 * 
	 * @param bytes
	 *            the raw bytes from the digest.
	 * @return the formatted bytes.
	 */
	private static String getFormattedText(byte[] bytes) {
		int len = bytes.length;
		StringBuilder buf = new StringBuilder(len * 2);
		// 把密文轉換成十六進位制的字串形式
		for (int j = 0; j < len; j++) {
			buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
			buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
		}
		return buf.toString();
	}

	public static String encode(String str) {
		if (str == null) {
			return null;
		}
		try {
			MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
			messageDigest.update(str.getBytes());
			return getFormattedText(messageDigest.digest());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

輸入資訊實體類 InputMessage 
<pre name="code" class="java">/*
 * 微信公眾平臺(JAVA) SDK
 *
 * Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved.
 * http://www.ansitech.com/weixin/sdk/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mor.maven.demo.mavenweb.model;

import java.io.Serializable;

import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * POST的XML資料包轉換為訊息接受物件
 * 
 * <p>
 * 由於POST的是XML資料包,所以不確定為哪種接受訊息,<br/>
 * 所以直接將所有欄位都進行轉換,最後根據<tt>MsgType</tt>欄位來判斷取何種資料
 * </p>
 * 
 */
@XStreamAlias("xml")
public class InputMessage implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	@XStreamAlias("ToUserName")
	private String ToUserName;
	@XStreamAlias("FromUserName")
	private String FromUserName;
	@XStreamAlias("CreateTime")
	private Long CreateTime;
	@XStreamAlias("MsgType")
	private String MsgType = "text";
	@XStreamAlias("MsgId")
	private Long MsgId;
	// 文字訊息
	@XStreamAlias("Content")
	private String Content;
	// 圖片訊息
	@XStreamAlias("PicUrl")
	private String PicUrl;
	// 位置訊息
	@XStreamAlias("LocationX")
	private String LocationX;
	@XStreamAlias("LocationY")
	private String LocationY;
	@XStreamAlias("Scale")
	private Long Scale;
	@XStreamAlias("Label")
	private String Label;
	// 連結訊息
	@XStreamAlias("Title")
	private String Title;
	@XStreamAlias("Description")
	private String Description;
	@XStreamAlias("Url")
	private String URL;
	// 語音資訊
	@XStreamAlias("MediaId")
	private String MediaId;
	@XStreamAlias("Format")
	private String Format;
	@XStreamAlias("Recognition")
	private String Recognition;
	// 事件
	@XStreamAlias("Event")
	private String Event;
	@XStreamAlias("EventKey")
	private String EventKey;
	@XStreamAlias("Ticket")
	private String Ticket;

	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 Long getMsgId() {
		return MsgId;
	}

	public void setMsgId(Long msgId) {
		MsgId = msgId;
	}

	public String getContent() {
		return Content;
	}

	public void setContent(String content) {
		Content = content;
	}

	public String getPicUrl() {
		return PicUrl;
	}

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

	public String getLocationX() {
		return LocationX;
	}

	public void setLocationX(String locationX) {
		LocationX = locationX;
	}

	public String getLocationY() {
		return LocationY;
	}

	public void setLocationY(String locationY) {
		LocationY = locationY;
	}

	public Long getScale() {
		return Scale;
	}

	public void setScale(Long scale) {
		Scale = scale;
	}

	public String getLabel() {
		return Label;
	}

	public void setLabel(String label) {
		Label = label;
	}

	public String getTitle() {
		return Title;
	}

	public void setTitle(String title) {
		Title = title;
	}

	public String getDescription() {
		return Description;
	}

	public void setDescription(String description) {
		Description = description;
	}

	public String getURL() {
		return URL;
	}

	public void setURL(String uRL) {
		URL = uRL;
	}

	public String getEvent() {
		return Event;
	}

	public void setEvent(String event) {
		Event = event;
	}

	public String getEventKey() {
		return EventKey;
	}

	public void setEventKey(String eventKey) {
		EventKey = eventKey;
	}

	public String getMediaId() {
		return MediaId;
	}

	public void setMediaId(String mediaId) {
		MediaId = mediaId;
	}

	public String getFormat() {
		return Format;
	}

	public void setFormat(String format) {
		Format = format;
	}

	public String getRecognition() {
		return Recognition;
	}

	public void setRecognition(String recognition) {
		Recognition = recognition;
	}

	public String getTicket() {
		return Ticket;
	}

	public void setTicket(String ticket) {
		Ticket = ticket;
	}
}


為了加入 CDATA 驗證建立的@interface類
package com.mor.maven.demo.mavenweb.model;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface XStreamCDATA {

}

改寫的XStream工具類
package com.mor.util;

import java.io.Writer;
import java.lang.reflect.Field;

import com.mor.maven.demo.mavenweb.model.XStreamCDATA;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
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;

/**
 * xml 轉換工具類
 * 
 * @author morning
 * @date 2015年2月16日 下午2:42:50
 */
public class SerializeXmlUtil {

	public static XStream createXstream() {
		return new XStream(new XppDriver() {
			@Override
			public HierarchicalStreamWriter createWriter(Writer out) {
				return new PrettyPrintWriter(out) {
					boolean cdata = false;
					Class<?> targetClass = null;

					@Override
					public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
						super.startNode(name, clazz);
						// 業務處理,對於用XStreamCDATA標記的Field,需要加上CDATA標籤
						if (!name.equals("xml")) {
							cdata = needCDATA(targetClass, name);
						} else {
							targetClass = clazz;
						}
					}

					@Override
					protected void writeText(QuickWriter writer, String text) {
						if (cdata) {
							writer.write("<![CDATA[");
							writer.write(text);
							writer.write("]]>");
						} else {
							writer.write(text);
						}
					}
				};
			}
		});
	}

	private static boolean needCDATA(Class<?> targetClass, String fieldAlias) {
		boolean cdata = false;
		// first, scan self
		cdata = existsCDATA(targetClass, fieldAlias);
		if (cdata)
			return cdata;
		// if cdata is false, scan supperClass until java.lang.Object
		Class<?> superClass = targetClass.getSuperclass();
		while (!superClass.equals(Object.class)) {
			cdata = existsCDATA(superClass, fieldAlias);
			if (cdata)
				return cdata;
			superClass = superClass.getClass().getSuperclass();
		}
		return false;
	}

	private static boolean existsCDATA(Class<?> clazz, String fieldAlias) {
		if ("MediaId".equals(fieldAlias)) {
			return true; // 特例新增 morning99
		}
		// scan fields
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			// 1. exists XStreamCDATA
			if (field.getAnnotation(XStreamCDATA.class) != null) {
				XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);
				// 2. exists XStreamAlias
				if (null != xStreamAlias) {
					if (fieldAlias.equals(xStreamAlias.value()))// matched
						return true;
				} else {// not exists XStreamAlias
					if (fieldAlias.equals(field.getName()))
						return true;
				}
			}
		}
		return false;
	}

}

輸出實體類 OutputMessage 
package com.mor.maven.demo.mavenweb.model;

import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * 
 * @author morning
 * @date 2015年2月16日 下午2:29:32
 */
@XStreamAlias("xml")
public class OutputMessage {

	@XStreamAlias("ToUserName")
	@XStreamCDATA
	private String ToUserName;

	@XStreamAlias("FromUserName")
	@XStreamCDATA
	private String FromUserName;

	@XStreamAlias("CreateTime")
	private Long CreateTime;

	@XStreamAlias("MsgType")
	@XStreamCDATA
	private String MsgType = "text";

	private ImageMessage Image;

	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 ImageMessage getImage() {
		return Image;
	}

	public void setImage(ImageMessage image) {
		Image = image;
	}

}

圖片資訊實體類
package com.mor.maven.demo.mavenweb.model;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("Image")
public class ImageMessage extends MediaIdMessage {
}

多媒體id 實體類
package com.mor.maven.demo.mavenweb.model;

import com.thoughtworks.xstream.annotations.XStreamAlias;

public class MediaIdMessage {
	@XStreamAlias("MediaId")
	@XStreamCDATA
	private String MediaId;

	public String getMediaId() {
		return MediaId;
	}

	public void setMediaId(String mediaId) {
		MediaId = mediaId;
	}

}


基本就這些類,也不知道拷貝全沒有。

不過在輸出xml的時候由於要新增CDATA標籤所以沒有實現完美,目前自己在SerializeXmlUtil 內添加了一下判斷


如果是子標籤下的值目前只能用這種方法加CDATA,不知道各位同學有沒有好的方法。

目前只是實現了伺服器認證,接收文字資訊並回復原文字資訊加上些附加資訊,接收圖片資訊並返回原圖片資訊。

後期會有擴充套件,先記錄到此。