由于微信的对access_token 和 jssdk-ticket 都有有效期限制,在页面中调用jssdk函数时需要校验,实现思路如下:
1 在程序里用定时器或者开启线程每7000s计算一次access_token和jssdk-ticket;
2 将access_token 和jssdk-ticket 通过静态变量 static 存在内存中。
3 在action中,设置成员变量 signature,将获取静态变量jssdk-ticket值计算后赋予signature,通过get set 方式将成员变量的值传给jsp页面
4 在jsp页面中通过el表达式获取signature值,noncestr、timestamp,url等值。
程序jsp+action实现
1 编写initservlet类启动线程,在web.xml文件中配置servlet 实现程序启动时即运行servlet.
InitServlet.java
/** * Created by lr on 2015/6/11 */ public class InitServlet extends HttpServlet { public void init() throws ServletException { new Thread(new TokenThread()).start(); } }
web.xml文件
<servlet> <servlet-name>initServlet</servlet-name> <servlet-class>cn.cp.wxphone.servlet.InitServlet</servlet-class> <!-- 程序启动时开始执行 --> <load-on-startup>0</load-on-startup> </servlet>
TokenThread.java
2 在线程中将token、ticket存为静态变量,并实现定时计算。package cn.cp.wxphone.Thread; import cn.cp.wxphone.manager.JsApiTicketManager; import cn.cp.wxphone.manager.TokenManager; import cn.cp.wxphone.pojo.Token; import cn.cp.wxphone.tag.AppTag; /** * Created by lr on 2015/6/11. */ public class TokenThread implements Runnable { public static Token accessToken = null; public static String jsApiTicket=null; public void run() { while (true) { try { accessToken = TokenManager.getToken(AppTag.AppID,AppTag.AppSecret ); jsApiTicket = JsApiTicketManager.getTicket().getJsi_Ticket(); if (null != accessToken) { System.out.println("获取access_token成功,有效时长{}秒 token:{}" + "," + accessToken.getExpiresIn() + "," + accessToken.getAccessToken()); // 休眠7000秒 Thread.sleep((accessToken.getExpiresIn() - 200) * 1000); // Thread.sleep(30*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); } } } }
appid和appsecret 在apptag类中定义为静态常量 public static final String APPID ="XXXXXX";
3 在action中获取静态变量ticket,并计算signatrue 。
action类 MarketAction.java
package cn.cp.wxphone.action; import cn.cp.wxphone.Thread.TokenThread; import cn.cp.wxphone.manager.JsApiTicketManager; import java.util.HashMap; import java.util.Map; /** * Created by lr on 2015/6/15. */ public class MarketAction extends BaseAction { //微信验证使用的变量 url,signature,AppId等 Map<String, String> entity = new HashMap<String, String>(); public String add() throws Exception { JsApiTicketManager jsM = new JsApiTicketManager(); String url = "http://cpchat.cpgroup.cn/wxproject/marketAction_add.action"; System.out.println("获取thread中jsApiTicket" + TokenThread.jsApiTicket); entity = jsM.sign(TokenThread.jsApiTicket, url); return "success"; } public Map<String, String> getEntity() { return entity; } public void setEntity(Map<String, String> entity) { this.entity = entity; } }
JsApiTicketManager.java 实现计算ticket 和signature,生成随机字符串。
package cn.cp.wxphone.manager; import cn.cp.wxphone.pojo.JsiTicket; import cn.cp.wxphone.pojo.Token; import cn.cp.wxphone.tag.AppTag; import net.sf.json.JSONException; import net.sf.json.JSONObject; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Formatter; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * Created by lr on 2015/6/17. */ public class JsApiTicketManager { private static final String jsiticket_url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; public static void main(String[] args){ JsApiTicketManager manager = new JsApiTicketManager(); JsiTicket ticket = manager.getTicket(); System.out.println("jsiTicket"+ticket.getJsi_Ticket()); String url ="http://cpchat.cpgroup.cn/wxproject/marketAction_add"; Map<String,String> resmap = new HashMap<String,String>(); resmap = manager.sign(ticket.getJsi_Ticket(),url); for (Map.Entry entry : resmap.entrySet()){ System.out.println("KEY:"+entry.getKey()+"VALUE:"+entry.getValue() ); } } // 获取jsapi_ticket public static JsiTicket getTicket() { JsiTicket ticket =null; TokenManager tokenManager = new TokenManager(); Token token = tokenManager.getToken(AppTag.AppID, AppTag.AppSecret); String url =jsiticket_url.replace("ACCESS_TOKEN",token.getAccessToken()); // 请求获取ticket JSONObject jsonObject= tokenManager.httpsRequest(url,"GET",null); if(null!=jsonObject){ try { ticket = new JsiTicket(); ticket.setJsi_Ticket(jsonObject.getString("ticket")); ticket.setExpiresIn(jsonObject.getInt("expires_in")); } catch (JSONException e) { ticket = null; // 获取ticket失败 System.out.println("获取ticket失败 errcode:{} errmsg:{}"+","+jsonObject.getInt("errcode")+","+jsonObject.getString("errmsg")); } } return ticket; } //获取计算后的signature,及其它字段 noncestr,timestamp,jsapi_ticket public static Map<String, String> sign(String jsapi_ticket, String url) { Map<String, String> ret = new HashMap<String, String>(); String nonce_str = create_nonce_str(); String timestamp = create_timestamp(); String string1; String signature = ""; //注意这里参数名必须全部小写,且必须有序 string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url; System.out.println(string1); try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes("UTF-8")); signature = byteToHex(crypt.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } ret.put("url", url); ret.put("jsapi_ticket", jsapi_ticket); ret.put("nonceStr", nonce_str); ret.put("timestamp", timestamp); ret.put("signature", signature); return ret; } private static String byteToHex(final byte[] hash) { Formatter formatter = new Formatter(); for (byte b : hash) { formatter.format("%02x", b); } String result = formatter.toString(); formatter.close(); return result; } private static String create_nonce_str() { return UUID.randomUUID().toString(); } private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000); } }
url地址与自定义菜单中view类型button定义的url 一致。
4 jsp页面中获取action中的值
<%@ page contentType="text/html; charset=utf-8" language="java"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <%@taglib prefix="s" uri="/struts-tags" %> <html> <head lang="en"> <meta charset="UTF-8"> <title>微信JS-SDK Demo</title> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0"> <link rel="stylesheet" href="css/style.css"> </head> <body ontouchstart=""> <div class="wxapi_container"> <div class="lbox_close wxapi_form"> <h3 id="menu-location">地理位置接口</h3> <span class="desc">使用微信内置地图查看位置接口</span> <button class="btn btn_primary" id="openLocation">openLocation</button> <span class="desc">获取地理位置接口</span> <button class="btn btn_primary" id="getLocation">getLocation</button> </div> </div> </body> <script src="boot/js/jweixin-1.0.0.js"></script> <script> wx.config({ debug: true, appId: 'wx4bffdab5471c8016', timestamp: '${entity.timestamp}', nonceStr: '${entity.nonceStr}', signature: '${entity.signature}', jsApiList: [ 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'openLocation', 'getLocation' ] }); </script> </html>
在使用el表达式时,出现了一些问题
如果是传数字类型 如 string tempStr=”123“ 时可写为 ${tempStr},但是当传字符时 应写为‘${tempStr}',要加‘单引号,调试时,因为这个问题浪费了很多时间。
之前一直搞不清楚jstl,el ,struct标签区别,及使用这里总结下
1) jsp页面中就支持EL表达式,在html中和javascript中均可通过${entity.name}的方式获取action中map类型的数据。
2)在jsp+struct2 的环境时,html中标签可使用struct标签来替代 text,button 等标签,能够简化textfield,select类型标签的取数。
3)jstl标签是el表达式的扩展,在有些日期类型的控件中,转换日期类型的数据比较方便。