1.0 開發背景
外包公司,所以各種各樣的項目都有接觸,有個需求,客戶要做不同的二維碼,通過二維碼關注以后,給用戶發送不同的消息,順便統計用戶來源,
本文用到了 微信帶參數二維碼接口和消息管理里的相關接口
注意 :在微信公眾號后台,設置了服務器配置 URL 並啟用后,會導致 微信后台設置的回復規則,以及底部菜單都全部失效!直接清空了!因為這時候 微信已經把公眾號消息和事件 推送給開發者配置的url中,讓開發者進行處理了。
本文先講述微信官方后台設置帶參二維碼,后續更新使用第三方平台開發實現這個功能,從而保證了用戶微信公眾號官方設置底部菜單以及自定義回復沒有失效!
2.0 開發准備
2.1老樣子 官方文檔 來一波 帶參二維碼 接收事件
2.2 需要在微信后台 配置服務器配置 ,微信會在你修改這個配置的時候 給你填寫的URL發送數據,需要提前把項目上傳服務器
這個東西 配置好之后 可以點擊開啟了 ,開啟后啊,這個微信會把所有用戶發送的信息,給轉發給填寫的url 上。開發者必須在五秒內回復微信服務器。否則 微信會進行三次重試,重試均失敗后,會給用戶提示 該公眾號暫時無法提供服務。 而且在公眾平台還有告警通知。
下面看代碼
1 package com.ysh.wxtest.controller; 2 3 import org.apache.log4j.Logger; 4 import org.dom4j.Document; 5 import org.dom4j.Element; 6 import org.dom4j.io.SAXReader; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.stereotype.Controller; 9 import org.springframework.web.bind.annotation.RequestMapping; 10 import weixin.popular.bean.xmlmessage.XMLMessage; 11 import weixin.popular.bean.xmlmessage.XMLTextMessage; 12 import weixin.popular.support.ExpireKey; 13 import weixin.popular.support.expirekey.DefaultExpireKey; 14 import weixin.popular.util.SignatureUtil; 15 16 import javax.servlet.ServletInputStream; 17 import javax.servlet.ServletOutputStream; 18 import javax.servlet.http.HttpServletRequest; 19 import javax.servlet.http.HttpServletResponse; 20 import java.io.*; 21 import java.util.HashMap; 22 import java.util.List; 23 import java.util.Map; 24 25 /** 26 * 消息接收控制層 27 * @author YaoShiHang 28 * @Date 15:15 2017-10-16 29 */ 30 @Controller 31 public class WxqrcodeController { 32 33 private final String TOKEN="xxxxxxxxxx"; //Wx 開發者設置的 token 34 private Logger loger = Logger.getLogger(getClass()); 35 //重復通知過濾 36 private static ExpireKey expireKey = new DefaultExpireKey(); 37 38 39 //微信推送事件 url 40 @RequestMapping("/openwx/getticket") 41 public void getTicket(HttpServletRequest request, HttpServletResponse response) 42 throws Exception { 43 ServletInputStream inputStream = request.getInputStream(); 44 ServletOutputStream outputStream = response.getOutputStream(); String signature = request.getParameter("signature"); 45 String timestamp = request.getParameter("timestamp"); 46 String nonce = request.getParameter("nonce"); 47 String echostr = request.getParameter("echostr"); 48 49 //首次請求申請驗證,返回echostr 50 if(echostr!=null){ 51 outputStreamWrite(outputStream,echostr); 52 return; 53 } 54 55 //驗證請求簽名 56 if(!signature.equals(SignatureUtil.generateEventMessageSignature(TOKEN,timestamp,nonce))){ 57 System.out.println("The request signature is invalid"); 58 return; 59 } 60 61 boolean isreturn= false; 62 loger.info("1.收到微信服務器消息"); 63 Map<String, String> wxdata=parseXml(request); 64 if(wxdata.get("MsgType")!=null){ 65 if("event".equals(wxdata.get("MsgType"))){ 66 loger.info("2.1解析消息內容為:事件推送"); 67 if( "subscribe".equals(wxdata.get("Event"))){ 68 loger.info("2.2用戶第一次關注 返回true哦"); 69 isreturn=true; 70 } 71 } 72 } 73 74 if(isreturn == true){ 75 //轉換XML 76 String key = wxdata.get("FromUserName")+ "__" 77 + wxdata.get("ToUserName")+ "__" 78 + wxdata.get("MsgId") + "__" 79 + wxdata.get("CreateTime"); 80 loger.info("3.0 進入回復 轉換對象:"+key); 81 82 if(expireKey.exists(key)){ 83 //重復通知不作處理 84 loger.info("3.1 重復通知了"); 85 return; 86 }else{ 87 loger.info("3.1 第一次通知"); 88 expireKey.add(key); 89 } 90 91 loger.info("3.2 回復你好"); 92 //創建回復 93 XMLMessage xmlTextMessage = new XMLTextMessage( 94 wxdata.get("FromUserName"), 95 wxdata.get("ToUserName"), 96 "你好"); 97 //回復 98 xmlTextMessage.outputStreamWrite(outputStream); 99 return; 100 } 101 loger.info("3.2 回復空"); 102 outputStreamWrite(outputStream,""); 103 } 104 105 /** 106 * 數據流輸出 107 * @param outputStream 108 * @param text 109 * @return 110 */ 111 private boolean outputStreamWrite(OutputStream outputStream, String text){ 112 try { 113 outputStream.write(text.getBytes("utf-8")); 114 } catch (UnsupportedEncodingException e) { 115 // TODO Auto-generated catch block 116 e.printStackTrace(); 117 return false; 118 } catch (IOException e) { 119 // TODO Auto-generated catch block 120 e.printStackTrace(); 121 return false; 122 } 123 return true; 124 } 125 126 /** 127 * dom4j 解析 xml 轉換為 map 128 * @param request 129 * @return 130 * @throws Exception 131 */ 132 public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { 133 // 將解析結果存儲在HashMap中 134 Map<String, String> map = new HashMap<String, String>(); 135 // 從request中取得輸入流 136 InputStream inputStream = request.getInputStream(); 137 // 讀取輸入流 138 SAXReader reader = new SAXReader(); 139 Document document = reader.read(inputStream); 140 // 得到xml根元素 141 Element root = document.getRootElement(); 142 // 得到根元素的所有子節點 143 List<Element> elementList = root.elements(); 144 145 // 遍歷所有子節點 146 for (Element e : elementList) 147 map.put(e.getName(), e.getText()); 148 149 // 釋放資源 150 inputStream.close(); 151 inputStream = null; 152 return map; 153 } 154 155 /** 156 * 回復微信服務器"文本消息" 157 * @param response 158 * @param returnvaleue 159 */ 160 public void output(HttpServletResponse response, String returnvaleue) { 161 try { 162 PrintWriter pw = response.getWriter(); 163 pw.write(returnvaleue); 164 loger.info("****************return valeue***************="+returnvaleue); 165 pw.flush(); 166 } catch (IOException e) { 167 e.printStackTrace(); 168 } 169 } 170 }
提供一個大神的幫助類
1 package weixin.popular.bean.xmlmessage; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.io.UnsupportedEncodingException; 6 import java.util.UUID; 7 8 import com.qq.weixin.mp.aes.AesException; 9 import com.qq.weixin.mp.aes.WXBizMsgCrypt; 10 11 import weixin.popular.bean.message.message.Message; 12 13 public abstract class XMLMessage { 14 15 protected String toUserName; 16 protected String fromUserName; 17 protected String msgType; 18 19 protected XMLMessage(String toUserName, String fromUserName, String msgType) { 20 super(); 21 this.toUserName = toUserName; 22 this.fromUserName = fromUserName; 23 this.msgType = msgType; 24 } 25 26 /** 27 * 子類自定義XML 28 * @return XML 29 */ 30 public abstract String subXML(); 31 32 /** 33 * 轉換為 Message 對象 34 * @return Message 35 */ 36 public abstract Message convert(); 37 38 public String toXML(){ 39 StringBuilder sb = new StringBuilder(); 40 sb.append("<xml>"); 41 sb.append("<ToUserName><![CDATA["+toUserName+"]]></ToUserName>"); 42 sb.append("<FromUserName><![CDATA["+fromUserName+"]]></FromUserName>"); 43 sb.append("<CreateTime>"+System.currentTimeMillis()/1000+"</CreateTime>"); 44 sb.append("<MsgType><![CDATA["+msgType+"]]></MsgType>"); 45 sb.append(subXML()); 46 sb.append("</xml>"); 47 return sb.toString(); 48 } 49 50 public boolean outputStreamWrite(OutputStream outputStream){ 51 try { 52 outputStream.write(toXML().getBytes("utf-8")); 53 outputStream.flush(); 54 } catch (UnsupportedEncodingException e) { 55 e.printStackTrace(); 56 return false; 57 } catch (IOException e) { 58 e.printStackTrace(); 59 return false; 60 } 61 return true; 62 } 63 64 public boolean outputStreamWrite(OutputStream outputStream,WXBizMsgCrypt bizMsgCrypt){ 65 if(bizMsgCrypt != null){ 66 try { 67 String outputStr = bizMsgCrypt.encryptMsg(toXML(), System.currentTimeMillis()+"",UUID.randomUUID().toString()); 68 outputStream.write(outputStr.getBytes("utf-8")); 69 outputStream.flush(); 70 } catch (UnsupportedEncodingException e) { 71 e.printStackTrace(); 72 return false; 73 } catch (IOException e) { 74 e.printStackTrace(); 75 return false; 76 } catch (AesException e) { 77 e.printStackTrace(); 78 return false; 79 } 80 return true; 81 }else{ 82 return outputStreamWrite(outputStream); 83 } 84 } 85 86 public String getToUserName() { 87 return toUserName; 88 } 89 90 public String getFromUserName() { 91 return fromUserName; 92 } 93 94 public String getMsgType() { 95 return msgType; 96 } 97 98 99 }