update
This commit is contained in:
+180
-64
@@ -1,79 +1,195 @@
|
||||
import { LoginResponse, LoginToken, LoginUser, UserToken } from "../types";
|
||||
import {defineStore} from "pinia";
|
||||
import {computed, reactive, readonly, ref} from "vue";
|
||||
import {CaptchaData, LoginRequest, LoginResponse, LoginUser} from "../types";
|
||||
import Cooker from "../utils/Cooker";
|
||||
import Storage from "../utils/Storage";
|
||||
import UserAPI from "../api/UserAPI";
|
||||
import {UserAPI} from "../api";
|
||||
|
||||
const loginUser = reactive<LoginUser>({
|
||||
token: undefined,
|
||||
user: undefined
|
||||
});
|
||||
|
||||
function isLogged(): boolean {
|
||||
return !!loginUser.user;
|
||||
/** 用户 Store 配置项 */
|
||||
interface UserStoreOptions {
|
||||
/** localStorage 储存 key,默认 "loginUser" */
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
async function login4Token(token: LoginToken): Promise<LoginResponse | null> {
|
||||
loginUser.token = token;
|
||||
// 未过期
|
||||
try {
|
||||
const resp = await UserAPI.login4Token();
|
||||
await updateToken(resp);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
await logout();
|
||||
/** 访问控制匹配模式 */
|
||||
export type AccessMatchMode = "and" | "or";
|
||||
|
||||
let _storageKey = "loginUser";
|
||||
|
||||
function normalizeAccessList(list?: string[]): string[] {
|
||||
if (!list?.length) {
|
||||
return [];
|
||||
}
|
||||
return null;
|
||||
return list
|
||||
.map(item => item?.trim())
|
||||
.filter((item): item is string => !!item);
|
||||
}
|
||||
|
||||
async function login4Storage(): Promise<LoginResponse | null> {
|
||||
if (!loginUser.user && (loginUser.token || Storage.has("token"))) {
|
||||
// 未登录,储存有令牌
|
||||
const token = Storage.getJSON("token") as UserToken;
|
||||
if (new Date().getTime() < token.expireAt) {
|
||||
return await login4Token(token);
|
||||
function matchAccess(sourceList?: string[], targetList?: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||
const normalizedTargetList = normalizeAccessList(Array.isArray(targetList) ? targetList : [targetList || ""]);
|
||||
if (!normalizedTargetList.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const sourceSet = new Set(normalizeAccessList(sourceList));
|
||||
if (!sourceSet.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode === "and") {
|
||||
return normalizedTargetList.every(item => sourceSet.has(item));
|
||||
}
|
||||
return normalizedTargetList.some(item => sourceSet.has(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置用户 Store,应在使用 useUserStore 之前调用
|
||||
* @param options 配置项
|
||||
*/
|
||||
function configureUserStore(options: UserStoreOptions): void {
|
||||
if (options.storageKey) {
|
||||
_storageKey = options.storageKey;
|
||||
}
|
||||
}
|
||||
|
||||
const useUserStore = defineStore("user", () => {
|
||||
type LoginUserState = LoginUser & Record<string, unknown>;
|
||||
|
||||
const loginUser = reactive<LoginUserState>({});
|
||||
const isRestored = ref(false);
|
||||
|
||||
const token = computed(() => loginUser.token ?? "");
|
||||
const isLoggedIn = computed(() => isValidLoginUser(loginUser));
|
||||
|
||||
function resetLoginUser(nextUser?: LoginUserState): void {
|
||||
Object.keys(loginUser).forEach((key) => {
|
||||
delete loginUser[key];
|
||||
});
|
||||
if (nextUser) {
|
||||
Object.assign(loginUser, nextUser);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function updateToken(loginResponse: LoginResponse): Promise<void> {
|
||||
loginUser.token = {
|
||||
id: loginResponse.id,
|
||||
value: loginResponse.token,
|
||||
expireAt: loginResponse.expireAt
|
||||
};
|
||||
loginUser.user = await UserAPI.view(loginResponse.id);
|
||||
|
||||
Storage.setJSON("token", {
|
||||
value: loginUser.token.value,
|
||||
expireAt: loginUser.token.expireAt
|
||||
} as UserToken);
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
await UserAPI.logout();
|
||||
|
||||
loginUser.token = undefined;
|
||||
loginUser.user = undefined;
|
||||
|
||||
Storage.remove("token");
|
||||
}
|
||||
|
||||
async function reloadProfile() {
|
||||
if (loginUser.token && loginUser.token.id) {
|
||||
loginUser.user = await UserAPI.view(loginUser.token.id);
|
||||
function isValidLoginUser(user?: LoginUserState | null): user is LoginResponse {
|
||||
return !!user?.token && !!user.expireAt && new Date().getTime() < user.expireAt;
|
||||
}
|
||||
}
|
||||
|
||||
const userStore = {
|
||||
loginUser,
|
||||
isLogged,
|
||||
login4Token,
|
||||
login4Storage,
|
||||
updateToken,
|
||||
logout,
|
||||
reloadProfile
|
||||
};
|
||||
function isLogged(): boolean {
|
||||
return isLoggedIn.value;
|
||||
}
|
||||
|
||||
function getLoginUser<T extends LoginUser = LoginUser>(): T | null {
|
||||
if (!isLogged()) {
|
||||
return null;
|
||||
}
|
||||
return loginUser as T;
|
||||
}
|
||||
|
||||
function getRoleList(): string[] {
|
||||
return normalizeAccessList(loginUser.user?.roleList);
|
||||
}
|
||||
|
||||
function getPermissionList(): string[] {
|
||||
return normalizeAccessList(loginUser.user?.permissionList);
|
||||
}
|
||||
|
||||
function hasRoles(roleList: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||
return matchAccess(getRoleList(), roleList, mode);
|
||||
}
|
||||
|
||||
function hasPermissions(permissionList: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||
return matchAccess(getPermissionList(), permissionList, mode);
|
||||
}
|
||||
|
||||
async function login<T extends LoginUser = LoginUser>(req: CaptchaData<LoginRequest>): Promise<T> {
|
||||
const result = await UserAPI.login(req);
|
||||
const nextUser = result as unknown as T;
|
||||
await updateLoginUser(nextUser);
|
||||
return nextUser;
|
||||
}
|
||||
|
||||
async function loginByToken<T extends LoginUser = LoginUser>(token: string): Promise<T | null> {
|
||||
loginUser.token = token;
|
||||
// 未过期
|
||||
try {
|
||||
const result = await UserAPI.loginByToken();
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const nextUser = result as unknown as T;
|
||||
await updateLoginUser(nextUser);
|
||||
return nextUser;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
clearLoginUser();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function loginByStorage<T extends LoginUser = LoginUser>(): Promise<T | null> {
|
||||
if (loginUser.token) {
|
||||
isRestored.value = true;
|
||||
return getLoginUser<T>();
|
||||
}
|
||||
|
||||
if (!Storage.has(_storageKey)) {
|
||||
isRestored.value = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 未登录,储存有令牌。
|
||||
const storageUser = Storage.getJSON(_storageKey) as T;
|
||||
if (isValidLoginUser(storageUser)) {
|
||||
const result = await loginByToken<T>(storageUser.token);
|
||||
isRestored.value = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
clearLoginUser();
|
||||
isRestored.value = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function updateLoginUser<T extends LoginUser = LoginUser>(user: T): Promise<void> {
|
||||
resetLoginUser(user as LoginUserState);
|
||||
Storage.setJSON(_storageKey, user);
|
||||
}
|
||||
|
||||
function clearLoginUser(): void {
|
||||
resetLoginUser();
|
||||
Cooker.remove("Token");
|
||||
Storage.remove(_storageKey);
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
try {
|
||||
await UserAPI.logout();
|
||||
} finally {
|
||||
clearLoginUser();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
isLoggedIn,
|
||||
isRestored,
|
||||
getRoleList,
|
||||
getPermissionList,
|
||||
hasRoles,
|
||||
hasPermissions,
|
||||
isLogged,
|
||||
loginUser: readonly(loginUser),
|
||||
getLoginUser,
|
||||
login,
|
||||
loginByToken,
|
||||
loginByStorage,
|
||||
updateLoginUser,
|
||||
clearLoginUser,
|
||||
logout
|
||||
};
|
||||
});
|
||||
|
||||
export {
|
||||
userStore
|
||||
useUserStore,
|
||||
configureUserStore
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user