小白学react之网页获取微信用户信息

react-webpack.png

通过上一篇《小白学react之EJS模版实战》我们学习了怎样通过EJS模版生成我们高定制化的index.html文件。

本篇我们将会继续延续我们的alt-tutorial项目的实战计划。去获取微信扫码用户的信息。并将头像显示在我们页面的右上角上。

终于实战效果将例如以下所看到的。

首先依据我们的站点url生成二维码,比方我们能够通过浏览器的FeHelper来生成:

FeHelper.png

然后用户通过微信扫码:

wechat_auth_demo.png

最后用户确定授权后获取到用户的基本信息,并将头像显示在右上角:

avatar_demo.png

1. 内网穿透准备

我们获取微信用户信息的过程中,须要给微信提供回调页面。为了方便调试,我们会将回调页面指向我们本地。

这种话,我们就须要有个办法能让外网(微信server)能够直接訪问到我们内网中来。

这里我用的内网穿透工具是ngrok,这是海外同行提供的一款内网穿透工具。使用非常方便。大家大可网上查看下该怎样配置,这里就不多费唇舌了。

安装好后,通过命令:

./ngrok http 8080

ngrok就会帮我们将http的8080端口暴露到外网,并为我们分配一个随机的外网訪问url地址:

ngrok.jpeg

2. 微信測试号准备

假设您的微信公众号是个人号的话,那么微信将仅仅会给你提供非常有限的一些调用接口。所以这里作为个人开发人员,我们在调试的时候须要用到微信測试号。

详细怎么进入到微信測试号,我这里也不会多废话。大家自行去搜索下就好了。

这里我们主要有几个事情须要做的:

2.1. 获取微信測试号的appID和appsecret

我们在获取微信用户信息的时候。须要先通过appID获取到code,然后再依据code和appsecret获得access_token。最后依据token获取到用户的信息。

所以我们这里须要获得appID和appsecret。进入微信測试号管理后台后,在左上方我们就能够看到相应的信息:

appID&appsecret.jpeg

2.2. 网页回调域名设置

微信用户扫码之后,会弹出授权信息。用户确定授权之后,微信将会依据提供的回调页面进行应用的继续的訪问。

我们在授权之后,希望用户能開始訪问我们的app,所以我们这里须要设置好回调域名。

在微信測试号后台的同一个管理页面以下。我们能够找到网页服务相关的设置:

网页账号.jpeg

找到“网页授权获取用户基本信息”之后。点击右边的改动会弹出以下页面,我们在此填入我们的回调域名。

注意别加上http前缀。这里我们填写的就是上面ngrok给我们生成的外网訪问url地址的域名部分:

网页回调域名设置.png

2.3. 关注測试号

通过測试号进行调试的话,我们的測试手机上的微信须要关注该測试号。我们相同能够在測试号管理后台的同一个页面进行扫码关注,达成我们的目的:

关注測试号.jpeg

3. client获取code

3.1. 获取用户信息流程简述

依据微信的《公众平台开发人员文档》。通过网页获取微信用户基本信息的流程例如以下:

  1. 引导用户进入授权页面允许授权。获取code
  2. 通过code换取网页授权access_token(与基础支持中的access_token不同)
  3. 假设须要。开发人员能够刷新网页授权access_token,避免过期
  4. 通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

作为练习。我们第3步能够跳过。

所以我们这里的第一步是先要去获得code。

依据文档。获取code的时候第一步就是去訪问以下的页面:

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

当中我们要提供的有appid和回调地址redirect_uri。其它部分保持不变。

3.2. 授权页面url生成

这里我们提供一个方法来生成这一长段url地址:

function generateGetCodeUrl(redirectURL) {

    return new URI("https://open.weixin.qq.com/connect/oauth2/authorize")
        .addQuery("appid", confidential.APP_ID)
        .addQuery("redirect_uri", redirectURL)
        .addQuery("response_type", "code")
        .addQuery("scope", "snsapi_userinfo")
        .addQuery("response_type", "code")
        .hash("wechat_redirect")
        .toString();
};

