mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-04-25 07:57:18 +08:00
feat: 实现登录登出的逻辑
This commit is contained in:
parent
8f46526cc0
commit
a021e7f9d5
@ -10,6 +10,8 @@ export type CaptchaDTO = {
|
||||
export type ConfigDTO = {
|
||||
/** 验证码开关 */
|
||||
isCaptchaOn: boolean;
|
||||
/** 系统字典配置(下拉选项之类的) */
|
||||
dictTypes: Map<String, Array<DictionaryData>>;
|
||||
};
|
||||
|
||||
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<string>;
|
||||
};
|
||||
|
||||
export type DictionaryData = {
|
||||
label: string;
|
||||
value: Number;
|
||||
cssTag: string;
|
||||
};
|
||||
|
||||
/** 获取系统配置接口 */
|
||||
@ -47,10 +59,5 @@ export const getCaptchaCode = () => {
|
||||
|
||||
/** 登录接口 */
|
||||
export const loginByPassword = (data: LoginByPasswordDTO) => {
|
||||
return http.request<ResponseData<any>>("post", "/login", { data });
|
||||
};
|
||||
|
||||
/** 刷新token */
|
||||
export const refreshTokenApi = (data?: object) => {
|
||||
return http.request<RefreshTokenResult>("post", "/refreshToken", { data });
|
||||
return http.request<ResponseData<TokenDTO>>("post", "/login", { data });
|
||||
};
|
||||
|
@ -32,8 +32,3 @@ export type RefreshTokenResult = {
|
||||
export const getLogin = (data?: object) => {
|
||||
return http.request<UserResult>("post", "/login", { data });
|
||||
};
|
||||
|
||||
/** 刷新token */
|
||||
export const refreshTokenApi = (data?: object) => {
|
||||
return http.request<RefreshTokenResult>("post", "/refreshToken", { data });
|
||||
};
|
||||
|
@ -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() {
|
||||
|
@ -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<DataInfo<number>>(sessionKey);
|
||||
const userInfo = storageSession().getItem<TokenDTO>(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页面
|
||||
|
@ -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<string>, b: Array<string>) {
|
||||
|
||||
/** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */
|
||||
function filterNoPermissionTree(data: RouteComponent[]) {
|
||||
const currentRoles =
|
||||
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? [];
|
||||
const roleKey =
|
||||
storageSession().getItem<TokenDTO>(sessionKey).currentUser.roleKey;
|
||||
const currentRoles = roleKey ? [roleKey] : [];
|
||||
const newTree = cloneDeep(data).filter((v: any) =>
|
||||
isOneOfArray(v.meta?.roles, currentRoles)
|
||||
);
|
||||
|
@ -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<DataInfo<number>>(sessionKey)?.username ?? "",
|
||||
storageSession().getItem<TokenDTO>(sessionKey)?.currentUser.userInfo
|
||||
.username ?? "",
|
||||
// 页面级别权限
|
||||
roles: storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? []
|
||||
roles: storageSession().getItem<TokenDTO>(sessionKey)?.currentUser.roleKey
|
||||
? [storageSession().getItem<TokenDTO>(sessionKey)?.currentUser.roleKey]
|
||||
: []
|
||||
}),
|
||||
actions: {
|
||||
/** 存储用户名 */
|
||||
@ -27,21 +29,6 @@ export const useUserStore = defineStore({
|
||||
SET_ROLES(roles: Array<string>) {
|
||||
this.roles = roles;
|
||||
},
|
||||
/** 登入 */
|
||||
async loginByUsername(data) {
|
||||
return new Promise<UserResult>((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<RefreshTokenResult>((resolve, reject) => {
|
||||
refreshTokenApi(data)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
setToken(data.data);
|
||||
resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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<T> {
|
||||
/** token */
|
||||
accessToken: string;
|
||||
@ -22,52 +26,23 @@ export const isRememberMeKey = "ag-is-remember-me";
|
||||
export const passwordKey = "ag-password";
|
||||
|
||||
/** 获取`token` */
|
||||
export function getToken(): DataInfo<number> {
|
||||
export function getToken(): TokenDTO {
|
||||
// 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
|
||||
return Cookies.get(tokenKey)
|
||||
? JSON.parse(Cookies.get(tokenKey))
|
||||
: storageSession().getItem(sessionKey);
|
||||
: storageSession().getItem<TokenDTO>(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<Date>) {
|
||||
let expires = 0;
|
||||
const { accessToken, refreshToken } = data;
|
||||
expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo<Date>改成DataInfo<number>即可
|
||||
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<string>) {
|
||||
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<DataInfo<number>>(sessionKey)?.username ?? "";
|
||||
const roles =
|
||||
storageSession().getItem<DataInfo<number>>(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信息 */
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
);
|
||||
config.headers["Authorization"] = formatToken(data.token);
|
||||
resolve(config);
|
||||
}
|
||||
} else {
|
||||
resolve(config);
|
||||
}
|
||||
});
|
||||
},
|
||||
error => {
|
||||
|
@ -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<Number> = {
|
||||
accessToken: res.data.token,
|
||||
expires: undefined,
|
||||
refreshToken: ""
|
||||
};
|
||||
setToken(tokenData);
|
||||
setTokenFromBackend(res.data);
|
||||
// 获取后端路由
|
||||
initRouter().then(() => {
|
||||
router.push(getTopMenu(true).path);
|
||||
|
Loading…
x
Reference in New Issue
Block a user