微信开发与代码的编写(一)

微信开发与代码的编写(一)

微信开发环境的搭建

目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动App开发领域主要分为以下几种类型

  

  我在平时的工作中接触得比较多的就是基于Android的Native App开发和基于微信公众号的Light App开发,今天就来带领大家快速进入微信公众号的开发领域.

微信开发环境搭建

  工欲善其事,必先利其器。要做微信公众号开发,那么要先准备好两样必不可少的东西:

  1、要有一个用来测试的公众号。

  2、用来调式代码的开发环境。

 

注册测试公众号

  微信公众号分为服务号、订阅号、企业号,订阅号可以个人申请,服务号和企业号要有企业资质才可以。

  我们所说的微信公众号开发指的是订阅号和服务号。

  关于订阅号和服务器的区别,官方是这样解释的

  服务号:主要偏向于服务交互(功能类似12315,114,银行,提供绑定信息,服务交互),每月可群发4条消息;服务号适用人群:媒体、企业、政府或其他组织。

  订阅号:主要偏向于为用户传达资讯,(功能类似报纸杂志,为用户提供新闻信息或娱乐趣事),每天可群发1条消息;订阅号适用人群:个人、媒体、企业、政府或其他组织。

  个人订阅号有一些接口是没有权限的,也就是说个人订阅号无法调用一些高级的权限接口,下图就是一个我的个人订阅号所具备权限列表,如下图所示:

  

  而一些高级接口,如生成二维码、网页授权、自定义菜单、微信支付这样的接口权限个人订阅号是没有调用权限的,如上图红色框起来的那些接口,个人订阅号都无法调用。

  幸运的是,微信公众平台提供了测试公众账号,测试公众号的注册地址为:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,只需要到这个页面,点击登录,并用自己的微信客户端扫码,并授权登录,就可以获得属于自己的测试公众号。测试公众号具备几乎所有的接口,所以平时学习微信公众号开发时,就可以去注册一个测试公众号,然后使用这个测试公众号做开发就可以了。不废话了,还是先注册一个测试公众号吧

  访问http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,注册一个测试公众号。

  注册测试公众号的步骤如下图所示:

  

    

   用微信扫描上述的二维码进行登录,登录成功后,就可以看到腾讯分配给我们的测试公众号的信息了,如下图所示:

  

  测试公众号的所拥有的接口权限如下:

  

  可以看到,测试公众号拥有大部分的接口调用权限,因此用测试公众号来学习微信开发是完全可以的。

 

搭建微信本地调试环境

  开发基于微信公众号的应用最大的痛苦之处就是调试问题,每次实现一个功能后都需要部署到一个公网服务器进行测试,因为微信用户每次向公众号发起请求时,微信服务器会先接收到用户的请求,然后再转发到我们的服务器上,也就是说,微信服务器是要和我们的服务器进行网络交互,所以我们必须保证我们的服务器外网可以访问到,这种部署到公网服务器进行测试的做法对于我们开发者来说简直是噩梦。所以我们要想一个办法可以做到本地部署,本地调试代码,而要做到这一点,那么我们要解决的问题就是将内网的部署服务器映射到外网,让微信服务器可以正常访问到,幸运的是,借助于第三方软件Ngrok,我们就可以做得到。Ngrok是一个免费的软件Ngrok,使用Ngrok后,我们就可以实现内网穿透,也就是说我们可以将内网的服务器映射到外网给别人访问,这对于我们在本地开发环境中调试微信代码是以及给用户演示一些东西非常快速和有帮助的,因为可以直接使用我们自己的内网的电脑作为服务器。

  国内提供Ngrok服务比较好的网站是:http://natapp.cn/,如下图所示:

  

  从http://natapp.cn/网站上下载ngrok客户端,如下图所示:

  

   下载完成后,得到一个压缩包,解压压缩包后,得到一个文件夹,里面有如下图所示的几个文件:

  

  打开CMD命令行窗口,进入到ngrok_windows目录下,然后输入如下命令:

  ngrok -config ngrok.cfg -subdomain xdp 8080,如下图所示:

  

  xdp是我自己自定义的一个域名, 8080为本地服务器的运行端口,执行完上述命令后,我们就可以本地的127.0.0.1:8080服务器映射到外网了,如下图所示:

  

  此时外网的用户可以直接使用http://xdp.ngrok.natapp.cn这个域名访问到我内网的127.0.0.1:8080服务器了,如下图所示:

  

  

  使用了ngrok之后,我们就可以把内网的服务器当成公网服务器来使用了.访问的速度也还在可以接受的范围内吧,截止到目前为止ngrok是可用的,微信公众号服务器是可以访问的,这样一来也就不妨碍我们做本地调式了。到此,我们的微信本地调试开发环境就算是搭建好了。

 