前面说的我们须要提供的两个数据中,回调页面地址我们通过參数redirectURL传入。appID我将其封装到一个独立的文件config/Confidential.js中,大家依据自己的情况填写就好了(须要注意的是,该文件我会在.gitignore文件里配置成不上传到github。所以大家记得自己自行加入)。

const confidential = {
    APP_ID: 'xxxxxx', //Please use your owe app id;
    APP_SECRET: 'xxxxxxxxx', //Please use your owe secret
};

export default confidential;
3.3. 进入根路由时引导用户进入授权页

封装好微信授权页面后,我们就要考虑应该在什么时间点引导用户进入授权页面了。

我这里是希望用户在扫码之后立马进入授权页面,所以我们应该在进入第一个页面之前就打开引导页。

那么怎样做到呢?这里我们须要用到react router的onEnter属性方法。通过这种方法,当用户訪问某个路由之前,会先运行onEnter指定的方法。

比方我们希望在进入根路由后先检查用户授权:

class RootRouters extends Component {
  ...
    render() {
        const { history } = this.props;
        return (
            <Router history = {history} >
                <Route name='index' path ="/" onEnter={this.wechatAuth.bind(this)} component={Home} >
                    <IndexRoute name="about" component={About}/>
                    <Route name="about" path ="/about" component={About} />
                    <Route name="locations" path ="/locations" component={Locations} />
                </Route>
            </Router>
        );
    }
  ...
}

那么当用户訪问http://localhost:8080 或者从外网訪问ngrok帮我们生成的外网url(我这里是http://79214cd7.ngrok.i )的时候,就会先去调用wechatAuth这种方法。

class RootRouters extends Component {
  ...
  wechatAuth(nextState, replace, next) {

        const uri = new URI(document.location.href);
        const query = uri.query(true);
        const {code} = query;

        if(code) {
            WechatUserStore.fetchUserInfo(code);

            next();

        } else {

            document.location = generateGetCodeUrl(document.location.href);
        }

    }
  ...
}

在wechatAuth这种方法中。最关键的就是后面这一句:

 document.location = generateGetCodeUrl(document.location.href);

其目的就是将当前页面的url作为回调页面传入到上面的generateGetCodeUrl里面,生成授权页面url,然后将其赋值给页面的document.location,其结果就是当前页面会被授权页面覆盖掉,其呈现效果就是用户将会进入到本文最上面提及的授权页面。

当用户授权之后。就会重定向到回调页面。也就是我们的ngrok生成的外网訪问url,且这时候该url会通过query的方式带上要传过来的code比方:http://79214cd7.ngrok.io?code=xxxxxxx&state=STATE 。当中xxxxxx就是code的值。

这样就又会再次訪问我们的根由路,也就会再次进入到wechatAuth这种方法里面。由于这次通过微信回调回来的訪问中query会带有code的信息。所以这该段代码前面会通过urijs包的功能来先把code解析出来。

这个时候我们就会推断这个code是否存在,假设不存在(用户手动扫码訪问页面的时候没有带query,所以这个code不存在)的话,就重定向到微信授权页面;存在的话。就通知server去依据code获取用户的基本信息。

3.4. client发送code到服务端请求用户信息

这里跟server的沟通我们封装到alt框架的Store里面。整一套alt的Actions,Store 和Source的构建,通过我们之前的学习。我相信我们已经是驾轻就熟的了。这里我们主要看下Source中是怎样和server沟通的就好了,其它有什么不清晰的大家能够通过文章后面的描写叙述直接查看源代码。或者往回翻下我站点上关于小白学react的系列文章。

import defaults from 'superagent-defaults';
import superagentPromisePlugin from 'superagent-promise-plugin';
var WechatUserActions = require('../actions/WechatUserActions');
import co from 'co';
const request = superagentPromisePlugin(defaults());


