前段时间用React写了几个微信公众号的网页,遇到的主要阻力就是微信的网页授权问题和JS-SDK使用问题。
根据微信官方网页授权介绍,只有在需要获取用户信息时,才需要用到微信网页授权机制。关于如何进行网页授权,官方文档已经介绍很详细了,这里主要记录一下使用过程中遇到的问题以及需要注意的地方。
由于一个公众号的网页授权access_token是所有人共用的,如果每个需要用到授权的服务都自己调用微信提供的获取access_token的接口,每调用一次都会刷新一遍access_token,之前的access_token将会过期,access_token也就无法实现缓存。而access_token默认是2小时内有效的,所以在开发时,需要有一个统一的授权中心,授权中心负责access_token的获取缓存和更新。
统一授权还有一个好处:在开发时,公众号上面的不同菜单的网页可能是部署在不同的服务器,但是公众号后台的网页授权域名设置只能设置一个,用统一授权就可以解决这个问题。网页授权域名设置为授权中心的域名,腾讯后台的回调都会回调到授权中心,由授权中心转发重定向回每个菜单的页面。
调用授权接口时,要求传入一个回调地址redirect_uri。这个redirect_uri的域名必须要和公众号平台-设置-公众号设置-功能设置-网页授权域名一致。
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
回调网页还要一个需要注意的地方:如果调用授权的页面和配置的redirect_uri是同一个页面,会出现从redirect_uri页面后退时,又重新进入到调用授权页,导致无法退出的问题。
这个问题可以通过监听popstate,如果返回到了授权页,则直接调用wx.closeWindow();
退出来解决。
window.addEventListener("popstate", function(e) {
console.log("test");
var documentUrl = document.location.href;
console.log("documentUrl=" + documentUrl );
if (documentUrl.indexOf("weixin.qq.com") !== -1) {
wx.closeWindow();
}
}.bind(this));
微信JS-SDK说明文档里介绍的引入js-sdk的方式是在script
标签里引入http://res.wx.qq.com/open/js/jweixin-1.4.0.js
。这种方式在react上并不合适。
React上集成的方式和文档介绍的方式只有引入方式不一样,react需要先安装weixin-js-sdk的依赖包,再import。
npm install weixin-js-sdk
import wx from 'weixin-js-sdk';
import React from 'react';
import './DeviceList.css';
import Device from './Device';
import DeviceIcon from '../images/device_icon.png';
import ReactList from 'react-list';
import wx from 'weixin-js-sdk';
import InfiniteScroll from 'react-infinite-scroller';
const baseUrl = process.env.PUBLIC_URL;
const serverIp = process.env.REACT_APP_SERVER_IP;
const resourceServerIp = process.env.REACT_APP_RESOURCE_SERVER_IP;
const wxAppId = process.env.REACT_APP_WX_APPID;
const jsAuthAddress = process.env.REACT_APP_WX_JS_AUTH_ADDRESS;
export default class DeviceList extends React.Component {
constructor(props) {
super(props);
this.state = {
tracks: [],
hasMoreItems: true,
nextHref: null,
latitude:-1,
longitude:0
};
}
componentWillMount() {
this.fetchSignatureAndLocation();
}
componentDidMount() {
}
loadItems(page) {
var that = this;
var url = serverIp + '/devices/' + this.state.longitude + '/' + this.state.latitude + '/'+ page;
if (this.state.nextHref) {
url = this.state.nextHref;
}
fetch(url)
.then((res) => res.json())
.then((responseJson) => {
if (responseJson.flag == "00") {
var tracks = that.state.tracks;
responseJson.data.list.map((track) => {
tracks.push(track);
});
if (responseJson.data.hasNextPage) {
that.setState({
tracks: tracks,
nextHref: serverIp + '/devices/' + this.state.longitude + '/' + this.state.latitude + '/' + responseJson.data.nextPage
});
} else {
that.setState({
hasMoreItems: false
});
}
} else {
alert(responseJson.message);
}
});
}
checkJSApi() {
wx.checkJsApi({
jsApiList: [
'getLocation'
],
success: function (res) {
console.log('wx check js api',res);
if (res.checkResult.getLocation === false) {
alert('你的微信版本太低,不支持微信JS接口,请升级到最新的微信版本!');
return;
}
}
});
}
fetchSignatureAndLocation() {
var that = this;
var url = jsAuthAddress +'?appid='+ wxAppId + '&url=' + resourceServerIp + baseUrl + "/";//统一授权中心
fetch(url)
.then((res) => res.json())
.then((responseJson) => {
console.log(responseJson.message)
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: wxAppId, // 必填,公众号的唯一标识
timestamp: responseJson.timestamp, // 必填,生成签名的时间戳
nonceStr: responseJson.noncestr, // 必填,生成签名的随机串
signature: responseJson.signature,// 必填,签名
jsApiList: [
'checkJsApi',
'openLocation',
'getLocation'
] // 必填,需要使用的JS接口列表
});
wx.ready(function () {
console.log('wx config ready');
//基础接口判断当前客户端版本是否支持指定JS接口
wx.getLocation({
type : 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success : function(res) {
// alert(JSON.stringify(res));
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
// $("#latitude").val(latitude);
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
// $("#longitude").val(longitude);
var speed = res.speed; // 速度,以米/每秒计
// $("#speed").val(speed);
var accuracy = res.accuracy; // 位置精度
// $("#accuracy").val(accuracy);
that.setState({
longitude: res.longitude,
latitude: res.latitude
})
},
cancel : function(res) {
alert('用户拒绝授权获取地理位置');
}
});
});
wx.error(function(res){
console.log('wx error', res);
alert('wx config error');
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
})
.catch((error) => {
console.error(error);
});
}
renderItem(index, key) {
var distanceStr = '距离您' + this.state.deviceList[index].distance;
var metroStationDes = this.state.deviceList[index].metroLine + '-'+ this.state.deviceList[index].name
return ( );
}
render() {
const loader = Loading ...;
var items = [];
let scrool;
this.state.tracks.map((track, i) => {
var distanceStr = '距离您' + track.distance;
var metroStationDes = track.metroLine + '-'+ track.name;
items.push(
);
});
if (this.state.latitude != -1) {
scrool =
{items}
}
return (
{scrool}
)
}
}
引入JS-SDK也是需要授权的,我们也可以做一个统一的授权中心。和网页授权不一样的是,即使有这个统一的授权中心,同一个公众号的使用了js-sdk的网页必须部署在同一个服务器下。因为js-sdk的不仅校验回调的域名,还会校验使用js-sdk的网页路径,只有调用腾讯的授权接口时传入的回调url和我们网页部署之后访问的url一致才能通过校验。