微信公众平台的基本原理

  在开始做之前,先简单介绍了微信公众平台的基本原理。

  微信服务器就相当于一个转发服务器,终端(手机、Pad等)发起请求至微信服务器,微信服务器然后将请求转发给我们的应用服务器。应用服务器处理完毕后,将响应数据回发给微信服务器,微信服务器再将具体响应信息回复到微信App终端。

  通信协议为:HTTP

  数据传输格式为:XML

  具体的流程如下图所示:

  

  来一张更加直观的图吧:

  

  我们需要做的事情,就是对微信服务器转发的HTTP请求做出响应。具体的请求内容,我们按照特定的XML格式去解析,处理完毕后,也要按照特定的XML格式返回。

 

微信公众号接入

  在微信公众平台开发者文档上,关于公众号接入这一节内容在接入指南上写的比较详细的,文档中说接入公众号需要3个步骤,分别是:

  1、填写服务器配置
  2、验证服务器地址的有效性
  3、依据接口文档实现业务逻辑

  其实,第3步已经不能算做公众号接入的步骤,而是接入之后,开发人员可以根据微信公众号提供的接口所能做的一些开发。

  第1步中服务器配置包含服务器地址(URL)、Token和EncodingAESKey。

  服务器地址即公众号后台提供业务逻辑的入口地址,目前只支持80端口,之后包括接入验证以及任何其它的操作的请求(例如消息的发送、菜单管理、素材管理等)都要从这个地址进入。接入验证和其它请求的区别就是,接入验证时是get请求,其它时候是post请求;

  Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性);

  EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。本例中全部以未加密的明文消息方式,不涉及此配置项。

  第2步,验证服务器地址的有效性,当点击“提交”按钮后,微信服务器将发送一个http的get请求到刚刚填写的服务器地址,并且携带四个参数:

  

  接到请求后,我们需要做如下三步,若确认此次GET请求来自微信服务器,原样返回echostr参数内容,则接入生效,否则接入失败。

  1. 将token、timestamp、nonce三个参数进行字典序排序
  2. 将三个参数字符串拼接成一个字符串进行sha1加密
  3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

  下面我们用Java代码来演示一下这个验证过程

  使用IDE(Eclipse或者IntelliJ IDEA)创建一个JavaWeb项目,这里我使用的是IntelliJ IDEA,项目目录结构如下图所示:

  

  编写一个servlevt,在其中的doGet方法中定义校验方法,具体代码如下:

