feat: 实现登录登出的逻辑

This commit is contained in:
valarchie 2023-07-04 21:55:03 +08:00
parent 8f46526cc0
commit a021e7f9d5
10 changed files with 85 additions and 147 deletions

View File

@ -10,6 +10,8 @@ export type CaptchaDTO = {
export type ConfigDTO = { export type ConfigDTO = {
/** 验证码开关 */ /** 验证码开关 */
isCaptchaOn: boolean; isCaptchaOn: boolean;
/** 系统字典配置(下拉选项之类的) */
dictTypes: Map<String, Array<DictionaryData>>;
}; };
export type LoginByPasswordDTO = { export type LoginByPasswordDTO = {
@ -23,16 +25,26 @@ export type LoginByPasswordDTO = {
captchaCodeKey: string; captchaCodeKey: string;
}; };
export type RefreshTokenResult = { /**
success: boolean; * token实现
data: { */
/** `token` */ export type TokenDTO = {
accessToken: string; /** token */
/** 用于调用刷新`accessToken`的接口时所需的`token` */ token: string;
refreshToken: string; /** 当前登录的用户 */
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx' */ currentUser: CurrentLoginUserDTO;
expires: Date; };
};
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) => { export const loginByPassword = (data: LoginByPasswordDTO) => {
return http.request<ResponseData<any>>("post", "/login", { data }); return http.request<ResponseData<TokenDTO>>("post", "/login", { data });
};
/** 刷新token */
export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refreshToken", { data });
}; };

View File

@ -32,8 +32,3 @@ export type RefreshTokenResult = {
export const getLogin = (data?: object) => { export const getLogin = (data?: object) => {
return http.request<UserResult>("post", "/login", { data }); return http.request<UserResult>("post", "/login", { data });
}; };
/** 刷新token */
export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refreshToken", { data });
};

View File

