前面已经学会了微信网页授权,现在微信网页的功能也可以开展起来啦!

首先,我们先来学习一下分享,如何在自己的页面获取分享接口及让小伙伴来分享呢?

今天的主人公: 微信 JS-SDK, 对应官方链接为:微信JS-SDK说明文档

 

经过分析,要使用微信SJ-SDK需要完成如下工作:

 

由以上分析,我们需要做服务器的注入验证,另外在需要分享的页面中引入js文件,这样就可以调用微信JS-SDK中的接口啦~

 

下面首先开始实现注入验证功能,主要分为如下几步:

第一步,获取access_token:

access_token是微信接口号开发的基本数据,建议存到数据库中保存。(第三篇中已实现,可参考)

第二步,获取jsapi_ticket:

由官方文档得知,只需Get方式调用接口:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

其中参数为第一步中获取的access_token,调用方法已在工具类中。另jsapi_ticket有效时间为2个小时,且每个页面均需用到接口验证,因此可参考access_token,

将jsapi_ticket 存至数据库,以便后续获取。

获取jsapi_ticket主方法可参考(请忽略注释问题):

 1     /**
 2      * 获取微信 js-api-ticket
 3      * @return
 4      */
 5     public JSAPITicket getJsApiTicket()
 6     {
 7 
 8         /*
 9          * 第一步,查询数据库中ticket是否已过期 未过期则直接获取
10          */
11         if (updateJSAPITicket())
12         {
13             return mJSAPITicket;
14         }
15 
16         /* 第二步,获取当前有效的access_token */
17         WeChatTokenService tWeChatTokenService = new WeChatTokenService();
18         // 此处获取测试账号的
19         String access_token = tWeChatTokenService.getToken(mAppid, mAppSecret).getToken();
20 
21         /* 第三步,则通过https调用获取 jsapi_ticket */
22         if (!getJSApiTicketbyhttps(access_token))
23         {
24             System.out.println("获取ticket失败!");
25             return null;
26         }
27 
28         return mJSAPITicket;
29     }
View Code

 其中jsapi_ticket对应实体类为:

 1 /**
 2  * 微信 JS-API-Ticket类
 3  * @author Damon
 4  */
 5 public class JSAPITicket implements Cloneable
 6 {
 7 
 8     // 微信 ticket流水号
 9     private String ticketid = "";
10 
11     // 微信jsapi_ticket
12     private String ticket = "";
13 
14     // 有效时间
15     private int expires_in = 0;
16 
17     // 微信appid
18     private String appid = "";
19 
20     // 申请用户密钥
21     private String appsecret = "";
22 
23     // 获取时间
24     private String createtime = "";
25 
26     public String getTicketid()
27     {
28         return ticketid;
29     }
30 
31     public void setTicketid(String ticketid)
32     {
33         this.ticketid = ticketid;
34     }
35 
36     public String getTicket()
37     {
38         return ticket;
39     }
40 
41     public void setTicket(String ticket)
42     {
43         this.ticket = ticket;
44     }
45 
46     public int getExpires_in()
47     {
48         return expires_in;
49     }
50 
51     public void setExpires_in(int expires_in)
52     {
53         this.expires_in = expires_in;
54     }
55 
56     public String getAppid()
57     {
58         return appid;
59     }
60 
61     public void setAppid(String appid)
62     {
63         this.appid = appid;
64     }
65 
66     public String getCreatetime()
67     {
68         return createtime;
69     }
70 
71     public void setCreatetime(String createtime)
72     {
73         this.createtime = createtime;
74     }
75 
76     public String getAppsecret()
77     {
78         return appsecret;
79     }
80 
81     public void setAppsecret(String appsecret)
82     {
83         this.appsecret = appsecret;
84     }
85 
86     @Override
87     public JSAPITicket clone() throws CloneNotSupportedException
88     {
89         // TODO Auto-generated method stub
90         JSAPITicket cloneTicket = (JSAPITicket) super.clone();
91         return cloneTicket;
92     }
93 
94 }
View Code

对应表结构可以参考:

对应的SQL脚本:

 1 drop table if exists WeChatJSAPITicket;
 2 
 3 /*==============================================================*/
 4 /* Table: WeChatJSAPITicket                                     */
 5 /*==============================================================*/
 6 create table WeChatJSAPITicket
 7 (
 8    ticketid             varchar(60) not null,
 9    ticket               varchar(300),
10    expires_in           int,
11    appid                varchar(60),
12    appsecret            varchar(60),
13    createtime           timestamp,
14    primary key (ticketid)
15 );
View Code

