消息回覆:當用戶給公衆號發送消息時,微信服務器會將用戶消息以xml格式經過POST請求到咱們配置好的服務器對應的接口,而咱們要作的事情就是根據消息類型等作相應的邏輯處理,並將最後的返回結果也經過xml格式return給微信服務器,微信方再傳達給用戶的這樣一個過程。 java
@Controller @RequestMapping("/wechat") publicclass WechatController { @Value("${DNBX_TOKEN}") private String DNBX_TOKEN; private static final Logger LOGGER = LoggerFactory.getLogger(WechatController.class); @Resource WechatService wechatService; /** * 微信接入 * @param wc * @return * @throws IOException */ @RequestMapping(value="/connect",method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody publicvoid connectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException{ // 將請求、響應的編碼均設置爲UTF-8(防止中文亂碼) request.setCharacterEncoding("UTF-8"); //微信服務器POST消息時用的是UTF-8編碼,在接收時也要用一樣的編碼,不然中文會亂碼; response.setCharacterEncoding("UTF-8"); //在響應消息(回覆消息給用戶)時,也將編碼方式設置爲UTF-8,原理同上;boolean isGet = request.getMethod().toLowerCase().equals("get"); PrintWriter out = response.getWriter(); try { if (isGet) { String signature = request.getParameter("signature");// 微信加密簽名 String timestamp = request.getParameter("timestamp");// 時間戳 String nonce = request.getParameter("nonce");// 隨機數 String echostr = request.getParameter("echostr");//隨機字符串 // 經過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,不然接入失敗 if (SignUtil.checkSignature(DNBX_TOKEN, signature, timestamp, nonce)) { LOGGER.info("Connect the weixin server is successful."); response.getWriter().write(echostr); } else { LOGGER.error("Failed to verify the signature!"); } }else{ String respMessage = "異常消息!"; try { respMessage = wechatService.weixinPost(request); out.write(respMessage); LOGGER.info("The request completed successfully"); LOGGER.info("to weixin server "+respMessage); } catch (Exception e) { LOGGER.error("Failed to convert the message from weixin!"); } } } catch (Exception e) { LOGGER.error("Connect the weixin server is error."); }finally{ out.close(); } } }
public class SignUtil { /** * 驗證簽名 * * @param token 微信服務器token,在env.properties文件中配置的和在開發者中心配置的必須一致 * @param signature 微信服務器傳過來sha1加密的證書籤名 * @param timestamp 時間戳 * @param nonce 隨機數 * @return */ public static boolean checkSignature(String token,String signature, String timestamp, String nonce) { String[] arr = new String[] { token, timestamp, nonce }; // 將token、timestamp、nonce三個參數進行字典序排序 Arrays.sort(arr); // 將三個參數字符串拼接成一個字符串進行sha1加密 String tmpStr = SHA1.encode(arr[0] + arr[1] + arr[2]); // 將sha1加密後的字符串可與signature對比,標識該請求來源於微信 return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false; } }
SHA1:算法
/** * 微信公衆平臺(JAVA) SDK * * SHA1算法 * * @author helijun 2016/06/15 19:49 */ 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); } } }
當你在公衆平臺提交保存,而且看到綠色的提示「接入成功」以後,恭喜你已經完成微信接入。這個過程須要細心一點,注意加密算法裏的大小寫,若是接入不成功,大多數狀況都是加密算法的問題,多檢查檢查。json
/** * 處理微信發來的請求 * * @param request * @return */ public String weixinPost(HttpServletRequest request) { String respMessage = null; try { // xml請求解析 Map<String, String> requestMap = MessageUtil.xmlToMap(request); // 發送方賬號(open_id) String fromUserName = requestMap.get("FromUserName"); // 公衆賬號 String toUserName = requestMap.get("ToUserName"); // 消息類型 String msgType = requestMap.get("MsgType"); // 消息內容 String content = requestMap.get("Content"); LOGGER.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType); // 文本消息 if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { //這裏根據關鍵字執行相應的邏輯,只有你想不到的,沒有作不到的 if(content.equals("xxx")){ } //自動回覆 TextMessage text = new TextMessage(); text.setContent("the text is" + content); text.setToUserName(fromUserName); text.setFromUserName(toUserName); text.setCreateTime(new Date().getTime() + ""); text.setMsgType(msgType); respMessage = MessageUtil.textMessageToXml(text); } /*else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件推送 String eventType = requestMap.get("Event");// 事件類型 if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 訂閱 respContent = "歡迎關注xxx公衆號!"; return MessageResponse.getTextMessage(fromUserName , toUserName , respContent); } else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定義菜單點擊事件 String eventKey = requestMap.get("EventKey");// 事件KEY值,與建立自定義菜單時指定的KEY值對應 logger.info("eventKey is:" +eventKey); return xxx; } } //開啓微信聲音識別測試 2015-3-30 else if(msgType.equals("voice")) { String recvMessage = requestMap.get("Recognition"); //respContent = "收到的語音解析結果:"+recvMessage; if(recvMessage!=null){ respContent = TulingApiProcess.getTulingResult(recvMessage); }else{ respContent = "您說的太模糊了,能不能從新說下呢?"; } return MessageResponse.getTextMessage(fromUserName , toUserName , respContent); } //拍照功能 else if(msgType.equals("pic_sysphoto")) { } else { return MessageResponse.getTextMessage(fromUserName , toUserName , "返回爲空"); }*/ // 事件推送 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) { String eventType = requestMap.get("Event");// 事件類型 // 訂閱 if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) { TextMessage text = new TextMessage(); text.setContent("歡迎關注,xxx"); text.setToUserName(fromUserName); text.setFromUserName(toUserName); text.setCreateTime(new Date().getTime() + ""); text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); respMessage = MessageUtil.textMessageToXml(text); } // TODO 取消訂閱後用戶再收不到公衆號發送的消息,所以不須要回復消息 else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消訂閱 } // 自定義菜單點擊事件 else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) { String eventKey = requestMap.get("EventKey");// 事件KEY值,與建立自定義菜單時指定的KEY值對應 if (eventKey.equals("customer_telephone")) { TextMessage text = new TextMessage(); text.setContent("0755-86671980"); text.setToUserName(fromUserName); text.setFromUserName(toUserName); text.setCreateTime(new Date().getTime() + ""); text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); respMessage = MessageUtil.textMessageToXml(text); } } } } catch (Exception e) { Logger.error("error......") } return respMessage; }
先貼代碼如上,大多都有註釋,讀一遍基本語義也懂了不須要多解釋。安全
有一個地方格外須要注意:服務器
上面標紅的fromUserName和toUserName恰好相反,這也是坑之一,還記得我當時調了好久,明明都沒有問題就是不通,最後把這兩個一換消息就收到了!其實回過頭想也對,返回給微信服務器這時自己角色就變了,因此發送和接收方也確定是相反的。微信
public class MessageUtil { /** * 返回消息類型:文本 */ public static final String RESP_MESSAGE_TYPE_TEXT = "text"; /** * 返回消息類型:音樂 */ public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; /** * 返回消息類型:圖文 */ public static final String RESP_MESSAGE_TYPE_NEWS = "news"; /** * 請求消息類型:文本 */ public static final String REQ_MESSAGE_TYPE_TEXT = "text"; /** * 請求消息類型:圖片 */ public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; /** * 請求消息類型:連接 */ public static final String REQ_MESSAGE_TYPE_LINK = "link"; /** * 請求消息類型:地理位置 */ public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; /** * 請求消息類型:音頻 */ public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; /** * 請求消息類型:推送 */ public static final String REQ_MESSAGE_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"; }
這裏爲了程序可讀性、擴展性更好一點,我作了一些封裝,定義了幾個常量,以及將微信傳過來的一些參數封裝成java bean持久化對象,核心代碼如上。重點講下xml和map之間的轉換app
其實這個問題要歸咎於微信是用xml通信,而咱們平時通常是用json,因此可能短期內會有點不適應微信公衆平臺
<!-- 解析xml --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.9</version> </dependency>
/** * xml轉換爲map * @param request * @return * @throws IOException */ @SuppressWarnings("unchecked") public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException{ Map<String, String> map = new HashMap<String, String>(); SAXReader reader = new SAXReader(); InputStream ins = null; try { ins = request.getInputStream(); } catch (IOException e1) { e1.printStackTrace(); } Document doc = null; try { doc = reader.read(ins); Element root = doc.getRootElement(); List<Element> list = root.elements(); for (Element e : list) { map.put(e.getName(), e.getText()); } return map; } catch (DocumentException e1) { e1.printStackTrace(); }finally{ ins.close(); } return null; }
/** * 文本消息對象轉換成xml * * @param textMessage 文本消息對象 * @return xml */ public static String textMessageToXml(TextMessage textMessage){ XStream xstream = new XStream(); xstream.alias("xml", textMessage.getClass()); return xstream.toXML(textMessage); }
到此爲止已經大功告成了,這個時候能夠在公衆號裏嘗試發送「測試」,你會收到微信回覆的「the text is 測試」,這也是上面代碼裏作的回覆處理,固然你也能夠發揮你的想象用他作全部你想作的事了,好比回覆1查天氣,2查違章等等....dom