Compare commits
	
		
			5 Commits
		
	
	
		
			af8ed90ab3
			...
			54672a5574
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 54672a5574 | |||
| 9ba45c37c2 | |||
| 24f091ac79 | |||
| a457dbabe9 | |||
| c546ad2459 | 
| @@ -6,7 +6,9 @@ | |||||||
|   "types": "dist/query-login.d.ts", |   "types": "dist/query-login.d.ts", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "build": "tsup", |     "build": "tsup", | ||||||
|     "watch": "tsup --watch" |     "watch": "tsup --watch", | ||||||
|  |     "dev": "tsup --watch", | ||||||
|  |     "dev:lib": "pnpm run dev" | ||||||
|   }, |   }, | ||||||
|   "keywords": [], |   "keywords": [], | ||||||
|   "author": "abearxiong <xiongxiao@xiongxiao.me>", |   "author": "abearxiong <xiongxiao@xiongxiao.me>", | ||||||
| @@ -16,7 +18,7 @@ | |||||||
|     "access": "public" |     "access": "public" | ||||||
|   }, |   }, | ||||||
|   "peerDependencies": { |   "peerDependencies": { | ||||||
|     "@kevisual/query": "^0.0.12" |     "@kevisual/query": "^0.0.15" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/node": "^22.13.11", |     "@types/node": "^22.13.11", | ||||||
|   | |||||||
| @@ -11,6 +11,10 @@ export interface Cache { | |||||||
|    * @update 删除缓存 |    * @update 删除缓存 | ||||||
|    */ |    */ | ||||||
|   del(): Promise<void>; |   del(): Promise<void>; | ||||||
|  |   /** | ||||||
|  |    * 初始化 | ||||||
|  |    */ | ||||||
|  |   init?: () => Promise<any>; | ||||||
| } | } | ||||||
|  |  | ||||||
| export type CacheLoginUser = { | export type CacheLoginUser = { | ||||||
| @@ -23,7 +27,7 @@ type CacheLogin = { | |||||||
|   loginUsers: CacheLoginUser[]; |   loginUsers: CacheLoginUser[]; | ||||||
| } & CacheLoginUser; | } & CacheLoginUser; | ||||||
|  |  | ||||||
| export type CacheStore<T = any> = { | export type CacheStore<T = Cache> = { | ||||||
|   name: string; |   name: string; | ||||||
|   cacheData: CacheLogin; |   cacheData: CacheLogin; | ||||||
|   /** |   /** | ||||||
| @@ -51,10 +55,6 @@ export type CacheStore<T = any> = { | |||||||
|    * 获取缓存的accessToken |    * 获取缓存的accessToken | ||||||
|    */ |    */ | ||||||
|   getAccessToken(): Promise<string>; |   getAccessToken(): Promise<string>; | ||||||
|   /** |  | ||||||
|    * 初始化 |  | ||||||
|    */ |  | ||||||
|   init(): Promise<void>; |  | ||||||
|   /** |   /** | ||||||
|    * 清除当前用户 |    * 清除当前用户 | ||||||
|    */ |    */ | ||||||
| @@ -63,9 +63,14 @@ export type CacheStore<T = any> = { | |||||||
|    * 清除所有用户 |    * 清除所有用户 | ||||||
|    */ |    */ | ||||||
|   clearAll(): Promise<void>; |   clearAll(): Promise<void>; | ||||||
| } & Cache; |  | ||||||
|  |  | ||||||
| type LoginCacheStoreOpts = { |   getValue(): Promise<CacheLogin>; | ||||||
|  |   setValue(value: CacheLogin): Promise<CacheLogin>; | ||||||
|  |   delValue(): Promise<void>; | ||||||
|  |   init(): Promise<any>; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export type LoginCacheStoreOpts = { | ||||||
|   name: string; |   name: string; | ||||||
|   cache: Cache; |   cache: Cache; | ||||||
| }; | }; | ||||||
| @@ -94,28 +99,41 @@ export class LoginCacheStore implements CacheStore<any> { | |||||||
|    * @param value |    * @param value | ||||||
|    * @returns |    * @returns | ||||||
|    */ |    */ | ||||||
|   async set(key: string, value: CacheLogin) { |   async setValue(value: CacheLogin) { | ||||||
|     await this.cache.set(key, value); |     await this.cache.set(this.name, value); | ||||||
|  |     this.cacheData = value; | ||||||
|     return value; |     return value; | ||||||
|   } |   } | ||||||
|   /** |   /** | ||||||
|    * 删除缓存 |    * 删除缓存 | ||||||
|    */ |    */ | ||||||
|   async del() { |   async delValue() { | ||||||
|     await this.cache.del(); |     await this.cache.del(); | ||||||
|   } |   } | ||||||
|   get(key: string): Promise<CacheLogin> { |   getValue(): Promise<CacheLogin> { | ||||||
|     return this.cache.get(key); |     return this.cache.get(this.name); | ||||||
|   } |   } | ||||||
|  |   /** | ||||||
|  |    * 初始化,设置默认值 | ||||||
|  |    */ | ||||||
|   async init() { |   async init() { | ||||||
|     this.cacheData = (await this.get(this.name)) || { |     const defaultData = { | ||||||
|       loginUsers: [], |       loginUsers: [], | ||||||
|       user: null, |       user: null, | ||||||
|       id: null, |       id: null, | ||||||
|       accessToken: null, |       accessToken: null, | ||||||
|       refreshToken: null, |       refreshToken: null, | ||||||
|     }; |     }; | ||||||
|  |     if (this.cache.init) { | ||||||
|  |       try { | ||||||
|  |         const cacheData = await this.cache.init(); | ||||||
|  |         this.cacheData = cacheData || defaultData; | ||||||
|  |       } catch (error) { | ||||||
|  |         console.log('cacheInit error', error); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       this.cacheData = (await this.getValue()) || defaultData; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   /** |   /** | ||||||
|    * 设置当前用户 |    * 设置当前用户 | ||||||
| @@ -131,7 +149,7 @@ export class LoginCacheStore implements CacheStore<any> { | |||||||
|     this.cacheData.id = user.id; |     this.cacheData.id = user.id; | ||||||
|     this.cacheData.accessToken = user.accessToken; |     this.cacheData.accessToken = user.accessToken; | ||||||
|     this.cacheData.refreshToken = user.refreshToken; |     this.cacheData.refreshToken = user.refreshToken; | ||||||
|     await this.set(this.name, this.cacheData); |     await this.setValue(this.cacheData); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getCurrentUser(): Promise<CacheLoginUser> { |   getCurrentUser(): Promise<CacheLoginUser> { | ||||||
| @@ -160,7 +178,7 @@ export class LoginCacheStore implements CacheStore<any> { | |||||||
|     this.cacheData.id = undefined; |     this.cacheData.id = undefined; | ||||||
|     this.cacheData.accessToken = undefined; |     this.cacheData.accessToken = undefined; | ||||||
|     this.cacheData.refreshToken = undefined; |     this.cacheData.refreshToken = undefined; | ||||||
|     await this.set(this.name, this.cacheData); |     await this.setValue(this.cacheData); | ||||||
|   } |   } | ||||||
|   async clearAll() { |   async clearAll() { | ||||||
|     this.cacheData.loginUsers = []; |     this.cacheData.loginUsers = []; | ||||||
| @@ -168,6 +186,6 @@ export class LoginCacheStore implements CacheStore<any> { | |||||||
|     this.cacheData.id = undefined; |     this.cacheData.id = undefined; | ||||||
|     this.cacheData.accessToken = undefined; |     this.cacheData.accessToken = undefined; | ||||||
|     this.cacheData.refreshToken = undefined; |     this.cacheData.refreshToken = undefined; | ||||||
|     await this.set(this.name, this.cacheData); |     await this.setValue(this.cacheData); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| import { Cache } from './login-cache.ts'; | import { Cache, LoginCacheStore, LoginCacheStoreOpts } from './login-cache.ts'; | ||||||
| import { homedir } from 'node:os'; | import { homedir } from 'node:os'; | ||||||
| import { join, dirname } from 'node:path'; | import { join, dirname } from 'node:path'; | ||||||
| import { readFileSync } from 'node:fs'; | import fs from 'node:fs'; | ||||||
| import { readFile, writeFile, unlink, stat, mkdir } from 'node:fs/promises'; | import { readFileSync, writeFileSync, accessSync } from 'node:fs'; | ||||||
|  | import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises'; | ||||||
| export const fileExists = async (filePath: string, createIfNotExists = false) => { | export const fileExists = async (filePath: string, createIfNotExists = false) => { | ||||||
|   try { |   try { | ||||||
|     await stat(filePath); |     accessSync(filePath, fs.constants.F_OK); | ||||||
|     return true; |     return true; | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     if (createIfNotExists) { |     if (createIfNotExists) { | ||||||
| @@ -76,37 +77,48 @@ export class StorageNode implements Storage { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| export class LoginNodeCache implements Cache { | export class LoginNodeCache implements Cache { | ||||||
|   name: string; |   filepath: string; | ||||||
|   cacheData: any; |  | ||||||
|   filePath: string; |  | ||||||
|   constructor(name: string) { |  | ||||||
|     this.name = name; |  | ||||||
|     const hostname = getHostName(); |  | ||||||
|     this.filePath = join(homedir(), '.config', 'envision', 'config', `${hostname}-${name}.json`); |  | ||||||
|     fileExists(this.filePath, true); |  | ||||||
|     this.loadCache(this.filePath); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|  |   constructor(filepath?: string) { | ||||||
|  |     this.filepath = filepath || join(homedir(), '.config', 'envision', 'config', `${getHostName()}-login.json`); | ||||||
|  |     fileExists(this.filepath, true); | ||||||
|  |   } | ||||||
|  |   async get(_key: string) { | ||||||
|  |     const data = readFileSync(this.filepath, 'utf-8'); | ||||||
|  |     try { | ||||||
|  |       const jsonData = JSON.parse(data); | ||||||
|  |       return jsonData; | ||||||
|  |     } catch (error) { | ||||||
|  |       console.log('get error', error); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   async set(_key: string, value: any) { | ||||||
|  |     const data = readFileSync(this.filepath, 'utf-8'); | ||||||
|  |     try { | ||||||
|  |       const jsonData = JSON.parse(data); | ||||||
|  |       const newData = { ...jsonData, ...value }; | ||||||
|  |       writeFileSync(this.filepath, JSON.stringify(newData, null, 2)); | ||||||
|  |     } catch (error) { | ||||||
|  |       console.log('set error', error); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   async del() { | ||||||
|  |     await unlink(this.filepath); | ||||||
|  |   } | ||||||
|   async loadCache(filePath: string) { |   async loadCache(filePath: string) { | ||||||
|     try { |     try { | ||||||
|       const data = await readFile(filePath, 'utf-8'); |       const data = await readFile(filePath, 'utf-8'); | ||||||
|       const jsonData = JSON.parse(data); |       const jsonData = JSON.parse(data); | ||||||
|       this.cacheData = jsonData; |       return jsonData; | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|  |       console.log('loadCache error', error); | ||||||
|       const defaultData = { loginUsers: [] }; |       const defaultData = { loginUsers: [] }; | ||||||
|       this.cacheData = defaultData; |  | ||||||
|       await writeFile(filePath, JSON.stringify(defaultData, null, 2)); |       await writeFile(filePath, JSON.stringify(defaultData, null, 2)); | ||||||
|  |       return defaultData; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   async init() { | ||||||
|   async get() { |     return await this.loadCache(this.filepath); | ||||||
|     return this.cacheData; |  | ||||||
|   } |  | ||||||
|   async set(key: string, value: any) { |  | ||||||
|     this.cacheData = value; |  | ||||||
|     await writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2)); |  | ||||||
|   } |  | ||||||
|   async del() { |  | ||||||
|     await unlink(this.filePath); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ export class QueryLoginNode extends QueryLogin { | |||||||
|     super({ |     super({ | ||||||
|       ...opts, |       ...opts, | ||||||
|       storage, |       storage, | ||||||
|       cache: new LoginNodeCache('login'), |       cache: new LoginNodeCache(), | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ export class QueryLogin { | |||||||
|   /** |   /** | ||||||
|    * query login cache, 非实际操作, 一个cache的包裹模块 |    * query login cache, 非实际操作, 一个cache的包裹模块 | ||||||
|    */ |    */ | ||||||
|   cache: CacheStore; |   cacheStore: CacheStore; | ||||||
|   isBrowser: boolean; |   isBrowser: boolean; | ||||||
|   load?: boolean; |   load?: boolean; | ||||||
|   storage: Storage; |   storage: Storage; | ||||||
| @@ -34,7 +34,7 @@ export class QueryLogin { | |||||||
|  |  | ||||||
|   constructor(opts?: QueryLoginOpts) { |   constructor(opts?: QueryLoginOpts) { | ||||||
|     this.query = opts?.query || new Query(); |     this.query = opts?.query || new Query(); | ||||||
|     this.cache = new LoginCacheStore({ name: 'login', cache: opts.cache }); |     this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts.cache }); | ||||||
|     this.isBrowser = opts?.isBrowser ?? true; |     this.isBrowser = opts?.isBrowser ?? true; | ||||||
|     this.init(); |     this.init(); | ||||||
|     this.onLoad = opts?.onLoad; |     this.onLoad = opts?.onLoad; | ||||||
| @@ -44,12 +44,19 @@ export class QueryLogin { | |||||||
|     this.query = query; |     this.query = query; | ||||||
|   } |   } | ||||||
|   private async init() { |   private async init() { | ||||||
|     await this.cache.init(); |     await this.cacheStore.init() | ||||||
|     this.load = true; |     this.load = true; | ||||||
|     this.onLoad?.(); |     this.onLoad?.(); | ||||||
|   } |   } | ||||||
|   async post<T = any>(data: any, opts?: DataOpts) { |   async post<T = any>(data: any, opts?: DataOpts) { | ||||||
|     return this.query.post<T>({ path: 'user', ...data }, opts); |     try { | ||||||
|  |       return this.query.post<T>({ path: 'user', ...data }, opts); | ||||||
|  |     } catch (error) { | ||||||
|  |       console.log('error', error); | ||||||
|  |       return { | ||||||
|  |         code: 400, | ||||||
|  |       } as any; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   /** |   /** | ||||||
|    * 登录, |    * 登录, | ||||||
| @@ -89,7 +96,7 @@ export class QueryLogin { | |||||||
|       if (resUser.code === 200) { |       if (resUser.code === 200) { | ||||||
|         const user = resUser.data; |         const user = resUser.data; | ||||||
|         if (user) { |         if (user) { | ||||||
|           this.cache.setLoginUser({ |           this.cacheStore.setLoginUser({ | ||||||
|             user, |             user, | ||||||
|             id: user.id, |             id: user.id, | ||||||
|             accessToken, |             accessToken, | ||||||
| @@ -107,10 +114,10 @@ export class QueryLogin { | |||||||
|    * @returns |    * @returns | ||||||
|    */ |    */ | ||||||
|   async queryRefreshToken(refreshToken?: string) { |   async queryRefreshToken(refreshToken?: string) { | ||||||
|     const _refreshToken = refreshToken || this.cache.getRefreshToken(); |     const _refreshToken = refreshToken || this.cacheStore.getRefreshToken(); | ||||||
|     let data = { refreshToken: _refreshToken }; |     let data = { refreshToken: _refreshToken }; | ||||||
|     if (!_refreshToken) { |     if (!_refreshToken) { | ||||||
|       await this.cache.clearCurrentUser(); |       await this.cacheStore.clearCurrentUser(); | ||||||
|       return { |       return { | ||||||
|         code: 401, |         code: 401, | ||||||
|         message: '请先登录', |         message: '请先登录', | ||||||
| @@ -137,7 +144,7 @@ export class QueryLogin { | |||||||
|   async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) { |   async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) { | ||||||
|     const that = this; |     const that = this; | ||||||
|     if (response?.code === 401) { |     if (response?.code === 401) { | ||||||
|       const hasRefreshToken = await that.cache.getRefreshToken(); |       const hasRefreshToken = await that.cacheStore.getRefreshToken(); | ||||||
|       if (hasRefreshToken) { |       if (hasRefreshToken) { | ||||||
|         const res = await that.queryRefreshToken(hasRefreshToken); |         const res = await that.queryRefreshToken(hasRefreshToken); | ||||||
|         if (res.code === 200) { |         if (res.code === 200) { | ||||||
| @@ -159,13 +166,57 @@ export class QueryLogin { | |||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           that.storage.removeItem('token'); |           that.storage.removeItem('token'); | ||||||
|           await that.cache.clearCurrentUser(); |           await that.cacheStore.clearCurrentUser(); | ||||||
|         } |         } | ||||||
|         return res; |         return res; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return response as any; |     return response as any; | ||||||
|   } |   } | ||||||
|  |   /** | ||||||
|  |    * 一个简单的401处理, 如果401,则刷新token, 如果refreshToken不存在,则返回401 | ||||||
|  |    * refetch 是否重新请求, 会有bug,无限循环,按需要使用 | ||||||
|  |    * @param response | ||||||
|  |    * @param ctx | ||||||
|  |    * @param opts | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async run401Action( | ||||||
|  |     response: Result, | ||||||
|  |     ctx?: { req?: any; res?: any; fetch?: any }, | ||||||
|  |     opts?: { | ||||||
|  |       /** | ||||||
|  |        * 是否重新请求, 会有bug,无限循环,按需要使用 | ||||||
|  |        */ | ||||||
|  |       refetch?: boolean; | ||||||
|  |       /** | ||||||
|  |        * check之后的回调 | ||||||
|  |        */ | ||||||
|  |       afterCheck?: (res: Result) => any; | ||||||
|  |       /** | ||||||
|  |        * 401处理后, 还是401, 则回调 | ||||||
|  |        */ | ||||||
|  |       afterAlso401?: (res: Result) => any; | ||||||
|  |     }, | ||||||
|  |   ) { | ||||||
|  |     const that = this; | ||||||
|  |     const refetch = opts?.refetch ?? false; | ||||||
|  |     if (response?.code === 401) { | ||||||
|  |       if (that.query.stop === true) { | ||||||
|  |         return { code: 500, success: false, message: 'refresh token loading...' }; | ||||||
|  |       } | ||||||
|  |       that.query.stop = true; | ||||||
|  |       const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch); | ||||||
|  |       that.query.stop = false; | ||||||
|  |       opts?.afterCheck?.(res); | ||||||
|  |       if (res.code === 401) { | ||||||
|  |         opts?.afterAlso401?.(res); | ||||||
|  |       } | ||||||
|  |       return res; | ||||||
|  |     } else { | ||||||
|  |       return response as any; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   /** |   /** | ||||||
|    * 获取用户信息 |    * 获取用户信息 | ||||||
|    * @param token |    * @param token | ||||||
| @@ -206,7 +257,7 @@ export class QueryLogin { | |||||||
|    * @returns |    * @returns | ||||||
|    */ |    */ | ||||||
|   async switchUser(username: string) { |   async switchUser(username: string) { | ||||||
|     const localUserList = await this.cache.getCurrentUserList(); |     const localUserList = await this.cacheStore.getCurrentUserList(); | ||||||
|     const user = localUserList.find((userItem) => userItem.user.username === username); |     const user = localUserList.find((userItem) => userItem.user.username === username); | ||||||
|     if (user) { |     if (user) { | ||||||
|       this.storage.setItem('token', user.accessToken || ''); |       this.storage.setItem('token', user.accessToken || ''); | ||||||
| @@ -230,11 +281,20 @@ export class QueryLogin { | |||||||
|     } |     } | ||||||
|     return res; |     return res; | ||||||
|   } |   } | ||||||
|  |   /** | ||||||
|  |    * 退出登陆,去掉token, 并删除缓存 | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|   async logout() { |   async logout() { | ||||||
|     this.storage.removeItem('token'); |     this.storage.removeItem('token'); | ||||||
|     this.cache.del(); |     const users = await this.cacheStore.getCurrentUserList(); | ||||||
|     return this.post<Result>({ key: 'logout' }); |     const tokens = users | ||||||
|  |       .map((user) => { | ||||||
|  |         return user?.accessToken; | ||||||
|  |       }) | ||||||
|  |       .filter(Boolean); | ||||||
|  |     this.cacheStore.delValue(); | ||||||
|  |     return this.post<Result>({ key: 'logout', data: { tokens } }); | ||||||
|   } |   } | ||||||
|   /** |   /** | ||||||
|    * 检查用户名的组,这个用户是否存在 |    * 检查用户名的组,这个用户是否存在 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user