请问 如何在request.js文件中 实现路由跳转,希望通过response的状态 来跳转到相应的画面,例如500 统一跳转到500画面,希望能统一处理
同时类似用户token在后端缓存失效后 再调用任何接口都应统一跳转到登陆画面 因为dva subscriptions功能无法订阅state 所以 如何做共同处理呢
谢谢
实际业务中,也碰到需要统一进行 token 等异常请求判断的场景,关注~
目前有这种需求,临时解决方案是粗暴跳转
关注 希望大大能做个JWT管理的模块
@lazy1523 你可以详细描述一下需求..
关注中。jwt的方案比较能减轻服务器多用户压力。
感觉这个ant-design-pro还没有vue-adminTemplate完善啊,如何统一处理401/403,如何处理权限,完全没有涉及到。。
正在完善!请耐心等待
login.js
import { routerRedux } from 'dva/router';
import { fakeAccountLogin } from '../services/api';
import token from '../utils/token';
export default {
namespace: 'login',
state: {
status: undefined,
},
effects: {
*login({ payload }, { call, put }) {
yield put({
type: 'changeSubmitting',
payload: true,
});
const response = yield call(fakeAccountLogin, payload);
yield put({
type: 'changeLoginStatus',
payload: response,
});
// Login successfully
token.save(response.token);
if (response.status === 'ok') {
yield put(routerRedux.push('/'));
}
},
*logout(_, { put }) {
// remove token in sessionStorage
token.remove()
yield put({
type: 'changeLoginStatus',
payload: {
status: false,
},
});
yield put(routerRedux.push('/user/login'));
},
},
reducers: {
changeLoginStatus(state, { payload }) {
return {
...state,
status: payload.status,
type: payload.type,
submitting: false,
};
},
changeSubmitting(state, { payload }) {
return {
...state,
submitting: payload,
};
},
},
};
``` js
token.js
import atob from 'atob';
import _ from 'lodash';
const STORAGE_TOKEN_NAME = 'TOKEN';
/**
``` js request.js
import fetch from 'dva/fetch';
import token from './token';
const codeMessage = {
200: '服务器成功返回请求的数据',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据,的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器',
502: '网关错误',
503: '服务不可用,服务器暂时过载或维护',
504: '网关超时',
};
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
const error = new Error(errortext);
error.response = response;
throw error;
}
function buildAuthorization () {
const tokenVal = token.get();
return (token !== '') ? `Bearer ${tokenVal}` : '';
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default function request(url, options) {
const defaultOptions = {
credentials: 'include',
};
const newOptions = { ...defaultOptions, ...options };
if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
...newOptions.headers,
};
newOptions.headers.Authorization = buildAuthorization(); // 增加的代码
newOptions.body = JSON.stringify(newOptions.body);
}
return fetch(url, newOptions)
.then(checkStatus)
.then((response) => {
if (newOptions.method === 'DELETE' || response.status === 204) {
return response.text();
}
return response.json();
});
}
~/src/index.js 进行全局异常捕捉,把 ~/src/utils/request.js 里的 notification 提醒移到 ~/src/index.js。import 'babel-polyfill';
import dva from 'dva';
import { routerRedux } from 'dva/router';
import { notification } from 'antd';
import 'moment/locale/zh-cn';
import './g2';
import './rollbar';
// import browserHistory from 'history/createBrowserHistory';
import './index.less';
// 1. Initialize
const app = dva({
onError(err, dispatch) {
const { response, message } = err;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: message,
});
if (status === 401) {
dispatch(routerRedux.push('/user/login'));
}
},
// history: browserHistory(),
});
// 2. Plugins
// app.use({});
// 3. Register global model
app.model(require('./models/global'));
// 4. Router
app.router(require('./router'));
// 5. Start
app.start('#root');
@chenshuai2144 #500 RP 对于 401 的处理应该跳转到 登录界面比较合理吧
@lawrence-peng 可以自己修改... 我们处理的是403 401 你可以自己处理
为了这个401跟服务端老哥撕逼死了好久他才愿意加😂
@lawrence-peng 请教一下,如果希望做登录保持的话,有什么比较好的处理方案呢?我这里所提的登录保持是指在token过期之后,自动使用refresh token去获取一个新的token,从而确保用户不会有体验上的问题。
我们现在的处理方案是登录之后执行setTimeout在token过期之前去服务端刷新token,不知道有没有更好的办法?
我建议是,重刷的逻辑交到后台的同学去实现。 获得两个token 向前端只返回 access_token,把 refresh_token 放到后端缓存中,并设置过期时间等于 refresh_token 的过期时间,后端验证到 access_token 过期,主动用 refresh_token 去换新的 access_token ,如果最后连 refresh_token 过期的话,就要求重新登录了。
@lawrence-peng request.js 里直接写了 headers 的状态有问题吧,应该是:
export default function request(url, options) {
const defaultOptions = {
credentials: 'include',
};
const newOptions = { ...defaultOptions, ...options };
if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
if (!(newOptions.body instanceof FormData)) {
newOptions.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
...newOptions.headers,
};
newOptions.body = JSON.stringify(newOptions.body);
} else {
// newOptions.body is FormData
newOptions.headers = {
Accept: 'application/json',
...newOptions.headers,
};
}
}
newOptions.headers = {
'Authorization': buildAuthorization(),
...newOptions.headers,
};
谢谢你的建议,这样处理的话,后端就得做有状态的服务或者说用这个access token作为缓存的key来映射这个refresh token
@chenshuai2144 #500 RP 对于 401 的处理应该跳转到 登录界面比较合理吧
关闭页面时
我的一些思路:
用户 token 管理思路:
- ~/src/utils/ 增加 token.js
- ~/src/models/login.js login 方法里,把后端返回的 token 通过 token.js 里的方法 save 到 sessionStorage。(注:用 sessionStorage 好处是用户关闭页面时,sessionStorage 的值也随着删除)
- ~/src/utils/request.js 发请求时,通过 header 头把 token 携带到后端进行验证
login.js import { routerRedux } from 'dva/router'; import { fakeAccountLogin } from '../services/api'; import token from '../utils/token'; export default { namespace: 'login', state: { status: undefined, }, effects: { *login({ payload }, { call, put }) { yield put({ type: 'changeSubmitting', payload: true, }); const response = yield call(fakeAccountLogin, payload); yield put({ type: 'changeLoginStatus', payload: response, }); // Login successfully token.save(response.token); if (response.status === 'ok') { yield put(routerRedux.push('/')); } }, *logout(_, { put }) { // remove token in sessionStorage token.remove() yield put({ type: 'changeLoginStatus', payload: { status: false, }, }); yield put(routerRedux.push('/user/login')); }, }, reducers: { changeLoginStatus(state, { payload }) { return { ...state, status: payload.status, type: payload.type, submitting: false, }; }, changeSubmitting(state, { payload }) { return { ...state, submitting: payload, }; }, }, };token.js import atob from 'atob'; import _ from 'lodash'; const STORAGE_TOKEN_NAME = 'TOKEN'; /** * JWT的方案 */ export default { parse() { let token = this.get(); try { const arr = token.split('.'); if (arr.length === 3) { token = atob(token.split('.')[1]); } return JSON.parse(token); } catch (ex) { throw ex; } }, check() { try { const payload = this.parse(); return !_.isEmpty(payload); } catch (ex) { this.remove(); return false; } }, get() { return sessionStorage.getItem(STORAGE_TOKEN_NAME); }, save(token) { sessionStorage.setItem(STORAGE_TOKEN_NAME, token); }, remove() { sessionStorage.removeItem(STORAGE_TOKEN_NAME); }, };import fetch from 'dva/fetch'; import token from './token'; const codeMessage = { 200: '服务器成功返回请求的数据', 201: '新建或修改数据成功。', 202: '一个请求已经进入后台排队(异步任务)', 204: '删除数据成功。', 400: '发出的请求有错误,服务器没有进行新建或修改数据,的操作。', 401: '用户没有权限(令牌、用户名、密码错误)。', 403: '用户得到授权,但是访问是被禁止的。', 404: '发出的请求针对的是不存在的记录,服务器没有进行操作', 406: '请求的格式不可得。', 410: '请求的资源被永久删除,且不会再得到的。', 422: '当创建一个对象时,发生一个验证错误。', 500: '服务器发生错误,请检查服务器', 502: '网关错误', 503: '服务不可用,服务器暂时过载或维护', 504: '网关超时', }; function checkStatus(response) { if (response.status >= 200 && response.status < 300) { return response; } const errortext = codeMessage[response.status] || response.statusText; const error = new Error(errortext); error.response = response; throw error; } function buildAuthorization () { const tokenVal = token.get(); return (token !== '') ? `Bearer ${tokenVal}` : ''; } /** * Requests a URL, returning a promise. * * @param {string} url The URL we want to request * @param {object} [options] The options we want to pass to "fetch" * @return {object} An object containing either "data" or "err" */ export default function request(url, options) { const defaultOptions = { credentials: 'include', }; const newOptions = { ...defaultOptions, ...options }; if (newOptions.method === 'POST' || newOptions.method === 'PUT') { newOptions.headers = { Accept: 'application/json', 'Content-Type': 'application/json; charset=utf-8', ...newOptions.headers, }; newOptions.headers.Authorization = buildAuthorization(); // 增加的代码 newOptions.body = JSON.stringify(newOptions.body); } return fetch(url, newOptions) .then(checkStatus) .then((response) => { if (newOptions.method === 'DELETE' || response.status === 204) { return response.text(); } return response.json(); }); }用户 token 在后端验证失败时的实现思路:
- 后端的实现:对前端请求携带的 token 进行验证,如果无效时,直接响应401状态码。
- 前端的实现:在 入口文件
~/src/index.js进行全局异常捕捉,把~/src/utils/request.js里的 notification 提醒移到~/src/index.js。import 'babel-polyfill'; import dva from 'dva'; import { routerRedux } from 'dva/router'; import { notification } from 'antd'; import 'moment/locale/zh-cn'; import './g2'; import './rollbar'; // import browserHistory from 'history/createBrowserHistory'; import './index.less'; // 1. Initialize const app = dva({ onError(err, dispatch) { const { response, message } = err; const { status, url } = response; notification.error({ message: `请求错误 ${status}: ${url}`, description: message, }); if (status === 401) { dispatch(routerRedux.push('/user/login')); } }, // history: browserHistory(), }); // 2. Plugins // app.use({}); // 3. Register global model app.model(require('./models/global')); // 4. Router app.router(require('./router')); // 5. Start app.start('#root');
关闭页面时? 应该是关闭浏览器时吧
Most helpful comment
我的一些思路:
用户 token 管理思路:
``` js
token.js
import atob from 'atob';
import _ from 'lodash';
const STORAGE_TOKEN_NAME = 'TOKEN';
/**
*/
export default {
parse() {
let token = this.get();
try {
const arr = token.split('.');
if (arr.length === 3) {
token = atob(token.split('.')[1]);
}
return JSON.parse(token);
} catch (ex) {
throw ex;
}
},
check() {
try {
const payload = this.parse();
return !_.isEmpty(payload);
} catch (ex) {
this.remove();
return false;
}
},
get() {
return sessionStorage.getItem(STORAGE_TOKEN_NAME);
},
save(token) {
sessionStorage.setItem(STORAGE_TOKEN_NAME, token);
},
remove() {
sessionStorage.removeItem(STORAGE_TOKEN_NAME);
},
};
用户 token 在后端验证失败时的实现思路:
~/src/index.js进行全局异常捕捉,把~/src/utils/request.js里的 notification 提醒移到~/src/index.js。