创建 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 +
"
&
timestamp
="
+ 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.csdn.net/lyq8479/article/details/9841371
微信开发第22篇,如何让 access_token 长期有效 http://blog.csdn.net/lyq8479/article/details/25076223
官网 http://mp.weixin.qq.com/wiki/4/9ac2e7b1f1d22e9e57260f6553822520.html
http://www.2cto.com/weixin/201506/406848.html