feat: add login by note modules

This commit is contained in:
xion 2025-03-22 00:52:43 +08:00
parent f88fd1037e
commit af8ed90ab3
11 changed files with 288 additions and 29 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
password=888888
envision_registry=https://kevisual.silkyai.cn/api/router

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
dist
node_modules
.DS_Store
.DS_Store
.env

View File

@ -1,6 +1,6 @@
{
"name": "@kevisual/query-login",
"version": "0.0.1",
"version": "0.0.2",
"description": "",
"main": "dist/query-login.js",
"types": "dist/query-login.d.ts",
@ -23,9 +23,13 @@
"tsup": "^8.4.0"
},
"exports": {
".": "./dist/query-login.js"
".": "./dist/query-login-browser.js",
"./base": "./dist/query-login.js",
"./node": "./dist/query-login-node.js",
"./browser": "./dist/query-login-browser.js"
},
"dependencies": {
"@kevisual/cache": "^0.0.1"
"@kevisual/cache": "^0.0.1",
"dotenv": "^16.4.7"
}
}

View File

@ -1,4 +1,17 @@
import { MyCache } from '@kevisual/cache';
export interface Cache {
/**
* @update
*/
get(key: string): Promise<any>;
/**
* @update
*/
set(key: string, value: any): Promise<any>;
/**
* @update
*/
del(): Promise<void>;
}
export type CacheLoginUser = {
user?: any;
@ -10,22 +23,14 @@ type CacheLogin = {
loginUsers: CacheLoginUser[];
} & CacheLoginUser;
export interface CacheStore<T = any> {
export type CacheStore<T = any> = {
name: string;
cacheData: CacheLogin;
/**
* cache
*/
cache: T;
/**
* @update
*/
get(key: string): Promise<any>;
/**
* @update
*/
set(key: string, value: CacheLogin): Promise<CacheLogin>;
/**
* @update
*/
del(): Promise<void>;
/**
*
*/
@ -58,13 +63,22 @@ export interface CacheStore<T = any> {
*
*/
clearAll(): Promise<void>;
}
export class LoginCacheStore implements CacheStore<MyCache<any>> {
cache: MyCache<any>;
} & Cache;
type LoginCacheStoreOpts = {
name: string;
cache: Cache;
};
export class LoginCacheStore implements CacheStore<any> {
cache: Cache;
name: string;
cacheData: CacheLogin;
constructor(name: string) {
this.cache = new MyCache(name);
constructor(opts: LoginCacheStoreOpts) {
if (!opts.cache) {
throw new Error('cache is required');
}
// @ts-ignore
this.cache = opts.cache;
this.cacheData = {
loginUsers: [],
user: undefined,
@ -72,7 +86,7 @@ export class LoginCacheStore implements CacheStore<MyCache<any>> {
accessToken: undefined,
refreshToken: undefined,
};
this.name = name;
this.name = opts.name;
}
/**
*

112
src/login-node-cache.ts Normal file
View File

@ -0,0 +1,112 @@
import { Cache } from './login-cache.ts';
import { homedir } from 'node:os';
import { join, dirname } from 'node:path';
import { readFileSync } from 'node:fs';
import { readFile, writeFile, unlink, stat, mkdir } from 'node:fs/promises';
export const fileExists = async (filePath: string, createIfNotExists = false) => {
try {
await stat(filePath);
return true;
} catch (error) {
if (createIfNotExists) {
await mkdir(dirname(filePath), { recursive: true });
return false;
}
return false;
}
};
export const readConfigFile = (filePath: string) => {
try {
const data = readFileSync(filePath, 'utf-8');
const jsonData = JSON.parse(data);
return jsonData;
} catch (error) {
return {};
}
};
export const getHostName = () => {
const configDir = join(homedir(), '.config', 'envision');
const configFile = join(configDir, 'config.json');
const config = readConfigFile(configFile);
const baseURL = config.baseURL || 'https://kevisual.cn';
const hostname = new URL(baseURL).hostname;
return hostname;
};
export class StorageNode implements Storage {
cacheData: any;
filePath: string;
constructor() {
this.cacheData = {};
const configDir = join(homedir(), '.config', 'envision');
const hostname = getHostName();
this.filePath = join(configDir, 'config', `${hostname}-storage.json`);
fileExists(this.filePath, true);
}
async loadCache() {
const filePath = this.filePath;
try {
const data = await readFile(filePath, 'utf-8');
const jsonData = JSON.parse(data);
this.cacheData = jsonData;
} catch (error) {
this.cacheData = {};
await writeFile(filePath, JSON.stringify(this.cacheData, null, 2));
}
}
get length() {
return Object.keys(this.cacheData).length;
}
getItem(key: string) {
return this.cacheData[key];
}
setItem(key: string, value: any) {
this.cacheData[key] = value;
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
}
removeItem(key: string) {
delete this.cacheData[key];
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
}
clear() {
this.cacheData = {};
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
}
key(index: number) {
return Object.keys(this.cacheData)[index];
}
}
export class LoginNodeCache implements Cache {
name: 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);
}
async loadCache(filePath: string) {
try {
const data = await readFile(filePath, 'utf-8');
const jsonData = JSON.parse(data);
this.cacheData = jsonData;
} catch (error) {
const defaultData = { loginUsers: [] };
this.cacheData = defaultData;
await writeFile(filePath, JSON.stringify(defaultData, null, 2));
}
}
async get() {
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);
}
}

View File

@ -0,0 +1,12 @@
import { QueryLogin, QueryLoginOpts } from './query-login.ts';
import { MyCache } from '@kevisual/cache';
type QueryLoginNodeOptsWithoutCache = Omit<QueryLoginOpts, 'cache'>;
export class QueryLoginBrowser extends QueryLogin {
constructor(opts: QueryLoginNodeOptsWithoutCache) {
super({
...opts,
cache: new MyCache('login'),
});
}
}

14
src/query-login-node.ts Normal file
View File

@ -0,0 +1,14 @@
import { QueryLogin, QueryLoginOpts } from './query-login.ts';
import { LoginNodeCache, StorageNode } from './login-node-cache.ts';
type QueryLoginNodeOptsWithoutCache = Omit<QueryLoginOpts, 'cache'>;
export const storage = new StorageNode();
await storage.loadCache();
export class QueryLoginNode extends QueryLogin {
constructor(opts: QueryLoginNodeOptsWithoutCache) {
super({
...opts,
storage,
cache: new LoginNodeCache('login'),
});
}
}

View File

@ -2,12 +2,14 @@ import { Query } from '@kevisual/query';
import type { Result, DataOpts } from '@kevisual/query/query';
import { setBaseResponse } from '@kevisual/query/query';
import { LoginCacheStore, CacheStore } from './login-cache.ts';
import { Cache } from './login-cache.ts';
type QueryLoginOpts = {
export type QueryLoginOpts = {
query?: Query;
isBrowser?: boolean;
onLoad?: () => void;
storage?: Storage;
cache: Cache;
};
export type QueryLoginData = {
username?: string;
@ -21,6 +23,9 @@ export type QueryLoginResult = {
export class QueryLogin {
query: Query;
/**
* query login cache cache的包裹模块
*/
cache: CacheStore;
isBrowser: boolean;
load?: boolean;
@ -29,7 +34,7 @@ export class QueryLogin {
constructor(opts?: QueryLoginOpts) {
this.query = opts?.query || new Query();
this.cache = new LoginCacheStore('login');
this.cache = new LoginCacheStore({ name: 'login', cache: opts.cache });
this.isBrowser = opts?.isBrowser ?? true;
this.init();
this.onLoad = opts?.onLoad;
@ -257,4 +262,38 @@ export class QueryLogin {
},
);
}
/**
*
* @param token
* @returns
*/
async checkLoginStatus(token: string) {
const res = await this.post({
path: 'user',
key: 'checkLoginStatus',
loginToken: token,
});
if (res.code === 200) {
const accessToken = res.data?.accessToken;
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken });
return res;
}
return false;
}
/**
* 使web登录,url地址, MD5和jsonwebtoken
*/
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
const randomId = Math.random().toString(36).substring(2, 15);
const timestamp = Date.now();
const tokenSecret = 'xiao' + randomId;
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
// 10分钟过期
expiresIn: 60 * 10, // 10分钟
});
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
return { url, token, tokenSecret };
}
}