package me.gacl.wx.web.servlet;
  
  import javax.servlet.ServletException;
  import javax.servlet.annotation.WebServlet;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import java.io.IOException;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.Arrays;
  
  /**
   * Created by xdp on 2016/1/25.
   * 使用@WebServlet注解配置WxServlet,urlPatterns属性指明了WxServlet的访问路径
   */
  @WebServlet(urlPatterns="/WxServlet")
  public class WxServlet extends HttpServlet {
  
      /**
       * Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
       * 比如这里我将Token设置为gacl
       */
     private final String TOKEN = "gacl";
  
      protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  
      }
  
      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          System.out.println("开始校验签名");
          /**
           * 接收微信服务器发送请求时传递过来的4个参数
           */
          String signature = request.getParameter("signature");//微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
          String timestamp = request.getParameter("timestamp");//时间戳
          String nonce = request.getParameter("nonce");//随机数
          String echostr = request.getParameter("echostr");//随机字符串
          //排序
          String sortString = sort(TOKEN, timestamp, nonce);
          //加密
          String mySignature = sha1(sortString);
          //校验签名
         if (mySignature != null && mySignature != "" && mySignature.equals(signature)) {
              System.out.println("签名校验通过。");
              //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
              //response.getWriter().println(echostr);
              response.getWriter().write(echostr);
          } else {
              System.out.println("签名校验失败.");
          }
  
      }
  
      /**
       * 排序方法
       *
       * @param token
       * @param timestamp
       * @param nonce
       * @return
       */
      public String sort(String token, String timestamp, String nonce) {
          String[] strArray = {token, timestamp, nonce};
         Arrays.sort(strArray);
          StringBuilder sb = new StringBuilder();
          for (String str : strArray) {
              sb.append(str);
          }
  
          return sb.toString();
      }
  
      /**
       * 将字符串进行sha1加密
       *
       * @param str 需要加密的字符串
       * @return 加密后的内容
       */
     public String sha1(String str) {
          try {
              MessageDigest digest = MessageDigest.getInstance("SHA-1");
              digest.update(str.getBytes());
              byte messageDigest[] = digest.digest();
              // Create Hex String
              StringBuffer hexString = new StringBuffer();
              // 字节数组转换为 十六进制 数
              for (int i = 0; i < messageDigest.length; i++) {
                  String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                  if (shaHex.length() < 2) {
                      hexString.append(0);
                  }
                  hexString.append(shaHex);
              }
              return hexString.toString();
  
          } catch (NoSuchAlgorithmException e) {
              e.printStackTrace();
          }
         return "";
     }
 }

  我这里用的Servlet3.0,使用Servlet3.0的好处就是可以直接使用@WebServlet注解映射Servlet的访问路径,不再需要在web.xml文件中进行配置.

  将WxStudy项目部署到Tomcat服务器中运行,直接启动项目,然后用ngrok将本地8080端口映射到外网(如何使用ngrok请参考博客《微信开发学习总结(一)——微信开发环境搭建》)。如下图所示:

  

  测试是否可以通过http://xdp.ngrok.natapp.cn地址正常访问,测试结果如下:

  

  可以看到,我们的项目已经可以被外网正常访问到了。

  进入微信测试公众号管理界面,在接口配置信息中填入映射的外网地址和token,如下图所示:

点击提交按钮,页面会提示配置成功,

  

  IDE的控制台中输出了校验通过的信息,如下图所示:

  

  到此,我们的公众号应用已经能够和微信服务器正常通信了,也就是说我们的公众号已经接入到微信公众平台了。

 

access_token管理

access_token介绍

  我们的公众号和微信服务器对接成功之后,接下来要做的就是根据我们的业务需求调用微信公众号提供的接口来实现相应的逻辑了。在使用微信公众号接口中都需要一个access_token。

  关于access_token,在微信公众平台开发者文档上的获取接口调用凭据有比较详细的介绍:access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token,开发者需要妥善保存access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。并且每天调用获取access_token接口的上限是2000次。

  总结以上说明,access_token需要做到以下两点:

  1.因为access_token有2个小时的时效性,要有一个机制保证最长2个小时重新获取一次。

  2.因为接口调用上限每天2000次,所以不能调用太频繁。

微信公众平台提供的获取access_token的接口

  关于access_token的获取方式,在微信公众平台开发者文档上有说明,公众号可以调用一个叫"获取access token"的接口来获取access_token。

  获取access token接口调用请求说明

    http请求方式: GET

    请求的URL地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
       

  我们可以看到,调用过程中需要传递appID和AppSecret,appID和AppSecret是在申请公众号的时候自动分配给公众号的,相当于公众号的身份标示,使用微信公众号的注册帐号登录到腾讯提供的微信公众号管理后台就可以看到自己申请的公众号的AppID和AppSecret,如下图所示:

             

  这是我申请公众号测试帐号时分配到的AppID和AppSecret。

