微信开发工具单位

2016-05-05 17:26:12 IT_LOSER 阅读数 1018

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

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

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

  微信服务器就相当于一个转发服务器,终端(手机、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

2019-11-27 15:15:44 qq_38003741 阅读数 255

 

import com.alibaba.fastjson.JSONObject;
import com.bhudy.entity.BhudyPlugin;
import com.bhudy.service.BhudyPluginService;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.util.*;

/**
 * Created by Administrator on 2019/8/23/023.
 */
@Component
public final class WeChatUtils {

    @Autowired
    private WeChatUtils(BhudyPluginService bhudyPluginService) {
        WeChatUtils.bhudyPluginService = bhudyPluginService;
    }

    private static BhudyPluginService bhudyPluginService; // 获取微信appid的接口

    private static Map<String, String> tokenMap = null;

    public static String wxGzhWorkOrder = "xxxxx"; // 工单状态通知Key
    public static String weGzhReport = "xxxxxx"; // 报告生成通知Key


    private static String authorizationCode = "authorization_code"; // 微信调用接口使用参数authorization_code
    private static String clientCredential = "client_credential"; // 微信调用接口使用参数client_credential


    /**
     * 发送报告生成通知
     * 报告生成通知Id: P9U-LYY4qtcKKqSoDb7sfqK4GlFQvDu8G5JxWOTkUQk
     * <p>
     * {{first.DATA}}
     * 报告类型:{{keyword1.DATA}}
     * 生成时间:{{keyword2.DATA}}
     * {{remark.DATA}}
     *
     * @return 是否发送成功
     */
    public static boolean sendWeGzhReport(String openId, String first, String remark, Map<String, Object> keywordMap) {
        return sendWxGzh(openId, WeChatUtils.weGzhReport, first, remark, keywordMap);
    }

    /**
     * 发送微信公众号工单消息提醒
     * 工单模板信息id: i5JtheQBLYM9VyByYR2EqrGlbdZiiFZVyA7rndbOAuM
     * <p>
     * {{first.DATA}}
     * 工单编号:{{keyword1.DATA}}
     * 工单标题:{{keyword2.DATA}}
     * 时间:{{keyword3.DATA}}
     * {{remark.DATA}}
     *
     * @return 是否发送成功
     */
    public static boolean sendWxGzhWorkOrder(String openId, String first, String remark, Map<String, Object> keywordMap) {
        return sendWxGzh(openId, WeChatUtils.wxGzhWorkOrder, first, remark, keywordMap);
    }

    /**
     * 发送微信公众号信息
     * <p>
     * 参数  是否必填   说明
     * touser  是  接收者openid
     * template_id 是  模板ID
     * url 否  模板跳转链接(海外帐号没有跳转能力)
     * miniprogram 否  跳小程序所需数据,不需跳小程序可不用传该数据
     * appid   是  所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
     * pagepath    否  所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
     * data    是  模板数据
     * color   否  模板内容字体颜色,不填默认为黑色
     * <p>
     * json数据模板
     * {"touser":"OPENID","template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY","url":"http://weixin.qq.com/download",
     * "miniprogram":{"appid":"xiaochengxuappid12345","pagepath":"index?foo=bar"},"data":{"first":{"value":"恭喜你购买成功!","color":"#173177"},
     * "keyword1":{"value":"巧克力","color":"#173177"},"keyword2":{"value":"39.8元","color":"#173177"},"keyword3":{"value":"2014年9月22日","color":"#173177"},
     * "remark":{"value":"欢迎再次购买!","color":"#173177"}}}
     *
     * @param openId     接收消息的用户openid
     * @param templateId 消息模板id
     * @param first      标题
     * @param remark     结尾
     * @param keywordMap 内容map
     * @return 是否发送成功
     */
    public static boolean sendWxGzh(String openId, String templateId, String first, String remark, Map<String, Object> keywordMap) {
        if (openId == null || openId.equals("")) return false;

        String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + WeChatUtils.getToken(BhudyPlugin.TENCENT_TAS);
        Map<String, Object> map = new HashMap<>();
        map.put("touser", openId);
        map.put("template_id", templateId);
        map.put("url", null);

        Map<String, Object> miniprogramMap = new HashMap<>();


        miniprogramMap.put("appid", bhudyPluginService.getBhudyPluginDataByType(BhudyPlugin.TENCENT_TAS).get(BhudyPlugin.appId));
        //miniprogramMap.put("pagepath", "index");
        map.put("miniprogram", miniprogramMap);

        Map<String, Object> dataMap = new HashMap<>();

        Map firstMap = new HashMap<>();
        firstMap.put("value", first);
        dataMap.put("first", firstMap);

        for (Map.Entry entrySet : keywordMap.entrySet()) {
            Map keyword1Map = new HashMap<>();
            keyword1Map.put("value", entrySet.getValue());
            dataMap.put(entrySet.getKey().toString(), keyword1Map);
        }

        Map remarkMap = new HashMap<>();
        remarkMap.put("value", remark);
        dataMap.put("remark", remarkMap);

        map.put("data", dataMap);

        Map<String, Object> resultMap = WeChatUtils.wxReqDataPost(url, JSONObject.toJSONString(map));
        return resultMap != null;
    }

    /**
     * 获取用户基本信息(UnionID机制)
     * 在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
     * 请注意,如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。
     * <p>
     * 参数  是否必须   说明
     * access_token    是  调用接口凭证
     * openid  是  普通用户的标识,对当前公众号唯一
     * lang    否  返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
     * ***********************************
     *
     * @return <br>
     * 参数  说明
     * subscribe   用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
     * openid  用户的标识,对当前公众号唯一
     * nickname    用户的昵称
     * sex 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
     * city    用户所在城市
     * country 用户所在国家
     * province    用户所在省份
     * language    用户的语言,简体中文为zh_CN
     * headimgurl  用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
     * subscribe_time  用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
     * unionid 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
     * remark  公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注
     * groupid 用户所在的分组ID(兼容旧的用户分组接口)
     * tagid_list  用户被打上的标签ID列表
     * subscribe_scene 返回用户关注的渠道来源,ADD_SCENE_SEARCH 公众号搜索,ADD_SCENE_ACCOUNT_MIGRATION 公众号迁移,ADD_SCENE_PROFILE_CARD 名片分享,ADD_SCENE_QR_CODE 扫描二维码,ADD_SCENEPROFILE LINK 图文页内名称点击,ADD_SCENE_PROFILE_ITEM 图文页右上角菜单,ADD_SCENE_PAID 支付后关注,ADD_SCENE_OTHERS 其他
     * qr_scene    二维码扫码场景(开发者自定义)
     * qr_scene_str    二维码扫码场景描述(开发者自定义)
     * UnionID
     */
    public static Map<String, Object> getUserInfo(String openId, Integer type) {
        String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + WeChatUtils.getToken(type) + "&openid=" + openId + "&lang=zh_CN";
        Map resultMap = WeChatUtils.wxReqDataGet(url);
        return resultMap;
    }

    /**
     * 获取公众号用户列表
     * 公众号可通过本接口来获取帐号的关注者列表,关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的)组成。一次拉取调用最多拉取10000个关注者的OpenID,可以通过多次拉取的方式来满足需求。
     * <p>
     * 参数  是否必须   说明
     * access_token    是  调用接口凭证
     * next_openid 是  第一个拉取的OPENID,不填默认从头开始拉取
     *
     * @param nextOpenid 第一个拉取的OPENID,不填默认从头开始拉取
     * @return <br>
     * 参数    说明
     * total   关注该公众账号的总用户数
     * count   拉取的OPENID个数,最大值为10000
     * data    列表数据,OPENID的列表
     * next_openid 拉取列表的最后一个用户的OPENID
     */
    public static Map<String, Object> getOpenIdListMap(String nextOpenid) {
        String url = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + getToken(BhudyPlugin.TENCENT_TAS);
        if (nextOpenid != null && nextOpenid.equals("")) {
            url += "&next_openid=" + nextOpenid;
        }

        Map resultMap = WeChatUtils.wxReqDataGet(url);
        return resultMap;
    }

    /**
     * @param type 1是公众号请求
     * @return * * *
     * 属性 类型 说明
     * access_token    string 获取到的凭证
     * expires_in  number 凭证有效时间,单位:秒。目前是7200秒之内的值。
     * errcode number 错误码
     * errmsg  string 错误信息
     * <p>
     * =====================================
     * <p>
     * errcode 的合法值
     * 值   说明 最低版本
     * -1  系统繁忙,此时请开发者稍候再试
     * 0   请求成功
     * 40001   AppSecret 错误或者 AppSecret 不属于这个小程序,请开发者确认 AppSecret 的正确性
     * 40002   请确保 grant_type 字段值为 client_credential
     * 40013   不合法的 AppID,请开发者检查 AppID 的正确性,避免异常字符,注意大小写
     */
    public static String getToken(Integer type) {
        Map<String, String> tokenMap = WeChatUtils.tokenMap;
        // 微信的access_token有效期是7200秒,所以我们的过期时间要比微信的快token
        if (tokenMap != null && Utils.formatLong(tokenMap.get("date")) >= (new Date().getTime() + (7200 * 1000))) {
            return tokenMap.get("access_token");
        }

        Map<String, String> weChatMap = bhudyPluginService.getBhudyPluginDataByType(type);
        String url = "https://api.weixin.qq.com/cgi-bin/token?appid=" + weChatMap.get(BhudyPlugin.appId) + "&secret=" + weChatMap.get(BhudyPlugin.appSecret) + "&grant_type=" + clientCredential;

        Map<String, Object> resultMap = WeChatUtils.wxReqDataGet(url);
        if (resultMap == null) throw new BhudyException(BhudyExceptionCode.CODE_29);

        String token = (String) resultMap.get("access_token");
        WeChatUtils.tokenMap = new HashMap<>();
        WeChatUtils.tokenMap.put("date", String.valueOf(new Date().getTime()));
        WeChatUtils.tokenMap.put("access_token", token);

        return token;
    }


    /**
     * @param code string 是    登录时获取的 code
     * @return * * *
     * 属性  类型 说明
     * openid  string 用户唯一标识
     * session_key string 会话密钥
     * unionid string 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明。
     * errcode number 错误码
     * errmsg  string 错误信息
     * <p>
     * ===================================
     * <p>
     * errcode 的合法值
     * 值   说明 最低版本
     * -1  系统繁忙,此时请开发者稍候再试
     * 0   请求成功
     * 40029   code 无效
     * 45011   频率限制,每个用户每分钟100次
     */
    public static Map<String, Object> getOpenIdAndSessionKey(String code, Integer type) {
        Map<String, String> weChatMap = bhudyPluginService.getBhudyPluginDataByType(type);
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + weChatMap.get(BhudyPlugin.appId) + "&secret=" + weChatMap.get(BhudyPlugin.appSecret) + "&grant_type=" + authorizationCode + "&js_code=" + code;
        return WeChatUtils.wxReqDataGet(url);
    }


    /**
     * 微信Post请求
     * 如果微信端返回错误码或者没有返回数据,这个方法直接返回null
     * 该方法没有使用线程,可能会卡死
     *
     * @param url    请求的url
     * @param params
     * @return
     */
    public static Map<String, Object> wxReqDataPost(String url, String params) {
        try {
            Map<String, Object> reulstMap = Utils.reqPost(url, params);
            if (reulstMap == null || (reulstMap.containsKey("errcode") && (Integer) reulstMap.get("errcode") != 0)) {
                return null;
            }
            return reulstMap;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 微信Get请求
     * 如果微信端返回错误码或者没有返回数据,这个方法直接返回null
     * 该方法没有使用线程,可能会卡死
     *
     * @param url 请求的url
     * @return
     */
    public static Map<String, Object> wxReqDataGet(String url) {
        try {
            Map<String, Object> reulstMap = Utils.reqGetMap(url);
            if (reulstMap == null || (reulstMap.containsKey("errcode") && (Integer) reulstMap.get("errcode") != 0)) {
                return null;
            }
            return reulstMap;
        } catch (Exception e) {
            return null;
        }
    }


    /**
     * 接收事件推送
     * 用法@RequestMapping(value = "/wx/api/v1/receiveEventPush.do", method = RequestMethod.POST, consumes = {"application/xml", "text/xml"}, produces = {"application/xml;charset=utf-8", "text/xml;charset=utf-8"})
     * public Object receiveEventPush(@RequestBody DOMSource domSource) {
     * return WxUtils.receiveEventPush(domSource);
     * }
     *
     * @param domSource
     * @return * * *
     * <p>
     * 目录 MsgType Event
     * 1 关注/取消关注事件
     * 2 扫描带参数二维码事件
     * 3 上报地理位置事件
     * 4 自定义菜单事件
     * 5 点击菜单拉取消息时的事件推送
     * 6 点击菜单跳转链接时的事件推送
     * <p>
     * <p>
     * 》》》1.关注/取消关注事件《《《
     * 参数  描述
     * ToUserName  开发者微信号
     * FromUserName    发送方帐号(一个OpenID)
     * CreateTime  消息创建时间 (整型)
     * MsgType 消息类型,event
     * Event   事件类型,subscribe(订阅)、unsubscribe(取消订阅)
     * <p>
     * <p>
     * 》》》2.扫描带参数二维码事件《《《
     * 参数  描述
     * ToUserName  开发者微信号
     * FromUserName    发送方帐号(一个OpenID)
     * CreateTime  消息创建时间 (整型)
     * MsgType 消息类型,event
     * Event   事件类型,subscribe
     * EventKey    事件KEY值,qrscene_为前缀,后面为二维码的参数值
     * Ticket  二维码的ticket,可用来换取二维码图片
     * <p>
     * <p>
     * 》》》3.上报地理位置事件《《《
     * 参数  描述
     * ToUserName  开发者微信号
     * FromUserName    发送方帐号(一个OpenID)
     * CreateTime  消息创建时间 (整型)
     * MsgType 消息类型,event
     * Event   事件类型,LOCATION
     * Latitude    地理位置纬度
     * Longitude   地理位置经度
     * Precision   地理位置精度
     * <p>
     * <p>
     * 》》》5.点击菜单拉取消息时的事件推送《《《
     * 参数  描述
     * ToUserName  开发者微信号
     * FromUserName    发送方帐号(一个OpenID)
     * CreateTime  消息创建时间 (整型)
     * MsgType 消息类型,event
     * Event   事件类型,CLICK
     * EventKey    事件KEY值,与自定义菜单接口中KEY值对应
     * <p>
     * <p>
     * 》》》6.点击菜单跳转链接时的事件推送《《《
     * 参数  描述
     * ToUserName  开发者微信号
     * FromUserName    发送方帐号(一个OpenID)
     * CreateTime  消息创建时间 (整型)
     * MsgType 消息类型,event
     * Event   事件类型,VIEW
     * EventKey    事件KEY值,设置的跳转URL
     * <p>
     * <p>
     * MsgType
     * 消息类型,接收事件推送为event
     * 消息类型,文本为text
     * 消息类型,图片为image
     * 消息类型,语音为voice
     * 消息类型,视频为video
     * 消息类型,音乐为music
     * 消息类型,图文为news
     */
    public static String receiveEventPush(DOMSource domSource) {
        Map<String, Object> xmlMap = domSourceToMap(domSource);

        if (xmlMap == null) return "";

        String msgType = (String) xmlMap.get("MsgType");
        String fromUserName = (String) xmlMap.get("FromUserName");
        String toUserName = (String) xmlMap.get("ToUserName");
        String event = (String) xmlMap.get("Event");
        String text;
        if (msgType.equals("event")) {
            if (event.equals("subscribe")) {
                // 关注事件
                text = "关注事件";
            } else if (event.equals("unsubscribe")) {
                // 取消关注事件
                text = "取消关注事件";
            } else {
                text = "对不起,无法识别消息类型";
            }
        } else if (msgType.equals("text")) {
            // 消息类型,为text
            text = "消息类型,文本为text";
        } else if (msgType.equals("image")) {
            // 消息类型,图片为image
            text = "消息类型,图片为image";
        } else if (msgType.equals("voice")) {
            // 消息类型,语音为voice
            text = "消息类型,语音为voice";
        } else if (msgType.equals("video")) {
            // 消息类型,视频为video
            text = "消息类型,视频为video";
        } else if (msgType.equals("music")) {
            // 消息类型,音乐为music
            text = "消息类型,音乐为music";
        } else if (msgType.equals("news")) {
            // 消息类型,图文为news
            text = "消息类型,图文为news";
        } else {
            text = "对不起,无法识别消息类型";
        }

        String returnData = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
                "    <xml>" +
                "        <Content>" + text + "</Content>" +
                "        <ToUserName>" + fromUserName + "</ToUserName>" +
                "        <FromUserName>" + toUserName + "</FromUserName>" +
                "        <CreateTime>" + new Date().getTime() / 1000 + "</CreateTime>" +
                "        <MsgType>text</MsgType>" +
                "    </xml>";

        //输出格式化后的json
        return returnData;
    }

    private static Map<String, Object> domSourceToMap(DOMSource domSource) {
        try {
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(domSource, result);

            SAXBuilder sb = new SAXBuilder();
            Document doc = sb.build(new StringReader(writer.toString()));
            Element root = doc.getRootElement();

            JSONObject json = new JSONObject();
            json.put(root.getName(), iterateElement(root));

            Map<String, Object> dataMap = JSONObject.parseObject(json.toJSONString(), Map.class);
            Map<String, Object> xmlMap = (Map<String, Object>) dataMap.get("xml");
            return xmlMap;
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JDOMException e) {
            e.printStackTrace();
        }

        return null;
    }

    private static JSONObject iterateElement(Element element) {
        List<Element> node = element.getChildren();
        JSONObject obj = new JSONObject();
        List list = null;
        for (Element child : node) {
            list = new LinkedList();
            String text = child.getTextTrim();
            if (text == null || text.equals("")) {
                if (child.getChildren().size() == 0) {
                    continue;
                }
                if (obj.containsKey(child.getName())) {
                    list = (List) obj.get(child.getName());
                }
                list.add(iterateElement(child)); //遍历child的子节点
                obj.put(child.getName(), list);
            } else {
                if (obj.containsKey(child.getName())) {
                    Object value = obj.get(child.getName());
                    try {
                        list = (List) value;
                    } catch (ClassCastException e) {
                        list.add(value);
                    }
                }
                if (child.getChildren().size() == 0) { //child无子节点时直接设置text
                    obj.put(child.getName(), text);
                } else {
                    list.add(text);
                    obj.put(child.getName(), list);
                }
            }
        }
        return obj;
    }

    /**
     * 验证微信绑定服务器的方法
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        //1.定义数组存放tooken,timestamp,nonce
        String[] arr = {"7i6L5SEu4NPuYiGVAXMy0ZnFxd6", timestamp, nonce};

        //2.对数组进行排序
        Arrays.sort(arr);

        //3.生成字符串
        StringBuffer sb = new StringBuffer();
        for (String s : arr) {
            sb.append(s);
        }

        //4.sha1加密,网上均有现成代码
        String temp = getSha(sb.toString());

        //5.将加密后的字符串,与微信传来的加密签名比较,返回结果
        return temp.equals(signature);
    }


    public static String getSha(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {

            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");

            mdTemp.update(str.getBytes("UTF-8"));


            byte[] md = mdTemp.digest();

            int j = md.length;

            char buf[] = new char[j * 2];

            int k = 0;

            for (int i = 0; i < j; i++) {

                byte byte0 = md[i];

                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];

                buf[k++] = hexDigits[byte0 & 0xf];

            }

            return new String(buf);

        } catch (Exception e) {

            // TODO: handle exception

            return null;

        }

    }


}

微信工具类需要的其他方法 

/**
 * 发送HttpPost请求
 *
 * @param strURL 服务地址
 * @param params json字符串,例如: "{ \"id\":\"12345\" }" ;其中属性名必须带双引号<br/>
 * @return 成功:返回json字符串<br/>
 */
public static Map<String, Object> reqPost(String strURL, String params) {
    BufferedReader reader;
    try {
        URL url = new URL(strURL);// 创建连接
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setDoOutput(true);
        connection.setDoInput(true);
        connection.setUseCaches(false);
        connection.setInstanceFollowRedirects(true);
        connection.setRequestMethod("POST"); // 设置请求方式
        connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
        connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
        connection.connect();
        //一定要用BufferedReader 来接收响应, 使用字节来接收响应的方法是接收不到内容的
        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); // utf-8编码
        out.append(params);
        out.flush();
        out.close();
        // 读取响应
        reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
        String line;
        String res = "";
        while ((line = reader.readLine()) != null) {
            res += line;
        }
        reader.close();

        return JSON.parseObject(res, Map.class);
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

/**
 * 请求url
 *
 * @param url
 * @return
 */
public static Map<String, Object> reqGetMap(String url) {
    try {
        URL reqURL = new URL(url);
        HttpsURLConnection openConnection = (HttpsURLConnection) reqURL.openConnection();
        openConnection.setConnectTimeout(10000);
        //这里我感觉获取openid的时间比较长,不过也可能是我网络的问题,
        //所以设置的响应时间比较长
        openConnection.connect();
        InputStream in = openConnection.getInputStream();

        StringBuilder builder = new StringBuilder();
        BufferedReader bufreader = new BufferedReader(new InputStreamReader(in));
        for (String temp = bufreader.readLine(); temp != null; temp = bufreader
                .readLine()) {
            builder.append(temp);
        }

        String result = builder.toString();
        in.close();
        openConnection.disconnect();

        Map<String, Object> resultMap = JSON.parseObject(result, Map.class);

        return resultMap;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

 

2016-10-27 23:42:00 weixin_33711647 阅读数 46

今晚引来了微信小程序开发工具《微信web开发者工具》的一次更新,此次更新新增了很多API,修复了一些bugs,也标记了一些即将废弃的API。这次更新也升级了开发工具本身的功能,新增的文件变动实时刷新功能,跟上了时代的步伐,赞一个。

更新概述

新增 19 个全新的 API (目前可以在开发工具上正常使用,客户端需要等待 6.3.29 版本发布)

拓展了 4 个组件属性,修复了数十个组件 bug

开发者工具添加实时刷新功能等 7 个功能

编辑模块增加自动保存、实时预览等 8 个功能

详细更新日志如下:

基础功能

A 增加 四个文件管理 API wx.getSavedFileList、wx.getSavedFileInfo、wx.removeSavedFile、wx.openDocument 详情

A 增加 四个数据管理 API wx.removeStorage、wx.removeStorageSync、wx.getStorageInfo、wx.getStorageInfoSync 详情

A 新增 四个交互反馈 API wx.showToast、wx.showModal、wx.hideModal、wx.showActionSheet 详情

A 新增 选择地理位置 API wx.chooseLocation 详情

A 新增 获取图片信息 API wx.getImageInfo 详情

A 新增 两个设备 API wx.getSystemInfoSync、wx.makePhoneCall 详情

A 新增 绘图 API wx.canvasToTempFilePath 详情

A 新增 音频媒体 API wx.createAudioContext 详情

A 新增 开放能力 API wx.checkSession 详情

A 新增 wx.navigateBack delta 参数,支持多层级返回 详情

A 新增 wx.sendSocketMessage、wx.onSocketMessage 支持收发 ArrayBuffer 数据类型

A 新增 getCurrentPages() 方法, 获取页面栈 详情

A 新增 Page.onReachBottom() 方法,监听页面到达底部 详情

A 新增 事件对象增加 changedTouches 列表,反应手指触摸位置的变化 详情

A 新增 [textarea/] 组件 详情

A 新增 [canvas/] 多点触摸 详情

A 新增 [canvas/] disable-scroll 属性 详情

A 新增 [image/] bindload 事件返回图片宽高 详情

A 新增 [text/] 嵌套 [text/] 功能 详情

A 新增 [video/] controls autoplay 属性 详情

A 新增 [video/] 支持设置弹幕、发送弹幕 详情

A 新增 [video/] 播放类事件和接口 详情

A 新增 WXML wx:key 支持,提升列表渲染时性能 详情

A 新增 WXML 关键字、数据路径计算 详情

U 新增 app.json tabBar 属性 position 用于指定显示位置 详情

F 修复 wx.navigateTo api 超过 5 个页面,调用失败无返回的问题

U 修复 [navigator/] 设置 display: flex 失效的问题

F 修复 [input/] text-align:center、text-align:right 不支持的问题

F 修复 [input/] 聚焦时异常的问题

F 修复 [image/] base64 图片显示的问题

F 修复 动态节点更新导致表单组件重置问题

F 修复 使用 rpx 单位部分手机出现边框显示不全的问题

F 修复 不同页面中相同的 canvas-id 共享同一个绘图上下文的问题

F 修复 page 里面的属性对象内的 function 失效的问题

F 修复 wx.drawCanvas 在 canvas 宽高为 0 的情况下画不出来的问题

F 修复 wx.request header 设置 'Content-Type' 异常的问题

D 即将移除 App.prototype.getCurrentPage 详情

D 即将移除组件: [toast/] [loading/] [action-sheet/] [modal/]

D 即将移除 [audio/] action 属性

开发者工具基础功能

A 增加 监听文件变化,实时刷新模拟器,默认开启,开发者可以在项目中主动关闭

A 增加 登陆界面增加代理配置、切换账号

A 增加 所有新增 API 以及 组件的调试功能

A 增加 更友好的错误提示功能

A 增加 wxml panel rpx 调试支持

A 增加 wxml panel 实时修改同步模拟器的功能

A 增加 wx.previewImage API 调试支持

F 修复 picker 组件的 start、end 无效的问题

F 修复 video 组件无法滑动进度条的问题

F 修复 wx.getSystemInfo 再有 tabbar 情况下获取高度错误的问题

F 修复 wx.chooseImage 设置count无效的问题

F 修复 Page.onLoad 时调用 wx.setNavigationBarTitle 失败的问题

F 修复 wxml panel 空白以及选择时候卡顿的问题

F 修复 脚本文件名字为中文时候的编译错误问题

F 修复 关闭项目时候没有关闭媒体播放的问题

编辑模块

A 增加 文件自动保存功能 详情

A 增加 编辑显示模拟器并且实时刷新

A 增加 Ctrl + \ 文件树展开和收起

A 增加 Ctrl + w 关闭当前编辑页面

A 增加 Ctrl + p 文件跳转

A 增加 Ctrl + m 打开或者关闭模拟器

A 增加 编辑器底部状态栏

A 增加 json wxml代码自动补全以及提示功能

F 修复光标丢失的问题

2019-07-12 09:22:38 zhugeaming2018 阅读数 139

>>原创文章,欢迎转载。转载请注明:转载自IT人故事会,谢谢!
>>原文链接地址:「小程序JAVA实战」微信开发者工具helloworld(三)

 

第一个小程序demo的运行,首选需要去使用开发工具

开发工具下载安装

https://mp.weixin.qq.com/cgi-bin/wx 点击开发工具,选择自己的系统版本进行下载,安装就可以了

安装完毕打开

  1. 打开下载后的安装文件

  1. 选择对应的安装目录

  1. 等待安装

  1. 安装完毕

  1. 打开安装后的文件

开发工具使用

  1. 扫描二维码

  1. 选择小程序

  1. 选择安装目录
    > 类似eclipse的项目路径

4.打开后的默认界面

做过web开发的都很熟悉Google Chrome浏览器,感觉跟他很像是吧。后面在使用中在详细的介绍某个按钮的功能,其实都不太复杂。

  1. 编译预览
    > 点击工具上的编译按钮,可以在工具的左侧模拟器界面看到这个小程序的表现,也可以点击预览按钮,通过微信的扫一扫在手机上体验你的第一个小程序。

目录结构(一)

https://developers.weixin.qq.com/miniprogram/dev/quickstart/basic/file.html#JSON-%E9%85%8D%E7%BD%AE

  1. app.js

    app.json 是对当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。

  2. app.json

    其实用来表示 pages/logs 目录下的 logs.json 这类和小程序页面相关的配置。如果你整个小程序的风格是蓝色调,那么你可以在 app.json 里边声明顶部颜色是蓝色即可。实际情况可能不是这样,可能你小程序里边的每个页面都有不一样的色调来区分不同功能模块,因此我们提供了 page.json,让开发者可以独立定义每个页面的一些属性,例如刚刚说的顶部颜色、是否允许下拉刷新等等。

  3. wxcss

    WXSS 具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改。新增了尺寸单位。在写 CSS 样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。WXSS 在底层支持新的尺寸单位 rpx ,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用的浮点数运算,所以运算结果会和预期结果有一点点偏差。
    提供了全局的样式和局部样式。和前边 app.json, page.json 的概念相同,你可以写一个 app.wxss 作为全局样式,会作用于当前小程序的所有页面,局部页面样式 page.wxss 仅对当前页面生效。
    此外 WXSS 仅支持部分 CSS 选择器。

  4. project.config.json

    通常大家在使用一个工具的时候,都会针对各自喜好做一些个性化配置,例如界面颜色、编译配置等等,当你换了另外一台电脑重新安装工具的时候,你还要重新配置。

目录结构(二)

wxml 等基本等同咱们开发的html,wxss是等同css, 通过这三个文件基本的就可以构成咱们的页面了。基本页面的构成就需要这3个文件。

目录结构(三)

utils 常用公共js的抽象。

目录结构(四)

在page内定义的一个logs文件夹,以后开发的时候可能需要定义很多个名称的文件夹。

微信小程序的文件和普通web前端的区别

PS:这就是大致的工具介绍,后面循序渐进慢慢的搞透,搞熟悉。

2016-10-14 17:45:10 qq_31383345 阅读数 77313

1. rpx :微信小程序开发中新出了尺寸单位rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。

如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

微信官方建议视觉稿以iPhone 6为标准.

2.rem rem(root em): 规定屏幕宽度为20rem;1rem = (750/20)rpx 。

微信小程序开发尺寸单位文档

http://blog.csdn.net/qq_31383345