diff --git a/src/api/common.ts b/src/api/common.ts index 2e5541c..26ee593 100644 --- a/src/api/common.ts +++ b/src/api/common.ts @@ -10,6 +10,8 @@ export type CaptchaDTO = { export type ConfigDTO = { /** 验证码开关 */ isCaptchaOn: boolean; + /** 系统字典配置(下拉选项之类的) */ + dictTypes: Map>; }; export type LoginByPasswordDTO = { @@ -23,16 +25,26 @@ export type LoginByPasswordDTO = { captchaCodeKey: string; }; -export type RefreshTokenResult = { - success: boolean; - data: { - /** `token` */ - accessToken: string; - /** 用于调用刷新`accessToken`的接口时所需的`token` */ - refreshToken: string; - /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ - expires: Date; - }; +/** + * 后端token实现 + */ +export type TokenDTO = { + /** token */ + token: string; + /** 当前登录的用户 */ + currentUser: CurrentLoginUserDTO; +}; + +export type CurrentLoginUserDTO = { + userInfo: any; + roleKey: string; + permissions: Set; +}; + +export type DictionaryData = { + label: string; + value: Number; + cssTag: string; }; /** 获取系统配置接口 */ @@ -47,10 +59,5 @@ export const getCaptchaCode = () => { /** 登录接口 */ export const loginByPassword = (data: LoginByPasswordDTO) => { - return http.request>("post", "/login", { data }); -}; - -/** 刷新token */ -export const refreshTokenApi = (data?: object) => { - return http.request("post", "/refreshToken", { data }); + return http.request>("post", "/login", { data }); }; diff --git a/src/api/user.ts b/src/api/user.ts index 9f8125b..3c56c4c 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -32,8 +32,3 @@ export type RefreshTokenResult = { export const getLogin = (data?: object) => { return http.request("post", "/login", { data }); }; - -/** 刷新token */ -export const refreshTokenApi = (data?: object) => { - return http.request("post", "/refreshToken", { data }); -}; diff --git a/src/layout/hooks/useNav.ts b/src/layout/hooks/useNav.ts index 8de2bf9..52eca3b 100644 --- a/src/layout/hooks/useNav.ts +++ b/src/layout/hooks/useNav.ts @@ -5,12 +5,15 @@ import { routeMetaType } from "../types"; import userAvatar from "@/assets/user.jpg"; import { getTopMenu } from "@/router/utils"; import { useGlobal } from "@pureadmin/utils"; +import { routerArrays } from "@/layout/types"; import { useRouter, useRoute } from "vue-router"; -import { router, remainingPaths } from "@/router"; +import { router, remainingPaths, resetRouter } from "@/router"; import { computed, type CSSProperties } from "vue"; import { useAppStoreHook } from "@/store/modules/app"; import { useUserStoreHook } from "@/store/modules/user"; import { usePermissionStoreHook } from "@/store/modules/permission"; +import { removeToken } from "@/utils/auth"; +import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; const errorInfo = "当前路由配置不正确,请检查配置"; @@ -67,7 +70,12 @@ export function useNav() { /** 退出登录 */ function logout() { - useUserStoreHook().logOut(); + useUserStoreHook().SET_USERNAME(""); + useUserStoreHook().SET_ROLES([]); + removeToken(); + useMultiTagsStoreHook().handleTags("equal", [...routerArrays]); + resetRouter(); + router.push("/login"); } function backTopMenu() { diff --git a/src/router/index.ts b/src/router/index.ts index ef66c29..5c340cc 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,7 +1,7 @@ // import "@/utils/sso"; import { getConfig } from "@/config"; import NProgress from "@/utils/progress"; -import { sessionKey, type DataInfo } from "@/utils/auth"; +import { sessionKey } from "@/utils/auth"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { usePermissionStoreHook } from "@/store/modules/permission"; import { @@ -25,6 +25,7 @@ import { buildHierarchyTree } from "@/utils/tree"; import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils"; import remainingRouter from "./modules/remaining"; +import { TokenDTO } from "@/api/common"; /** 自动导入全部静态路由,无需再手动引入!匹配 src/router/modules 目录(任何嵌套级别)中具有 .ts 扩展名的所有文件,除了 remaining.ts 文件 * 如何匹配所有文件请看:https://github.com/mrmlnc/fast-glob#basic-syntax @@ -108,7 +109,7 @@ router.beforeEach((to: ToRouteType, _from, next) => { handleAliveRoute(to); } } - const userInfo = storageSession().getItem>(sessionKey); + const userInfo = storageSession().getItem(sessionKey)?.currentUser; NProgress.start(); const externalLink = isUrl(to?.name as string); if (!externalLink) { @@ -125,7 +126,7 @@ router.beforeEach((to: ToRouteType, _from, next) => { } if (userInfo) { // 无权限跳转403页面 - if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) { + if (to.meta?.roles && !isOneOfArray(to.meta?.roles, [userInfo.roleKey])) { next({ path: "/error/403" }); } // 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面 diff --git a/src/router/utils.ts b/src/router/utils.ts index 2436e2b..6302d9d 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -19,7 +19,7 @@ import { import { getConfig } from "@/config"; import { menuType } from "@/layout/types"; import { buildHierarchyTree } from "@/utils/tree"; -import { sessionKey, type DataInfo } from "@/utils/auth"; +import { sessionKey } from "@/utils/auth"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { usePermissionStoreHook } from "@/store/modules/permission"; const IFrame = () => import("@/layout/frameView.vue"); @@ -28,6 +28,7 @@ const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}"); // 动态路由 import { getAsyncRoutes } from "@/api/routes"; +import { TokenDTO } from "@/api/common"; function handRank(routeInfo: any) { const { name, path, parentId, meta } = routeInfo; @@ -83,8 +84,9 @@ function isOneOfArray(a: Array, b: Array) { /** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */ function filterNoPermissionTree(data: RouteComponent[]) { - const currentRoles = - storageSession().getItem>(sessionKey)?.roles ?? []; + const roleKey = + storageSession().getItem(sessionKey).currentUser.roleKey; + const currentRoles = roleKey ? [roleKey] : []; const newTree = cloneDeep(data).filter((v: any) => isOneOfArray(v.meta?.roles, currentRoles) ); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 978c02f..c184101 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -4,19 +4,21 @@ import { userType } from "./types"; import { routerArrays } from "@/layout/types"; import { router, resetRouter } from "@/router"; import { storageSession } from "@pureadmin/utils"; -import { getLogin, refreshTokenApi } from "@/api/user"; -import { UserResult, RefreshTokenResult } from "@/api/user"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; -import { type DataInfo, setToken, removeToken, sessionKey } from "@/utils/auth"; +import { type removeToken, sessionKey } from "@/utils/auth"; +import { TokenDTO } from "@/api/common"; export const useUserStore = defineStore({ id: "ag-user", state: (): userType => ({ // 用户名 username: - storageSession().getItem>(sessionKey)?.username ?? "", + storageSession().getItem(sessionKey)?.currentUser.userInfo + .username ?? "", // 页面级别权限 - roles: storageSession().getItem>(sessionKey)?.roles ?? [] + roles: storageSession().getItem(sessionKey)?.currentUser.roleKey + ? [storageSession().getItem(sessionKey)?.currentUser.roleKey] + : [] }), actions: { /** 存储用户名 */ @@ -27,21 +29,6 @@ export const useUserStore = defineStore({ SET_ROLES(roles: Array) { this.roles = roles; }, - /** 登入 */ - async loginByUsername(data) { - return new Promise((resolve, reject) => { - getLogin(data) - .then(data => { - if (data) { - setToken(data.data); - resolve(data); - } - }) - .catch(error => { - reject(error); - }); - }); - }, /** 前端登出(不调用接口) */ logOut() { this.username = ""; @@ -50,21 +37,6 @@ export const useUserStore = defineStore({ useMultiTagsStoreHook().handleTags("equal", [...routerArrays]); resetRouter(); router.push("/login"); - }, - /** 刷新`token` */ - async handRefreshToken(data) { - return new Promise((resolve, reject) => { - refreshTokenApi(data) - .then(data => { - if (data) { - setToken(data.data); - resolve(data); - } - }) - .catch(error => { - reject(error); - }); - }); } } }); diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 0b215b9..67c3a3f 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -2,7 +2,11 @@ import Cookies from "js-cookie"; import { storageSession } from "@pureadmin/utils"; import { useUserStoreHook } from "@/store/modules/user"; import { aesEncrypt, aesDecrypt } from "@/utils/crypt"; +import { TokenDTO } from "@/api/common"; +/** + * 原版前端token实现 + */ export interface DataInfo { /** token */ accessToken: string; @@ -22,52 +26,23 @@ export const isRememberMeKey = "ag-is-remember-me"; export const passwordKey = "ag-password"; /** 获取`token` */ -export function getToken(): DataInfo { +export function getToken(): TokenDTO { // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错 return Cookies.get(tokenKey) ? JSON.parse(Cookies.get(tokenKey)) - : storageSession().getItem(sessionKey); + : storageSession().getItem(sessionKey).token; } /** - * @description 设置`token`以及一些必要信息并采用无感刷新`token`方案 - * 无感刷新:后端返回`accessToken`(访问接口使用的`token`)、`refreshToken`(用于调用刷新`accessToken`的接口时所需的`token`,`refreshToken`的过期时间(比如30天)应大于`accessToken`的过期时间(比如2小时))、`expires`(`accessToken`的过期时间) - * 将`accessToken`、`expires`这两条信息放在key值为authorized-token的cookie里(过期自动销毁) - * 将`username`、`roles`、`refreshToken`、`expires`这四条信息放在key值为`user-info`的sessionStorage里(浏览器关闭自动销毁) + * 后端处理token */ -export function setToken(data: DataInfo) { - let expires = 0; - const { accessToken, refreshToken } = data; - expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo改成DataInfo即可 - const cookieString = JSON.stringify({ accessToken, expires }); +export function setTokenFromBackend(data: TokenDTO): void { + const cookieString = JSON.stringify(data); + Cookies.set(tokenKey, cookieString); - expires > 0 - ? Cookies.set(tokenKey, cookieString, { - expires: (expires - Date.now()) / 86400000 - }) - : Cookies.set(tokenKey, cookieString); - - function setSessionKey(username: string, roles: Array) { - useUserStoreHook().SET_USERNAME(username); - useUserStoreHook().SET_ROLES(roles); - storageSession().setItem(sessionKey, { - refreshToken, - expires, - username, - roles - }); - } - - if (data.username && data.roles) { - const { username, roles } = data; - setSessionKey(username, roles); - } else { - const username = - storageSession().getItem>(sessionKey)?.username ?? ""; - const roles = - storageSession().getItem>(sessionKey)?.roles ?? []; - setSessionKey(username, roles); - } + useUserStoreHook().SET_USERNAME(data.currentUser.userInfo.username); + useUserStoreHook().SET_ROLES([data.currentUser.roleKey]); + storageSession().setItem(sessionKey, data); } /** 删除`token`以及key值为`user-info`的session信息 */ diff --git a/src/utils/crypt.ts b/src/utils/crypt.ts index 97c6e9d..43d9099 100644 --- a/src/utils/crypt.ts +++ b/src/utils/crypt.ts @@ -8,10 +8,19 @@ const publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh6HkK+rCM37FAzCHVythTc6pxvr551K07CRhdX/NjCddHAuQMOd/57R5fiIwgVNEfCsD1cIyS6A8IWj4DtJLR2t29JehPpqiFSJ4hNtDcLNxNJiYRcCQvyMQeyQIPE5Ljc35c72YwDtQAsIJChsauyLrc+E6HC3gn1JDm18HNXwIDAQAB"; // 加密 -export function rsaEncrypt(txt) { +export function rsaEncrypt(txt): string { const encryptor = new JSEncrypt(); - encryptor.setPublicKey(publicKey); // 设置公钥 - return encryptor.encrypt(txt); // 对数据进行加密 + // 设置公钥 + encryptor.setPublicKey(publicKey); + // 对数据进行加密 + const encryptedValue = encryptor.encrypt(txt); + + // Check if the encrypted value is a boolean + if (typeof encryptedValue === "boolean") { + throw new Error("Encryption failed: Encrypted value returned a boolean"); + } + + return encryptedValue; } const aesKey = "agileboot1234567"; diff --git a/src/utils/http/index.ts b/src/utils/http/index.ts index a77210b..c07c52c 100644 --- a/src/utils/http/index.ts +++ b/src/utils/http/index.ts @@ -12,7 +12,6 @@ import { import { stringify } from "qs"; import NProgress from "../progress"; import { getToken, formatToken } from "@/utils/auth"; -import { useUserStoreHook } from "@/store/modules/user"; const { VITE_APP_BASE_API } = import.meta.env; // 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1 @@ -76,42 +75,18 @@ class PureHttp { return config; } /** 请求白名单,放置一些不需要token的接口(通过设置请求白名单,防止token过期后再请求造成的死循环问题) */ - const whiteList = ["/refreshToken", "/login", "/captchaImage"]; + const whiteList = [ + "/refreshToken", + "/login", + "/captchaImage", + "/getConfig" + ]; return whiteList.some(v => config.url.indexOf(v) > -1) ? config : new Promise(resolve => { const data = getToken(); - - console.log("当前token:" + data.accessToken); - if (data) { - const now = new Date().getTime(); - const expired = parseInt(data.expires) - now <= 0; - if (expired) { - if (!PureHttp.isRefreshing) { - PureHttp.isRefreshing = true; - // token过期刷新 - useUserStoreHook() - .handRefreshToken({ refreshToken: data.refreshToken }) - .then(res => { - const token = res.data.accessToken; - config.headers["Authorization"] = formatToken(token); - PureHttp.requests.forEach(cb => cb(token)); - PureHttp.requests = []; - }) - .finally(() => { - PureHttp.isRefreshing = false; - }); - } - resolve(PureHttp.retryOriginalRequest(config)); - } else { - config.headers["Authorization"] = formatToken( - data.accessToken - ); - resolve(config); - } - } else { - resolve(config); - } + config.headers["Authorization"] = formatToken(data.token); + resolve(config); }); }, error => { diff --git a/src/views/login/index.vue b/src/views/login/index.vue index ef17561..e5986c1 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -31,9 +31,7 @@ import { getIsRememberMe, savePassword, getPassword, - removePassword, - setToken, - DataInfo + removePassword } from "@/utils/auth"; import dayIcon from "@/assets/svg/day.svg?component"; @@ -41,6 +39,7 @@ import darkIcon from "@/assets/svg/dark.svg?component"; import Lock from "@iconify-icons/ri/lock-fill"; import User from "@iconify-icons/ri/user-3-fill"; import * as CommonAPI from "@/api/common"; +import { setTokenFromBackend } from "../../utils/auth"; defineOptions({ name: "Login" @@ -85,12 +84,7 @@ const onLogin = async (formEl: FormInstance | undefined) => { }).then(res => { if (res.code === 0) { // 登录成功后 将token存储到sessionStorage中 - const tokenData: DataInfo = { - accessToken: res.data.token, - expires: undefined, - refreshToken: "" - }; - setToken(tokenData); + setTokenFromBackend(res.data); // 获取后端路由 initRouter().then(() => { router.push(getTopMenu(true).path);