获取access_token方案以及具体实现

  这里采用的方案是这样的,定义一个默认启动的servlet,在init方法中启动一个Thread,这个进程中定义一个无限循环的方法,用来获取access_token,当获取成功后,此进程休眠7000秒(7000秒=1.944444444444444小时),否则休眠3秒钟继续获取。流程图如下:

  

  下面正式开始在工程中实现以上思路,因为返回的数据都是json格式,这里会用到阿里的fastjson库,为构造请求和处理请求后的数据序列化和反序列化提供支持。

  1.定义一个AccessToken实体类

 package me.gacl.wx.entry;
 
 /**
   * AccessToken的数据模型
  * Created by xdp on 2016/1/25.
  */
 public class AccessToken {
 
     //获取到的凭证
     private String accessToken;
    //凭证有效时间,单位:秒
     private int expiresin;
 
     public String getAccessToken() {
         return accessToken;
     }
 
     public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
     }
 
     public int getExpiresin() {
         return expiresin;
     }
 
     public void setExpiresin(int expiresin) {
         this.expiresin = expiresin;
     }
 }

     2.定义一个AccessTokenInfo类,用于存放获取到的AccessToken,代码如下:

 package me.gacl.wx.Common;
 
  import me.gacl.wx.entry.AccessToken;
 
 /**
  * Created by xdp on 2016/1/25.
  */
 public class AccessTokenInfo {
 
     //注意是静态的
     public static AccessToken accessToken = null;
 }

  3.编写一个用于发起https请求的工具类NetWorkHelper,代码如下:

 package me.gacl.wx.util;
 
 import javax.net.ssl.*;
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URL;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 
 /**
 * 访问网络用到的工具类
  */
 public class NetWorkHelper {
 
     /**
      * 发起Https请求
      * @param reqUrl 请求的URL地址
      * @param requestMethod
      * @return 响应后的字符串
      */
     public String getHttpsResponse(String reqUrl, String requestMethod) {
         URL url;
         InputStream is;
         String resultData = "";
         try {
             url = new URL(reqUrl);
             HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
             TrustManager[] tm = {xtm};
 
             SSLContext ctx = SSLContext.getInstance("TLS");
             ctx.init(null, tm, null);
 
             con.setSSLSocketFactory(ctx.getSocketFactory());
             con.setHostnameVerifier(new HostnameVerifier() {
                 @Override
                 public boolean verify(String arg0, SSLSession arg1) {
                     return true;
                 }
             });
 
 
             con.setDoInput(true); //允许输入流,即允许下载
 
             //在android中必须将此项设置为false
             con.setDoOutput(false); //允许输出流,即允许上传
             con.setUseCaches(false); //不使用缓冲
             if (null != requestMethod && !requestMethod.equals("")) {
                 con.setRequestMethod(requestMethod); //使用指定的方式
             } else {
                 con.setRequestMethod("GET"); //使用get请求
             }
             is = con.getInputStream();   //获取输入流,此时才真正建立链接
             InputStreamReader isr = new InputStreamReader(is);
             BufferedReader bufferReader = new BufferedReader(isr);
             String inputLine;
             while ((inputLine = bufferReader.readLine()) != null) {
                 resultData += inputLine + "\n";
             }
             System.out.println(resultData);
 
         } catch (Exception e) {
             e.printStackTrace();
         }
         return resultData;
     }
 
     X509TrustManager xtm = new X509TrustManager() {
         @Override
         public X509Certificate[] getAcceptedIssuers() {
             return null;
         }
 
         @Override
         public void checkServerTrusted(X509Certificate[] arg0, String arg1)
                 throws CertificateException {
 
         }
 
         @Override
         public void checkClientTrusted(X509Certificate[] arg0, String arg1)
                 throws CertificateException {
 
         }
     };
 }

  getHttpsResponse方法是请求一个https地址,参数requestMethod为字符串“GET”或者“POST”,传null或者“”默认为get方式。

  4.定义一个默认启动的servlet,在init方法中启动一个新的线程去获取accessToken

 package me.gacl.wx.web.servlet;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import me.gacl.wx.Common.AccessTokenInfo;
 import me.gacl.wx.entry.AccessToken;
 import me.gacl.wx.util.NetWorkHelper;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebInitParam;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 
 /**
  * 用于获取accessToken的Servlet
  * Created by xdp on 2016/1/25.
  */
 @WebServlet(
         name = "AccessTokenServlet",
         urlPatterns = {"/AccessTokenServlet"},
         loadOnStartup = 1,
         initParams = {
                 @WebInitParam(name = "appId", value = "wxbe4d433e857e8bb1"),
                 @WebInitParam(name = "appSecret", value = "ccbc82d560876711027b3d43a6f2ebda")
         })
 public class AccessTokenServlet extends HttpServlet {
 
     @Override
     public void init() throws ServletException {
         System.out.println("启动WebServlet");
         super.init();
 
         final String appId = getInitParameter("appId");
         final String appSecret = getInitParameter("appSecret");
 
         //开启一个新的线程
         new Thread(new Runnable() {
             @Override
             public void run() {
                 while (true) {
                     try {
                         //获取accessToken
                         AccessTokenInfo.accessToken = getAccessToken(appId, appSecret);
                         //获取成功
                         if (AccessTokenInfo.accessToken != null) {
                             //获取到access_token 休眠7000秒,大约2个小时左右
                             Thread.sleep(7000 * 1000);
                             //Thread.sleep(10 * 1000);//10秒钟获取一次
                         } else {
                             //获取失败
                             Thread.sleep(1000 * 3); //获取的access_token为空 休眠3秒
                         }                     } catch (Exception e) {
                         System.out.println("发生异常:" + e.getMessage());
                         e.printStackTrace();
                         try {
                             Thread.sleep(1000 * 10); //发生异常休眠1秒
                         } catch (Exception e1) {
 
                         }
                     }
                 }
 
             }
         }).start();
     }
 
     /**
      * 获取access_token
      *
      * @return AccessToken
      */
     private AccessToken getAccessToken(String appId, String appSecret) {
         NetWorkHelper netHelper = new NetWorkHelper();
         /**
          * 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。
          */
         String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
         //此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}
         String result = netHelper.getHttpsResponse(Url, "");
         System.out.println("获取到的access_token="+result);
         //使用FastJson将Json字符串解析成Json对象
         JSONObject json = JSON.parseObject(result);
         AccessToken token = new AccessToken();
         token.setAccessToken(json.getString("access_token"));
         token.setExpiresin(json.getInteger("expires_in"));
         return token;
     }
 }

        AccessTokenServlet采用注解的方式进行配置
  至此代码实现完毕,将项目部署,看到控制台输出如下:

  

  为了方便看效果,可以把休眠时间设置短一点,比如10秒获取一次,然后将access_token输出。

  下面做一个测试jsp页面,并把休眠时间设置为10秒,这样过10秒刷新页面,就可以看到变化

 <%-- Created by IntelliJ IDEA. --%>
  <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  <%@ page import="me.gacl.wx.Common.AccessTokenInfo"%>
  <html>
   <head>
     <title></title>
   </head>
   <body>
     微信学习
     <hr/>
     access_token为:<%=AccessTokenInfo.accessToken.getAccessToken()%>
   </body>
 </html>

  

  10秒钟后刷新页面,access_token变了,如下图所示:

  

 