@ -5,12 +5,15 @@ import { routeMetaType } from "../types";
import userAvatar from "@/assets/user.jpg"; import userAvatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils"; import { getTopMenu } from "@/router/utils";
import { useGlobal } from "@pureadmin/utils"; import { useGlobal } from "@pureadmin/utils";
import { routerArrays } from "@/layout/types";
import { useRouter, useRoute } from "vue-router"; import { useRouter, useRoute } from "vue-router";
import { router, remainingPaths } from "@/router"; import { router, remainingPaths, resetRouter } from "@/router";
import { computed, type CSSProperties } from "vue"; import { computed, type CSSProperties } from "vue";
import { useAppStoreHook } from "@/store/modules/app"; import { useAppStoreHook } from "@/store/modules/app";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import { removeToken } from "@/utils/auth";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
const errorInfo = "当前路由配置不正确,请检查配置"; const errorInfo = "当前路由配置不正确,请检查配置";
@ -67,7 +70,12 @@ export function useNav() {
/** 退出登录 */ /** 退出登录 */
function logout() { function logout() {
useUserStoreHook().logOut(); useUserStoreHook().SET_USERNAME("");
useUserStoreHook().SET_ROLES([]);
removeToken();
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter();
router.push("/login");
} }
function backTopMenu() { function backTopMenu() {

View File

@ -1,7 +1,7 @@
// import "@/utils/sso"; // import "@/utils/sso";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import NProgress from "@/utils/progress"; import NProgress from "@/utils/progress";
import { sessionKey, type DataInfo } from "@/utils/auth"; import { sessionKey } from "@/utils/auth";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import { import {
@ -25,6 +25,7 @@ import { buildHierarchyTree } from "@/utils/tree";
import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils"; import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils";
import remainingRouter from "./modules/remaining"; import remainingRouter from "./modules/remaining";
import { TokenDTO } from "@/api/common";
/** src/router/modules .ts remaining.ts /** src/router/modules .ts remaining.ts
* https://github.com/mrmlnc/fast-glob#basic-syntax * https://github.com/mrmlnc/fast-glob#basic-syntax
@ -108,7 +109,7 @@ router.beforeEach((to: ToRouteType, _from, next) => {
handleAliveRoute(to); handleAliveRoute(to);
} }
} }
const userInfo = storageSession().getItem<DataInfo<number>>(sessionKey); const userInfo = storageSession().getItem<TokenDTO>(sessionKey)?.currentUser;
NProgress.start(); NProgress.start();
const externalLink = isUrl(to?.name as string); const externalLink = isUrl(to?.name as string);
if (!externalLink) { if (!externalLink) {
@ -125,7 +126,7 @@ router.beforeEach((to: ToRouteType, _from, next) => {
} }
if (userInfo) { if (userInfo) {
// 无权限跳转403页面 // 无权限跳转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" }); next({ path: "/error/403" });
} }
// 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面 // 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面

View File

@ -19,7 +19,7 @@ import {
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import { menuType } from "@/layout/types"; import { menuType } from "@/layout/types";
import { buildHierarchyTree } from "@/utils/tree"; import { buildHierarchyTree } from "@/utils/tree";
import { sessionKey, type DataInfo } from "@/utils/auth"; import { sessionKey } from "@/utils/auth";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
const IFrame = () => import("@/layout/frameView.vue"); 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 { getAsyncRoutes } from "@/api/routes";
import { TokenDTO } from "@/api/common";
function handRank(routeInfo: any) { function handRank(routeInfo: any) {
const { name, path, parentId, meta } = routeInfo; const { name, path, parentId, meta } = routeInfo;
@ -83,8 +84,9 @@ function isOneOfArray(a: Array<string>, b: Array<string>) {
/** 从sessionStorage里取出当前登陆用户的角色roles过滤无权限的菜单 */ /** 从sessionStorage里取出当前登陆用户的角色roles过滤无权限的菜单 */
function filterNoPermissionTree(data: RouteComponent[]) { function filterNoPermissionTree(data: RouteComponent[]) {
const currentRoles = const roleKey =
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? []; storageSession().getItem<TokenDTO>(sessionKey).currentUser.roleKey;
const currentRoles = roleKey ? [roleKey] : [];
const newTree = cloneDeep(data).filter((v: any) => const newTree = cloneDeep(data).filter((v: any) =>
isOneOfArray(v.meta?.roles, currentRoles) isOneOfArray(v.meta?.roles, currentRoles)
); );

View File

@ -4,19 +4,21 @@ import { userType } from "./types";
import { routerArrays } from "@/layout/types"; import { routerArrays } from "@/layout/types";
import { router, resetRouter } from "@/router"; import { router, resetRouter } from "@/router";
import { storageSession } from "@pureadmin/utils"; import { storageSession } from "@pureadmin/utils";
import { getLogin, refreshTokenApi } from "@/api/user";
import { UserResult, RefreshTokenResult } from "@/api/user";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; 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({ export const useUserStore = defineStore({
id: "ag-user", id: "ag-user",
state: (): userType => ({ state: (): userType => ({
// 用户名 // 用户名
username: 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: { actions: {
/** 存储用户名 */ /** 存储用户名 */
@ -27,21 +29,6 @@ export const useUserStore = defineStore({
SET_ROLES(roles: Array<string>) { SET_ROLES(roles: Array<string>) {
this.roles = roles; 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() { logOut() {
this.username = ""; this.username = "";
@ -50,21 +37,6 @@ export const useUserStore = defineStore({
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]); useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter(); resetRouter();
router.push("/login"); 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);
});
});
} }
} }
}); });

View File

@ -2,7 +2,11 @@ import Cookies from "js-cookie";
import { storageSession } from "@pureadmin/utils"; import { storageSession } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { aesEncrypt, aesDecrypt } from "@/utils/crypt"; import { aesEncrypt, aesDecrypt } from "@/utils/crypt";
import { TokenDTO } from "@/api/common";
/**
* token实现
*/
export interface DataInfo<T> { export interface DataInfo<T> {
/** token */ /** token */
accessToken: string; accessToken: string;
@ -22,52 +26,23 @@ export const isRememberMeKey = "ag-is-remember-me";
export const passwordKey = "ag-password"; export const passwordKey = "ag-password";
/** 获取`token` */ /** 获取`token` */
export function getToken(): DataInfo<number> { export function getToken(): TokenDTO {
// 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错 // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
return Cookies.get(tokenKey) return Cookies.get(tokenKey)
? JSON.parse(Cookies.get(tokenKey)) ? JSON.parse(Cookies.get(tokenKey))
: storageSession().getItem(sessionKey); : storageSession().getItem<TokenDTO>(sessionKey).token;
} }
/** /**
* @description `token``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里
*/ */
export function setToken(data: DataInfo<Date>) { export function setTokenFromBackend(data: TokenDTO): void {
let expires = 0; const cookieString = JSON.stringify(data);
const { accessToken, refreshToken } = data; Cookies.set(tokenKey, cookieString);
expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳将此处代码改为expires = data.expires然后把上面的DataInfo<Date>改成DataInfo<number>即可
const cookieString = JSON.stringify({ accessToken, expires });
expires > 0 useUserStoreHook().SET_USERNAME(data.currentUser.userInfo.username);
? Cookies.set(tokenKey, cookieString, { useUserStoreHook().SET_ROLES([data.currentUser.roleKey]);
expires: (expires - Date.now()) / 86400000 storageSession().setItem(sessionKey, data);
})
: 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);
}
} }
/** 删除`token`以及key值为`user-info`的session信息 */ /** 删除`token`以及key值为`user-info`的session信息 */

View File

@ -8,10 +8,19 @@ const publicKey =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh6HkK+rCM37FAzCHVythTc6pxvr551K07CRhdX/NjCddHAuQMOd/57R5fiIwgVNEfCsD1cIyS6A8IWj4DtJLR2t29JehPpqiFSJ4hNtDcLNxNJiYRcCQvyMQeyQIPE5Ljc35c72YwDtQAsIJChsauyLrc+E6HC3gn1JDm18HNXwIDAQAB"; "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh6HkK+rCM37FAzCHVythTc6pxvr551K07CRhdX/NjCddHAuQMOd/57R5fiIwgVNEfCsD1cIyS6A8IWj4DtJLR2t29JehPpqiFSJ4hNtDcLNxNJiYRcCQvyMQeyQIPE5Ljc35c72YwDtQAsIJChsauyLrc+E6HC3gn1JDm18HNXwIDAQAB";
// 加密 // 加密
export function rsaEncrypt(txt) { export function rsaEncrypt(txt): string {
const encryptor = new JSEncrypt(); 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"; const aesKey = "agileboot1234567";

View File

@ -12,7 +12,6 @@ import {
import { stringify } from "qs"; import { stringify } from "qs";
import NProgress from "../progress"; import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth"; import { getToken, formatToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user";
const { VITE_APP_BASE_API } = import.meta.env; const { VITE_APP_BASE_API } = import.meta.env;
// 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1 // 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1
@ -76,42 +75,18 @@ class PureHttp {
return config; return config;
} }
/** 请求白名单放置一些不需要token的接口通过设置请求白名单防止token过期后再请求造成的死循环问题 */ /** 请求白名单放置一些不需要token的接口通过设置请求白名单防止token过期后再请求造成的死循环问题 */
const whiteList = ["/refreshToken", "/login", "/captchaImage"]; const whiteList = [
"/refreshToken",
"/login",
"/captchaImage",
"/getConfig"
];
return whiteList.some(v => config.url.indexOf(v) > -1) return whiteList.some(v => config.url.indexOf(v) > -1)
? config ? config
: new Promise(resolve => { : new Promise(resolve => {
const data = getToken(); const data = getToken();
config.headers["Authorization"] = formatToken(data.token);
console.log("当前token:" + data.accessToken); resolve(config);
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);
}
}); });
}, },
error => { error => {

View File

@ -31,9 +31,7 @@ import {
getIsRememberMe, getIsRememberMe,
savePassword, savePassword,
getPassword, getPassword,
removePassword, removePassword
setToken,
DataInfo
} from "@/utils/auth"; } from "@/utils/auth";
import dayIcon from "@/assets/svg/day.svg?component"; 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 Lock from "@iconify-icons/ri/lock-fill";
import User from "@iconify-icons/ri/user-3-fill"; import User from "@iconify-icons/ri/user-3-fill";
import * as CommonAPI from "@/api/common"; import * as CommonAPI from "@/api/common";
import { setTokenFromBackend } from "../../utils/auth";
defineOptions({ defineOptions({
name: "Login" name: "Login"
@ -85,12 +84,7 @@ const onLogin = async (formEl: FormInstance | undefined) => {
}).then(res => { }).then(res => {
if (res.code === 0) { if (res.code === 0) {
// tokensessionStorage // tokensessionStorage
const tokenData: DataInfo<Number> = { setTokenFromBackend(res.data);
accessToken: res.data.token,
expires: undefined,
refreshToken: ""
};
setToken(tokenData);
// //
initRouter().then(() => { initRouter().then(() => {
router.push(getTopMenu(true).path); router.push(getTopMenu(true).path);