参考微信JS-SDK说明文档 看到网上很多都说微信的说明文档很坑,在我看来,仔细阅读的话,介绍还是很全的。
1.首先在JSP页面引入http://res.wx.qq.com/open/js/jweixin-1.1.0.js
2.通过config接口注入权限验证配置
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,企业号的唯一标识,此处填写企业号corpid
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录1
jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
3.通过ready接口处理成功验证
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
4.通过error接口处理失败验证
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
5.调起微信扫一扫
wx.scanQRCode({
desc: 'scanQRCode desc',
needResult: 0, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
success: function (res) {
// 回调
}
error: function(res){
if(res.errMsg.indexOf('function_not_exist') > 0){
alert('版本过低请升级')
}
}
});
具体代码实现如下:
scanBarcode.jsp
微信验签代码 WxController.java
/**
* 微信验签
* @param url
* @return
*/
@RequestMapping(value = "/jsapisign", method = {RequestMethod.GET, RequestMethod.POST}, produces = MEDIATYPE_CHARSET_JSON_UTF8)
@ResponseBody
public String jsApiSign(String url) {
//添加微信js签名信息
Map signMap = WXJsapiticket.jsApiSign(url);
return JSON.toJSONString(signMap);
}
用到的方法类
WXJsapiticket.java
public class WXJsapiticket {
private static Logger logger = LoggerFactory.getLogger(WxController.class);
/**
* 微信jsapi验签
* @param url
* @return
*/
public static Map jsApiSign(String url) {
Map ret = new HashMap();
String nonce_str = CheckUtil.create_nonce_str();
String timestamp = CheckUtil.create_timestamp();
String jsapi_ticket = getJsApiTicket();
String string1 = CheckUtil.getString1(nonce_str,timestamp,jsapi_ticket,url);
String signature = CheckUtil.getSha1(string1);
ret.put("appid", WXConstants.APPID);//取你自己的公众号appid
ret.put("url", url);
ret.put("jsapi_ticket", jsapi_ticket);
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
logger.info("jsApiSign------url=" + url + "-----jsapi_ticket=" + jsapi_ticket + "--------nonceStr=" + nonce_str + "--------timestamp=" + timestamp + "-------signature=" + signature);
return ret;
}
public static String getJsApiTicket(){
Map map = JsApiTicketCache.getInstance().getJsApiTicketAndExpiresIn();
return (String) map.get("jsapi_ticket");
}
}
CheckUtil.java
public class CheckUtil {
public static final String token = "xiaodou"; //开发者自行定义Token
/**
* 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成字符串string1
* @param nonce_str
* @param timestamp
* @param jsapi_ticket
* @param url
* @return
*/
public static String getString1(String nonce_str,String timestamp,String jsapi_ticket,String url){
//1.定义数组存放nonce_str,timestamp,jsapi_ticket,url
String[] arr = {"noncestr="+nonce_str,"timestamp="+timestamp,"jsapi_ticket="+jsapi_ticket,"url="+url};
//2.对数组进行排序
Arrays.sort(arr);
//3.生成字符串
StringBuffer sb = new StringBuffer();
for(String s : arr){
sb.append(s);
sb.append("&");
}
sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
public static String getSha1(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;
}
}
public static String create_nonce_str() {
return UUID.randomUUID().toString();
}
public static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
JsApiTicketCatch.java(对jsapi_ticket的缓存可自行设置,redis或者存到数据库都是可行的)
/**
* 缓存ticket
*/
public class JsApiTicketCache {
//缓存jsapi_ticket的Map,map中包含jsapiTicket,expiresIn和缓存的时间戳time
private Map map = new HashMap();
private static JsApiTicketCache jsApiTicketCache = null;
private JsApiTicketCache() { }
// 静态工厂方法
public static JsApiTicketCache getInstance() {
if (jsApiTicketCache == null) {
jsApiTicketCache = new JsApiTicketCache();
}
return jsApiTicketCache;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
/**
* 获取 jsapi_ticket expires_in
* @return
*/
public Map getJsApiTicketAndExpiresIn() {
Map result = new HashMap();
JsApiTicketCache jsApiTicketCache = JsApiTicketCache.getInstance();
Map map = jsApiTicketCache.getMap();
String time = map.get("time");
String jsapiTicket = map.get("jsapi_ticket");
String expiresIn = map.get("expires_in");
Long nowDate = new Date().getTime();
if (jsapiTicket != null && time != null && expiresIn!=null) {
//这里设置过期时间为微信规定的过期时间减去5分钟
int outTime = (Integer.parseInt(expiresIn)-300) * 1000;
if (nowDate - Long.parseLong(time) < outTime) {
System.out.println("-----从缓存读取jsapi_ticket:" + jsapiTicket);
//从缓存中拿数据为返回结果赋值
result.put("jsapi_ticket", jsapiTicket);
result.put("expires_in", expiresIn);
}
} else {
JsapiTicket info = WeiXinUtil.getjsapiTicket();//实际中这里要改为你自己调用微信接口去获取jsapi_ticket和expires_in
System.out.println("-----通过调用微信接口获取jsapi_ticket:" + info.getJsapiTicket());
//将信息放置缓存中
map.put("time", nowDate + "");
map.put("jsapi_ticket", info.getJsapiTicket());
map.put("expires_in", info.getExpiresIn()+"");
//为返回结果赋值
result.put("jsapi_ticket", info.getJsapiTicket());
result.put("expires_in", info.getExpiresIn());
}
return result;
}
}
WeiXinUtil.java
jsapi_ticket_url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";//GET方式请求获得jsapi_ticket
/**
* 获取jsapi_ticket
* @return
*/
public static JsapiTicket getjsapiTicket() {
JsapiTicket jsapiTicket = null ;
String accessToken = (String) AccessTokenCache.getInstance().getAcessTokenAndExpiresIn().get("access_token");
String requestUrl = WXConstants.jsapi_ticket_url.replace("ACCESS_TOKEN", accessToken);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
jsapiTicket = new JsapiTicket();
jsapiTicket.setJsapiTicket(jsonObject.getString("ticket"));
jsapiTicket.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
jsapiTicket = null;
// 获取token失败
log.error("获取ticket失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
return jsapiTicket;
}
accessToken的获取参照微信公众号开发之获取access_token,也可根据自己的定义获取。
JsapiTicket.java
public class JsapiTicket {
private String id;
private String jsapiTicket;
private int expiresIn;
public String getJsapiTicket() {
return jsapiTicket;
}
public void setJsapiTicket(String jsapiTicket) {
this.jsapiTicket = jsapiTicket;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
在这里总结一下开发中需要注意的地方(我遇到的坑):
1.确认url是页面完整的url(请在当前页面alert(location.href.split('#')[0])确认),包括'http(s)://'部分,以及'?'后面的GET参数部分,但不包括'#'hash后面的部分。
2.在生成string1时,拼接的参数均为小写,特别注意noncestr,而在jsp页面中为nonceStr(所有的参数一定要根据微信接口中的定义,否则会导致验签失败)。
3.验证签名是否正确,可在 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign页面工具进行校验。