React-umi-request动态刷新Token功能实现及node.js 代码逻辑
<p>在Antd-pro里面,使用的是umi-request,为了实现动态刷新token,我使用了拦截器。</p><p>拦截器更新token有两种:</p>
<p><strong><span style="font-size: 14px">方法一</span></strong>:在请求发起前拦截每个请求,判断token的有效时间是否已经过期,若已过期,则将请求挂起,先刷新token后再继续请求。</p>
<ul>
<li>优点: 在请求前拦截,能节省请求,省流量。</li>
<li>缺点: 需要后端额外提供一个token过期时间的字段 refreshTime ;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。</li>
</ul>
<p><strong>方法二</strong>:拦截返回后的数据。先发起请求,接口返回过期后,先刷新token,再进行一次重试。</p>
<ul>
<li>优点:不需额外的token过期字段,不需判断时间。</li>
<li>缺点: 会消耗多一次请求,耗流量。</li>
</ul>
<p>综上考虑,方法一和二优缺点是互补的,方法一有校验失败的风险(本地时间被篡改时,当然一般没有用户闲的蛋疼去改本地时间的啦),方法二更简单粗暴,等知道服务器已经过期了再重试一次,只是会耗多一个请求。</p>
<p>我看有的博主用了方法二,我个人更喜欢方法一,(原因就是有的请求要求并发,for循环,一次出去10个请求,所以嘛。。。)</p>
<p>下面是实现过程</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* request 网络请求工具
* 更详细的 api 文档: https://github.com/umijs/umi-request
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { extend } from </span>'umi-request'<span style="color: rgba(0, 0, 0, 1)">;
import { notification } from </span>'antd'<span style="color: rgba(0, 0, 0, 1)">;
import { routerRedux } from </span>'dva/router'<span style="color: rgba(0, 0, 0, 1)">;
import { getUserToken, saveUserToken, clearAuthority } from </span>'./authority'<span style="color: rgba(0, 0, 0, 1)">;
import jwt_decode from </span>'jwt-decode'<span style="color: rgba(0, 0, 0, 1)">
const codeMessage </span>=<span style="color: rgba(0, 0, 0, 1)"> {
</span>200: '服务器成功返回请求的数据。'<span style="color: rgba(0, 0, 0, 1)">,
</span>201: '新建或修改数据成功。'<span style="color: rgba(0, 0, 0, 1)">,
</span>202: '一个请求已经进入后台排队(异步任务)。'<span style="color: rgba(0, 0, 0, 1)">,
</span>204: '删除数据成功。'<span style="color: rgba(0, 0, 0, 1)">,
</span>400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。'<span style="color: rgba(0, 0, 0, 1)">,
</span>401: '用户没有权限(令牌、用户名、密码错误)。'<span style="color: rgba(0, 0, 0, 1)">,
</span>403: '用户得到授权,但是访问是被禁止的。'<span style="color: rgba(0, 0, 0, 1)">,
</span>404: '发出的请求针对的是不存在的记录,服务器没有进行操作。'<span style="color: rgba(0, 0, 0, 1)">,
</span>406: '请求的格式不可得。'<span style="color: rgba(0, 0, 0, 1)">,
</span>410: '请求的资源被永久删除,且不会再得到的。'<span style="color: rgba(0, 0, 0, 1)">,
</span>422: '当创建一个对象时,发生一个验证错误。'<span style="color: rgba(0, 0, 0, 1)">,
</span>500: '服务器发生错误,请检查服务器。'<span style="color: rgba(0, 0, 0, 1)">,
</span>502: '网关错误。'<span style="color: rgba(0, 0, 0, 1)">,
</span>503: '服务不可用,服务器暂时过载或维护。'<span style="color: rgba(0, 0, 0, 1)">,
</span>504: '网关超时。'<span style="color: rgba(0, 0, 0, 1)">,
};
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 异常处理程序
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
const errorHandler </span>= error =><span style="color: rgba(0, 0, 0, 1)"> {
const { response } </span>=<span style="color: rgba(0, 0, 0, 1)"> error;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (response &&<span style="color: rgba(0, 0, 0, 1)"> response.status) {
const errorText </span>= codeMessage ||<span style="color: rgba(0, 0, 0, 1)"> response.statusText;
const { status, url } </span>=<span style="color: rgba(0, 0, 0, 1)"> response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">response) {
notification.error({
description: </span>'您的网络发生异常,无法连接服务器'<span style="color: rgba(0, 0, 0, 1)">,
message: </span>'网络异常'<span style="color: rgba(0, 0, 0, 1)">,
});
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> response;
};
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 配置request请求时的默认参数
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
const request </span>=<span style="color: rgba(0, 0, 0, 1)"> extend({
errorHandler,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 默认错误处理</span>
credentials: 'include', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 默认请求是否带上cookie</span>
<span style="color: rgba(0, 0, 0, 1)">});
let _cacheRequest </span>=<span style="color: rgba(0, 0, 0, 1)"> [];
let guoqi </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
let ispending </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
request.interceptors.request.use(async (url, options) </span>=><span style="color: rgba(0, 0, 0, 1)"> {
const token </span>=<span style="color: rgba(0, 0, 0, 1)"> getUserToken();
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">( token ){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果有token 就走token逻辑</span>
const headers =<span style="color: rgba(0, 0, 0, 1)"> {
Authorization: `Bearer ${token}`,
};
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果是刷新token接口,就直接过,不要拦截它!!!</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>( url === '/weiqinketop/api/account/login/getnewtoken'<span style="color: rgba(0, 0, 0, 1)">){
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ({
url: url,
options: { ...options, headers: headers },
});
}
let decodeToken </span>=<span style="color: rgba(0, 0, 0, 1)"> jwt_decode(token);
const { iat, exp, refreshTime } </span>=<span style="color: rgba(0, 0, 0, 1)"> decodeToken;
const maxTime </span>= exp*1000+<span style="color: rgba(0, 0, 0, 1)">refreshTime;
const nowTime </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date().getTime();
console.log( parseInt((maxTime </span>- nowTime)/1000) +'秒后重新登录')
<span style="color: rgba(0, 0, 255, 1)">if</span>( nowTime >=<span style="color: rgba(0, 0, 0, 1)"> maxTime){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">token过期,而且延期token也过去了,那么,清空你的数据,直接返回登录,不允许操作了</span>
console.log('超时了'<span style="color: rgba(0, 0, 0, 1)">)
clearAuthority()
location.href </span>= '/user/login'
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
}
console.log( parseInt((exp</span>*1000 - nowTime)/1000) + '秒后第一个过期' )
<span style="color: rgba(0, 0, 255, 1)">if</span>( nowTime >= exp*1000<span style="color: rgba(0, 0, 0, 1)"> ){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">只是过期了,那就去拿新的token</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">( ispending ){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果正在发送中,此请求就等一会吧,生成一个Promise 等新token返回的时候,我再resolve</span>
}<span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果没发送,立刻改为发送状态</span>
ispending = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果过期了还没请求新token,那就请求新token 记得带上旧token</span>
request('/weiqinketop/api/account/login/getnewtoken'<span style="color: rgba(0, 0, 0, 1)">,{
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
data:{}
}).then((res)</span>=><span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 0, 255, 1)">if</span>( res.status === 200<span style="color: rgba(0, 0, 0, 1)"> ){
ispending </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
let token </span>=<span style="color: rgba(0, 0, 0, 1)"> res.token;
saveUserToken( token )
_cacheRequest.map(cb </span>=><span style="color: rgba(0, 0, 0, 1)"> cb())
}
})
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Promise((resolve, reject) =><span style="color: rgba(0, 0, 0, 1)"> {
_cacheRequest.push(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
resolve()
})
});
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ({
url: url,
options: { ...options, headers: headers },
});
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ({
url: url,
options: options,
});
})
//第二个拦截器,为什么会存在第二个拦截器呢?就是因为第一个拦截器有可能返回Promise,那么Promise由第二个拦截器处理吧。之前因为这个问题跟umi提了issues。原来是我没搞明白。。。
request.interceptors.request.use(async (url, options) </span>=><span style="color: rgba(0, 0, 0, 1)"> {
const token </span>=<span style="color: rgba(0, 0, 0, 1)"> getUserToken();
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">( token ){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果有token 就走token逻辑</span>
const headers =<span style="color: rgba(0, 0, 0, 1)"> {
Authorization: `Bearer ${token}`,
};
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ({
url: url,
options: { ...options, headers: headers },
});
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ({
url: url,
options: options,
});
})
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> request;</pre>
</div>
<p>说下中途遇到的问题吧,原本只有第一个拦截器,返回Promise,总是会造成请求二次重发,我后来请教官方,得知 resolve( request( url, options) ) 导致的重发请求,现在已经进行了修复,可以正常使用了,后台使用的node.js KOA,下面贴下代码吧</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*** Token功能类 ***</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
const jwt </span>= require('jsonwebtoken'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*** secret编码 ***</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
const secret </span>= '002c6'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">****
* 过期时间延长24小时
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> const refreshTime = 86400000;</span><span style="color: rgba(0, 128, 0, 1)">
/*</span><span style="color: rgba(0, 128, 0, 1)">****
* 过期时间延长10s
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
const refreshTime </span>= 10000<span style="color: rgba(0, 0, 0, 1)">;
class Token{
constructor() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.jwt =<span style="color: rgba(0, 0, 0, 1)"> jwt;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.secret =<span style="color: rgba(0, 0, 0, 1)"> secret;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">******生成的格式就是{alg: "HS256",typ: "JWT"}.{name: "你的名字字段",refreshTime: 10000,iat: 1573704935,exp: 1573704947. </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
createToken( payload , dateStr ){
payload.refreshTime </span>=<span style="color: rgba(0, 0, 0, 1)"> refreshTime;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.jwt.sign(payload, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.secret, { expiresIn: dateStr });
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> module.exports = {</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> proToken : new Token()</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> }</span>
<span style="color: rgba(0, 0, 0, 1)">
exports.proToken </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Token()</pre>
</div>
<p> </p><br><br>
来源:https://www.cnblogs.com/qkstart/p/11856168.html
頁:
[1]