diff --git a/frontend/config/vite.config.dev.ts b/frontend/config/vite.config.dev.ts index 587bb5cf67..0afaf74739 100644 --- a/frontend/config/vite.config.dev.ts +++ b/frontend/config/vite.config.dev.ts @@ -15,7 +15,7 @@ export default mergeConfig( '/front': { target: 'http://101.43.186.75:8081/', changeOrigin: true, - rewrite: (path) => path.replace(/^\/front/, ''), + rewrite: (path: string) => path.replace(/^\/front/, ''), }, }, }, diff --git a/frontend/src/api/http/checkStatus.ts b/frontend/src/api/http/checkStatus.ts index d40888dd01..56ab023da1 100644 --- a/frontend/src/api/http/checkStatus.ts +++ b/frontend/src/api/http/checkStatus.ts @@ -1,23 +1,22 @@ import { Message, Modal } from '@arco-design/web-vue'; import { useI18n } from '@/hooks/useI18n'; -import useUser from '@/store/modules/user'; +import { useRouter } from 'vue-router'; import type { ErrorMessageMode } from '#/axios'; export default function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void { const { t } = useI18n(); - const userStore = useUser(); let errMessage = ''; - switch (status) { case 400: errMessage = `${msg}`; break; - // 401: Not logged in - case 401: + case 401: { errMessage = msg || t('api.errMsg401'); - userStore.logout(); + const router = useRouter(); + router.push('/login'); break; + } case 403: errMessage = t('api.errMsg403'); break; diff --git a/frontend/src/api/http/index.ts b/frontend/src/api/http/index.ts index 50c9fa02ce..d6eab06693 100644 --- a/frontend/src/api/http/index.ts +++ b/frontend/src/api/http/index.ts @@ -107,14 +107,12 @@ const transform: AxiosTransform = { /** * @description: 请求拦截器处理 */ - requestInterceptors: (config, options) => { + requestInterceptors: (config) => { // 请求之前处理config const token = getToken(); if (token && (config as Recordable)?.requestOptions?.withToken !== false) { - // jwt token - (config as Recordable).headers.Authorization = options.authenticationScheme - ? `${options.authenticationScheme} ${token}` - : token; + const { sessionId, csrfToken } = token; + (config as Recordable).headers = { 'X-AUTH-TOKEN': sessionId, 'CSRF-TOKEN': csrfToken }; } return config; }, @@ -162,8 +160,6 @@ const transform: AxiosTransform = { }, }; -const { sessionId, csrfToken } = getToken(); - function createAxios(opt?: Partial) { return new MSAxios( deepMerge( @@ -174,7 +170,7 @@ function createAxios(opt?: Partial) { authenticationScheme: '', baseURL: `${window.location.origin}/${import.meta.env.VITE_API_BASE_URL as string}`, timeout: 30 * 1000, - headers: { 'Content-Type': ContentTypeEnum.JSON, 'X-AUTH-TOKEN': sessionId, 'CSRF-TOKEN': csrfToken }, + headers: { 'Content-Type': ContentTypeEnum.JSON }, // 如果是form-data格式 // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, // 数据处理方式 diff --git a/frontend/src/api/modules/user/index.ts b/frontend/src/api/modules/user/index.ts index a759e3a41d..c6ec5bf9e5 100644 --- a/frontend/src/api/modules/user/index.ts +++ b/frontend/src/api/modules/user/index.ts @@ -1,19 +1,18 @@ 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 { LoginData, LoginRes } from '@/models/user'; -import type { UserState } from '@/store/modules/user/types'; export function login(data: LoginData) { return MSR.post({ url: LoginUrl, data }); } -export function logout() { - return MSR.post({ url: LogoutUrl }); +export function isLogin() { + return MSR.get({ url: isLoginUrl }); } -export function getUserInfo() { - return MSR.post({ url: GetUserInfoUrl }); +export function logout() { + return MSR.get({ url: LogoutUrl }); } export function getMenuList() { diff --git a/frontend/src/api/requrls/user.ts b/frontend/src/api/requrls/user.ts index 2ce8b3e868..e828467c48 100644 --- a/frontend/src/api/requrls/user.ts +++ b/frontend/src/api/requrls/user.ts @@ -1,4 +1,4 @@ export const LoginUrl = '/login'; -export const LogoutUrl = '/api/user/logout'; -export const GetUserInfoUrl = '/api/user/info'; +export const isLoginUrl = '/is-login'; +export const LogoutUrl = '/signout'; export const GetMenuListUrl = '/api/user/menu'; diff --git a/frontend/src/components/pure/menu/index.vue b/frontend/src/components/pure/menu/index.vue index a75d95f0ed..dddcf86129 100644 --- a/frontend/src/components/pure/menu/index.vue +++ b/frontend/src/components/pure/menu/index.vue @@ -17,9 +17,9 @@ const { t } = useI18n(); const appStore = useAppStore(); const userStore = useUserStore(); + const { logout } = useUser(); const router = useRouter(); const route = useRoute(); - const { logout } = useUser(); const { menuTree } = useMenuTree(); const collapsed = computed({ get() { @@ -121,7 +121,6 @@ { label: t('personal.switchOrg'), icon: , - event: () => {}, }, { divider: , @@ -129,7 +128,7 @@ { label: t('personal.exit'), icon: , - event: logout, + event: () => logout(), }, ]; diff --git a/frontend/src/components/pure/ms-table/useTable.ts b/frontend/src/components/pure/ms-table/useTable.ts index 9a8587cd4a..bb78d308ad 100644 --- a/frontend/src/components/pure/ms-table/useTable.ts +++ b/frontend/src/components/pure/ms-table/useTable.ts @@ -154,6 +154,7 @@ export default function useTableProps( setPagination({ current: data.current, total: data.total }); return data; } catch (err) { + // eslint-disable-next-line no-console console.log(err); // TODO 表格异常放到solt的empty } finally { diff --git a/frontend/src/hooks/useUser.ts b/frontend/src/hooks/useUser.ts index 7a8e8a60f2..b91726304e 100644 --- a/frontend/src/hooks/useUser.ts +++ b/frontend/src/hooks/useUser.ts @@ -11,17 +11,15 @@ export default function useUser() { const router = useRouter(); const userStore = useUserStore(); const { t } = useI18n(); - const logout = async (logoutTo?: string) => { + /** + * 登出 + * @param logoutTo 登出后跳转的页面 + * @returns + */ + const logout = async () => { await userStore.logout(); - const currentRoute = router.currentRoute.value; Message.success(t('message.logoutSuccess')); - router.push({ - name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login', - query: { - ...router.currentRoute.value.query, - redirect: currentRoute.name as string, - }, - }); + router.push({ name: 'login' }); }; return { logout, diff --git a/frontend/src/mock/user.ts b/frontend/src/mock/user.ts index 5b9d05f6e1..b66321671f 100644 --- a/frontend/src/mock/user.ts +++ b/frontend/src/mock/user.ts @@ -1,14 +1,15 @@ import Mock from 'mockjs'; 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'; setupMock({ + mock: false, setup() { // 用户信息 - Mock.mock(makeMockUrl(GetUserInfoUrl), () => { - if (isLogin()) { + Mock.mock(makeMockUrl('/api/userinfo'), async () => { + if (await isLogin()) { const role = window.localStorage.getItem('userRole') || 'admin'; return successResponseWrap({ name: '王立群', @@ -32,7 +33,7 @@ setupMock({ return failResponseWrap(null, '未登录', 50008); }); - // 登出 + // 登陆 Mock.mock(makeMockUrl(LoginUrl), () => { return successResponseWrap({}); }); diff --git a/frontend/src/models/user.ts b/frontend/src/models/user.ts index be69b72943..b88d67a877 100644 --- a/frontend/src/models/user.ts +++ b/frontend/src/models/user.ts @@ -5,8 +5,35 @@ export interface LoginData { authenticate: string; } +export interface UserRole { + id: string; + createTime: number; + createUser: string; + roleId: string; + sourceId: string; + userId: string; +} + // 登录返回 export interface LoginRes { - sessionId: 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[]; } diff --git a/frontend/src/router/guard/permission.ts b/frontend/src/router/guard/permission.ts index fcfd93cb2c..28957629d4 100644 --- a/frontend/src/router/guard/permission.ts +++ b/frontend/src/router/guard/permission.ts @@ -1,31 +1,17 @@ -import type { Router, RouteRecordNormalized } from 'vue-router'; +import type { Router } from 'vue-router'; import NProgress from 'nprogress'; // progress bar import usePermission from '@/hooks/usePermission'; -import { useAppStore } from '@/store'; import { WHITE_LIST, NOT_FOUND } from '../constants'; export default function setupPermissionGuard(router: Router) { router.beforeEach(async (to, from, next) => { - const appStore = useAppStore(); const Permission = usePermission(); 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; - while (serverMenuConfig.length && !exist) { - const element = serverMenuConfig.shift(); - if (element?.name === to.name) exist = true; + const exist = WHITE_LIST.find((el) => el.name === to.name); - if (element?.children) { - serverMenuConfig.push(...(element.children as unknown as RouteRecordNormalized[])); - } - } - if (exist && permissionsAllow) { + if (exist || permissionsAllow) { next(); } else next(NOT_FOUND); NProgress.done(); diff --git a/frontend/src/router/guard/userLoginInfo.ts b/frontend/src/router/guard/userLoginInfo.ts index 9896bd6833..d4694181c5 100644 --- a/frontend/src/router/guard/userLoginInfo.ts +++ b/frontend/src/router/guard/userLoginInfo.ts @@ -1,32 +1,12 @@ import type { Router, LocationQueryRaw } from 'vue-router'; import NProgress from 'nprogress'; // progress bar - -import { useUserStore } from '@/store'; import { isLogin } from '@/utils/auth'; export default function setupUserLoginInfoGuard(router: Router) { router.beforeEach(async (to, from, next) => { NProgress.start(); - const userStore = useUserStore(); - if (isLogin()) { - 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, - }); - } - } + if (to.name !== 'login' && (await isLogin())) { + next(); } else { // 未登录的都直接跳转至登录页,访问的页面地址缓存到 query 上 if (to.name === 'login') { diff --git a/frontend/src/router/routes/modules/api-test.ts b/frontend/src/router/routes/modules/api-test.ts index 53f0fcafa4..6d0dda9e39 100644 --- a/frontend/src/router/routes/modules/api-test.ts +++ b/frontend/src/router/routes/modules/api-test.ts @@ -4,6 +4,7 @@ import { AppRouteRecordRaw } from '../types'; const ApiTest: AppRouteRecordRaw = { path: '/api-test', name: 'apiTest', + redirect: '/api-test/list', component: DEFAULT_LAYOUT, meta: { locale: 'menu.apiTest', diff --git a/frontend/src/store/modules/user/index.ts b/frontend/src/store/modules/user/index.ts index 8649dbcc11..d3517edd6a 100644 --- a/frontend/src/store/modules/user/index.ts +++ b/frontend/src/store/modules/user/index.ts @@ -1,5 +1,5 @@ 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 { removeRouteListener } from '@/utils/route-listener'; import useAppStore from '../app'; @@ -51,22 +51,17 @@ const useUserStore = defineStore('user', { 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) { try { const res = await userLogin(loginForm); setToken(res.sessionId, res.csrfToken); + const appStore = useAppStore(); + if (appStore.currentOrgId === '') { + // 第一次进系统才设置组织 ID,后续已经持久化存储了 + appStore.setCurrentOrgId(res.lastOrganizationId || ''); + } + this.setInfo(res); } catch (err) { clearToken(); throw err; diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 398a5e72f4..c9d65f6068 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -1,8 +1,15 @@ +import { isLogin as isLoginFun } from '@/api/modules/user'; + const SESSION_ID = 'sessionId'; const CSRF_TOKEN = 'csrfToken'; -const isLogin = () => { - return !!localStorage.getItem(SESSION_ID); +const isLogin = async () => { + try { + await isLoginFun(); + return true; + } catch (err) { + return false; + } }; // 获取token const getToken = () => { diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue index a48e19a145..7edd23f9a8 100644 --- a/frontend/src/views/login/components/login-form.vue +++ b/frontend/src/views/login/components/login-form.vue @@ -101,13 +101,6 @@ setLoading(true); try { 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')); const { rememberPassword } = loginConfig.value; const { username, password } = values; @@ -115,6 +108,13 @@ // The actual production environment requires encrypted storage. loginConfig.value.username = rememberPassword ? username : ''; loginConfig.value.password = rememberPassword ? password : ''; + const { redirect, ...othersQuery } = router.currentRoute.value.query; + router.push({ + name: (redirect as string) || 'apiTest', + query: { + ...othersQuery, + }, + }); } catch (err) { errorMessage.value = (err as Error).message; } finally {