58
src/test-node/login.ts Normal file
View File

@ -0,0 +1,58 @@
import { QueryLoginNode, storage } from '../query-login-node.ts';
import { Query } from '@kevisual/query';
import dotenv from 'dotenv';
dotenv.config();
const { password, envision_registry } = process.env;
const query = new Query({
url: envision_registry,
});
query.before(async (options) => {
const token = storage.getItem('token');
if (token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token}`,
};
}
return options;
});
const queryLogin = new QueryLoginNode({
query,
isBrowser: false,
});
const login = async () => {
const res = await queryLogin
.login({
username: 'root',
password: password,
})
.then((res) => {
console.log(res);
});
return res;
};
// login();
const getMe = async () => {
const res = await queryLogin.getMe();
console.log(res);
};
getMe();
// console.log('token', storage.getItem('token'));
const switchUser = async (username: string) => {
const res = await queryLogin.switchUser(username);
console.log(res);
};
// switchUser('admin');
// switchUser('root');
const logout = async () => {
const res = await queryLogin.logout();
console.log(res);
};
// logout();

View File

@ -1,4 +1,4 @@
import { QueryLogin } from '../query-login.ts';
import { QueryLoginBrowser } from '../query-login-browser.ts';
import { Query } from '@kevisual/query';
const query = new Query({
url: 'https://kevisual.silkyai.cn/api/router',
@ -14,7 +14,7 @@ query.before(async (options) => {
}
return options;
});
const queryLogin = new QueryLogin({
const queryLogin = new QueryLoginBrowser({
query,
isBrowser: true,
});

View File

@ -1,7 +1,7 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/query-login.ts'],
entry: ['src/query-login.ts', 'src/query-login-browser.ts', 'src/query-login-node.ts'],
splitting: false,
sourcemap: false,
@ -10,4 +10,7 @@ export default defineConfig({
dts: true,
outDir: 'dist',
tsconfig: 'tsconfig.json',
define: {
IS_BROWSER: 'false',
},
});