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 }