var WechatUserSource = {
  fetchUserInfo() {
    return {
      remote(state,code) {
        return co(function *() {
          let userInfo = null;
          const getUserInfoUrl = `/api/user_info?code=${code}`;
          try {
            let result = yield request.get(getUserInfoUrl);
            userInfo = result.text;
          } catch (e) {
            userInfo = null;
          }
          //console.log("userInfo:", userInfo);
          return userInfo;
        });
      },

      local() {
        // Never check locally, always fetch remotely.
        return null;
      },

      success: WechatUserActions.updateUserInfo,
      error: WechatUserActions.getUserInfoFailed,
      loading: WechatUserActions.getUserInfo,
    }
  }
};

module.exports = WechatUserSource;

这里跟服务端的沟通事实上和之前的LocationSource没有太大差别。相同是发送一个get的request请求/api/user_info?

code=${code}到server端,并在request的url中带上code这个query。当服务端通过code获取到用户信息之后,再返回来就出发WechatUserActions的updateUserInfo这个Action。跟着这个Action就会出发WechatUserStore中的onUpdateUserInfo的方法:

class WechatUserStore {
  constructor() {
    this.userInfo = [];
    this.errorMessage = null;

    this.bindActions(WechatUserActions);

    this.exportAsync(WechatUserSource);

  }
  onUpdateUserInfo(userInfo) {
    this.userInfo = JSON.parse(userInfo);
    this.errorMessage = null;
  }
  ...
}

这种方法会将返回来的用户信息字串转化成json格式,然后保存起来本WechatUserStore的userInfo成员变量上面。

4. server端依据code获取用户信息

如今client已经获取到code,并将code通过/api/user_info?code=${code}请求传到server端。那么server端要做的事情就是依据这个code来获得相应的用户信息。

4.1. 服务端依据code和appsecret获取access_token和openid

当中server要做的第一步事情就是依据client传过来的code获取到訪问用户信息的access_token。

那么我们这里首要的就是要在express服务端应用中加入”/api/user_info”的訪问路由:

