微信公眾平臺 java示例 接收訊息並回復
最近公司在開發微信專案,所以自己也試著申請了個人的訂閱服務號,實現了通過微信接收資訊轉發至java後臺解析並回復的訊息的簡單功能,在還沒忘記的時候記錄一下,以便日後查閱,並且貢獻出程式碼希望能給大家一個參考。
好首先你要看下面的示例,要事先申請微信公眾平臺的訂閱服務號(個人只能申請這個),地址https://mp.weixin.qq.com ,申請的範例我這裡就不講了,一般根據提示可以自行完成,如果這都完成不了,那隻能去度娘翻翻了。
要想讓使用者傳送給公眾帳號的訊息轉發給java後臺伺服器,首先要 在開發者中心 進行 伺服器配置 ,
下圖為認證啟動後小效果:
你要先進入到 修改配置裡面,如下圖:
你要填寫這幾個文字框內的內容,
1.URL 不用解釋了,就是微信將使用者發來的訊息轉發到你伺服器的請求的地址,我讓微信把請求傳送到本地服務這樣方便除錯。
推薦一款反向代理的工具 pagekite.net ,感興趣的朋友可以去搜索一下。使用相當方便,就是需要python2.7.x環境支援,然後執行下載的一個指令碼,輸入你的郵箱,然後在輸入你要設定的域名字首,就搞定,下次執行就不用在輸入,它影射的是本地80埠,所以你啟動服務的時候記得改成80埠就對了,還有這個貌似對於一個郵箱只有31天和5個連線的限制,PS:郵箱這東西都是免費的,你懂的。
2.Token:這個長度符合就行 自己隨意
3.EncodingAESKey
下面介紹我的程式碼:
我的開發環境是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,不知道各位同學有沒有好的方法。
目前只是實現了伺服器認證,接收文字資訊並回復原文字資訊加上些附加資訊,接收圖片資訊並返回原圖片資訊。
後期會有擴充套件,先記錄到此。