主方法调用的明细方法为:

  1     /**
  2      * 获取微信JS-API-Ticket信息
  3      * @return
  4      */
  5     private boolean updateJSAPITicket()
  6     {
  7         // 查询数据库数据,如果有则不用更新,无则需要更新
  8         Connection con = null;
  9         PreparedStatement stmt = null;
 10         ResultSet rs = null;
 11         // 判断当前token是否在有效时间内
 12         String sql = " select * from wechatjsapiticket where appid ='" + mAppid + "' and appsecret ='" + mAppSecret
 13                 + "' and ( current_timestamp -createtime) <expires_in order by createTime desc limit 0,1";
 14         System.out.println(sql);
 15         try
 16         {
 17             // 创建数据库链接
 18             con = DBConnPool.getConnection();
 19             // 创建处理器
 20             stmt = con.prepareStatement(sql);
 21             // 查询Token,读取1条记录
 22             rs = stmt.executeQuery();
 23             if (rs.next())
 24             {
 25                 mJSAPITicket.setTicketid(rs.getString("ticketid"));
 26                 mJSAPITicket.setTicket(rs.getString("ticket"));
 27                 mJSAPITicket.setExpires_in(rs.getInt("expires_in"));
 28                 mJSAPITicket.setAppid(rs.getString("appid"));
 29                 mJSAPITicket.setAppsecret(rs.getString("appsecret"));
 30             }
 31             else
 32             {
 33                 System.out.println("未查询到对应ticket");
 34                 return false;
 35             }
 36         }
 37         catch (Exception e)
 38         {
 39             // TODO: handle exception
 40             return false;
 41         }
 42 
 43 
 44         return true;
 45     }
 46 
 47 
 48     /**
 49      * 调用请求获取ticket
 50      * @param access_token
 51      * @return
 52      */
 53     private boolean getJSApiTicketbyhttps(String access_token)
 54     {
 55 
 56         String current_time = new Date().getTime() + "";
 57 
 58         try
 59         {
 60             // 请求地址
 61             String path = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
 62             path = path.replace("ACCESS_TOKEN", access_token);
 63             String strResp = WeChatUtil.doHttpsGet(path, "");
 64             System.out.println(strResp);
 65 
 66             // 解析获取的token信息
 67             Map<String, Object> tMap = WeChatUtil.jsonToMap(strResp);
 68 
 69             System.out.println(tMap.toString());
 70 
 71             mJSAPITicket.setTicketid(WeChatUtil.getMaxJSAPITicketID());
 72             mJSAPITicket.setTicket((String) tMap.get("ticket"));
 73             mJSAPITicket.setExpires_in(Integer.parseInt((String) tMap.get("expires_in")));
 74             mJSAPITicket.setAppid(mAppid);
 75             mJSAPITicket.setAppsecret(mAppSecret);
 76             mJSAPITicket.setCreatetime(current_time);
 77 
 78             System.out.println(mJSAPITicket.getTicket());
 79 
 80         }
 81         catch (HttpException e)
 82         {
 83             // TODO Auto-generated catch block
 84             e.printStackTrace();
 85             return false;
 86         }
 87         catch (IOException e)
 88         {
 89             // TODO Auto-generated catch block
 90             e.printStackTrace();
 91             return false;
 92         }
 93 
 94         // 存储JS-API-Ticket至数据库
 95         if (!saveJSAPITicket(mJSAPITicket))
 96         {
 97             return false;
 98         }
 99 
100         return true;
101     }
102 
103     /**
104      * 将获取到的ticket信息存到数据库
105      * @param tJSAPITicket
106      * @return
107      */
108     private boolean saveJSAPITicket(JSAPITicket tJSAPITicket)
109     {
110         PreparedStatement pst = null;
111         Connection conn = null;
112         try
113         {
114             JSAPITicket ticket = tJSAPITicket.clone();
115 
116             System.out.println(ticket.getTicketid() + ticket.getTicket());
117 
118             conn = DBConnPool.getConnection();
119             // 创建预处理器
120             pst = conn.prepareStatement("insert into wechatjsapiticket(ticketid, ticket, expires_in,appid, appsecret,createtime) values (?,?,?,?,?,?)");
121 
122             pst.setString(1, ticket.getTicketid());
123             pst.setString(2, ticket.getTicket());
124             pst.setInt(3, ticket.getExpires_in());
125             pst.setString(4, ticket.getAppid());
126             pst.setString(5, ticket.getAppsecret());
127             long now = new Date().getTime();
128             pst.setTimestamp(6, new java.sql.Timestamp(Long.parseLong(ticket.getCreatetime())));
129             pst.execute();
130 
131         }
132         catch (CloneNotSupportedException e)
133         {
134             // TODO Auto-generated catch block
135             e.printStackTrace();
136             return false;
137         }
138         catch (SQLException e)
139         {
140             // TODO Auto-generated catch block
141             e.printStackTrace();
142             return false;
143         }
144         catch (Exception e)
145         {
146             // TODO: handle exception
147             System.out.println("出现额外异常");
148             return false;
149         }
150 
151         return true;
152     }
View Code

 这样就方便我们获取access_ticket啦!

