Vue Wechat

项目结构

  • 前端:Vue
  • 后端:Koa
  • 数据库:MySQL

问题

  • SPA单页应用如何解决微信授权时的跳转?
  • SPA单页应用应该在那个环节获取微信授权呢?

微信网页授权

微信网页授权操作流程

  1. 用户同意授权以获取code
  2. 通过code换取网页授权的access_token

首先需要明确的是,前后端分离的项目,是不能直接在前端使用AJAX调用微信接口获取到用户信息的。为什么?前端是基于浏览器,浏览器具有同源策略限制。使用AJAX直接获取本域名外的地址,是需要跨域处理的。虽然前端提供了JSONP,但需要服务端配置。你在前端使用JSONP访问微信的接口,微信的程序员是不会配合你的。所以除了获取code直接使用跳转到微信,微信在跳转会你的前端页面是可行的。不过需要将前端的地址配置到公众后台。否则redirect_uri不会被微信识别。至于网页授权的其他接口,直接使用AJAX访问,微信则会提示跨域错误。因此,必须将这些接口放到后端来实现。

用户授权获取code,发起授权何时触发呢?

  • 前端获取code传递给后端

向微信发起授权时需传递一个重定向的 redirectUri 地址,该地址用于获取微信返回的 code。当微信接收到授权请求后会将 code 拼接到 redirectUri 链接后面并重定向回来。我们可以拿到code之后再传递给后端,后端利用 code 去获取access_token。

若前后端分离且使用不同的URL地址,此时需要在微信公众平台配置前后端两套域名,因为前端也调用了微信接口,不然会出现 redirect_uri 参数错误的提示。

  • 前端传递授权地址,后端调用微信接口

前端首先获取当前地址并传递给后端,后端通过微信授权接口,将此地址作为微信授权中的redirect_uri参数,重定向到前端后获取code。

前端首先使用路由拦截,然后先后端发起请求。后端向微信发起获取code的请求,微信重定向到前端授权页面,在前端授权页面中获取code值。前端获取code值后向后端发送请求,后端向微信发起授权请求并获取用户数据,返回给前端。

  • 后端认证授权返回前端地址

前后端约定授权成功后返回的前端地址,用户直接访问后端微信授权的地址。后端与微信交互时前端不参与。当授权成功后,后端在跳转到前端指定地址。若前端地址修改,则后端必须更改。

这里采取的方式是在前端通过微信接口获取code,再请求后端通过微信接口获取access_token,通过access_token获取用户信息,完成用户关注后自动注册。

需要注意的,由于前端使用微信接口,针对使用vue做了前后端分离的项目,需要提前为前端配置域名,并在微信公众平台中添加,这样前端才能访问微信接口,否则会出现 redirect_uri 错误的提示。

微信网页授权获取票据code

微信的code参数作为换取access_token的票据,每次用户同意授权后,所携带的code值都不一样。code只能使用一次,若5分钟未被使用将自动过期。

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE&connect_redirect=1#wechat_redirect

由于微信接口授权操作安全等级较高,在发起授权请求时,微信会对授权链接做正则匹配校验。若链接参数顺序错误,授权页面将无法正常访问。

接口参数 必填 描述
appid 微信公众号开发者唯一编号
redirect_uri 微信授权后重定向的回调链接地址,必须使用urlencode对链接进行处理。
response_type 返回类型,默认为code。
scope 应用授权作用域,分为snsapi_base和snsapi_user_info。
state 重定向后会携带state参数,开发者可自定义参数值,128个字节。
#wechat_redirect -

确保微信公众号拥有授权作用域即scope参数的权限下,使用授权接口。微信服务号获取高级接口后,默认拥有scope参数中的snsapi_basesnsapi_userinfo

应用授权作用域 描述
snsapi_base 静默授权,不弹出授权界面,直接跳转,只能获取用户openid,无法获取用户信息。
snsapi_userinfo 主动授权,弹出授权界面,可通过openid获取昵称、性别、所在地。

静默授权由于是自动授权并跳转的,因此对用户来说是无感知的,但这种授权方式只能获取用户的openid,无法获取用户其他信息。在只需要标识用户而无需收集其他信息的场景中使用。比如投票、点赞等。

主动授权是当用户进入页面后会弹出授权窗口,需手动同意。因此可获取用户的基本信息。对于已关注微信公众号的用户,当用户从公众号会话或自定义菜单进入授权页面时,即使使用snsapi_userinfo也是静默授权的。

当用户同意授权后页面间跳转到redirect_uri/?code=CODE&state=STATE页面。

vue中使用微信授权获取code值

$ vim Index.vue

