js安全域名配置很重要,前端的服务的域名,非常重要!
1、先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
JS接口安全域名需要和示例格式一模一样,不需要带http/https
备注:登录后可在“开发者中心”查看对应的接口权限。
2、需要开启开发者密钥,配置ip白名单(后端服务器ip,获取access_token等白名单不拦截)。
注意:ip白名单每天都在变化,可以参考官方获取ip方式
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_the_WeChat_server_IP_address.html
设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口。
注意事项:
1、可填写五个域名或路径(例:wx.qq.com或wx.qq.com/mp),需使用由字母、数字、“-”或中文组成的合法域名,不支持IP地址、端口号及短链域名。
2、填写的域名须通过ICP备案的验证。
3、 将文件MP_verify_Pd7Rn8KfdBv1xmNh.txt(点击下载)上传至填写域名或路径指向的web服务器(或虚拟主机)的目录(若填写域名,将文件放置在域名根目录下,例如wx.qq.com/MP_verify_Pd7Rn8KfdBv1xmNh.txt;若填写路径,将文件放置在路径目录下,例如wx.qq.com/mp/MP_verify_Pd7Rn8KfdBv1xmNh.txt),并确保可以访问。
4、 一个自然月内最多可修改并保存五次,本月剩余保存次数:5
细节需要注意内容:
简单理解就是,前端h5项目运行的服务器域名,注意是域名,不是IP!
有一个txt文件要放置在提供的域名目录下,可以是根目录,也可以指定目录下面,配置好之后,直接浏览器地址栏访问这个路径,能打开就成功了,此处还有一个坑:
如果把这个txt文件放置在根目录,你的h5访问路径就不能带路径,也就是放置在根路径下面,http://xxx,xxx/index.html是没有问题的,如果h5项目是http://xxx.xxx/mp/index.html,这是行不通;
同理,如果你把这个txt文件放置在某个文件下,如:http://xxx.xxx/mp,放在mp文件夹下,那你的h5也要放在这个路径下面,http://xxx.xxx/mp/index.html;不然肯定有问题;
参考链接:
https://www.cnblogs.com/web-wjg/p/11346656.html
https://www.csdn.net/tags/OtDacgwsNDE0ODUtYmxvZwO0O0OO0O0O.html
https://www.mk2048.com/blog/blog_kijhh0j20hab.html
在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.6.0.js
如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js/jweixin-1.6.0.js (支持https)。
备注:支持使用 AMD/CMD 标准模块加载方法加载
要从后端获取如下内容:
{
"traceID": "09a3cad4-f426-4167-a375-c0676b4709e",
"retCode": 200,
"retMsg": "success",
"data": {
"signature": "a42a05f53400e380a000ef3832740f804ab",
"appId": "wx6d67b63",
"jsapi_ticket": "O3SMpm8bG7kJnF36aXbe8-83NpQ5KpEh_hfAfmkSN73KjH2CcJdtqwyli__Tj7uujod5sK7L6T69JQ",
"url": "https://xxxxxx分享地址",
"nonceStr": "66e4a737-ca38-467d-a53f-6d5e4d24b0b1",
"timestamp": "1650878949"
}
}
java签名算法可以下载微信公众平台demo
获取签名算法中,需要开发者传入 jsapi_ticket 和 url ,其中 jsapi_ticket 需要通过 http://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=ACCESS_TOKEN 接口获取,url 为调用页面的完整 url
注意:
1. jsapi_ticket 的有效期为 7200 秒,开发者必须全局缓存 jsapi_ticket ,防止超过调用频率。
因为微信公众平台java的demo只实现了签名算法,还需要自己去获取jsapi_ticket
jsapi_ticket的获取又需要依赖access_token故贴出实现代码如下:
jspi_ticket应该失效前通过看门狗去获取更新,让它一直有效,这里是通过缓存方式,但并发很高时,可以发生缓存穿透,一瞬间耗光获取access_token的次数(可能性极低)
/**
* 常量appId
**/
private static String APP_ID = "wx673b63";
/**
* 常量appSecret 开发者密钥
**/
private static String APP_SECRET = "89168a79f7884";
/**
* 常量redis缓存超时 单位秒 微信有效期2小时
**/
private static int TICKET_TIMEOUT = 30*60;
/**
* 微信 access_token 地址
**/
private static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
/**
* 微信 ticket 地址
**/
private static String TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
/**
* 微信 jsApiTicket redis key
**/
private static String REDIS_TICKET = "jsApiTicket_";
@Autowired
public RedisTemplate redisTemplate;
/**
* @Description 微信分享,获取前端JS-SDK配置
**/
@Override
public ResponseResult getWeiXinConfig(String url){
//wxInfo[0] appId
//wxInfo[1] appsecret
String[] wxInfo = new String[]{APP_ID,APP_SECRET};
String jsapi_ticket = "";
//获取jsapi_ticket
String ticketResString = getShareJsApiTicket(wxInfo);
//解析ticket
if (StringUtils.isNotEmpty(ticketResString)) {
Integer code = (Integer) JSONPath.read(ticketResString, "$.errcode");
if (code == 0) {
jsapi_ticket = JSONObject.parseObject(ticketResString).get("ticket").toString();
}
}
//返回为空,无法获取ticket
if (StringUtils.isEmpty(jsapi_ticket)) {
throw new TikTokListServiceException(ResultEnum.WEIXIN_TICKET_ERROR);
}
// 注意 URL 一定要动态获取,不能 hardcode
Map ret = WeiXinSignUtil.sign(jsapi_ticket, url,wxInfo[0]);
return ResultUtil.getResult(ResultEnum.SUCCESS, ret);
}
/**
* @Description 微信分享,获取JsapiTicket
**/
private String getShareJsApiTicket(String[] wxInfo) {
String jsApiTicket = "";
//jsapi_ticket 的有效期为 7200 秒,必须全局缓存 jsapi_ticket ,防止超过调用频率。access_token 次数2000
/**
* 考虑并发情况
* 1、并发后一个access_token获取,在没有获取到ticket之前,前一个access_token 失效,ticket就无法获取。
* 考虑后解决方案:
* 微信公众平台已解决:中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器可对外继续输出的老access_token,
* 此时公众平台后台会保证在5分钟内,新老access_token都可用,这保证了第三方业务的平滑过渡
*
* 2、以后多次出现200+请求同时进来的时候,缓存失效,去请求微信,200*n > 2000后,使用次数就被限制
* 考虑后解决方案:(未实施)
* a.使用看门狗线程在缓存要过期时,生成新的ticket,替换缓存,请求一直命中缓存
* b.在缓存失效后加双重检查锁,生成缓存前只允许一个线程操作对微信请求
*
* 3、后续开发注意 微信运营描述:access_token可能大于2小时,也有可能小于两小时
*
**/
try {
jsApiTicket = (String) redisTemplate.opsForValue().get(REDIS_TICKET+wxInfo[0]);
}catch (Exception e){
e.printStackTrace();
}
if(StringUtils.isNotEmpty(jsApiTicket)){
log.warn("ticket from redis jsApiTicket: " + jsApiTicket);
return jsApiTicket;
}
//获取accessToken
String accessToken = getWeiXinAccessToken(wxInfo);
if (StringUtils.isEmpty(accessToken)) { // 获取 accessToken 失败
return null;
}
//ticket请求
String url = TICKET_URL+"?access_token="+ accessToken + "&type=jsapi";
//获取ticket
jsApiTicket = httpReqExecute(url);
log.warn("ticket from weixin api jsapiTicket is: " + jsApiTicket);
if(StringUtils.isNotEmpty(jsApiTicket)) {
// 向redis里写内容,第二个参数为过期时间,单位为:秒
redisTemplate.opsForValue().set(REDIS_TICKET+wxInfo[0],jsApiTicket,TICKET_TIMEOUT, TimeUnit.SECONDS);
return jsApiTicket;
}
return null;
}
/**
* @Description 微信分享,获取access_token
**/
private String getWeiXinAccessToken(String[] wxInfo){
String url = ACCESS_TOKEN_URL+"?grant_type=client_credential&appid="
+ wxInfo[0] + "&secret=" + wxInfo[1];
String result = this.httpReqExecute(url);
log.warn("from weixin api accessToken: " + result);
Integer code = null;
try {
code = (Integer) JSONPath.read(result, "$.errcode");
} catch (RuntimeException e) {
e.printStackTrace();
}
try {
if(StringUtils.isNotEmpty(result)) {
// 解析access_token
String accessToken = JSONObject.parseObject(result).get("access_token").toString();
return accessToken;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* @Description HTTP远程调用
**/
private String httpReqExecute(String url) {
String result = "";
DefaultHttpClient httpclient = null ;
try {
httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
// 执行
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
if(entity != null && response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(entity, "UTF-8");
}
} catch (Exception e) {
log.error(" 微信分享 调用微信 API 失败!", e);
} finally { // 关闭连接,释放资源
httpclient.getConnectionManager().shutdown();
}
return result;
}
所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,需要使用的JS接口列表
});
分享接口
请注意,原有的 wx.onMenuShareTimeline、wx.onMenuShareAppMessage、wx.onMenuShareQQ、wx.onMenuShareQZone 接口,即将废弃。请尽快迁移使用客户端6.7.2及JSSDK 1.4.0以上版本支持的 wx.updateAppMessageShareData、wx.updateTimelineShareData接口。
自定义“分享给朋友”及“分享到QQ”按钮的分享内容(1.4.0)
wx.ready(function () { //需在用户可能点击分享按钮前就先调用
wx.updateAppMessageShareData({
title: '', // 分享标题
desc: '', // 分享描述
link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: '', // 分享图标
success: function () {
// 设置成功
}
})
});
自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容(1.4.0)
wx.ready(function () { //需在用户可能点击分享按钮前就先调用
wx.updateTimelineShareData({
title: '', // 分享标题
link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: '', // 分享图标
success: function () {
// 设置成功
}
})
});
常见2个错误及解决方法
invalid signature签名错误。建议按如下顺序检查:
1、确认签名算法正确,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
2、确认config中nonceStr(js中驼峰标准大写S), timestamp与用以签名中的对应noncestr, timestamp一致。
3、确认url是页面完整的url(请在当前页面alert(location.href.split(‘#’)[0])确认),包括’http(s)/‘部分,以及’?‘后面的GET参数部分,但不包括’#'hash后面的部分。
4、确认 config 中的 appid 与用来获取 jsapi_ticket 的 appid 一致。
确保一定缓存access_token和jsapi_ticket。
5、确保你获取用来签名的url是动态获取的,动态页面可参见实例代码中php的实现方式。如果是html的静态页面在前端通过ajax将url传到后台签名,前端需要用js获取当前页面除去’#‘hash部分的链接(可用location.href.split(’#')[0]获取,而且需要encodeURIComponent),因为页面一旦分享,微信客户端会在你的链接末尾加入其它参数,如果不是动态获取当前链接,将导致分享后的页面签名失败。
the permission value is offline verifying这个错误是因为config没有正确执行,或者是调用的JSAPI没有传入config的jsApiList参数中。建议按如下顺序检查:
1、确认config正确通过。
2、如果是在页面加载好时就调用了JSAPI,则必须写在wx.ready的回调中。
3、确认config的jsApiList参数包含了这个JSAPI。