第三步,实现数据签名:

签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。

这里需要用到四个参数,具体分析如下:

参数 说明
noncestr 随机字符串,可用java.util.UUUID类实现
jsapi_ticket 调用前面方法获取
timestamp 当前时间戳
url 传入参数,每次由前端传入

 

 

 

 

 

另外,参数加密算法为SHA1加密,因此实现方法为:

 1     /**
 2      * ticket数据签名
 3      * @return
 4      */
 5     public WeChatJSAPISign getSignTicket(String requestUrl)
 6     {
 7         // 随机字符串
 8         String noncestr = UUID.randomUUID().toString().replace("-", "");
 9         String jsapi_ticket = getJsApiTicket().getTicket();
10         long timestamp = new Date().getTime();
11 
12         String params = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + noncestr + "&timestamp=" + timestamp + "&url="
13                 + requestUrl;
14         String signature = "";
15 
16         System.out.println("params:" + params);
17 
18         try
19         {
20             MessageDigest crypt = MessageDigest.getInstance("SHA-1");
21             crypt.reset();
22             crypt.update(params.getBytes("UTF-8"));
23             signature = WeChatUtil.byteToHex(crypt.digest());
24         }
25         catch (NoSuchAlgorithmException e)
26         {
27             e.printStackTrace();
28         }
29         catch (UnsupportedEncodingException e)
30         {
31             e.printStackTrace();
32         }
33 
34         WeChatJSAPISign tChatJSAPISign = new WeChatJSAPISign();
35 
36         tChatJSAPISign.setAppId(mAppid);
37         tChatJSAPISign.setNoncestr(noncestr);
38         tChatJSAPISign.setTimestamp(timestamp);
39         tChatJSAPISign.setSignature(signature);
40 
41         return tChatJSAPISign;
42     }
View Code

返回定义的参数对象,定义如下:

 1 /**
 2  * // JS-API-Ticket 签名类
 3  * @author Damon
 4  */
 5 public class WeChatJSAPISign
 6 {
 7 
 8     // JS-API-Ticket appid
 9     private String appId = "";
10 
11     // JS-API-Ticket 随机字符串
12     private String noncestr = "";
13 
14     // JS-API-Ticket 时间
15     private long timestamp = 0;
16 
17     // JS-API-Ticket 签名
18     private String signature = "";
19 
20     public String getAppId()
21     {
22         return appId;
23     }
24 
25     public void setAppId(String appId)
26     {
27         this.appId = appId;
28     }
29 
30     public String getNoncestr()
31     {
32         return noncestr;
33     }
34 
35     public void setNoncestr(String noncestr)
36     {
37         this.noncestr = noncestr;
38     }
39 
40     public long getTimestamp()
41     {
42         return timestamp;
43     }
44 
45     public void setTimestamp(long timestamp)
46     {
47         this.timestamp = timestamp;
48     }
49 
50     public String getSignature()
51     {
52         return signature;
53     }
54 
55     public void setSignature(String signature)
56     {
57         this.signature = signature;
58     }
59 
60 }
View Code

到这,注入验证的服务器端功能就完成了。

下面也进行页面的编写和调用验证。

第一步,先写一个分享页面(基本的html页面即可),可参考(由于我的工程默认编码GBK,编码请注意):

 1 <%@ page language="java" contentType="text/html; charset=GBK"   pageEncoding="GBK"%>
 2 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 3 <html>
 4 <head>
 5 <meta http-equiv="Content-Type" content="text/html; charset=GBK">
 6 <title>damon's share page</title>
 7 <%@include file="wechat_config.jsp" %>
 8 <script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
 9 <link rel="stylesheet" href="./weui/weui.css"/>