在vue的craeted方法中,首先判断运行环境是否为生产环境,然后判断是否为微信浏览器,通过获取当前连接地址中的code参数来判断,是否使用微信接口来授权。如果没有code则跳转授权。若获取到code值则根据code值去请求后台微信接口获取用户信息。

created(){
    const env = process.env.NODE_ENV;
    if(env === "production"){
        if(!this.isWechat()){
            Toast("请在微信中打开");
            return;
        }
        const code = this.$route.query.code;
        //console.log(code, !code, !!code, code===undefined);
        if(code === undefined){
            const appId = process.env.VUE_APP_WX_APPID;
            //const redirectUrl = process.env.VUE_APP_URL;
            const redirectUrl = window.location.href;//当前地址
            this.auth(appId, redirectUrl);
        }else{
            //微信授权获取用户信息
            const url = `${this.apiUrl}/api/grant?code=${code}`;
            this.axios.get(url).then(res=>{
                const ret = res.data;
                if(ret.code !== 200){
                    Toast(ret.message);
                    return;
                }
            }).catch(err=>{
                //console.error(err);
            });
        }
    }
    this.env = env;
},
methods:{    //微信浏览器判断
    isWechat(){
        const ua = window.navigator.userAgent.toLowerCase();
        return ua.indexOf('micromessenger') !== -1;
    },
    //微信授权
    auth(appId, redirectUrl, state="STATE"){
        const redirectUri = encodeURIComponent(redirectUrl);
        let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
        console.log(url);
        window.location.href = url;
    },
}

是否需要缓存code呢?

当用户进入目标URL首先会获取code,然后将code传递给后端以获取用户信息并返回。缓存code会出现的问题是,如果code缓存时间过长则会出现重复code。这里面涉及的问题是code是否能够被重复消费呢?如果使用code作为缓存键值,则需要定时清理。当使用code能获取到缓存则走缓存,否则重新获取。为避免出现code出现重复,因此需要定理清理缓存。因为code是一次性的,若重复消费则在获取微信openid时会出现为空的现象。

{
  “errcode”:40163,
  "errmsg":"code been used"
}

如何解决刷新网页出现code重复消息的问题呢?

通过code获取openid时,code只能使用一次。因此,可以在第一次获取到opennid后就将其缓存起来。刷新时判断openid是否存在,若不存在则通过code获取openid。

JavaScript封装

//网页授权 获取临时凭证code
exports.authorize = (appid, redirect_uri, state="STATE", scope="snsapi_userinfo")=>{
    const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
    return url;
};

access_token

微信开发中access_token分为两种,一种是普通的access_token即公众号接口全局唯一票据,是调用接口访问的凭证。另一种是网页授权时通过code换取的access_token,用于获取微信用户信息。若两种access_token发生混淆则会出现"invalid access_token"的错误。

网页授权凭证 access_token

当用户在微信客户端中访问第三方页面时,微信公众号可通过微信网页授权机制,获取用户的基本信息。

微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获得一个网页授权特有的接口调用凭证access_token,通过此access_token可以在授权后调用接口获取微信用户基本信息。

网页授权凭证access_token有效期为7200秒,用于获取对应微信用户的信息,与微信用户是一对一的关系且调用次数无限制。使用网页授权时,不管用户是否关注过微信公众号,均可获取对应用户的个人信息。

网页授权接口地址GET

GET https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
接口参数 必须 描述
appid 微信公众号唯一标识,开发者账号。
secret 微信公众号开发者密钥
code -
grant_type 授权类型,为authorization_code。

取网页授权凭证时会遇到跨域问题,解决的方法是利用代理进行中转。

跨域是浏览器的同源策略问题而衍生出的需求,跨域请求是指服务器A的页面去请求服务器2的资源,服务器1和2之间只要域名、端口、IP不同都属于跨域。

在Node.js环境中,可以使用SuperAgent,SuperAgent是一个轻量级、灵活的、易读的、低学习曲线的客户端请求代理模块。

成功返回

{ 
  "access_token":"ACCESS_TOKEN",
  "expires_in":7200,
  "refresh_token":"REFRESH_TOKEN",
  "openid":"OPENID",
  "scope":"SCOPE" 
}
成功参数 描述
access_token 网页授权接口调用凭证
expires_in 网页授权接口调用凭证超时时间,单位为秒。
refresh_token 用户刷新access_token
openid 用户唯一标识
scope 用户授权作用域

当用户未关注公众号时,访问需网络授权的页面时,也会产生一个用户和公众号唯一的openid。

错误返回

{
 errcode: 40125,
 errmsg: 'invalid appsecret, view more at http://t.cn/RAEkdVq, hints: [ req_id: YhmAs.5ce-Jw_PAa ]'
}

