微信公众号开发之JSSDK使用

标签: 微信 分类: Javascript 创建时间:2019-10-09 05:22:31 更新时间:2024-11-15 10:49:44

这一篇文章,或者说这一系列文章,都是记录我在开发微信公众号时遇到的各种各样的问题,或者是我开发中想到的一些事情。

1.关于开发者密码(AppSecret)的存储

开发者密码在微信公众平台的基本设置页面中进行设置,启用后,微信将不在存储AppSecret,网页上有句话:“开发者密码是校验公众号开发者身份的密码,具有极高的安全性。切记勿把密码直接交给第三方开发者或直接存储在代码中。如需第三方代开发公众号,请使用授权方式接入。” 不存在代码中,那存在哪里呢?

我觉得可以保存数据库,或者是服务器上的配置文件中,只要不让前端拿到的地方就好了。

2.java代码生成微信jssdk签名

想要引入微信的jssdk,需要wx.config进行配置,配置项

1
2
3
4
5
6
7
8
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,需要使用的JS接口列表
});

必填的几项,一般都通过后台获取。这里提供后台生成签名的示例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;

/*
* 微信开发Api
* */
@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;

//获取 access_token
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;
}
//获取 jsapi_ticket
private String getJsapi_ticket(){
String jsapi_ticket="";
//获取 access_token,假定一定会获取到正确的acces_token
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获取成功
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());
//jsapi_ticket
String jsapi_ticket=redisUtils.get("jsapi_ticket");
if(jsapi_ticket==null){
jsapi_ticket=getJsapi_ticket();
}
// 当前请求url
String url=request.getRequestURL().toString();
// 构造签名字符串
String string1="jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"&timestamp="+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, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: result.timestamp, // 必填,生成签名的时间戳,这里从后台获取
nonceStr: result.nonceStr, // 必填,生成签名的随机串,这里从后台获取
signature: result.signature,// 必填,签名,这里从后台获取
jsApiList: [
'getLocation'
] // 必填,需要使用的JS接口列表
});

打开微信开发者工具,调用这个测试页面,出现了: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);
//jsapi_ticket
String jsapi_ticket=redisUtils.get("jsapi_ticket");
if(jsapi_ticket==null){
jsapi_ticket=getJsapi_ticket();
}
// 当前请求url
String url = request.getHeader("Referer");
// 如果是直接输入的地址,或者不是从本网站访问的重定向到本网站的首页
if (url == null) {
url=request.getRequestURL().toString();
}

// 构造签名字符串
String string1="jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"&timestamp="+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位随机字符串&timestamp=精确到秒的时间戳&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, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: result.timestamp, // 必填,生成签名的时间戳
nonceStr: result.nonceStr, // 必填,生成签名的随机串
signature: result.signature,// 必填,签名
jsApiList: [
'getLocation'
] // 必填,需要使用的JS接口列表
});
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
console.log("ddd");
wx.getLocation({
type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: function (res) {
console.log(res);
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
var speed = res.speed; // 速度,以米/每秒计
var accuracy = res.accuracy; // 位置精度
}
});
});
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
},
error:function(errText){
console.log(errText);
}
});
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。