通過上一篇《小白學react之EJS模版實戰》我們學習了怎樣通過EJS模版生成我們高定制化的index.html文件。
本篇我們將會繼續延續我們的alt-tutorial項目的實戰計划。去獲取微信掃碼用戶的信息。並將頭像顯示在我們頁面的右上角上。
終於實戰效果將例如以下所看到的。
首先依據我們的站點url生成二維碼,比方我們能夠通過瀏覽器的FeHelper來生成:
然后用戶通過微信掃碼:
最后用戶確定授權后獲取到用戶的基本信息,並將頭像顯示在右上角:
1. 內網穿透准備
我們獲取微信用戶信息的過程中,須要給微信提供回調頁面。為了方便調試,我們會將回調頁面指向我們本地。
這種話,我們就須要有個辦法能讓外網(微信server)能夠直接訪問到我們內網中來。
這里我用的內網穿透工具是ngrok,這是海外同行提供的一款內網穿透工具。使用非常方便。大家大可網上查看下該怎樣配置,這里就不多費唇舌了。
安裝好后,通過命令:
./ngrok http 8080
ngrok就會幫我們將http的8080端口暴露到外網,並為我們分配一個隨機的外網訪問url地址:
2. 微信測試號准備
假設您的微信公眾號是個人號的話,那么微信將僅僅會給你提供非常有限的一些調用接口。所以這里作為個人開發人員,我們在調試的時候須要用到微信測試號。
詳細怎么進入到微信測試號,我這里也不會多廢話。大家自行去搜索下就好了。
這里我們主要有幾個事情須要做的:
2.1. 獲取微信測試號的appID和appsecret
我們在獲取微信用戶信息的時候。須要先通過appID獲取到code,然后再依據code和appsecret獲得access_token。最后依據token獲取到用戶的信息。
所以我們這里須要獲得appID和appsecret。進入微信測試號管理后台后,在左上方我們就能夠看到相應的信息:
2.2. 網頁回調域名設置
微信用戶掃碼之后,會彈出授權信息。用戶確定授權之后,微信將會依據提供的回調頁面進行應用的繼續的訪問。
我們在授權之后,希望用戶能開始訪問我們的app,所以我們這里須要設置好回調域名。
在微信測試號后台的同一個管理頁面以下。我們能夠找到網頁服務相關的設置:
找到“網頁授權獲取用戶基本信息”之后。點擊右邊的改動會彈出以下頁面,我們在此填入我們的回調域名。
注意別加上http前綴。這里我們填寫的就是上面ngrok給我們生成的外網訪問url地址的域名部分:
2.3. 關注測試號
通過測試號進行調試的話,我們的測試手機上的微信須要關注該測試號。我們相同能夠在測試號管理后台的同一個頁面進行掃碼關注,達成我們的目的:
3. client獲取code
3.1. 獲取用戶信息流程簡述
依據微信的《公眾平台開發人員文檔》。通過網頁獲取微信用戶基本信息的流程例如以下:
- 引導用戶進入授權頁面允許授權。獲取code
- 通過code換取網頁授權access_token(與基礎支持中的access_token不同)
- 假設須要。開發人員能夠刷新網頁授權access_token,避免過期
- 通過網頁授權access_token和openid獲取用戶基本信息(支持UnionID機制)
作為練習。我們第3步能夠跳過。
所以我們這里的第一步是先要去獲得code。
依據文檔。獲取code的時候第一步就是去訪問以下的頁面:
當中我們要提供的有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的格式例如以下:
這里我們須要同一時候提供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
同一時候
本文由天地會珠海分舵編寫。轉載需授權,喜歡點個贊,吐槽請評論,進一步交流請關注本人天地會珠海分舵以及《微信程序開發》主題。
《未完待續》