出现错误码40125,提示非法的AppSecret。核对32位AppSecret并未发现错误,可微信公众平台重置AppSecret。并在代码中对应位置错误处理。出现这种问题,可能是微信公众平台中的AppSecret被重置导致原始AppSecret失效造成的。

JavaScript封装

//网页授权 根据code换取access_token。
exports.accessToken =  (appid, secret, code)=>{
    const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`;
    return this.httpGet(url);
};

刷新访问凭证

关于refresh_token为什么需要刷新呢?

因为access_token的使用周期是7200秒(2小时),若超时则失效就无法再使用。此时就需要进行刷新操作,根据原来获取access_token时返回的refresh_token来刷新。

refresh_token的有效期为30天,但refresh_token失效后则需用户重新授权。

https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
接口参数 必须 描述
appid 微信公众号唯一标识
grant_type 授权类型refresh_token
refresh_token 通过access_token获取的refresh_token

成功返回

{ 
  "access_token":"ACCESS_TOKEN",
  "expires_in":7200,
  "refresh_token":"REFRESH_TOKEN",
  "openid":"OPENID",
  "scope":"SCOPE" 
}
返回参数 描述
access_token 网页授权接口调用凭证
expires_in access_token接口调用凭证超时秒数
refresh_token 用户刷新的access_token
openid 用户唯一标识
scope 用户授权作用域,多个使用逗号分隔

错误返回

{
  "errcode":40029,
  "errmsg":"invalid code"
}

JS封装

//网页授权 access_token超时刷新
exports.refreshToken = (appid, refresh_token)=>{
    const url = `https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=${appid}&grant_type=refresh_token&refresh_token=${refresh_token}`;
    return this.httpGet(url);
};

接口访问凭证access_token

access_token是微信公众号全局唯一票据,是公众号调用接口访问的凭证。

access_token存储至少要保存512个字符空间,有效期为2小时7200秒,需定时刷新。当access_token过期时,才需要再次调用接口获取。理想情况下,一个7x24小时运行的系统,每天需要获取12次access_token,即每隔2小时获取一次。若在有效期内再次获取access_token将会导致上次获取的access_token失效。

获取access_token接口调用频率限制为2000次/天,因此需要将获取到的access_token存储起来,然后定期调用access_token更新,以保证随时取出的access_token都是有效的。

接口地址GET

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
接口参数 必须 描述
grant_type 获取access_token使用client_credential
appid 第三方用户唯一凭证
secret 第三方用户唯一凭证密钥,即appsecret。

成功返回

{
  "access_token":"ACCESS_TOKEN",
  "expires_in":7200
}
成功返回参数 描述
access_token 接口访问凭证
expires_in 凭证有效时长,单位为秒。

错误返回

{
  "errcode":40013,
  "errmsg":"invalid appid"
}
错误返回参数 描述
errcode 错误编码
errmsg 错误信息

JS封装方法

//获取微信接口访问凭证
exports.token= async (appid, secret)=>{
    const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
    return this.httpGet(url);
};

获取微信用户信息

网页授权后若需拉取用户信息,则必须在获取code值将scope接口访问作用域设置为snsapi_userinfo。在通过code获取access_token和openid后,在通过接口获取用户信息。

接口地址

GET https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
接口参数 描述
access_token 网页授权接口调用凭证
openid 用户唯一标识
lang 返回国家地区语言版本,zh_CN简体 / zh_TW繁体 / en英语

成功返回

{    
  "openid": "OPENID",
  "nickname": "NICKNAME",
  "sex": "1",
  "province": "PROVINCE"
  "city": "CITY",
  "country": "COUNTRY",
  "headimgurl":    "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
  "privilege": [ "PRIVILEGE1" "PRIVILEGE2"     ],
  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
返回参数 描述
opoenid 用户唯一标识
unionid 微信公众号绑定到微信开放平台后才会出现此字段
nickname 微信用户昵称
headimgurl 微信用户头像
sex 微信用户性别 0未知 / 1男性 / 2女性
province 省份
city 城市
country 国家,CN中国
privilege 微信用户特权信息,JSON格式。
//网页授权 获取微信用户信息
exports.userinfo =  (access_token, openid, lang="zh_CN")=>{
    const url = `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=${lang}`;
    return this.httpGet(url);
};

微信自定义分享

微信自定义分享主要是分享网页,是将自己的页面分享到微信中以卡片形式展现,含有标题、摘要、缩略图,而非单纯的分享链接。

  • 在微信公众号管理平台中设置JS接口安全域名,域名需通过ICP备案。
  • 微信自定义分享需通过微信认证
  • 微信自定义分享只能定义分享内容和格式,不能为DOM元素绑定事件来执行分享,只能点击微信右上角的分享才有效果。
  • 微信自定义分享需首先引入JSSDK的JS文件

微信自定义分享JSSDK文件

http://res.wx.qq.com/open/js/jweixin-1.2.0.js
http://res.wx.qq.com/open/js/jweixin-1.4.0.js
http://res2.wx.qq.com/open/js/jweixin-1.4.0.js
...

使用JSSDK的页面首先必须注入配置信息,否则将无法调用,每个URL仅需调用一次,对于变化URL的SPA的WebApp可以在每次URL变化时进行调用。

注入配置

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

配置参数可使用后台接口获取,需要获取的参数包括signature、nonceStr、timestamp,最核心的参数是signature签名。

生成签名

  1. 后台获取微信接口全局访问凭证 access_token,接口调用单日2000次限制。
  2. 使用access_token获取jsapi_ticket
  3. 生成签名

获取接口全局访问凭证

//微信接口 获取微信接口访问凭证 access_token
exports.token = async (appid, secret)=>{
    const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
    return this.httpGet(url);
};

成功返回

成功返回参数 描述
access_token 接口访问凭证
expires_in 凭证有效时长,单位为秒,默认7200秒即2小时。

由于access_token有效期为2小时,每日使用上限为2000次。需要缓存并定时刷新,缓存的方式可以使用设置过期时间的Session,也可以使用设置过期时间的Redis。另外,最好添加上时间标识。

使用Redis保存access_token时,由于每个appid会对应一个不同的access_token,因此使用hash哈希形式保存对象,以appid作为key。

jsapi_ticket

jsapi_ticket 是微信公众号用于调用微信JS接口的临时票据,正常情况下,jsapi_ticket的有效期是7200秒即2小时。jsapi_ticket可通过全局接口访问票据access_token来获取。由于获取jsapi_ticket的API接口调用次数具有每日上限,频繁刷新jsapi_ticket将会导致API调用受限,影响自身业务,因此开发者必须在自己的服务器中全局缓存jsapi_ticket。

接口地址

GET https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
//微信接口 获取授权页ticket
exports.getTicket = (access_token, type="jsapi")=>{
    const url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=${type}`;
    return this.httpGet(url);
};