app.get("/api/user_info", function(req,res) {
  ...
}

然后在该代码里面開始获取access_token的信息。

依据微信开发文档,获取该access_token的请求url的格式例如以下:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

这里我们须要同一时候提供appID,appsecret和刚才获得的code。

所以我们这里仅仅须要按需求填进去就好了:

const code = req.query.code;
const getTokenUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${confidential.APP_ID}&secret=${confidential.APP_SECRET}&code=${code}&grant_type=authorization_code`;

填好之后我们就能够通过superAgent来将该请求发送到微信server并读取返回了:

    co ( function *() {
        try {
            let result = yield request.get(getTokenUrl);
            tokenInfo = JSON.parse(result.text);
        }catch (e) {
            console.log("exception on getting access token");
            tokenInfo = null;
        }
        console.log("token info:",tokenInfo);
    ...
  }

从官方提供的开发文档能够看到。终于返回的数据有:

{
   "access_token":"ACCESS_TOKEN",
   "expires_in":7200,
   "refresh_token":"REFRESH_TOKEN",
   "openid":"OPENID",
   "scope":"SCOPE",
   "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

当中access_token就是我们以下获取用户须要用到的token,而openid就是该訪问用户的唯一id信息,这就是我们的目标用户。

4.2 服务端依据access_token和openid获取用户信息

获取到access_token和用户的openid之后,我们就能够開始获取用户的基本信息了。

获取用户的基本信息的请求uri格式例如以下:

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

这个请求uri比起前面获取access_token的略微简单点。我们仅仅须要传入access_token和用户的openid就ok了。

整个请求和解析流程代码例如以下:

        if( tokenInfo != null ) {
            const getUserInfoUrl = `https://api.weixin.qq.com/sns/userinfo?

access_token=${tokenInfo.access_token}&openid=${tokenInfo.openid}&lang=zh_CN`; try { const result2 = yield request.get(getUserInfoUrl); userInfo = JSON.parse(result2.text); console.log("userInfo:",userInfo); } catch(e) { console.log("Exception on getting user info"); userInfo = null; } console.log("userInfo:",userInfo); } if (userInfo) { res.send(userInfo); }

当我们前面获取的access_token没有不论什么问题的时候,我们就会依据该token和用户的openid发送请求到微信server来获取用户基本信息。

当成功获取到用户的基本信息,最后就将其返回给相应的请求client。

5. client渲染用户信息

为了能将用户的头像在全部页面都进行显示,我们这里将头像的显示实如今Home页面里面。

我们的Home页面之前仅仅是显示前面的几个标签而已。随着我们慢慢将很多其它的内容加到Home页面,起维护起来必定会变得困难。

所以这里我们顺便将src/components/Home.jsx的代码略微重构下。将头部导航标签,显示内容,头像,分开不同的组件,以方便维护:

import React from 'react'
import { Link } from 'react-router'
import  './Home.scss'
import BaseLayout from "./BaseLayout.jsx";
import WechatUserStore from "../stores/WechatUserStore";
var AltContainer = require('alt-container');

//src='http://wx.qlogo.cn/mmopen/Q3auHgzwzM7HOy8WWY1gCyH8PW2DEhY0S3Rz44cn8TJpGwNWyRuYblWuRPEAPhJ69NXC2TFBYjmODoOxfElibRVnLcaHroQ9HqqItGicxPsYk/0'/>

class Headers extends React.Component{
    render() {
        console.log("props.in header:", this.props);

        return (
            <div>
                <nav >
                    <li className="home__tab__li"><Link to="/locations">名胜古迹</Link></li>
                    <li className="home__tab__li"><Link to="/about">关于techgogogo</Link></li>
                </nav>
            </div>

        )
    }
}

class Avatar extends React.Component{
    render() {
        console.log("props.in avatar:", this.props);
        if (WechatUserStore.isLoading()) {
            return (
                <div className="home__avatar">
                    <img src="ajax-loader.gif" />
                </div>
            )
        }

        return (
            <div className="home__avatar">
                <img
                    src={this.props.userInfo.headimgurl}/>
            </div>
        )
    }
}

class Contents extends React.Component{
    render() {
        console.log("props.in content:", this.props);

        return (
            <div>
                <div style={{clear: "both"}}></div>
                {this.props.contents}
            </div>
        )
    }
}

class Home extends React.Component{

    render() {
        return (
            <BaseLayout title="Home" style={{"backgroundColor": "white"}}>

                <Headers/>

                <AltContainer store={WechatUserStore}>
                    <Avatar />
                </AltContainer>


                <div>
                    <Contents contents={this.props.children}> </Contents>
                </div>
            </BaseLayout>
        )
    }
}

module.exports = Home;

这样看起来就清晰多了。且改动不论什么一个组件都不会影响到其它组件,从而让代码更吻合上一篇文章提到的OCP原则。

这里新添加的微信用户头像组件就是Avatar这个Component。

起渲染逻辑就是,当用户信息还没有从服务端取到的时候,显示的是loading的gif图片。当已经获得了用户信息之后,显示的就是微信用户的头像。

最后显示的位置和样式我们通过在Home.scss里面添加avatar样式来实现:

    &__avatar {
        border-radius: 50%;
        border: rem(19px) solid white;
        margin: rem(5px) rem(20px);
        height: rem(260px);
        width: rem(260px);
        background: #d3d3d3;
        overflow: hidden;
        float:right;
        img {
            width: 100%;
            height: 100%;
        }

    }

设计的基本考虑就是将圆形显示该图片,且浮动在页面的右上角。详细效果请查看文章開始时候的demo图片。

5. 源代码

git clone https://github.com/kzlathander/alt-tutorial-webpack.git
cd alt-tutorial-webpack
checkout 09
npm install
npm run build

同一时候

本文由天地会珠海分舵编写。转载需授权,喜欢点个赞,吐槽请评论,进一步交流请关注本人天地会珠海分舵以及《微信程序开发》主题。

《未完待续》

posted @ 2018-04-03 08:51  zhchoutai  阅读(1598)  评论(0编辑  收藏  举报