Java微信公众号开发
现在人工智能非常火爆,很多朋友都想学,但是一般的教程都是为博硕生准备的,太难看懂了。最近发现了一个非常适合小白入门的教程,不仅通俗易懂而且还很风趣幽默。所以忍不住分享一下给大家
微信公众平台是腾讯为了让用户申请和管理微信公众账号而推出的一个web平台。微信公众账号的种类可以分为3种,并且一旦选定不可更改。按照功能的限制从小到大依次为:订阅号、服务号、企业号。个人只能注册订阅号。注册地址:https://mp.weixin.qq.com/。
开发环境的准备
- 微信公众号
- 外网映射工具(开发调试)
与微信的对接的URL应该满足以下的条件:
- 在公网上能够访问
- 只支持80端口
映射工具有很多,例如花生壳,ngrok可以将内网映射到公网上面,这样就可以使用公网访问本机的网络服务。下载链接: http://pan.baidu.com/s/1i3u26St 密码: v4e8(里面有简明的教程)。
微信公众号的数据交互原理
开发模式的接入
进入微信公众号平台之后进入开发者中心,在开发者中心中找到开发者文档,在新手指南中有接入的相关步骤。依据接入文档有以下的实现:
package org.gpf.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.gpf.util.CheckUtil; /** * 接收微信服务器发送的4个参数并返回echostr */ public class WeixinServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 接收微信服务器以Get请求发送的4个参数 String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); PrintWriter out = response.getWriter(); if (CheckUtil.checkSignature(signature, timestamp, nonce)) { out.print(echostr); // 校验通过,原样返回echostr参数内容 } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
校验工具类:
package org.gpf.util; import java.util.Arrays; import org.apache.commons.codec.digest.DigestUtils; /** * 校验的工具类 */ public class CheckUtil { private static final String token = "weixin"; public static boolean checkSignature(String signature,String timestamp,String nonce){ String[] arr = new String[] { token, timestamp, nonce }; // 排序 Arrays.sort(arr); // 生成字符串 StringBuilder content = new StringBuilder(); for (int i = 0; i < arr.length; i++) { content.append(arr[i]); } // sha1加密 String temp = getSHA1String(content.toString()); return temp.equals(signature); // 与微信传递过来的签名进行比较 } private static String getSHA1String(String data){ return DigestUtils.sha1Hex(data); // 使用commons codec生成sha1字符串 } }
Servlet配置:
servlet> <servlet-name>WeixinServlet</servlet-name> <servlet-class>org.gpf.servlet.WeixinServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>WeixinServlet</servlet-name> <url-pattern>/wx.do</url-pattern> </servlet-mapping>
接下来通过映射工具将本地的服务器映射到公网,从公网访问Servlet。
开发模式和编辑模式是互斥的,如果启动了开发模式,则自定义菜单和自动回复将失效!
消息的接收和响应
参照文档,当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。所以我们需要更改我们的Servlet中的doPost方法,因为微信服务器与我们的服务器之间是通过XML传递数据的,因此我们需要实现消息实体与XML之间的互相转换。可以采用第三方jar包XStream完成。
处理微信服务器与本机服务器进行交互的Servlet:
package org.gpf.servlet; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.dom4j.DocumentException; import org.gpf.po.TextMeaasge; import org.gpf.util.CheckUtil; import org.gpf.util.MessageUtil; /** * 微信消息的接收和响应 */ public class WeixinServlet extends HttpServlet { /** * 接收微信服务器发送的4个参数并返回echostr */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 接收微信服务器以Get请求发送的4个参数 String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); PrintWriter out = response.getWriter(); if (CheckUtil.checkSignature(signature, timestamp, nonce)) { out.print(echostr); // 校验通过,原样返回echostr参数内容 } } /** * 接收并处理微信客户端发送的请求 */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/xml;charset=utf-8"); PrintWriter out = response.getWriter(); try { Map<String, String> map = MessageUtil.xmlToMap(request); String toUserName = map.get("ToUserName"); String fromUserName = map.get("FromUserName"); String msgType = map.get("MsgType"); String content = map.get("Content"); String message = null; if ("text".equals(msgType)) { // 对文本消息进行处理 TextMeaasge text = new TextMeaasge(); text.setFromUserName(toUserName); // 发送和回复是反向的 text.setToUserName(fromUserName); text.setMsgType("text"); text.setCreateTime(new Date().getTime()); text.setContent("你发送的消息是:" + content); message = MessageUtil.textMessageToXML(text); System.out.println(message); } out.print(message); // 将回应发送给微信服务器 } catch (DocumentException e) { e.printStackTrace(); }finally{ out.close(); } } }
按照微信的接口文档编写的文本消息实体类:
package org.gpf.util; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.gpf.po.TextMeaasge; import com.thoughtworks.xstream.XStream; /** * 实现消息的格式转换(Map类型和XML的互转) */ public class MessageUtil { /** * 将XML转换成Map集合 */ public static Map<String, String>xmlToMap(HttpServletRequest request) throws IOException, DocumentException{ Map<String, String> map = new HashMap<String, String>(); SAXReader reader = new SAXReader(); // 使用dom4j解析xml InputStream ins = request.getInputStream(); // 从request中获取输入流 Document doc = reader.read(ins); Element root = doc.getRootElement(); // 获取根元素 List<Element> list = root.elements(); // 获取所有节点 for (Element e : list) { map.put(e.getName(), e.getText()); System.out.println(e.getName() + "--->" + e.getText()); } ins.close(); return map; } /** * 将文本消息对象转换成XML */ public static String textMessageToXML(TextMeaasge textMessage){ XStream xstream = new XStream(); // 使用XStream将实体类的实例转换成xml格式 xstream.alias("xml", textMessage.getClass()); // 将xml的默认根节点替换成“xml” return xstream.toXML(textMessage); } }