成功返回

{
  "errcode":0,
  "errmsg":"ok",
  "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
  "expires_in":7200
}

封装

由于项目是前后端分离,前端使用vue后端使用koa,后端使用koa做HTTP服务器,用于提供API接口使用。当前端获取到code时,会见code传递给后端。

后端封装微信接口的处理方法

$ npm i -S superagent
$ vim wechat.js
const crypto = require("crypto");
const superagent = require("superagent");

//微信接口错误信息
const error = {
    "-1":"系统繁忙,请稍后再试。",
    "0":"请求成功",
    "40001":"AppSecret错误",
    "40002":"请确保grant_type为client_credential",
    "40164":"调用接口的IP不在白名单内"
};

//SHA1加密
exports.sha1 = code=>crypto.createHash("sha1").update(code).digest("hex");
//生成随机字符串
exports.noncestr = _=>Math.random().toString(36).substr(2, 15);
//生成秒级时间戳
exports.timestamp = _=>parseInt(new Date().getTime() / 1000);
//生成签名
exports.signature = args=>{
    const raw = `jsapi_ticket=${args.jsapi_ticket}&noncestr=${args.noncestr}×tamp=${args.timestamp}&url=${args.url}`;
    console.log(raw);
    const sha1 = this.sha1(raw);
    console.log(sha1);
    return sha1;
};
//微信接口 获取微信接口访问凭证 access_token
exports.token = async (appid, secret)=>{
    const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
    return this.httpGet(url);
};
//微信接口 获取授权页ticket
exports.getTicket = (access_token, type="jsapi")=>{
    const url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=${type}`;
    return this.httpGet(url);
};
//网页授权 获取临时凭证code
exports.authorize = (appid, redirect_uri, state="STATE", scope="snsapi_userinfo")=>{
    const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
    return url;
};
//网页授权 根据code换取access_token。
exports.accessToken =  (appid, secret, code)=>{
    const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`;
    return this.httpGet(url);
};
//网页授权 access_token超时刷新
exports.refreshToken = (appid, refresh_token)=>{
    const url = `https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=${appid}&grant_type=refresh_token&refresh_token=${refresh_token}`;
    return this.httpGet(url);
};
//网页授权 获取微信用户信息
exports.userinfo =  (access_token, openid, lang="zh_CN")=>{
    const url = `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=${lang}`;
    return this.httpGet(url);
};

//CURL发送HTTP协议GET请求
exports.httpGet = async url=>{
    console.log(url);
    const response = await superagent.get(url);
    const result = JSON.parse(response.text);
    //console.log(result);
    return result;
};

你可能感兴趣的:(Vue Wechat)