refactor: 登陆&获取菜单&登出

This commit is contained in:
RubyLiu 2023-07-04 12:05:26 +08:00 committed by 刘瑞斌
parent 9888af431e
commit 7a8b8677bd
16 changed files with 89 additions and 100 deletions

View File

@ -15,7 +15,7 @@ export default mergeConfig(
'/front': { '/front': {
target: 'http://101.43.186.75:8081/', target: 'http://101.43.186.75:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/front/, ''), rewrite: (path: string) => path.replace(/^\/front/, ''),
}, },
}, },
}, },

View File

@ -1,23 +1,22 @@
import { Message, Modal } from '@arco-design/web-vue'; import { Message, Modal } from '@arco-design/web-vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useUser from '@/store/modules/user'; import { useRouter } from 'vue-router';
import type { ErrorMessageMode } from '#/axios'; import type { ErrorMessageMode } from '#/axios';
export default function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void { export default function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void {
const { t } = useI18n(); const { t } = useI18n();
const userStore = useUser();
let errMessage = ''; let errMessage = '';
switch (status) { switch (status) {
case 400: case 400:
errMessage = `${msg}`; errMessage = `${msg}`;
break; break;
// 401: Not logged in case 401: {
case 401:
errMessage = msg || t('api.errMsg401'); errMessage = msg || t('api.errMsg401');
userStore.logout(); const router = useRouter();
router.push('/login');
break; break;
}
case 403: case 403:
errMessage = t('api.errMsg403'); errMessage = t('api.errMsg403');
break; break;

View File

@ -107,14 +107,12 @@ const transform: AxiosTransform = {
/** /**
* @description: * @description:
*/ */
requestInterceptors: (config, options) => { requestInterceptors: (config) => {
// 请求之前处理config // 请求之前处理config
const token = getToken(); const token = getToken();
if (token && (config as Recordable)?.requestOptions?.withToken !== false) { if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token const { sessionId, csrfToken } = token;
(config as Recordable).headers.Authorization = options.authenticationScheme (config as Recordable).headers = { 'X-AUTH-TOKEN': sessionId, 'CSRF-TOKEN': csrfToken };
? `${options.authenticationScheme} ${token}`
: token;
} }
return config; return config;
}, },
@ -162,8 +160,6 @@ const transform: AxiosTransform = {
}, },
}; };
const { sessionId, csrfToken } = getToken();
function createAxios(opt?: Partial<CreateAxiosOptions>) { function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new MSAxios( return new MSAxios(
deepMerge( deepMerge(
@ -174,7 +170,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
authenticationScheme: '', authenticationScheme: '',
baseURL: `${window.location.origin}/${import.meta.env.VITE_API_BASE_URL as string}`, baseURL: `${window.location.origin}/${import.meta.env.VITE_API_BASE_URL as string}`,
timeout: 30 * 1000, timeout: 30 * 1000,
headers: { 'Content-Type': ContentTypeEnum.JSON, 'X-AUTH-TOKEN': sessionId, 'CSRF-TOKEN': csrfToken }, headers: { 'Content-Type': ContentTypeEnum.JSON },
// 如果是form-data格式 // 如果是form-data格式
// headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
// 数据处理方式 // 数据处理方式

View File

@ -1,19 +1,18 @@
import MSR from '@/api/http/index'; import MSR from '@/api/http/index';
import { LoginUrl, LogoutUrl, GetUserInfoUrl, GetMenuListUrl } from '@/api/requrls/user'; import { LoginUrl, LogoutUrl, GetMenuListUrl, isLoginUrl } from '@/api/requrls/user';
import type { RouteRecordNormalized } from 'vue-router'; import type { RouteRecordNormalized } from 'vue-router';
import type { LoginData, LoginRes } from '@/models/user'; import type { LoginData, LoginRes } from '@/models/user';
import type { UserState } from '@/store/modules/user/types';
export function login(data: LoginData) { export function login(data: LoginData) {
return MSR.post<LoginRes>({ url: LoginUrl, data }); return MSR.post<LoginRes>({ url: LoginUrl, data });
} }
export function logout() { export function isLogin() {
return MSR.post<LoginRes>({ url: LogoutUrl }); return MSR.get<LoginRes>({ url: isLoginUrl });
} }
export function getUserInfo() { export function logout() {
return MSR.post<UserState>({ url: GetUserInfoUrl }); return MSR.get<LoginRes>({ url: LogoutUrl });
} }
export function getMenuList() { export function getMenuList() {

View File

@ -1,4 +1,4 @@
export const LoginUrl = '/login'; export const LoginUrl = '/login';
export const LogoutUrl = '/api/user/logout'; export const isLoginUrl = '/is-login';
export const GetUserInfoUrl = '/api/user/info'; export const LogoutUrl = '/signout';
export const GetMenuListUrl = '/api/user/menu'; export const GetMenuListUrl = '/api/user/menu';

View File

@ -17,9 +17,9 @@
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore(); const userStore = useUserStore();
const { logout } = useUser();
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const { logout } = useUser();
const { menuTree } = useMenuTree(); const { menuTree } = useMenuTree();
const collapsed = computed({ const collapsed = computed({
get() { get() {
@ -121,7 +121,6 @@
{ {
label: t('personal.switchOrg'), label: t('personal.switchOrg'),
icon: <icon-swap />, icon: <icon-swap />,
event: () => {},
}, },
{ {
divider: <a-divider class="ms-dropdown-divider" />, divider: <a-divider class="ms-dropdown-divider" />,
@ -129,7 +128,7 @@
{ {
label: t('personal.exit'), label: t('personal.exit'),
icon: <icon-export />, icon: <icon-export />,
event: logout, event: () => logout(),
}, },
]; ];

View File

@ -154,6 +154,7 @@ export default function useTableProps(
setPagination({ current: data.current, total: data.total }); setPagination({ current: data.current, total: data.total });
return data; return data;
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.log(err); console.log(err);
// TODO 表格异常放到solt的empty // TODO 表格异常放到solt的empty
} finally { } finally {

View File

@ -11,17 +11,15 @@ export default function useUser() {
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const { t } = useI18n(); const { t } = useI18n();
const logout = async (logoutTo?: string) => { /**
*
* @param logoutTo
* @returns
*/
const logout = async () => {
await userStore.logout(); await userStore.logout();
const currentRoute = router.currentRoute.value;
Message.success(t('message.logoutSuccess')); Message.success(t('message.logoutSuccess'));
router.push({ router.push({ name: 'login' });
name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login',
query: {
...router.currentRoute.value.query,
redirect: currentRoute.name as string,
},
});
}; };
return { return {
logout, logout,

View File

@ -1,14 +1,15 @@
import Mock from 'mockjs'; import Mock from 'mockjs';
import setupMock, { successResponseWrap, failResponseWrap, makeMockUrl } from '@/utils/setup-mock'; import setupMock, { successResponseWrap, failResponseWrap, makeMockUrl } from '@/utils/setup-mock';
import { GetMenuListUrl, LogoutUrl, GetUserInfoUrl, LoginUrl } from '@/api/requrls/user'; import { GetMenuListUrl, LogoutUrl, LoginUrl } from '@/api/requrls/user';
import { isLogin } from '@/utils/auth'; import { isLogin } from '@/utils/auth';
setupMock({ setupMock({
mock: false,
setup() { setup() {
// 用户信息 // 用户信息
Mock.mock(makeMockUrl(GetUserInfoUrl), () => { Mock.mock(makeMockUrl('/api/userinfo'), async () => {
if (isLogin()) { if (await isLogin()) {
const role = window.localStorage.getItem('userRole') || 'admin'; const role = window.localStorage.getItem('userRole') || 'admin';
return successResponseWrap({ return successResponseWrap({
name: '王立群', name: '王立群',
@ -32,7 +33,7 @@ setupMock({
return failResponseWrap(null, '未登录', 50008); return failResponseWrap(null, '未登录', 50008);
}); });
// 登 // 登
Mock.mock(makeMockUrl(LoginUrl), () => { Mock.mock(makeMockUrl(LoginUrl), () => {
return successResponseWrap({}); return successResponseWrap({});
}); });

View File

@ -5,8 +5,35 @@ export interface LoginData {
authenticate: string; authenticate: string;
} }
export interface UserRole {
id: string;
createTime: number;
createUser: string;
roleId: string;
sourceId: string;
userId: string;
}
// 登录返回 // 登录返回
export interface LoginRes { export interface LoginRes {
sessionId: string;
csrfToken: string; csrfToken: string;
createTime: number;
createUser: string;
email: string;
enabled: boolean;
id: string;
language: string;
lastOrganizationId: string;
lastProjectId: string;
name: string;
phone: string;
platformInfo: string;
seleniumServer: string;
sessionId: string;
source: string;
updateTime: number;
updateUser: string;
userRolePermissions: UserRole[];
userRoleRelations: UserRole[];
userRoles: UserRole[];
} }

View File

@ -1,31 +1,17 @@
import type { Router, RouteRecordNormalized } from 'vue-router'; import type { Router } from 'vue-router';
import NProgress from 'nprogress'; // progress bar import NProgress from 'nprogress'; // progress bar
import usePermission from '@/hooks/usePermission'; import usePermission from '@/hooks/usePermission';
import { useAppStore } from '@/store';
import { WHITE_LIST, NOT_FOUND } from '../constants'; import { WHITE_LIST, NOT_FOUND } from '../constants';
export default function setupPermissionGuard(router: Router) { export default function setupPermissionGuard(router: Router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
const appStore = useAppStore();
const Permission = usePermission(); const Permission = usePermission();
const permissionsAllow = Permission.accessRouter(to); const permissionsAllow = Permission.accessRouter(to);
// 针对来自服务端的菜单配置进行处理
if (!appStore.appAsyncMenus.length && !WHITE_LIST.find((el) => el.name === to.name)) {
await appStore.fetchServerMenuConfig();
}
const serverMenuConfig = [...appStore.appAsyncMenus, ...WHITE_LIST];
let exist = false; const exist = WHITE_LIST.find((el) => el.name === to.name);
while (serverMenuConfig.length && !exist) {
const element = serverMenuConfig.shift();
if (element?.name === to.name) exist = true;
if (element?.children) { if (exist || permissionsAllow) {
serverMenuConfig.push(...(element.children as unknown as RouteRecordNormalized[]));
}
}
if (exist && permissionsAllow) {
next(); next();
} else next(NOT_FOUND); } else next(NOT_FOUND);
NProgress.done(); NProgress.done();

View File

@ -1,32 +1,12 @@
import type { Router, LocationQueryRaw } from 'vue-router'; import type { Router, LocationQueryRaw } from 'vue-router';
import NProgress from 'nprogress'; // progress bar import NProgress from 'nprogress'; // progress bar
import { useUserStore } from '@/store';
import { isLogin } from '@/utils/auth'; import { isLogin } from '@/utils/auth';
export default function setupUserLoginInfoGuard(router: Router) { export default function setupUserLoginInfoGuard(router: Router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start(); NProgress.start();
const userStore = useUserStore(); if (to.name !== 'login' && (await isLogin())) {
if (isLogin()) { next();
if (userStore.role) {
next();
} else {
try {
await userStore.info();
next();
} catch (error) {
// 获取用户信息错误则先退出,重定向到登录页
await userStore.logout();
next({
name: 'login',
query: {
redirect: to.name,
...to.query,
} as LocationQueryRaw,
});
}
}
} else { } else {
// 未登录的都直接跳转至登录页,访问的页面地址缓存到 query 上 // 未登录的都直接跳转至登录页,访问的页面地址缓存到 query 上
if (to.name === 'login') { if (to.name === 'login') {

View File

@ -4,6 +4,7 @@ import { AppRouteRecordRaw } from '../types';
const ApiTest: AppRouteRecordRaw = { const ApiTest: AppRouteRecordRaw = {
path: '/api-test', path: '/api-test',
name: 'apiTest', name: 'apiTest',
redirect: '/api-test/list',
component: DEFAULT_LAYOUT, component: DEFAULT_LAYOUT,
meta: { meta: {
locale: 'menu.apiTest', locale: 'menu.apiTest',

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { login as userLogin, logout as userLogout, getUserInfo } from '@/api/modules/user'; import { login as userLogin, logout as userLogout } from '@/api/modules/user';
import { setToken, clearToken } from '@/utils/auth'; import { setToken, clearToken } from '@/utils/auth';
import { removeRouteListener } from '@/utils/route-listener'; import { removeRouteListener } from '@/utils/route-listener';
import useAppStore from '../app'; import useAppStore from '../app';
@ -51,22 +51,17 @@ const useUserStore = defineStore('user', {
this.$reset(); this.$reset();
}, },
// 获取用户信息
async info() {
const res = await getUserInfo();
const appStore = useAppStore();
if (appStore.currentOrgId === '') {
// 第一次进系统才设置组织 ID后续已经持久化存储了
appStore.setCurrentOrgId(res.organization || '');
}
this.setInfo(res);
},
// 登录 // 登录
async login(loginForm: LoginData) { async login(loginForm: LoginData) {
try { try {
const res = await userLogin(loginForm); const res = await userLogin(loginForm);
setToken(res.sessionId, res.csrfToken); setToken(res.sessionId, res.csrfToken);
const appStore = useAppStore();
if (appStore.currentOrgId === '') {
// 第一次进系统才设置组织 ID后续已经持久化存储了
appStore.setCurrentOrgId(res.lastOrganizationId || '');
}
this.setInfo(res);
} catch (err) { } catch (err) {
clearToken(); clearToken();
throw err; throw err;

View File

@ -1,8 +1,15 @@
import { isLogin as isLoginFun } from '@/api/modules/user';
const SESSION_ID = 'sessionId'; const SESSION_ID = 'sessionId';
const CSRF_TOKEN = 'csrfToken'; const CSRF_TOKEN = 'csrfToken';
const isLogin = () => { const isLogin = async () => {
return !!localStorage.getItem(SESSION_ID); try {
await isLoginFun();
return true;
} catch (err) {
return false;
}
}; };
// 获取token // 获取token
const getToken = () => { const getToken = () => {

View File

@ -101,13 +101,6 @@
setLoading(true); setLoading(true);
try { try {
await userStore.login(values as LoginData); await userStore.login(values as LoginData);
const { redirect, ...othersQuery } = router.currentRoute.value.query;
router.push({
name: (redirect as string) || 'apiTest',
query: {
...othersQuery,
},
});
Message.success(t('login.form.login.success')); Message.success(t('login.form.login.success'));
const { rememberPassword } = loginConfig.value; const { rememberPassword } = loginConfig.value;
const { username, password } = values; const { username, password } = values;
@ -115,6 +108,13 @@
// The actual production environment requires encrypted storage. // The actual production environment requires encrypted storage.
loginConfig.value.username = rememberPassword ? username : ''; loginConfig.value.username = rememberPassword ? username : '';
loginConfig.value.password = rememberPassword ? password : ''; loginConfig.value.password = rememberPassword ? password : '';
const { redirect, ...othersQuery } = router.currentRoute.value.query;
router.push({
name: (redirect as string) || 'apiTest',
query: {
...othersQuery,
},
});
} catch (err) { } catch (err) {
errorMessage.value = (err as Error).message; errorMessage.value = (err as Error).message;
} finally { } finally {