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 = {
/** 验证码开关 */
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 });
};

View File

@ -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 });
};

View File

@ -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() {

View File

@ -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页面

View File

@ -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)
);

View File

@ -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);
});
});
}
}
});

View File

@ -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信息 */

View File

@ -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";

View File

@ -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 => {

View File

@ -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) {
// tokensessionStorage
const tokenData: DataInfo<Number> = {
accessToken: res.data.token,
expires: undefined,
refreshToken: ""
};
setToken(tokenData);
setTokenFromBackend(res.data);
//
initRouter().then(() => {
router.push(getTopMenu(true).path);