java编写微信公众账号收发消息
这两天无聊就申请了一个微信的订阅号,然后从网上搜集了些材料,通过搜集的材料使我的公众号可以接收文本消息并返回简单的文本信息。这里做下记录,日后如果要深入研究一下可以从这里捡起。
拿到订阅号后我把头像设置了一下与微信号设置了一番。对于我们来说,最好奇的莫过于它能干嘛,所以就点击了自动回复,自定义了回复内容,然后用自己微信号关注了我的订阅号。关注后,立马给我发了我自己设置的信息。顿时觉得好新奇,感觉不错,然后就是看到自定义菜单了,这东西好啊,可以自己定义菜单,说干就干,我立马设置了三个菜单(一级菜单最多有三个,这些东西腾讯都说的很明白),设置好后我就取消关注重新加了一遍,但是自定义菜单没有出来!!有点懊恼,这是怎么回事,没有放弃的我重复了几次还是无功而返。原来是这里有一个审核的过程,等一天左右审核完后,我再次访问我的公众号时就了菜单了,哈哈。
好了,上面说了这么多,是我在玩的过程中的细节,作为一个程序员来说,最想了解的肯定是微信可以让我们开发者来做什么。微信为我们提供了两种方式:(1)编辑模式
(2)开发者模式 上文中所做的都是使用了编辑模式,也就是说刚申请完的订阅号是以编辑模式供申请者使用的。
为了可以利用开发者模式,我们需要通过开发者认证,在页面最下面有一个开发者中心,点击后通过申请,那么就是作为开发者的第一步。
1.开发者认证相关
首先,你注册完订阅号后(我是个人玩,所以申请了个人订阅号,不过现在才发现在开发者模式下没有获得自定义菜单的权限),点击开发者中心,然后可以申请成为开发者
然后,就可以看到我们的开发者ID 了,其中包含应用ID与应用秘钥这两个,通过这两个东西我们可以获取我们公众号的唯一票据access_token,也就是唯一辨识你公众号的东西。下面是去取得这个标示的方法。
将你的应用id与秘钥填上就可以获取到你的access_token 了,在这里只是说一下这个东西,对于我在接收与消息的试验中没有用到它,但是在自定义菜单(可惜我的是个人订阅号,没有权限!)等功能时就会用到了,不然微信服务器怎么知道你是哪个公众号,然后给你定义菜单呢!O(∩_∩)O
2.看了看我可怜的权限,我打算稍微了解一下收发消息的接口。(在这里我也只是实验了简单的文本收发,图片与语音等也大同小异,等用到再研究也可以)
要想可以收到用户发的消息,并做处理后返回消息,那肯定是作为第三方平台来支持了(相对于微信服务器,我们当然是第三方了),收发消息按我自己的理解就是用户发消息到公众号,微信服务器将用户发的信息通过特定格式发给第三方平台,然后第三方平台解析微信服务器发来的消息,处理并以特定的格式返回给服务器,服务器再通过公众号呈现给用户。
那作为第三方平台的我们当然要是一个可以访问的地址了。不然微信服务器如何将信息发给你呢,你说是不?好了那么可以上图了
这个地方就是要配置我们服务器的地方,当然你肯定会问如何配置自己的服务器了。那么下面慢慢告诉你:
(1)首先,这个URL是部署在服务器上的一个应用,而这个应用就是供微信服务器访问用的,而且在微信服务器访问这个服务器地址时是要签名验证的,微信官网也给了php的demo。不过我对php不熟悉,所以我就用了java,那么用java怎么做呢?
由于只是最简单的访问验证,那么java的话最方便的就是用一个servlet了,我们只需要将官网给的demo用java来实现一下就可以了。从网上可以搜到好多现成的代码(我也是从网上找的)。那么这里帖一下:
这段代码可以说是一个验证工具类
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; public class SignUtil { /** * 与接口配置信息中的 token 要一致,这里赋予什么值,在接口配置信息中的Token就要填写什么值, * 两边保持一致即可,建议用项目名称、公司名称缩写等,我在这里用的是项目名称weixinface */ private static String token = "你自己的token(这里的token与你上图中配置服务器token相同)"; /** * 验证签名 * @param signature * @param timestamp * @param nonce * @return */ public static boolean checkSignature(String signature, String timestamp, String nonce){ String[] arr = new String[]{token, timestamp, nonce}; // 将 token, timestamp, nonce 三个参数进行字典排序 Arrays.sort(arr); StringBuilder content = new StringBuilder(); for(int i = 0; i < arr.length; i++){ content.append(arr[i]); } MessageDigest md = null; String tmpStr = null; try { md = MessageDigest.getInstance("SHA-1"); // 将三个参数字符串拼接成一个字符串进行 shal 加密 byte[] digest = md.digest(content.toString().getBytes()); tmpStr = byteToStr(digest); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } content = null; // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信 return tmpStr != null ? tmpStr.equals(signature.toUpperCase()): false; } /** * 将字节数组转换为十六进制字符串 * @param digest * @return */ private static String byteToStr(byte[] digest) { // TODO Auto-generated method stub String strDigest = ""; for(int i = 0; i < digest.length; i++){ strDigest += byteToHexStr(digest[i]); } return strDigest; } /** * 将字节转换为十六进制字符串 * @param b * @return */ private static String byteToHexStr(byte b) { // TODO Auto-generated method stub char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] tempArr = new char[2]; tempArr[0] = Digit[(b >>> 4) & 0X0F]; tempArr[1] = Digit[b & 0X0F]; String s = new String(tempArr); return s; } }(2)再需要的当然就是我们最重要的servlet了。上代码:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); // 微信加密签名 String signature = request.getParameter("signature"); // 时间戮 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); // 随机字符串 String echostr = request.getParameter("echostr"); PrintWriter out = response.getWriter(); // 通过检验 signature 对请求进行校验,若校验成功则原样返回 echostr,表示接入成功,否则接入失败 if(SignUtil.checkSignature(signature, timestamp, nonce)){ out.print(echostr); } }这段代码是在servlet中get方法中的,因为在验证服务器的时候微信服务器是通过GET方式访问的。
完整代码我们将会在下面讲到收发消息的时候一起给附上。(ps:如何建立servlet以及在web里配置servlet这里不讲了,这些东西可以百度)
现在好了,运行你服务器上的tomcat将写好的servlet运行起来,在URL中写上你你servlet的访问地址,在Token那写上与代码里设置的token值(仔细看代码,有个token),消息加解秘钥可以随机生成,因为是测试,消息加解密方式我就选择了明文,你也可以选择其他的。
这样提交后,如果没问题就会出现提示提交成功,那么恭喜你!你的服务器验证就通过了。
提示:相信很多人在这一步会有问题,如果你的问题是servlet等方面的问题,那么请你去百度了解一下,然后我会在下面将整个源码附上。对于像我来说,最大的问题是去哪里弄个服务器呀,没有关系,可以去新浪申请一个sae账号,然后在这里创建自己的应用,将代码发布到上面,具体的不细说了,可以百度。由于我在学校里,是nat映射的内网,以前用花生壳工具是不能映射到公网上的,不过现在查了一下,现在的新花生壳竟然强大到可以无需公网ip,无需端口映射就可以映射公网的功能,好奇心驱动下的我下载了一个,垃圾捆绑不少,最重要的是要我付1块钱,我就没有去实验,有兴趣的可以试试,看看这个新花生壳怎么样。
(3)有了自己的服务器可以访问了,那么真正的开发才开始。上文提到了,我这个订阅号由于是个人的,所以没有获得开发模式下的自定义菜单,那么就拿微信给的收发消息的接口玩起来。从网上搜集了部分资料,然后放到代码里整合了一下,经过几次尝试,终于可以通过用户发文字或者简单常用表情,然后将处理内容返回给用户。有了上面说的消息收发的过程,然后去看一下微信给出的消息格式,相信你看到下面的代码也会明白的。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; @SuppressWarnings("serial") public class wechatservlet extends HttpServlet { public wechatservlet() { super(); } public void destroy() { super.destroy(); } /** * 验证url和token */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); // 微信加密签名 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(echostr!=null&&!echostr.isEmpty()) { // 通过检验 signature 对请求进行校验,若校验成功则原样返回 echostr,表示接入成功,否则接入失败 if(SignUtil.checkSignature(signature, timestamp, nonce)){ out.print(echostr); } } responseMsg( request, response); out.close(); out = null; } public void responseMsg(HttpServletRequest request,HttpServletResponse response){ String postStr=null; try{ postStr=this.readStreamParameter(request.getInputStream()); }catch(Exception e){ e.printStackTrace(); } if (null!=postStr&&!postStr.isEmpty()){ Document document=null; try{ document = DocumentHelper.parseText(postStr); }catch(Exception e){ e.printStackTrace(); } if(null==document){ try { response.getWriter().print(""); } catch (IOException e) { e.printStackTrace(); } return; } Element root=document.getRootElement(); String fromUsername = root.elementText("FromUserName"); String toUsername = root.elementText("ToUserName"); String keyword = root.elementTextTrim("Content"); String time = new Date().getTime()+""; String textTpl = "<xml>"+ "<ToUserName><![CDATA[%1$s]]></ToUserName>"+ "<FromUserName><![CDATA[%2$s]]></FromUserName>"+ "<CreateTime>%3$s</CreateTime>"+ "<MsgType><![CDATA[%4$s]]></MsgType>"+ "<Content><![CDATA[%5$s]]></Content>"+ "<FuncFlag>0</FuncFlag>"+ "</xml>"; if(null!=keyword&&!keyword.equals("")) { String msgType = "text"; String contentStr = "您发送的消息是: "+keyword; String resultStr = textTpl.format(textTpl, fromUsername, toUsername, time, msgType, contentStr); try { response.getWriter().print(resultStr); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ try { response.getWriter().print("Input something…"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }else { try { response.getWriter().print(""); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public String readStreamParameter(ServletInputStream in){ StringBuilder buffer = new StringBuilder(); BufferedReader reader=null; try{ reader = new BufferedReader(new InputStreamReader(in)); String line=null; while((line = reader.readLine())!=null){ buffer.append(line); } }catch(Exception e){ e.printStackTrace(); }finally{ if(null!=reader){ try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return buffer.toString(); } /** *用户向公众平台发信息并自动返回信息 */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet( request, response); } public void init() throws ServletException { } }这里在解析xml文件的时候用到了dom4j,所以你在使用这段代码的时候将需要的dom4j的jar包导入。
现在,一切都准备妥当了,我们已经有了这个servlet完整的代码了,还有最上面的验证签名的工具类,那么我们所需要的代码已经全了,如果你跟我一样用的是sae,那么抓紧将运行起来的代码打包成war包放到你的应用里吧(sae要求你的war包名字跟你的应用名相同)
打开你的微信,向你的公众号发送一条文本测试一下吧!O(∩_∩)O~(也可以先用微信给你的测试工具去测试一下)