微信开发工具固定图片位置_微信jsapi位置获取能 用微信开发工具调试么 - CSDN
  • 上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。 一、微信公众平台的基本原理  在开始做之前,先简单介绍了微信公众平台的基本...

    上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。

    一、微信公众平台的基本原理

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

      微信服务器就相当于一个转发服务器,终端(手机、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方法中定义校验方法,具体代码如下:

    复制代码
      1 package me.gacl.wx.web.servlet;
      2 
      3 import javax.servlet.ServletException;
      4 import javax.servlet.annotation.WebServlet;
      5 import javax.servlet.http.HttpServlet;
      6 import javax.servlet.http.HttpServletRequest;
      7 import javax.servlet.http.HttpServletResponse;
      8 import java.io.IOException;
      9 import java.security.MessageDigest;
     10 import java.security.NoSuchAlgorithmException;
     11 import java.util.Arrays;
     12 
     13 /**
     14  * Created by xdp on 2016/1/25.
     15  * 使用@WebServlet注解配置WxServlet,urlPatterns属性指明了WxServlet的访问路径
     16  */
     17 @WebServlet(urlPatterns="/WxServlet")
     18 public class WxServlet extends HttpServlet {
     19 
     20     /**
     21      * Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
     22      * 比如这里我将Token设置为gacl
     23      */
     24     private final String TOKEN = "gacl";
     25 
     26     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     27 
     28     }
     29 
     30     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     31         System.out.println("开始校验签名");
     32         /**
     33          * 接收微信服务器发送请求时传递过来的4个参数
     34          */
     35         String signature = request.getParameter("signature");//微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
     36         String timestamp = request.getParameter("timestamp");//时间戳
     37         String nonce = request.getParameter("nonce");//随机数
     38         String echostr = request.getParameter("echostr");//随机字符串
     39         //排序
     40         String sortString = sort(TOKEN, timestamp, nonce);
     41         //加密
     42         String mySignature = sha1(sortString);
     43         //校验签名
     44         if (mySignature != null && mySignature != "" && mySignature.equals(signature)) {
     45             System.out.println("签名校验通过。");
     46             //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
     47             //response.getWriter().println(echostr);
     48             response.getWriter().write(echostr);
     49         } else {
     50             System.out.println("签名校验失败.");
     51         }
     52 
     53     }
     54 
     55     /**
     56      * 排序方法
     57      *
     58      * @param token
     59      * @param timestamp
     60      * @param nonce
     61      * @return
     62      */
     63     public String sort(String token, String timestamp, String nonce) {
     64         String[] strArray = {token, timestamp, nonce};
     65         Arrays.sort(strArray);
     66         StringBuilder sb = new StringBuilder();
     67         for (String str : strArray) {
     68             sb.append(str);
     69         }
     70 
     71         return sb.toString();
     72     }
     73 
     74     /**
     75      * 将字符串进行sha1加密
     76      *
     77      * @param str 需要加密的字符串
     78      * @return 加密后的内容
     79      */
     80     public String sha1(String str) {
     81         try {
     82             MessageDigest digest = MessageDigest.getInstance("SHA-1");
     83             digest.update(str.getBytes());
     84             byte messageDigest[] = digest.digest();
     85             // Create Hex String
     86             StringBuffer hexString = new StringBuffer();
     87             // 字节数组转换为 十六进制 数
     88             for (int i = 0; i < messageDigest.length; i++) {
     89                 String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
     90                 if (shaHex.length() < 2) {
     91                     hexString.append(0);
     92                 }
     93                 hexString.append(shaHex);
     94             }
     95             return hexString.toString();
     96 
     97         } catch (NoSuchAlgorithmException e) {
     98             e.printStackTrace();
     99         }
    100         return "";
    101     }
    102 }
    复制代码

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

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

      

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

      

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

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

     

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

      

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

      

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

    三、access_token管理

    3.1、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次,所以不能调用太频繁。

    3.2、微信公众平台提供的获取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。

    3.3、获取access_token方案以及具体实现

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

      

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

      1.定义一个AccessToken实体类

    复制代码
     1 package me.gacl.wx.entry;
     2 
     3 /**
     4  * AccessToken的数据模型
     5  * Created by xdp on 2016/1/25.
     6  */
     7 public class AccessToken {
     8 
     9     //获取到的凭证
    10     private String accessToken;
    11     //凭证有效时间,单位:秒
    12     private int expiresin;
    13 
    14     public String getAccessToken() {
    15         return accessToken;
    16     }
    17 
    18     public void setAccessToken(String accessToken) {
    19         this.accessToken = accessToken;
    20     }
    21 
    22     public int getExpiresin() {
    23         return expiresin;
    24     }
    25 
    26     public void setExpiresin(int expiresin) {
    27         this.expiresin = expiresin;
    28     }
    29 }
    复制代码

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

    复制代码
     1 package me.gacl.wx.Common;
     2 
     3 import me.gacl.wx.entry.AccessToken;
     4 
     5 /**
     6  * Created by xdp on 2016/1/25.
     7  */
     8 public class AccessTokenInfo {
     9 
    10     //注意是静态的
    11     public static AccessToken accessToken = null;
    12 }
    复制代码

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

    复制代码
     1 package me.gacl.wx.util;
     2 
     3 import javax.net.ssl.*;
     4 import java.io.BufferedReader;
     5 import java.io.InputStream;
     6 import java.io.InputStreamReader;
     7 import java.net.URL;
     8 import java.security.cert.CertificateException;
     9 import java.security.cert.X509Certificate;
    10 
    11 /**
    12  * 访问网络用到的工具类
    13  */
    14 public class NetWorkHelper {
    15 
    16     /**
    17      * 发起Https请求
    18      * @param reqUrl 请求的URL地址
    19      * @param requestMethod
    20      * @return 响应后的字符串
    21      */
    22     public String getHttpsResponse(String reqUrl, String requestMethod) {
    23         URL url;
    24         InputStream is;
    25         String resultData = "";
    26         try {
    27             url = new URL(reqUrl);
    28             HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
    29             TrustManager[] tm = {xtm};
    30 
    31             SSLContext ctx = SSLContext.getInstance("TLS");
    32             ctx.init(null, tm, null);
    33 
    34             con.setSSLSocketFactory(ctx.getSocketFactory());
    35             con.setHostnameVerifier(new HostnameVerifier() {
    36                 @Override
    37                 public boolean verify(String arg0, SSLSession arg1) {
    38                     return true;
    39                 }
    40             });
    41 
    42 
    43             con.setDoInput(true); //允许输入流,即允许下载
    44 
    45             //在android中必须将此项设置为false
    46             con.setDoOutput(false); //允许输出流,即允许上传
    47             con.setUseCaches(false); //不使用缓冲
    48             if (null != requestMethod && !requestMethod.equals("")) {
    49                 con.setRequestMethod(requestMethod); //使用指定的方式
    50             } else {
    51                 con.setRequestMethod("GET"); //使用get请求
    52             }
    53             is = con.getInputStream();   //获取输入流,此时才真正建立链接
    54             InputStreamReader isr = new InputStreamReader(is);
    55             BufferedReader bufferReader = new BufferedReader(isr);
    56             String inputLine;
    57             while ((inputLine = bufferReader.readLine()) != null) {
    58                 resultData += inputLine + "\n";
    59             }
    60             System.out.println(resultData);
    61 
    62         } catch (Exception e) {
    63             e.printStackTrace();
    64         }
    65         return resultData;
    66     }
    67 
    68     X509TrustManager xtm = new X509TrustManager() {
    69         @Override
    70         public X509Certificate[] getAcceptedIssuers() {
    71             return null;
    72         }
    73 
    74         @Override
    75         public void checkServerTrusted(X509Certificate[] arg0, String arg1)
    76                 throws CertificateException {
    77 
    78         }
    79 
    80         @Override
    81         public void checkClientTrusted(X509Certificate[] arg0, String arg1)
    82                 throws CertificateException {
    83 
    84         }
    85     };
    86 }
    复制代码

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

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

    复制代码
     1 package me.gacl.wx.web.servlet;
     2 
     3 import com.alibaba.fastjson.JSON;
     4 import com.alibaba.fastjson.JSONObject;
     5 import me.gacl.wx.Common.AccessTokenInfo;
     6 import me.gacl.wx.entry.AccessToken;
     7 import me.gacl.wx.util.NetWorkHelper;
     8 
     9 import javax.servlet.ServletException;
    10 import javax.servlet.annotation.WebInitParam;
    11 import javax.servlet.annotation.WebServlet;
    12 import javax.servlet.http.HttpServlet;
    13 
    14 /**
    15  * 用于获取accessToken的Servlet
    16  * Created by xdp on 2016/1/25.
    17  */
    18 @WebServlet(
    19         name = "AccessTokenServlet",
    20         urlPatterns = {"/AccessTokenServlet"},
    21         loadOnStartup = 1,
    22         initParams = {
    23                 @WebInitParam(name = "appId", value = "wxbe4d433e857e8bb1"),
    24                 @WebInitParam(name = "appSecret", value = "ccbc82d560876711027b3d43a6f2ebda")
    25         })
    26 public class AccessTokenServlet extends HttpServlet {
    27 
    28     @Override
    29     public void init() throws ServletException {
    30         System.out.println("启动WebServlet");
    31         super.init();
    32 
    33         final String appId = getInitParameter("appId");
    34         final String appSecret = getInitParameter("appSecret");
    35 
    36         //开启一个新的线程
    37         new Thread(new Runnable() {
    38             @Override
    39             public void run() {
    40                 while (true) {
    41                     try {
    42                         //获取accessToken
    43                         AccessTokenInfo.accessToken = getAccessToken(appId, appSecret);
    44                         //获取成功
    45                         if (AccessTokenInfo.accessToken != null) {
    46                             //获取到access_token 休眠7000秒,大约2个小时左右
    47                             Thread.sleep(7000 * 1000);
    48                             //Thread.sleep(10 * 1000);//10秒钟获取一次
    49                         } else {
    50                             //获取失败
    51                             Thread.sleep(1000 * 3); //获取的access_token为空 休眠3秒
    52                         }
    53                     } catch (Exception e) {
    54                         System.out.println("发生异常:" + e.getMessage());
    55                         e.printStackTrace();
    56                         try {
    57                             Thread.sleep(1000 * 10); //发生异常休眠1秒
    58                         } catch (Exception e1) {
    59 
    60                         }
    61                     }
    62                 }
    63 
    64             }
    65         }).start();
    66     }
    67 
    68     /**
    69      * 获取access_token
    70      *
    71      * @return AccessToken
    72      */
    73     private AccessToken getAccessToken(String appId, String appSecret) {
    74         NetWorkHelper netHelper = new NetWorkHelper();
    75         /**
    76          * 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。
    77          */
    78         String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
    79         //此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}
    80         String result = netHelper.getHttpsResponse(Url, "");
    81         System.out.println("获取到的access_token="+result);
    82         //使用FastJson将Json字符串解析成Json对象
    83         JSONObject json = JSON.parseObject(result);
    84         AccessToken token = new AccessToken();
    85         token.setAccessToken(json.getString("access_token"));
    86         token.setExpiresin(json.getInteger("expires_in"));
    87         return token;
    88     }
    89 }
    复制代码

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

      

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

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

    复制代码
     1 <%-- Created by IntelliJ IDEA. --%>
     2 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
     3 <%@ page import="me.gacl.wx.Common.AccessTokenInfo"%>
     4 <html>
     5   <head>
     6     <title></title>
     7   </head>
     8   <body>
     9     微信学习
    10     <hr/>
    11     access_token为:<%=AccessTokenInfo.accessToken.getAccessToken()%>
    12   </body>
    13 </html>
    复制代码

      

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

      

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

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

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

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

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

    复制代码
      1 package me.gacl.wx.util;
      2 
      3 import org.dom4j.Document;
      4 import org.dom4j.Element;
      5 import org.dom4j.io.SAXReader;
      6 
      7 import javax.servlet.http.HttpServletRequest;
      8 import java.io.InputStream;
      9 import java.text.DateFormat;
     10 import java.text.SimpleDateFormat;
     11 import java.util.Date;
     12 import java.util.HashMap;
     13 import java.util.List;
     14 import java.util.Map;
     15 
     16 /**
     17  * 消息处理工具类
     18  * Created by xdp on 2016/1/26.
     19  */
     20 public class MessageHandlerUtil {
     21 
     22     /**
     23      * 解析微信发来的请求(XML)
     24      * @param request
     25      * @return map
     26      * @throws Exception
     27      */
     28     public static Map<String,String> parseXml(HttpServletRequest request) throws Exception {
     29         // 将解析结果存储在HashMap中
     30         Map<String,String> map = new HashMap();
     31         // 从request中取得输入流
     32         InputStream inputStream = request.getInputStream();
     33         System.out.println("获取输入流");
     34         // 读取输入流
     35         SAXReader reader = new SAXReader();
     36         Document document = reader.read(inputStream);
     37         // 得到xml根元素
     38         Element root = document.getRootElement();
     39         // 得到根元素的所有子节点
     40         List<Element> elementList = root.elements();
     41 
     42         // 遍历所有子节点
     43         for (Element e : elementList) {
     44             System.out.println(e.getName() + "|" + e.getText());
     45             map.put(e.getName(), e.getText());
     46         }
     47 
     48         // 释放资源
     49         inputStream.close();
     50         inputStream = null;
     51         return map;
     52     }
     53 
     54     // 根据消息类型 构造返回消息
     55     public static String buildXml(Map<String,String> map) {
     56         String result;
     57         String msgType = map.get("MsgType").toString();
     58         System.out.println("MsgType:" + msgType);
     59         if(msgType.toUpperCase().equals("TEXT")){
     60             result = buildTextMessage(map, "孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!");
     61         }else{
     62             String fromUserName = map.get("FromUserName");
     63             // 开发者微信号
     64             String toUserName = map.get("ToUserName");
     65             result = String
     66                     .format(
     67                             "<xml>" +
     68                                     "<ToUserName><![CDATA[%s]]></ToUserName>" +
     69                                     "<FromUserName><![CDATA[%s]]></FromUserName>" +
     70                                     "<CreateTime>%s</CreateTime>" +
     71                                     "<MsgType><![CDATA[text]]></MsgType>" +
     72                                     "<Content><![CDATA[%s]]></Content>" +
     73                                     "</xml>",
     74                             fromUserName, toUserName, getUtcTime(),
     75                             "请回复如下关键词:\n文本\n图片\n语音\n视频\n音乐\n图文");
     76         }
     77 
     78         return result;
     79     }
     80 
     81     /**
     82      * 构造文本消息
     83      *
     84      * @param map
     85      * @param content
     86      * @return
     87      */
     88     private static String buildTextMessage(Map<String,String> map, String content) {
     89         //发送方帐号
     90         String fromUserName = map.get("FromUserName");
     91         // 开发者微信号
     92         String toUserName = map.get("ToUserName");
     93         /**
     94          * 文本消息XML数据格式
     95          * <xml>
     96              <ToUserName><![CDATA[toUser]]></ToUserName>
     97              <FromUserName><![CDATA[fromUser]]></FromUserName>
     98              <CreateTime>1348831860</CreateTime>
     99              <MsgType><![CDATA[text]]></MsgType>
    100              <Content><![CDATA[this is a test]]></Content>
    101              <MsgId>1234567890123456</MsgId>
    102          </xml>
    103          */
    104         return String.format(
    105                 "<xml>" +
    106                         "<ToUserName><![CDATA[%s]]></ToUserName>" +
    107                         "<FromUserName><![CDATA[%s]]></FromUserName>" +
    108                         "<CreateTime>%s</CreateTime>" +
    109                         "<MsgType><![CDATA[text]]></MsgType>" +
    110                         "<Content><![CDATA[%s]]></Content>" + "</xml>",
    111                 fromUserName, toUserName, getUtcTime(), content);
    112     }
    113 
    114     private static String getUtcTime() {
    115         Date dt = new Date();// 如果不需要格式,可直接用dt,dt就是当前系统时间
    116         DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");// 设置显示格式
    117         String nowTime = df.format(dt);
    118         long dd = (long) 0;
    119         try {
    120             dd = df.parse(nowTime).getTime();
    121         } catch (Exception e) {
    122 
    123         }
    124         return String.valueOf(dd);
    125     }
    126 }
    复制代码

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

      

    4.2.在WxServlet的doPost方法中处理请求

      WxServlet的doPost方法的代码如下:

    复制代码
     1  /**
     2      * 处理微信服务器发来的消息
     3      */
     4     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     5         // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息
     6         // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
     7         request.setCharacterEncoding("UTF-8");
     8         response.setCharacterEncoding("UTF-8");
     9         System.out.println("请求进入");
    10         String result = "";
    11         try {
    12             Map<String,String> map = MessageHandlerUtil.parseXml(request);
    13             System.out.println("开始构造消息");
    14             result = MessageHandlerUtil.buildXml(map);
    15             System.out.println(result);
    16             if(result.equals("")){
    17                 result = "未正确响应";
    18             }
    19         } catch (Exception e) {
    20             e.printStackTrace();
    21             System.out.println("发生异常:"+ e.getMessage());
    22         }
    23         response.getWriter().println(result);
    24     }
    复制代码

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

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

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

      

     

      

      

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

      

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

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

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

      

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

      本篇的内容就这么多了。本文的内容和代码参考了用java开发微信公众号:公众号接入和access_token管理(二)这篇博客,在此对作者"风的姿态"表示感谢。

      下载测试项目部署运行时,由于项目中使用的是Servlet3.0,所以要求部署的Tomcat必须是7.x,以前也写过几篇关于Servlet3.0的博客,大家有兴趣的话可以去看看,本篇博文对应的测试项目代码下载http://pan.baidu.com/s/1hrfcGks

    展开全文
  • 微信开发与代码的编写(一) 微信开发环境的搭建 目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动App开发领域主要分为以下几种类型    我在平时的工作中接触得比较多的就是基于Android的Native...

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

    微信开发环境的搭建

    目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动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>

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

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

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 过年前后做了个微信公众号项目,已经过去一段时间了,抽空回忆总结下基本流程吧,不然很快估计自己就忘了。。 微信公众平台官网:https://mp.weixin.qq.com 文章目录一、注册公众号二、了解公众号管理页面三、必备...

    背景:
    过年前后做了个微信公众号项目,已经过去一段时间了,抽空回忆总结下基本流程吧,不然很快估计自己就忘了。。

    微信公众平台官网:https://mp.weixin.qq.com



    一、注册公众号

    在这里插入图片描述
    首先注册时可以看到公众号有三种类型,个人用户大多数选择订阅号,而企业用户一般选择服务号和企业号

    我们平常大多数关注的都是订阅号,他们统一都放置在微信应用的订阅号消息列表中,没有微信支付等高级功能,只是用于发布文章等基础功能。
    在这里插入图片描述

    服务号企业号都在会话列表,和我们的微信好友是同级别的位置,具备微信支付等高级功能,一般是某个企业品牌的对外操作窗口,如海底捞火锅、顺丰速运等。
    在这里插入图片描述

    我们前期开发测试只需要注册个人订阅号即可,真正开发使用的是开发者工具里的测试号,具体下面会说。

    真正生产的话,使用的都是经过微信认证的订阅号、服务号、企业号。


    二、了解公众号管理页面

    我们在微信公众平台扫码登录后可以发现管理页面左侧菜单栏有丰富的功能:
    在这里插入图片描述
    大概可以分为这几大模块:
    首页功能小程序管理推广统计设置开发

    作为开发人员,首先应该关注的是设置、开发模块;而作为产品运营人员,关注的是功能、管理、推广模块;作为数据分析人员,关注的是统计模块。

    首先我们不妨各个功能模块都点击看一看,大概了解下我们能做些什么。可以确认的是,这个微信公众平台当然不只是给开发人员使用的,它提供了很多非技术人员可在UI界面上交互操作的功能模块。

    如配置消息回复、自定义菜单、发布文章等:
    在这里插入图片描述
    这个时候我们可能会想:这些功能好像非技术人员都能随意操作,那么还需要我们技术人员去开发吗?

    答案是: 如果只是日常简单的推送文章,就像我们关注的大多数公众号一样,那确实不需要技术人员去开发;但是,如果你想将你们的网站嵌入进去公众号菜单里(这里指的是把前端项目的首页链接配置在自定义菜单),并且实现微信端的独立登录认证、获取微信用户信息、微信支付等高级功能,或者觉得UI交互的配置方式无法满足你的需求,你需要更加自由、随心所欲的操作,那么我们就必须启用开发者模式了,通过技术人员的手段去灵活控制公众号。

    这里有一点需要注意,如果我们决定技术人员开发公众号,必须启用服务器配置,而这将导致UI界面设置的自动回复和自定义菜单失效!

    我们在 开发 - 基本配置 - 服务器配置 中点击启用
    在这里插入图片描述
    在这里插入图片描述
    我们团队就遇到过这种情况:两个项目组共用一个公众号,结果一个启用了服务器配置,使另一个项目组手动配置的菜单失效了。所以要注意这点!

    至于服务器配置中的选项代表什么意思、如何填写,我们下面再讲。


    三、必备开发者工具的使用

    在这里插入图片描述
    我们进入 开发 - 开发者工具, 可以发现微信提供了六种开发者工具,其中前四种属于开发必备:开发者文档在线接口调试工具web开发者工具公众平台测试账号

    1.开发者文档

    在这里插入图片描述
    这个不用说!在我们开发中属于最最最基础和重要的东西了,我们要想熟练开发公众号,首先必须熟读开发者文档!有些功能的开发甚至非要反复研读、咬文嚼字一番不可。PS:该文档吐槽的地方也不少,有些地方的确讲的不够明确!

    2.在线接口调试工具

    在这里插入图片描述
    这个工具也算比较实用,包含大多数接口的在线调试,我们可以直接在上面输入参数,获取微信服务端的返回结果。

    3.web开发者工具

    在这里插入图片描述
    这个工具是一款桌面应用,需要下载,它通过模拟微信客户端的UI使得开发者可以使用这个工具方便地在PC或者Mac上进行开发和调试工作,一般是前端使用该工具进行页面、接口调试。

    4.公众平台测试账号

    在这里插入图片描述
    这个测试号工具对我们的重要性可以说是仅次于开发者文档。我们可以创建测试号无需申请、认证真实的公众帐号、可在测试帐号中体验并测试微信公众平台所有高级接口。并且所有的配置都可在一个页面上编辑,使开发测试变得极其便利。


    四、细读开发者文档

    文档地址:https://mp.weixin.qq.com/wiki

    需要注意的是,细读开发者文档不是让你所有模块都去阅读,而是重点的重复细读,非重点的选择性阅读。
    在这里插入图片描述
    其中前两个模块:开始前必读开始开发,属于重点关注对象,也是整个微信开发的基石所在,需要多读几遍。其次是微信网页开发模块微信网页授权,比较难理解,需要特别注意。其他的模块则根据你们的项目功能需求,有选择性的阅读即可。

    这里我就不多罗嗦了,大家看文档去吧!下面我会描述一些重点内容的实际操作情况以及代码,请确保你已经浏览过文档


    五、开发流程重点解析


    1.开发环境准备

    这里所谓的开发环境准备主要指的是我们项目服务端和微信服务端的网络通讯环境准备。

    我们平常开发可能只需要IP端口就能通讯,顶多配置下白名单放行,但微信公众号开发我们需要通过域名通讯(微信会访问我们配置的域名地址:服务器基本配置中的URL,下面会介绍),也就是我们各自开发环境需要拥有独立的域名,微信就能通过这个域名请求到我们的本地开发服务,各自进行开发测试。

    而我们一般都是内网开发,整个内网只有一个对外域名,所以这时就需要 内网穿透 ,为我们每个开发人员配置各自开发机器的域名。

    那如何进行内网穿透呢?你首先可以找下你们的网管,看他能不能帮你解决,如果不能,那就安装内网穿透工具,我们自己动手!

    我选择的内网穿透工具是natapp,这个有免费版、收费版,免费版的域名会随机变化,而收费版可以拥有固定域名,建议选择收费版,9元每月并不贵;大家可以对照natapp的文档安装使用,并不难。
    在这里插入图片描述
    这样我们本地开发环境就拥有自己的域名啦!然后就可以在测试号管理页面配置本地访问地址URL了。

    2.服务器基本配置

    无论是在真实公众号开发 - 基本配置 - 服务器配置,还是在 测试号管理 中,我们都可以看到这几个基本参数:
    开发者ID(AppID)、开发者密码(AppSecret)、服务器地址(URL)、令牌(Token)

    AppID 是公众号唯一开发识别码,配合开发者密码可调用公众号的接口能力,大多数微信接口都需要附带该参数。

    AppSecret 是校验公众号开发者身份的密码,具有极高的安全性。切记勿把密码直接交给第三方开发者或直接存储在代码中。如需第三方代开发公众号,请使用授权方式接入。其中获取accessToken就需要同时传入AppID和AppSecret获取。

    URL 是开发者用来接收微信消息和事件的接口URL,也就是我们服务后端的入口地址,需要注意的是该地址必须以域名形式填写,且必须以http 或 https 开头,分别支持80端口和443端口。如:http://yuanj.natapp1.cc/wechat。

    Token 可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性),也就是我们项目和微信服务端进行通信时,必须保证公众平台配置的Token和我们后台代码配置的Token保持一致,这样微信就能验证我们身份。

    注:EncodingAESKey 参数由开发者手动填写或随机生成,将用作消息体加解密密钥,我们前期可以采用明文模式进行开发测试,暂时先不用关注。
    在这里插入图片描述
    我们点击提交时,微信会以GET请求的方式访问我们配置的URL地址,并附加几个参数进行验证,所以你需要在该地址对应的项目后端接口里对这几个参数进行加工处理返回微信需要的结果,这样就可以验证成功,使微信服务端认可你配置的URL和Token参数,后续就能互相通信了!
    在这里插入图片描述
    具体情况可以阅读微信文档 - 开始前必读 - 接入指南

    这里附上该接口的Java代码:

    /**
     * 微信对接验证接口
     * */
    @RestController
    @RequestMapping(value = "/wechat")
    public class ValidateController {
        @Autowired
        WechatConfig wechatConfig;
    
       @RequestMapping(value = "", method = RequestMethod.GET)
       public void validate(HttpServletRequest req, HttpServletResponse resp) {
            System.out.println("-----开始校验签名-----");
    
            // 接收微信服务器发送请求时传递过来的参数
            String signature = req.getParameter("signature");
            String timestamp = req.getParameter("timestamp");
            String nonce = req.getParameter("nonce"); //随机数
            String echostr = req.getParameter("echostr");//随机字符串
    
            // 将token、timestamp、nonce三个参数进行字典序排序并拼接为一个字符串
            String TOKEN = wechatConfig.getToken();
            String sortStr = sort(TOKEN,timestamp,nonce);
            
            // 字符串进行shal加密
            String mySignature = WechatUtils.shal(sortStr);
            
            // 校验微信服务器传递过来的签名 和  加密后的字符串是否一致, 若一致则签名通过
            if(!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)){
                System.out.println("-----签名校验通过-----");
                try {
                    resp.getWriter().write(echostr);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else {
                System.out.println("-----校验签名失败-----");
            }
        }
        
       /**
         * 参数排序
         * @param token
         * @param timestamp
         * @param nonce
         * @return
         */
        public static 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();
        }
    }
    
    

    3.存取access_token参数

    access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时(7200秒),需定时刷新重复获取将导致上次获取的access_token失效

    access_token这个参数非常重要,几乎贯穿整个微信公关号项目开发,我们如何在有效期内定时刷新获取呢?
    如果我们的微信公众号项目是单服务架构,可以直接作为静态变量存储在内存里;如果是多服务,可以用中间件存储Redis、数据库都可以。SpringBoot项目内部可以通过@Scheduled注解,执行定时任务,既然access_token有效期是2小时,那我们可以一小时刷新获取一次,将其存入Redis,覆盖之前的access_token。


    4.公众号消息管理

    在这里插入图片描述
    很多公众号都可以通过消息发送来与其进行交互,那这样的功能如何代码实现呢?

    具体我们可以在微信文档 - 消息管理 模块查阅:
    在这里插入图片描述
    在此我要提到的一点就 微信公众号的消息交互都是通过XML格式进行的!这点就很坑了。。现在我们前后端、服务端的消息传输基本都是Json格式了,也习惯了Json格式的解析处理,所以遇到XMl格式的处理又要多费些事了。

    为什么微信采用XML格式呢?我个人猜测是几年前还是XML格式的天下,当时Json还没有这么流行,腾讯毕竟是产品业务驱动的,当然选择当时开发人员最熟悉的XML格式了开发,后面随着微信平台的普及,用户越来越多,想重构改成Json格式估计也十分困难,所以历史就遗留下来了呗。。

    我在此推荐一个GitHub上一个微信开发 Java SDK,里面有整个微信开发平台很多功能模块造好的轮子,我们可以参考下直接使用:
    https://github.com/Wechat-Group/WxJava
    在这里插入图片描述
    比如现在对于XMl消息解析这个需求,上面就提供了完整详尽的代码。


    5.获取openid以及网页授权(重难点)

    注意,这是公众号开发的重难点之一,请把技术文档中的微信网页授权模块多读两遍,然后带着疑问来看我的解析。

    (1)先明确为什么需要网页授权?我们的目的是什么?

    答:用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。也就是通过这种授权机制,我们能获取微信用户信息,比如:头像、昵称、地区、个性签名等。

    (2)既然目的是获取用户基本信息,微信不是提供了专门的接口吗?非要网页授权?

    答:在文档的 用户管理 - 获取用户基本信息(UnionID机制) 模块可以看到的确有获取用户基本信息接口:
    在这里插入图片描述
    可以看到,这个接口只需要提供openid或者unionid,即可直接获取用户基本信息。那么问题来了,openid(unionid)又是如何获取呢?

    微信平台提供了两种方式获取用户的openid

    第一种方式:

    用户与公众号产生消息交互时,会以POST请求的方式向我们配置的服务器URL地址发送XML格式的消息,并附带该用户对应公众号的openid!关于什么是消息交互我们可以查看文档中的消息管理模块,比如我们在公众号输入栏中发送文字图片语音等属于普通消息交互,我们关注、取关、点击自定义菜单等属于事件消息交互,每当前端用户进行这个操作时,微信服务端都会向我们项目后台发送POST请求给我们传达信息:
    在这里插入图片描述
    可以看到,这个推送数据包中就包含了用户的消息交互类型、时间以及我们需要的openid!也就是说,无论用户在公众号里干了啥操作,我们都能知道他这个操作干了啥,以及他是谁(openid),这时就能调用 用户管理 - 获取用户基本信息(UnionID机制) 接口获取用户基本信息了。

    别高兴太早,这种通过消息交互获取用户信息的方式,用户占主动地位,我们项目后端服务被动接受,那么如果我有个基本需求:我想在自定义菜单 - 对应我们网站的前端页面上展示微信用户基本信息,能做到吗?你如何把后台接收到的消息和前端用户关联绑定?
    可见,这种被动的方式并不能实现该功能,我们需要主动出击,在前端就能获取到当前操作用户的openid!

    第二种方式:

    这种方式就是通过网页授权机制主动出击!详情见下文。

    (3)网页授权有哪几种机制?分别是怎样实现?应用于什么场景?

    答:主要有两种机制,对应两种scope:

    snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)。

    snsapi_userinfo为scope发起的网页授权,是用来获取用户基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

    光看这两句解释你可能有一堆疑问,我们逐一分析:

    两种机制的前面授权步骤相同,大概如下:

    我们先要按照文档要求构造一个链接https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
    其中重点参数是redirect_uri,这个参数填的既可以是前端项目url,也可以是后端接口url,然后点击这个链接后,微信服务端经过重定向到我们填写的redirect_uri,会在此redirect_uri后拼接上一个code参数!然后前端或者后端通过code参数就可以调微信接口https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code获取openid等信息了:
    在这里插入图片描述
    这里讲下 snsapi_basesnsapi_userinfo不同点

    首先snsapi_base静默授权,什么意思呢?就是用户没有感知;与之对应的就是非静默授权snsapi_userinfo了,这个scope公众号会弹出一个小窗口需要用户手动点击授权,类似这种:
    在这里插入图片描述
    那么这两种scope授权的优劣势在哪呢?

    snsapi_base 的优势在于用户无感知,体验好,方便快捷;劣势在于获取openid后只能通过用户管理 - 获取用户基本信息(UnionID机制) 接口获取用户基本信息,而这种方式需要确保用户已经关注,不然是没有相关信息的!
    snsapi_userinfo 的优势在于无需用户关注公众号,只要用户点击了授权确认,即可通过access_token和openid调用专门的拉去用户信息接口获取信息,比较暴力。。;劣势在于需要用户手动授权,可能影响用户体验
    在这里插入图片描述

    在此说下,我们项目是通过snsapi_base静默授权的,其中redirect_uri配置的是前端项目首页地址(前后端分离),并将构造的这个链接封装起来,直接配置在自定义菜单里,那么用户点击菜单,就直接重定向到前端项目,然后前端获取code参数调用后端获取openid接口,将获取的openid缓存到客户端,以便后面使用。

    (4)想要进行网页授权,我们需要在公众平台配置什么吗?

    答:需要!
    如果是测试号,需要在 测试号管理 - 体验接口权限表 - 网页服务 - 网页帐号 点击 修改
    在这里插入图片描述
    在这里插入图片描述
    在这里配置的是回调页面redirect_uri的域名

    如果是正式号(需要微信认证),需要在 开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息 的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;

    而且正式号其他配置的地方也和测试号不一样,比如多了IP白名单、域名根路径下的txt验证文件,这个稍微摸索下应该没啥问题的。


    over 暂时就回忆这么多了。。。可能有遗漏大家可以提出哈 ~ 下一篇博客写几个开发时的小问题补充下吧

    展开全文
  • 微信开发之获取openid

    2018-12-31 15:05:16
    微信公众号支付的接口即预下单接口需要传openid,但是单元测试调该接口的话,就得直接传固定的测试参数,没有办法像实际生产流程中那样获取openid,所以这里先通过微信官方的网页授权方法获取openid。 首先我们先...

    微信公众号支付的接口即预下单接口需要传openid,但是单元测试调该接口的话,就得直接传固定的测试参数,没有办法像实际生产流程中那样获取openid,所以这里先通过微信官方的网页授权方法获取openid。

    首先我们先保证前提条件以满足,完成公众号开发的相关步骤。

    按照开发文档的步骤来,这里是链接https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3

    这里直接从回调地址设置开始(前面的步骤已完成的情况下)


    1、放置MP_verify文件到授权目录下

    请将文件MP_verify_wY984BJIuZNHf8yR.txt上传至上传至dohko.m.test.com/order/(你的回调地址)指向的web服务器(或虚拟主机)的目录(可以放在该服务器节点根目录下)

    保证可访问https://dohko.m.test.com/MP_verify_wY984BJIuZNHf8yR.txt

    2、微信管理后台设置绑定授权目录

    3、微信客户端打开如下这种url

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx9440000cd8000000&redirect_uri=https%3a%2f%2fdohko.m.test.com%2forder%2f&response_type=code&scope=snsapi_base&state=1&connect_redirect=1#wechat_redirect

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx9440000cd8000000&redirect_uri=https%3a%2f%2fdohko.m.test.com%2forder%2f&response_type=code&scope=snsapi_userinfo&state=1&connect_redirect=1#wechat_redirec

    参数说明:

    链接中参数传自己的对应参数即可,appid为测试公众号的appid,redirect_uri为自己支付成功的回调地址(尽量使用https链接,微信文档中有说明,为了保证返回code的安全,scope参数可以传snsapi_base,也可以传snsapi_userinfo,区别在于snsapi_base不会弹出授权页面,而snsapi_userinfo会弹出一个授权页面,如果只需要得到openid,传snsapi_base即可,如果需要用户其他信息就snsapi_userinfo,但还需要多一步)

    注意链接在微信客户端内打开,为了便于调试用电脑微信打开即可。

    不能浏览器打开!!

    但是电脑版在显示链接时存在这样一个问题,如图

    这样就不是一个完整的链接,解决这个问题可以将回调地址urlencode编码一下即可(给出一个工具页http://tool.chinaz.com/tools/urlencode.aspx),如下图正确链接

    点击后授权公众号,然后会跳转至回调地址并且会携带code及state参数,因为我的回调地址无法直接这样访问,所以没有显示,但为了看到url,我们将此访问用浏览器打开

    然后浏览器中就能看到这样的url(这里传的scope为snsapi_userinfo,传snsapi_base发现浏览器打开不好使。。)

    https://dohko.m.test.com/order/?code=001c7QXy0CuMUd1vgmXy0tOyXy0c7QXE&state=1

    这里的code值仅能生效一次,且5分钟未使用就会失效,所以抓紧时间下一步。。

    4、后续链接均可浏览器打开,为了方便这里使用postman进行访问。

    访问链接模板:

    https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

    参数说明:

    appid和secret应该是在申请公众号的时候就会获得,code就是我们上一步得到的,然后填入相应参数访问即可,下图为code5分钟未使用失效了。。

    正确,未失效的

    5、此时到上一步已经结束了,我们已经获取到了openid,而这一步是用来获取用户详细信息的,根据官方说明,这一步仅能在scope为snsapi_userinfo访问

    参数说明:

    openid就是我们获取到的用户标识,传入即可

     

    已上就是获取openid的全部步骤。。

    展开全文
  • 购买套餐还赠送经典微信开发课程——[微信公众号_独立知识点]环境搭建。该课程针对各种复杂的网络环境,讲解如何构建开发环境,已解决“没有服务器”、“没有固定IP”等开发者遇到的窘境。 课程采用独立知识点讲解...
  • 本节书摘来自华章计算机《微信公众平台开发:从...3.4 微信开发调试工具 3.4.1 微信调试器 微信调试器是方倍工作室开发的用于微信公众平台接口开发在线调试的工具,具有Token校验、模拟关注及取消关注、发送文本/...

    本节书摘来自华章计算机《微信公众平台开发:从零基础到ThinkPHP5高性能框架实践》一书中的第3章,第3.4节,作者 方倍工作室,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

    3.4 微信开发调试工具

    3.4.1 微信调试器

    微信调试器是方倍工作室开发的用于微信公众平台接口开发在线调试的工具,具有Token校验、模拟关注及取消关注、发送文本/图片/语音/视频/位置/链接、模拟事件发送等功能。

    微信调试器运行时推荐使用Chrome或Firefox浏览器,以获得更好的兼容。

    微信调试器的地址是http://debug.fangbei.org/ ,其界面如图3-27所示。

    下面介绍微信调试器的使用方法。

    screenshot

    1.Token校验

    在URL和Token文本框中分别填写好微信公众号的接口URL和Token,如图3-28所示。这里的校验是明文方式的校验,不需要填写EncodingAESKey。

    单击“Token验证”按钮,如果Token校验成功,将提示校验成功消息,如图3-29所示。如果Token校验失败,将提示校验失败消息。

    screenshot

    2.发送消息

    选择消息类型,如“文本”,将列出该消息类型的各项参数,在各项参数中填入要发送的参数内容,网站已经默认填充了一些固定的参数,然后单击“发送消息”按钮。“发送消息”文本框中将显示本次发送的XML,“接收消息”文本框中将会显示接收到的XML数据,如图3-30所示。

    同时页面右侧会显示微信效果预览图,如图3-31所示。

    如果“接收消息”文本框中没有返回XML或者返回的内容中包含非XML格式的数据,则说明返回不正确,需要修改自己的接口程序。

    3.4.2 接口调试工具

    微信公众平台提供了在线接口调试工具,网址为http://mp.weixin.qq.com/debug/

    screenshot

    该工具旨在帮助开发者检测调用微信公众平台开发者API时发送的请求参数是否正确,提交相关信息后可获得服务器的验证结果。在线接口调试工具的界面如图3-32所示。

    screenshot

    其使用说明如下。

    1)选择合适的接口。在“接口类型”和“接口列表”中选择要调试的接口,如图3-33所示。
    2)系统会生成该接口的参数表,用户可以直接在文本框内填入对应的参数值,如图3-34所示。其中,红色星号表示该字段必填。
    3)单击“检查问题”按钮,即可得到相应的调试信息,如图3-35所示。

    screenshot

    展开全文
  • 微信小程序是近些年一项新的事物。互联网的发展过程中,总会不断出现新的事物,因为逆水行舟不进则退,创新才是互联网科技发展的原动力。...今天主要介绍下开发工具、项目结构、全局配置文件及页面跳转等。
  • 由于要接入微信公众号,查看了官网上的接入文档,必需是80端口而且微信服务器需要验证token,那我本地开发不可能每次都把源码上传到服务器上吧,而且也不方便,这就需要能内网穿透的工具,能让外网通过外网域名访问...
  • 后面在调试的过程中,真机连上一段时间没有管它,这段时间过程中,微信开发者工具会自动断开与真机的连接的。 当再次打算重新扫面真机调试二维码时,就出现了: 已结束,请重新开始的状况 (当时没有及时截图,该图...
  • 个人感觉微信小程序的IDE用起来有时候不太方便,可能是之前用惯了Eclipse的原因吧...微信小程序的开发工具不支持直接将文件拷贝到目录下,所以首先要将图片文件导入到本地目录下,然后编写工具类获取屏幕的宽度和高度.
  • DIY官网打造微信小程序制作平台支持在线可视化制作小程序组件及在线可视化设计小程序数据源能力。如果您还在想如何制作小程序,如何快速开发小程序吗?在线拖拉布局助您开发并一键生成微信小程序代码。 全套源码...
  • 前言: 这几天一直想搞个公众号玩一玩。然后去阿里云买了个云服务器ECS(学生9.9/月),因为一开始接触云服务器,所以选了...放进tomcat的webapps目录下,启动tomcat(当然端口是80,因为微信公众号的URL只支持80和4...
  • 当电脑开机以后,我们需要一次次的输入网址或者重新打开微信,那有没有什么可以提高工作效率的事情呢? 今天,我们试试用.bat文件打开试试 1.首先,新建一个TXT文件 2.然后打开编辑文件 代码如下: 这里表示开启三...
  • 早上在论坛上看到有人写了关于图片等比例缩放的文章,只是判断了图片宽是否大于屏幕宽.我之前在做Android的时候也会遇到图片等比例缩放的问题.应该是用图片宽高比和屏幕宽高比做判断.做个笔记. 老规矩,先上图. ...
  • 微信开发者工具: 当前系统代理不是安全代理? 命令行输入regedit或按快捷键win+R然后输入regedit,打开注册表编辑器------通过下面的路径打开 HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/...
  • 在做微信开发时,今天犯了一个错误,检查了半天才检查出来。微信在向第三方推送信息时会带上一个openid 这是发送信息者的唯一标识,看文档说是唯一标识 就以为这个id只与微信号有关,而今天才发现,openid=加密...
  • 微信小程序的测试发布在没有 CI/CD 等相关工具的情况下,存在着如下的问题: 小程序开发助手中,同一个开发者只能显示一个开发版本 测试同事找开发要二维码,效率较低 本地生成的二维码会出现携带本地代码、未及时...
  • C#微信开发

    2017-12-24 10:35:37
    C#开发微信门户及应用教程   作者:伍华聪   C#开发微信门户及应用(1)--开始使用微信接口 6 1、微信账号 6 2、微信菜单定义 7 3、接入微信的链接处理 8 4、使用开发方式创建菜单 14 5、我创建的菜单案例 17 C#...
1 2 3 4 5 ... 20
收藏数 16,436
精华内容 6,574
关键字:

微信开发工具固定图片位置