创建 AccessToken实体类 和 JsapiTicket 实体类 ,用来长久保存 token 和 ticket 值
package com.play.bean; /**
* 微信通用接口凭证
*/
public class AccessToken {
// 获取到的凭证
private String token;
// 凭证有效时间,单位:秒
private int expiresIn;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
}
package com.play.bean; /**
* 微信通用接口凭证
*/
public class JsapiTicket {
// 获取到的凭证
private String ticket;
// 凭证有效时间,单位:秒
private int expiresIn;
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
}
定期获取并存储 access_token 的流程为:
Web服务器启动时就加载一个Servlet,在Servlet的init()方法中启动一个线程,在线程的run()方法中通过死循环+Thread.sleep()的方式定期获取
access_token,然后将获取到的 access_token 保存在public static修饰的变量中。
在工程中创建一个InitServlet类,该类的代码如下:
package com.play.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import com.play.thread.TokenThread;
import com.play.util.WeixinUtil;
/**
* 初始化servlet
*/
public class InitServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void init() throws ServletException {
// 获取web.xml中配置的参数
TokenThread.appid = getInitParameter("appid");
TokenThread.appsecret = getInitParameter("appsecret");
System.out.println("weixin api appid:{}", TokenThread.appid);
System.out.println("weixin api appsecret:{}", TokenThread.appsecret);
// 未配置appid、appsecret时给出提示
if ("".equals(TokenThread.appid) || "".equals(TokenThread.appsecret)) {
log.error("appid and appsecret configuration error, please check carefully.");
} else {
// 启动定时获取access_token的线程
new Thread(new TokenThread()).start();
}
}
}
从上面的代码可以看出,InitServlet类只重写了init()方法,并没有重写 doGet() 和 doPost() 两个方法,因为我们并不打算让 InitServlet 来处理访问请求。
init()方法的实现也比较简单,先获取在 web.xml 中配置的参数 appid 和 appsecret,再启动线程 TokenThread 定时获取 access_token。
InitServlet在web.xml中的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>initServlet</servlet-name>
<servlet-class>
com.play.servlet.InitServlet
</servlet-class>
<!-- 配置获取access_token所需参数appid和appsecret -->
<init-param>
<param-name>appid</param-name>
<param-value>wxwxwxxwxx54wx54wx54</param-value>
</init-param>
<init-param>
<param-name>appsecret</param-name>
<param-value>4xw544xw44x4wwx4xw4wx4wx</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
InitServlet在 web.xml 中的配置与普通 Servlet 的配置有几点区别:
1)通过配置 <init-param> 向 Servlet 中传入参数;
2)通过配置 <load-on-startup> 使得Web服务器启动时就加载该 Servlet;
3)没有配置 <servlet-mapping>,因为 InitServlet 并不对外提供访问。
TokenThread的源代码如下:
package com.play.util;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import com.play.util.StringUtil;
import com.play.bean.AccessToken;
import com.play.bean.JsapiTicket;
/**
* 定时获取微信 access_token 和 jsapiTicket 的线程
*/
public class TokenThread implements Runnable {
// 第三方用户唯一凭证
public static String appid = "";
// 第三方用户唯一凭证密钥
public static String appsecret = "";
public static AccessToken accessToken = null;
public static JsapiTicket jsapiTicket = null;
public void run() {
while (true) {
try {
accessToken = WeixinUtil.getAccessToken(appid, appsecret);
if (null != accessToken) {
System.out.println("获取access_token成功,有效时长"+ accessToken.getExpiresIn()+"秒 token:"+ accessToken.getToken());
try{
jsapiTicket = WeixinUtil.getJsapiTicket(accessToken.getToken());
if(jsapiTicket!=null){
System.out.println("获取jsapiTicket成功,有效时长"+ jsapiTicket.getExpiresIn()+"秒 ticket:"+ jsapiTicket.getTicket());
}
}catch(Exception e){
// 如果jsapiTicket为null,60秒后再获取
Thread.sleep(60 * 1000);
}
// 休眠7000秒
Thread.sleep((accessToken.getExpiresIn() - 200) * 1000);
} else {
// 如果access_token为null,60秒后再获取
Thread.sleep(60 * 1000);
}
} catch (InterruptedException e) {
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException e1) {
System.out.println(e1);
}
System.out.println( e);
}
}
}
}
编写 WeixinUtil 工具类来实现发送的GET请求,并获取对应的 AccessToken 和 JsapiTicket
package com.play.util;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import com.play.bean.AccessToken;
import com.play.bean.JsapiTicket;
/**
* 公众平台通用接口工具类
*/
public class WeixinUtil {
// 获取access_token的接口地址(GET) 限200(次/天)
public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/**
* 发起https请求并获取结果
*
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
*/
public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
StringBuffer buffer = new StringBuffer();
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
if ("GET".equalsIgnoreCase(requestMethod))
httpUrlConn.connect();
// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
} catch (ConnectException ce) {
ce.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
/**
* 获取access_token
*
* @param appid 凭证
* @param appsecret 密钥
* @return
*/
public static AccessToken getAccessToken(String appid, String appsecret) {
//获取公众号access_token的链接
String access_token = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
AccessToken accessToken = null;
String requestUrl = access_token.replace("APPID", appid).replace("APPSECRET", appsecret);
//String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
accessToken = new AccessToken();
accessToken.setToken(jsonObject.getString("access_token"));
accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
accessToken = null;
// 获取token失败
System.out.println("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
return accessToken;
}
/**
* 获取jsapi_ticket
*
* @param appid 凭证
* @param appsecret 密钥
* @return
*/
public static JsapiTicket getJsapiTicket(String accessToken) {
//获取公众号jsapi_ticket的链接
String jsapi_ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
JsapiTicket jsapiticket = null; //ticket分享值
if(accessToken != null){
String requestUrl = jsapi_ticket_url.replace("ACCESS_TOKEN", accessToken);
//String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
jsapiticket = new JsapiTicket();
jsapiticket.setTicket(jsonObject.getString("ticket"));
jsapiticket.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
jsapiticket = null;
// 获取ticket失败
System.out.println("获取ticket失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
}else{
System.out.println("*****token为空 获取ticket失败******");
}
return jsapiticket;
}
}
额外补充:获取 JsapiTicket 是用来验证签名,使用分享接口等操作所需要的。
我们可以在 TokenThread 类 中添加 share 方法来获取所需要的参数 (例如,分享功能需要:appId、timestamp、nonceStr、signature等参数)
public void share(HttpServletRequest request) throws Exception { StringBuffer homeUrl = request.getRequestURL(); String queryString =request.getQueryString(); if(StringUtils.isNotBlank(queryString)){ homeUrl.append("?").append(queryString); } long timestamp = System.currentTimeMillis() / 1000; String nonceStr = UUID.randomUUID().toString(); String signature = SignUtil.getSignature( weiXinBaseService.getJsTicket(), nonceStr, timestamp, homeUrl.toString()); logger.info("url="+homeUrl); logger.info("nonceStr=" + nonceStr); logger.info("timestamp=" + timestamp); logger.info("signature=" + signature); logger.info("appid=" + WebConfig.get("weixin.appid")); request.setAttribute("appid", WebConfig.get("weixin.appid")); request.setAttribute("timestamp", timestamp); request.setAttribute("nonceStr", nonceStr); request.setAttribute("signature", signature); }
编写工具类 SignUtil 来实现校验操作
/** * 获得分享链接的签名。 * @param ticket * @param nonceStr //随机字符串 * @param timeStamp //时间戳 * @param url //请求页面的链接 * @return * @throws Exception */ public static String getSignature(String ticket, String nonceStr, long timeStamp, String url) throws Exception { String sKey = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "×tamp=" + timeStamp + "&url=" + url; return getSignature(sKey); } /** * 验证签名。 * * @param signature * @param timestamp * @param nonce * @return */ public static String getSignature(String sKey) throws Exception { String ciphertext = null; MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] digest = md.digest(sKey.toString().getBytes()); ciphertext = byteToStr(digest); return ciphertext.toLowerCase(); } /** * 将字节数组转换为十六进制字符串 * * @param byteArray * @return */ private static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; } /** * 将字节转换为十六进制字符串 * * @param mByte * @return */ private static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F]; String s = new String(tempArr); return s; }
前端页面中引入微信js文件并配置config
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script> var shareTitle = "分享链接的开头!"; var shareImg = "分享链接的图片地址" wx.config({ debug: false, // appId: '${appid}', // 必填,公众号的唯一标识 timestamp: '${timestamp}', // 必填,生成签名的时间戳 nonceStr: '${nonceStr}', // 必填,生成签名的随机串 signature: '${signature}',// 必填,签名,见附录1 jsApiList: [ 'onMenuShareTimeline', 'onMenuShareAppMessage', 'showOptionMenu' ] // 必填,需要使用的JS接口列表 });wx.ready(function () {
wx.showOptionMenu();wx.onMenuShareTimeline({ title: '', // 分享标题 link: '', // 分享链接 imgUrl: '', // 分享图标 success: function () { // 用户确认分享后执行的回调函数 }, cancel: function () { // 用户取消分享后执行的回调函数 } });wx.onMenuShareAppMessage({ title: '', // 分享标题 desc: '', // 分享描述 link: '', // 分享链接 imgUrl: '', // 分享图标 type: '', // 分享类型,music、video或link,不填默认为link dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空 success: function () { // 用户确认分享后执行的回调函数 }, cancel: function () { // 用户取消分享后执行的回调函数 } });
});
参考资料
PS:微信开发第14篇,如何获取 access_token http://blog.youkuaiyun.com/lyq8479/article/details/9841371
微信开发第22篇,如何让 access_token 长期有效 http://blog.youkuaiyun.com/lyq8479/article/details/25076223
官网 http://mp.weixin.qq.com/wiki/4/9ac2e7b1f1d22e9e57260f6553822520.html
http://www.2cto.com/weixin/201506/406848.html
1361

被折叠的 条评论
为什么被折叠?