10 
11 <!-- 
12 <script src="wechat_config.js"></script>
13  -->
14 <script type="text/javascript">
15 wx.config({
16     debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
17     appId: '${appid}', // 必填,公众号的唯一标识
18     timestamp: '${timestamp}', // 必填,生成签名的时间戳
19     nonceStr: '${noncestr}', // 必填,生成签名的随机串
20     signature: '${signature}',// 必填,签名,见附录1
21     jsApiList: ['onMenuShareTimeline','onMenuShareQQ','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
22 });
23 
24 wx.ready(function(){
25     
26 // 分享到朋友圈
27 wx.onMenuShareTimeline({
28         title: '玩玩微信公众号Java版之六:微信网页授权', // 分享标题
29         link: 'http://www.cnblogs.com/cooldamon/p/7219400.html', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
30         imgUrl: 'http://damonhouse.iok.la/wechat/pic1.jpg' // 分享图标
31     });
32     
33 //分享到QQ
34 wx.onMenuShareQQ({
35         title: '玩玩微信公众号Java版之六:微信网页授权', // 分享标题
36         desc: '分享测试', // 分享描述
37         link: 'http://www.cnblogs.com/cooldamon/p/7219400.html', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
38         imgUrl: 'http://damonhouse.iok.la/wechat/pic1.jpg' // 分享图标
39     });
40     
41 //分享到QQ空间
42 wx.onMenuShareQZone({
43     title: '玩玩微信公众号Java版之六:微信网页授权', // 分享标题
44     desc: '分享QQ空间测试', // 分享描述
45     link: 'http://www.cnblogs.com/cooldamon/p/7219400.html', // 分享链接
46     imgUrl: 'http://damonhouse.iok.la/wechat/pic1.jpg' // 分享图标
47 });
48     
49     
50 });
51 
52 wx.error(function(res){
53     // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
54 });
55 
56 function shareMe()
57 {
58     //分享到QQ空间
59     wx.onMenuShareQZone({
60         title: '玩玩微信公众号Java版之六:微信网页授权', // 分享标题
61         desc: '分享QQ空间测试', // 分享描述
62         link: 'http://www.cnblogs.com/cooldamon/p/7219400.html', // 分享链接
63         imgUrl: 'http://damonhouse.iok.la/wechat/pic1.jpg' // 分享图标
64     });
65     
66 }
67 
68 </script>
69 </head>
70 <body>
71 <button class="weui-btn weui-btn_plain-primary" onclick="shareMe();">欢迎分享</button>
72 </body>
73 </html>
View Code

另外这里对微信接口调用写了一个功能的方法,引入wechat_config.jsp

 1 <%@page import="com.wechat.pojo.WeChatJSAPISign"%>
 2 <%@page import="com.wechat.bl.WeChatJSAPIService"%>
 3 <%
 4 // 微信js-jdk 配置接口处理
 5 // 第一步,获取参数
 6 %>
 7 
 8 <%
 9 
10 String url = request.getRequestURL().toString();
11 System.out.println(url);
12 WeChatJSAPIService tWeChatJSAPIService = new WeChatJSAPIService();
13 
14 WeChatJSAPISign tWeChatJSAPISign = tWeChatJSAPIService.getSignTicket(url);
15 System.out.println( tWeChatJSAPISign.getAppId());
16 System.out.println( tWeChatJSAPISign.getNoncestr());
17 System.out.println( tWeChatJSAPISign.getTimestamp());
18 System.out.println( tWeChatJSAPISign.getSignature());
19 
20 request.setAttribute("appid", tWeChatJSAPISign.getAppId());
21 request.setAttribute("noncestr", tWeChatJSAPISign.getNoncestr());
22 request.setAttribute("timestamp", tWeChatJSAPISign.getTimestamp());
23 request.setAttribute("signature", tWeChatJSAPISign.getSignature());
24 
25 %>
View Code

其中说明:

1、引入http://res.wx.qq.com/open/js/jweixin-1.2.0.j

2、验证接口中请注意参数名称(这里粗心弄错了,导致验证失败),另外验证错误原因可参考官方错误说明:附录5-常见错误及解决方法.

3、对应的接口功能,最终实现在左上角的更多按钮,请参考页面:

4、最终实现效果如下(以分享到qq空间为例):

 

这里多出了【分享到手机QQ】和【分享到QQ空间】两个按钮,点击【分享到QQ空间】,可看到:

 

恭喜你,成功做出了自己的分享页面! 继续加油吧~