这一篇文章,或者说这一系列文章,都是记录我在开发微信公众号时遇到的各种各样的问题,或者是我开发中想到的一些事情。
1.关于开发者密码(AppSecret)的存储
开发者密码在微信公众平台的基本设置页面中进行设置,启用后,微信将不在存储AppSecret,网页上有句话:“开发者密码是校验公众号开发者身份的密码,具有极高的安全性。切记勿把密码直接交给第三方开发者或直接存储在代码中。如需第三方代开发公众号,请使用授权方式接入。” 不存在代码中,那存在哪里呢?
我觉得可以保存数据库,或者是服务器上的配置文件中,只要不让前端拿到的地方就好了。
2.java代码生成微信jssdk签名
想要引入微信的jssdk,需要wx.config进行配置,配置项
1 2 3 4 5 6 7 8
| wx.config({ debug: true, appId: '', timestamp: , nonceStr: '', signature: '', jsApiList: [] });
|
必填的几项,一般都通过后台获取。这里提供后台生成签名的示例java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| package com.proheng.gis;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.proheng.gis.utils.HttpClientUtils; import com.proheng.gis.utils.RedisUtils; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController @RequestMapping("weixinapi") public class WeiXinApi { private final Logger logger= LoggerFactory.getLogger(WeiXinApi.class);
@Value("${weixin.app_secret}") private String app_secret; @Value("${weixin.app_id}") private String app_id;
@Autowired private RedisUtils redisUtils;
private String getAccess_token(){ String access_token=""; int expires_in=0; String url="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+app_id+"&secret="+app_secret; try { String result= HttpClientUtils.sendHttpGet(url); JSONObject resultObj= JSON.parseObject(result); if(resultObj.getString("errcode")!=null){ throw new Exception("获取access_token失败,"+result); }else { access_token=resultObj.getString("access_token"); expires_in=resultObj.getInteger("expires_in"); redisUtils.set("access_token",access_token,expires_in); } }catch (Exception e){ logger.error("getAccess_token",e); } return access_token; } private String getJsapi_ticket(){ String jsapi_ticket=""; String access_token=redisUtils.get("access_token"); if(access_token==null){ access_token=getAccess_token(); } String url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+access_token+"&type=jsapi"; try{ String result=HttpClientUtils.sendHttpGet(url); JSONObject resultObj=JSON.parseObject(result);
if(resultObj.getInteger("errcode")==0){ jsapi_ticket=resultObj.getString("ticket"); int expires_in=resultObj.getInteger("expires_in"); redisUtils.set("jsapi_ticket",jsapi_ticket,expires_in); }else { throw new Exception("获取jsapi_ticket失败,"+result); } }catch (Exception e){ logger.error("getJsapi_ticket",e); } return jsapi_ticket; } private String signature(){ String signature="";
return signature; }
@RequestMapping("getJsSDK") public JSONObject getJsSDK(HttpServletRequest request){ JSONObject resultObj=new JSONObject(); try { String noncestr= RandomStringUtils.randomAlphanumeric(16); String timestamp=String.valueOf(System.currentTimeMillis()); String jsapi_ticket=redisUtils.get("jsapi_ticket"); if(jsapi_ticket==null){ jsapi_ticket=getJsapi_ticket(); } String url=request.getRequestURL().toString(); String string1="jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"×tamp="+timestamp+"&url="+url;
String signatureStr= DigestUtils.sha1(string1).toString();
resultObj.put("signature",signatureStr); resultObj.put("nonceStr",noncestr); resultObj.put("timestamp",timestamp);
}catch (Exception e){ logger.error("getJsSDK",e); } return resultObj; }
}
|
说明:
(1) 关于配置安全域名的设置,我的理解是,你在哪个页面引入的微信jssdk,这个域名要填到:“公众号设置”的“功能设置”里填写“JS接口安全域名” 里面。
(2) 关于“同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用”,我的理解是在安全域名下的两个不同的页面,比如:www.example.com/a.html和www.example.com/b.html,要分别在两个页面中进行wx.config的初始化。每次url变化的话,都要调用一次。
3.签名错误config:fail,Error: 系统错误,错误码:63002,invalid signature
前台配置代码,可以参见第五章(获取用户位置)。
1 2 3 4 5 6 7 8 9 10
| wx.config({ debug: true, appId: '', timestamp: result.timestamp, nonceStr: result.nonceStr, signature: result.signature, jsApiList: [ 'getLocation' ] });
|
打开微信开发者工具,调用这个测试页面,出现了:config:fail,Error: 系统错误,错误码:63002,invalid signature。
生成签名的代码java代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @RequestMapping("getJsSDK") public JSONObject getJsSDK(HttpServletRequest request){ JSONObject resultObj=new JSONObject(); try { String noncestr= RandomStringUtils.randomAlphanumeric(16); String timestamp=String.valueOf(System.currentTimeMillis()/1000); String jsapi_ticket=redisUtils.get("jsapi_ticket"); if(jsapi_ticket==null){ jsapi_ticket=getJsapi_ticket(); } String url = request.getHeader("Referer"); if (url == null) { url=request.getRequestURL().toString(); }
String string1="jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"×tamp="+timestamp+"&url="+url;
String signatureStr= DigestUtils.sha1Hex(string1);
resultObj.put("signature",signatureStr); resultObj.put("nonceStr",noncestr); resultObj.put("timestamp",timestamp);
}catch (Exception e){ logger.error("getJsSDK",e); } return resultObj; }
|
DigestUtils.sha1Hex签名算法使用的jar包
1 2 3 4 5
| <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.13</version> </dependency>
|
说明
(1) 生成签名时的时间戳精确到秒,而不是毫秒。
(2) jssdk验证签名的逻辑是这样的,获取到当前网页的url,加上你在wx.config中配置的appId、timestamp和nonceStr,传递到微信后台,通过签名逻辑算法,获取到signature,和你在html的wx.config配置中配置的signature比对,如果一致,则证明你的wx.config配置正确,如果不一致,则证明你配置的不正确。所以,在配置signature是,出现签名不正确的情况,可以检查这几个参数是不是正确,签名验证工具。
(3) 自己的后台生成验证签名的逻辑是这样的(微信后台也是这样验证签名是否正确的):首先根据你的appId和app_secret,获取到你的access_token( 验证接口 ),然后使用access_token,获取到你的jsapi_ticket( 验证接口 ),然后通过构造签名字符串string1:”jsapi_ticket=你的ticket&noncestr=16位随机字符串×tamp=精确到秒的时间戳&url=调用jssdk的网页地址,最后将签名字符串string1进行sha1加密,就得到了signature( 验证签名的接口 ),连同timestamp和nonceStr一起传递到前台就可以配置wx.config了。微信后台唯一省略的步骤就是第一步,因为他不需要你的app_secret,通过你的appId就可以到它自己服务器的缓存中拿到你的jsapi_ticket,连同你配置的timestamp和nonceStr就能生成signature,然后就是和你生成的signature比对,看看是不是一致了。
最后的成功效果如下:
总结一下,使用jssdk的步骤:
(1) 微信公众号后台配置安全域名
(2) 将微信要求的文件MP_verify_AKxhZkUe8kuvT7Wr.txt放到安全域名的指定位置
(3) 编写后台的签名算法,根据appid和appSecret等生成签名返回给前台,url一般是Referer
(4) 在安全域名下的某个js脚本中引入jssdk(本地开发时,可以修改host文件,将本地地址指向安全域名,比如127.0.0.1指向bibichua.github.io,这样就可以在微信开发者账号中测试了),wx.config中的签名和时间戳由后台获取
(5) 打开微信开发者工具,测试相关内容。
4.获取用户地理位置
错误记录:”getLocation:fail, the permission value is offline verifying”,主要问题是签名错误(这个错误真是很烦人啊),解决方式,可以查看第三章。
解决了签名的问题,就可以愉快的玩耍了:
(1) 步骤一:引入js文件
在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js
(2) 步骤二:通过config接口注入权限验证配置
(3) 步骤三:通过ready接口处理成功验证
(4) 步骤四:通过error接口处理失败验证
获取地理位置的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| $.ajax({ url:"http://localhost:8068/weixinapi/getJsSDK", async:false, success:function(data){ console.log(data); var result=data; wx.config({ debug: true, appId: '', timestamp: result.timestamp, nonceStr: result.nonceStr, signature: result.signature, jsApiList: [ 'getLocation' ] }); wx.ready(function(){ console.log("ddd"); wx.getLocation({ type: 'wgs84', success: function (res) { console.log(res); var latitude = res.latitude; var longitude = res.longitude; var speed = res.speed; var accuracy = res.accuracy; } }); }); wx.error(function(res){ }); }, error:function(errText){ console.log(errText); } });
|