接收微信服务器发送的消息并做出响应

  经过上述的三步,我们开发前的准备工作已经完成了,接下来要做的就是接收微信服务器发送的消息并做出响应

  从微信公众平台接口消息指南中可以了解到,当用户向公众帐号发消息时,微信服务器会将消息通过POST方式提交给我们在接口配置信息中填写的URL,而我们就需要在URL所指向的请求处理类WxServlet的doPost方法中接收消息、处理消息和响应消息。

编写一个用于处理消息的工具类

  编写处理消息的工具栏,工具类代码如下:

 package me.gacl.wx.util;
  
  import org.dom4j.Document;
  import org.dom4j.Element;
  import org.dom4j.io.SAXReader;
  
  import javax.servlet.http.HttpServletRequest;
  import java.io.InputStream;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * 消息处理工具类
   * Created by xdp on 2016/1/26.
   */
  public class MessageHandlerUtil {
  
      /**
       * 解析微信发来的请求(XML)
       * @param request
       * @return map
       * @throws Exception
       */
      public static Map<String,String> parseXml(HttpServletRequest request) throws Exception {
          // 将解析结果存储在HashMap中
          Map<String,String> map = new HashMap();
         // 从request中取得输入流
          InputStream inputStream = request.getInputStream();
          System.out.println("获取输入流");
          // 读取输入流
         SAXReader reader = new SAXReader();
          Document document = reader.read(inputStream);
          // 得到xml根元素
          Element root = document.getRootElement();
          // 得到根元素的所有子节点
          List<Element> elementList = root.elements();
  
          // 遍历所有子节点
         for (Element e : elementList) {
              System.out.println(e.getName() + "|" + e.getText());
              map.put(e.getName(), e.getText());
          }
  
          // 释放资源
          inputStream.close();
          inputStream = null;
          return map;
      }
  
      // 根据消息类型 构造返回消息
      public static String buildXml(Map<String,String> map) {
          String result;
          String msgType = map.get("MsgType").toString();
          System.out.println("MsgType:" + msgType);
          if(msgType.toUpperCase().equals("TEXT")){
              result = buildTextMessage(map, "孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!");
          }else{
              String fromUserName = map.get("FromUserName");
              // 开发者微信号
              String toUserName = map.get("ToUserName");
              result = String
                      .format(
                              "<xml>" +
                                      "<ToUserName><![CDATA[%s]]></ToUserName>" +
                                      "<FromUserName><![CDATA[%s]]></FromUserName>" +
                                      "<CreateTime>%s</CreateTime>" +
                                      "<MsgType><![CDATA[text]]></MsgType>" +
                                     "<Content><![CDATA[%s]]></Content>" +
                                      "</xml>",
                              fromUserName, toUserName, getUtcTime(),
                              "请回复如下关键词:\n文本\n图片\n语音\n视频\n音乐\n图文");
          }
  
          return result;
      }
  
      /**
       * 构造文本消息
       *
       * @param map
       * @param content
       * @return
       */
      private static String buildTextMessage(Map<String,String> map, String content) {
          //发送方帐号
          String fromUserName = map.get("FromUserName");
          // 开发者微信号
          String toUserName = map.get("ToUserName");
          /**
           * 文本消息XML数据格式
           * <xml>
               <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>
          </xml>
          */
         return String.format(
                 "<xml>" +
                         "<ToUserName><![CDATA[%s]]></ToUserName>" +
                         "<FromUserName><![CDATA[%s]]></FromUserName>" +
                         "<CreateTime>%s</CreateTime>" +
                         "<MsgType><![CDATA[text]]></MsgType>" +
                         "<Content><![CDATA[%s]]></Content>" + "</xml>",
                 fromUserName, toUserName, getUtcTime(), content);
     }
 
    private static String getUtcTime() {
         Date dt = new Date();// 如果不需要格式,可直接用dt,dt就是当前系统时间
         DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");// 设置显示格式
         String nowTime = df.format(dt);
         long dd = (long) 0;
         try {
             dd = df.parse(nowTime).getTime();
         } catch (Exception e) {
 
         }
        return String.valueOf(dd);
     }
 }

 为了方便解析微信服务器发送给我们的xml格式的数据,这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-2.0.0-RC1.jar)

  

 

