微信公众平台 提供了很棒的 服务模式。使得用户和商家沟通更便捷,可以提供更好的会员服务。消息推送,优惠信息推送。它还提供了自定义菜单功能。从程序员视角我们看看如何做开发:
平台结构:
用户操作手机发送消息时:
用户APP(微信) -> 微信公共平台 -> 我们开发的网站后台(基于微信公众平台REST API开发)
后台根据用户发送的内容,回复消息时:
用户APP(微信) <- 微信公共平台 <- 我们开发的网站后台(基于微信公众平台REST API开发)
因为是基于微信开发的。我们需要做的是,建立一个网站,用于接收“ 微信公共平台发来的消息并回复”,而 微信公共平台 会将 “消息”发送到用户的手机。
如何做?
准备:定义一个Servlet,其get方法处理“验证消息真实性”,POST方法处理“接收和回复消息”
第一步,验证消息真实性 参考:开发指南
需要处理GET消息,上代码:
/** * 验证 消息请求 是否 合法 * @param request * @param response * @throws IOException */ public void rerifyRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { if(token == null) throw new IllegalArgumentException("未指定要验证的token"); /** * 第二步:验证URL有效性 * * 开发者提交信息后,微信服务器将发送GET请求到填写的URL上,GET请求携带四个参数: * * 参数 描述 signature * 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 timestamp * 时间戳 nonce 随机数 echostr 随机字符串 * 开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器 * ,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。 * * 加密/校验流程如下: 1. 将token、timestamp、nonce三个参数进行字典序排序 2. * 将三个参数字符串拼接成一个字符串进行sha1加密 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 * 检验signature的PHP示例代码: * * */ Logger logger = Logger.getLogger("pdwy"); logger.log(Level.INFO, "日志开始"); String signature = request.getParameter("signature"); String nonce = request.getParameter("nonce"); String timestamp = request.getParameter("timestamp"); String echostr = request.getParameter("echostr"); // logger.log(Level.INFO, String.format( // "收到:signature=%s,nonce=%s,timestamp=%s,echostr=%s", signature, // nonce, timestamp, echostr)); if (null == signature || "".equals(signature)) { response.sendError(500); return; } String[] arrs = new String[] { token, timestamp, nonce }; Arrays.sort(arrs); StringBuilder sb = new StringBuilder(); for (int i = 0; i < arrs.length; i++) { sb.append(arrs[i]); } String newSignature = sha1(sb.toString()); if (signature.equals(newSignature)) { PrintWriter pw = response.getWriter(); pw.write(echostr); pw.close(); } }
第二步 ,接收消息和回复消息
需要处理post消息,上代码:
/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse * response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("utf-8"); request.setCharacterEncoding("utf-8"); // 读消息 String messageBody = readMessageBodyFromRequest(request); LogHelper.i( " 收到POST的消息:" + messageBody); String replayMsgStr = handleMsg(messageBody); // 写入消息 if (replayMsgStr != null) writeResponse(replayMsgStr, response); else writeResponse("", response); }
我们会收到一些xml格式的消息,接收到POST来的消息后,我们需要解析它,如何解析?下面是个文本消息的解析示例:
/** * 文本消息 * * ToUserName 开发者微信号 FromUserName 发送方帐号(一个OpenID) CreateTime 消息创建时间 (整型) MsgType * text Content 文本消息内容 MsgId 消息id,64位整型 * * @author yunfei * */ public class TextMessage { /* * <ToUserName><![CDATA[toUser]]></ToUserName> * <FromUserName><![CDATA[fromUser]]></FromUserName> * <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> * <Content><![CDATA[this is a test]]></Content> * <MsgId>1234567890123456</MsgId> */ public String ToUserName; public String FromUserName; public String CreateTime; public String MsgType = "text"; public String Content; public String MsgId; @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(String.format("%s=%s", "ToUserName", ToUserName)); sb.append(String.format("%s=%s", "FromUserName", FromUserName)); sb.append(String.format("%s=%s", "CreateTime", CreateTime)); sb.append(String.format("%s=%s", "MsgType", MsgType)); sb.append(String.format("%s=%s", "Content", Content)); sb.append(String.format("%s=%s", "MsgId", MsgId)); return sb.toString(); } public static TextMessage fromXml(String xml){ return TextMessageReader.readTextMessage(xml); } } class TextMessageReader { public static TextMessage readTextMessage(String xmlStr) { try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputSource inputSource = new InputSource(new StringReader( xmlStr)); inputSource.setEncoding("utf-8"); Document doc = builder.parse(inputSource); NodeList nl = doc.getElementsByTagName("xml"); if (nl != null && nl.getLength() > 0) { Element rootElement = (Element) nl.item(0); String ToUserName = rootElement .getElementsByTagName("ToUserName").item(0) .getTextContent(); String FromUserName = rootElement .getElementsByTagName("FromUserName").item(0) .getTextContent(); String CreateTime = rootElement .getElementsByTagName("CreateTime").item(0) .getTextContent(); String MsgType = rootElement.getElementsByTagName("MsgType") .item(0).getTextContent(); String Content = rootElement.getElementsByTagName("Content") .item(0).getTextContent(); String MsgId = rootElement.getElementsByTagName("MsgId") .item(0).getTextContent(); TextMessage bean; bean = new TextMessage(); bean.ToUserName = ToUserName; bean.FromUserName = FromUserName; bean.CreateTime = CreateTime; bean.MsgType = MsgType; bean.Content = Content; bean.MsgId = MsgId; return bean; } } catch (Exception e) { e.printStackTrace(); LogHelper.e( "Error:" + e.getMessage()); } return null; } }
好吧。我们完成了解析的步骤。我们将xml消息解析成了 实体,然后根据不同的消息,不同的消息内容做我们的业务处理,收到“你好“,我们回复个”你也好“等。示例:
package weixinmobile.services.handlers; import weixinFundation.core.common.WeixinMessageHandler; import weixinFundation.core.messages.MusicMessageReply; import weixinFundation.core.messages.TextAndImageMessageReply; import weixinFundation.core.messages.TextAndImageMessageReply.Article; import weixinFundation.core.messages.TextMessage; import weixinFundation.core.messages.TextMessageReply; import weixinFundation.core.utils.DateUtil; import weixinFundation.core.utils.LogHelper; import weixinmobile.services.handlers.TalkManager.TaskResponse; public class TextMessageHandler implements WeixinMessageHandler { @Override public boolean handleMsg(String messageType, String messageBody, WeixinMessageHandlerResult result) { // 判读消息类型,按类型读取消息 if ("text".equals(messageType)) { TextMessage msg = TextMessage.fromXml(messageBody); String content = msg.Content; // 根据对白内容,返回对话 TaskResponse response = new TaskResponse(); if ( TalkManager.talk(content, response)) { String replyMsgStr = response.content; TextMessageReply replayMsg = null; replayMsg = TextMessageReply.createTextReplyMessage(msg.ToUserName, msg.FromUserName, replyMsgStr); String replayMsgStr = replayMsg.toXml(); result.result = replayMsgStr; return true; } //当用户的消息,没有对应的处理内容。默认处理。 TextMessageReply replayMsg = null; replayMsg = TextMessageReply.createTextReplyMessage(msg.ToUserName, msg.FromUserName, DEFALUT_MESSAGE); String replayMsgStr = replayMsg.toXml(); result.result = replayMsgStr; return true; } return false; } public static final String DEFALUT_MESSAGE = "请根据提示选择你的操作: " + "1.图文消息示例。 " + "2.公司名称。 " + "3.联系方式。 " + "4.回复文本消息带超链接的演示。 " + "5.回复音乐消息示例。 "; } /** * 对话 * * @author yunfei * */ public class TalkManager { /** * 处理回复 * * @param content * @return */ public static boolean talk(String content, TaskResponse response) { if ("2".equals(content) || content.contains("公司")) { response.content = "北京-----发展有限公司 - 农业资源管理事业部"; return true; } else if ("3".equals(content) || content.contains("电话")) { response.content = "我们的联系电话是:010-xxxxx"; return true; }else if ("4".equals(content)) { response.content = "更多信息请查看:<a href='http://www.baidu.com'>详情</a>"; return true; } return false; // return String.format("“%s”??? 没听清,你再说一遍。", content); } public static class TaskResponse { public String content; } }
我们构建了回复消息,处理POST消息的最后一步是写入 ”响应该次POST的内容,就是我们回复的消息内容“,我们一般会构建以个 ”回复的消息实体“再将实体转成成 xml格式的字符串,写入到响应流(HttpResponse).示例:
/** * 回复文本消息 * @author yunfei * */ public class TextMessageReply implements IReplyMessage{ /* * <xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[你好]]></Content> </xml> * */ public String ToUserName; public String FromUserName; public String CreateTime; public String MsgType = "text"; public String Content; public String toXml() { return TextMessageReplyWriter.toXml(this); } --------------------------- class TextMessageReplyWriter { /** * <xml> <ToUserName><![CDATA[toUser]]></ToUserName> * <FromUserName><![CDATA[fromUser]]></FromUserName> * <CreateTime>12345678</CreateTime> <MsgType><![CDATA[text]]></MsgType> * <Content><![CDATA[你好]]></Content> </xml> * * @param replayMsg * @return */ public static String toXml(TextMessageReply replayMsg) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder dbuilder = null; try { dbuilder = dbf.newDocumentBuilder(); } catch (Exception ex) { ex.printStackTrace(); } Document doc = dbuilder.newDocument(); Element root = doc.createElement("xml"); doc.appendChild(root); Element e; e = doc.createElement("ToUserName"); e.appendChild(doc.createCDATASection(replayMsg.ToUserName)); root.appendChild(e); e = doc.createElement("FromUserName"); e.appendChild(doc.createCDATASection(replayMsg.FromUserName)); root.appendChild(e); e = doc.createElement("CreateTime"); e.setTextContent(replayMsg.CreateTime); root.appendChild(e); e = doc.createElement("MsgType"); e.appendChild(doc.createCDATASection(replayMsg.MsgType)); root.appendChild(e); e = doc.createElement("Content"); e.appendChild(doc.createCDATASection(replayMsg.Content)); root.appendChild(e); StringWriter sw = new StringWriter(); XmlWriteUtil.callDomWriter(doc, sw, "utf-8"); String xmlRes = sw.getBuffer().toString(); return xmlRes; } }
将xml回复的内容写入到响应流,就完成了一次的消息响应。
不过,还有很多事情要做。消息的类型还有很多,还有事件的处理。我们需要根据不同的消息做不同的消息封装,对回复的各种类型的消息做封装。
我写了自己的类库:weinxinFundation,
- 实现了基本的 接收消息,回复消息(图文消息,音乐消息等)
- 基本的事件(关注,地理,扫描二维码等)的封装类库。
- 支持可扩展的消息处理器,自定义处理消息的方式。消息的实现方法处理器逻辑和类库逻辑分离。便于二次开发。
- 消息链表方式,一个消息不被处理就会沿这链表继续传递,直到被处理后结束。
提供使用 weinxinFundation 框架开发的演示源代码,在这里你也可以反编译拿到我weinxinFundation 源代码。
点击下载 http://yunpan.cn/QaceVpHfIiInj 访问密码 16f1
暂不免费提供weinxinFundation 源代码的下载,需要请联系我vir56k@163.com
我的其他文章: