起因
- 搭配的vue-beehive里的axios鉴权封装拷贝到uniapp时用不了
笔者最近在做一个用uniapp搭配uview开发的微信小程序,正在发愁是不是要重新搭建一个配套的权限管理系统时,突然想起很早之前在掘金收藏的一篇开源文章 【Node全栈项目开源】🚀 Vue + Egg.js + Mysql 的 JS全栈实践。动态菜单,RBAC权限模型,WebSocket实现站内信,这个系统很适合做二开,在鉴权这块做得还不错(对我来说,简直就是太棒了,想细致了解的可以在掘金搜下图的作者即可),然后就开始了魔改之路!
所遇问题
- axios在小程序的宿主环境内不存在window,导致axios用不了
- 按照beehive的作者方式去写时,session写入失败(原因是小程序端与浏览器的本地缓存不一致)
- 小程序端加密时node-rsa加密报 Secure random number generation is not supported by this browser. Use Chrome
- 改用JSEncrypt时遇到的问题与axios类似,也是缺失window环境,导致加密报错
问题1 axios 不能在小程序使用 寻找替代品 uview http库
- 基于uview http 按照 vue-beehive封装
- 各位大佬嘴下留情,几乎全部直接抄beehive的
import _ from 'lodash';
import qs from 'qs';
import { isArray, isNumber } from '@/utils/validate.js';
import store from '@/store/index.js';
const contentType = 'application/json;charset=UTF-8'
module.exports = (vm) => {
// 初始化请求配置
uni.$u.http.setConfig((config) => {
/* config 为默认全局配置*/
config.baseURL = `http://192.168.0.119:7001/api`; //宿舍
// config.baseURL = `http://192.168.178.20:7001/api`; //教室
config.header = {
'Content-Type': contentType,
'Authorization': '',
'x-csrf-token': ''
};
return config
})
const successCode = [200, 201, 204, 0];
const noAuthenticationCode = 401;
const invalidCode = 402;
const invalidRequestCode = 400;
const noPermissionCode = 403;
const noFoundCode = 404;
// 请求拦截
uni.$u.http.interceptors.request.use((config) => { // 可使用async await 做异步操作
// 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{}
config.data = config.data || {}
if (store.getters['user/accessToken']) {
config.header['Authorization'] = store.getters['user/accessToken'];
config.header['x-csrf-token'] = store.getters['user/accessCsrf'];
}
// 如果config.data存在,且是Json
if (config.data && Object.prototype.toString.call(config.data).slice(8, -1).toLowerCase() === 'object') {
//这里会过滤所有为null的key,如果不需要请自行注释
config.data = _.pickBy(config.data, item => !_.isNull(item));
}
//只针对get方式进行序列化
if (config.method.toLowerCase() === 'get') {
//这里会过滤所有为null的key,如果不需要请自行注释
config.params = _.pickBy(config.params, item => !_.isNull(item));
// 对所有字符串参数做反斜杠处理,解决反斜杠参数搜索有误问题
for (const paramsKey in config.params) {
if (_.isString(config.params[paramsKey])) {
config.params[paramsKey] = config.params[paramsKey].replace(/\\/g, '\\\\');
}
}
config.paramsSerializer = function (params) {
return qs.stringify(params, { arrayFormat: 'repeat' });
};
}
if (contentType === 'application/x-www-form-urlencoded;charset=UTF-8') {
if (config.data && !config.data.param) {
config.data = qs.stringify(config.data);
}
}
return config
}, config => { // 可使用async await 做异步操作
return Promise.reject(config)
})
const errNotification = (
title = null,
duration = 2000
) => {
uni.showToast({
title,
icon: 'error',
duration
});
};
// 响应拦截
uni.$u.http.interceptors.response.use(async (response) => { /* 对响应成功做点什么 可使用async await 做异步操作*/
console.log(response,'响应拦截');
const { statusCode, data, config } = response;
// 如果是token过期,则刷新token并重试,如果刷新失败,则跳到登录
if (statusCode === 202 && data.code === 401) {
try {
await store.dispatch('user/refreshToken');
return await uni.$u.http.request(config)
} catch (e) {
await store.dispatch('user/resetAccessToken');
uni.$u.route({
type: 'switchTab',
url: 'pages/my/my',
})
return Promise.reject();
}
}
const { code, msg } = data;
let codeVerification = false;
if (isNumber(successCode)) {
codeVerification = code !== successCode;
}
if (isArray(successCode)) {
for (let i = 0; i < successCode.length; i++) {
if (code === i) {
codeVerification = code !== i;
break;
}
}
}
if (codeVerification) {
switch (code) {
case invalidCode:
// errNotification('错误', msg || `后端接口${code}异常`);
errNotification('错误',msg);
store.dispatch('user/resetAccessToken');
break;
default:
errNotification('错误',msg);
break;
}
return Promise.reject('请求异常拦截:' + JSON.stringify({ url: config.url, code, msg }) || 'Error');
} else {
return data;
}
}, (error) => {
// 对响应错误做点什么 (statusCode !== 200)
console.log(error,'====错误');
// console.log(error?.status,'====错误');
console.log(noAuthenticationCode);
console.log(error?.statusCode);
switch (error?.statusCode) {
case invalidRequestCode: {
const data = error?.data;
switch (data.code) {
case 1451:
errNotification('操作失败,当前数据存在关联数据');
break;
case 1062:
data.data.detail.forEach(item => {
if (item.type === 'unique violation' && item.value) {
errNotification(`${item.value} 已经存在`);
}
});
break;
default:
if (data.error) {
errNotification(data.error);
} else {
errNotification(data.msg || `后端接口${error?.statusCode}异常`);
}
break;
}
break;
}
case noAuthenticationCode:
// await store.dispatch('user/resetAccessToken');
// router.push(`/login?redirect=${router.app.$route.fullPath}`);
if (error?.data?.msg) {
console.log(error?.data.msg);
errNotification('请登录');
setTimeout(() => {
uni.$u.route({
type: 'switchTab',
url: 'pages/my/my',
})
}, 2000)
}
break;
case noPermissionCode:
errNotification('无权限');
break;
case noFoundCode:
errNotification('找不到');
break;
default:
console.log(error || `后端接口${error.request?.statusCode}异常`)
errNotification('错误11');
break;
}
return Promise.reject(error)
})
}
问题 把beehive 存储session的方式改为uniapp独有的 uni.getStorageSync
function getLocalJwt() {
try {
// return (localStorage.getItem(tokenTableName) && JSON.parse(localStorage.getItem(tokenTableName))[field]) || '';
return (uni.getStorageSync(tokenTableName) && JSON.parse(uni.getStorageSync(tokenTableName))[field]) || '';
} catch (e) {
// localStorage.removeItem(tokenTableName);
uni.removeStorageSync(tokenTableName);
throw e;
}
}
问题3,4 解决node-rsa 加密报错
最好的解决办法,得不到就毁掉(没毛病,不兼容没法去处理,只能换库了)
更换为JSEncrypt时,报window is not defined,根据网上的提示去补环境,下图来源某客园 资深if-else侠 的文章截图
补充环境后小程序端 rsa加密可用,刷新token时一直提示 secret 不匹配,原因是后端解密用的node-rsa解密。前端都用了JSEncrypt,那为了支持小程序的鉴权登录,就只能再次修改后端的JSEncrypt源码做兼容写法了