在WxServlet的doPost方法中处理请求

  WxServlet的doPost方法的代码如下:

  /**
      * 处理微信服务器发来的消息
      */
     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息
         // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
         request.setCharacterEncoding("UTF-8");
         response.setCharacterEncoding("UTF-8");
         System.out.println("请求进入");
         String result = "";
         try {
             Map<String,String> map = MessageHandlerUtil.parseXml(request);
             System.out.println("开始构造消息");
             result = MessageHandlerUtil.buildXml(map);
             System.out.println(result);
             if(result.equals("")){
                 result = "未正确响应";
             }
         } catch (Exception e) {
             e.printStackTrace();
             System.out.println("发生异常:"+ e.getMessage());
         }
         response.getWriter().println(result);
     }

  到此,我们的WxServlet已经可以正常处理用户的请求并做出响应了.接下来我们测试一下我们开发好的公众号应用是否可以正常和微信用户交互

  将WxStudy部署到Tomcat服务器,启动服务器,记得使用ngrok将本地Tomcat服务器的8080端口映射到外网,保证接口配置信息的URL地址:http://xdp.ngrok.natapp.cn/WxServlet可以正常与微信服务器通信

  登录到我们的测试公众号的管理后台,然后用微信扫描一下测试号的二维码,如下图所示:

  关注成功后,我们开发好的公众号应用会先给用户发一条提示用户操作的文本消息,微信用户根据提示操作输入"文本",我们的公众号应用接收到用户请求后就给用户回复了一条我们自己构建好的文本消息,如下图所示:

 

  我们的公众号应用响应给微信用户的文本消息的XML数据如下:

 <xml>
   <ToUserName><![CDATA[ojADgs0eDaqh7XkTM9GvDmdYPoDw]]></ToUserName>
   <FromUserName><![CDATA[gh_43df3882c452]]></FromUserName>
   <CreateTime>1453755900000</CreateTime>
   <MsgType><![CDATA[text]]></MsgType>
   <Content><![CDATA[孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!]]></Content>
</xml>

  测试公众号的管理后台也可以看到关注测试号的用户列表,如下图所示:

  通过这个简单的入门程序,我们揭开了微信开发的神秘面纱了.

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 10
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值