From c01820d1c66f4028eaa44af1e7918219dde32c7d Mon Sep 17 00:00:00 2001 From: xion Date: Wed, 26 Feb 2025 23:59:42 +0800 Subject: [PATCH] token cache --- package.json | 4 +- pnpm-lock.yaml | 10 +- src/app-run.ts | 22 ++- src/app.ts | 9 +- src/command/login.ts | 31 ++++ src/command/ls-token.ts | 42 +++++- src/command/publish.ts | 2 +- src/command/router.ts | 99 +++++++++--- src/module/login/login-by-web.ts | 2 +- src/route/system-config/cache-token.ts | 200 +++++++++++++++++++++++++ src/route/system-config/index.ts | 3 +- src/scripts/system-config.ts | 53 +++++++ 12 files changed, 433 insertions(+), 44 deletions(-) create mode 100644 src/route/system-config/cache-token.ts create mode 100644 src/scripts/system-config.ts diff --git a/package.json b/package.json index f3faabe..d20be9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/envision-cli", - "version": "0.0.23", + "version": "0.0.24", "description": "envision command tools", "main": "dist/index.js", "type": "module", @@ -60,7 +60,7 @@ }, "dependencies": { "@kevisual/load": "^0.0.4", - "@kevisual/router": "^0.0.6", + "@kevisual/router": "^0.0.7", "crypto-js": "^4.2.0", "jsonwebtoken": "^9.0.2", "pg-hstore": "^2.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b0a4e2..bb71045 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^0.0.4 version: 0.0.4 '@kevisual/router': - specifier: ^0.0.6 - version: 0.0.6 + specifier: ^0.0.7 + version: 0.0.7 crypto-js: specifier: ^4.2.0 version: 4.2.0 @@ -410,8 +410,8 @@ packages: '@kevisual/query@0.0.7-alpha.3': resolution: {integrity: sha512-zNTIbyU87dlp8ZeLvPoc1ou7cZCL60to4xptyMD3VKsldL/dDSAMf7JWwUivNiq9lRxk9KVEZA7YX558mzeQcw==} - '@kevisual/router@0.0.6': - resolution: {integrity: sha512-7FQUY87Zy5A4V30OAggRbGpO/Asd7SUpnhHv8mlxnSFFTto25xpXmjHYp12mu/HJTsHM7RTaxVEyD1DeP44D2A==} + '@kevisual/router@0.0.7': + resolution: {integrity: sha512-4n1Tp4YLoraJv7jtfy7jbuLGyAj0B2QkTlnlEDHCUTlEUOvOkjtf7DHAe2SL92fTgXhSbod0I/0vUcDF85oj/w==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1973,7 +1973,7 @@ snapshots: '@kevisual/query@0.0.7-alpha.3': {} - '@kevisual/router@0.0.6': + '@kevisual/router@0.0.7': dependencies: path-to-regexp: 8.2.0 selfsigned: 2.4.1 diff --git a/src/app-run.ts b/src/app-run.ts index a517755..26a7eac 100644 --- a/src/app-run.ts +++ b/src/app-run.ts @@ -8,13 +8,23 @@ type Message = { key?: string; payload?: any; }; -export const runApp = async (msg: Message) => { - const { code, body, message } = await app.router.parse(msg); - const res = { code, data: body }; - if (message) { - res['message'] = message; +type Response = { + code: number; + data?: any; + message?: string; +}; +export const runApp = async (msg: Message): Promise => { + try { + const { code, body, message } = await app.call(msg); + const res = { code, data: body }; + if (message) { + res['message'] = message; + } + return res as { code: number; data: any; message?: string }; + } catch (error) { + console.error('runApp error', error); + return { code: 500, message: error.message }; } - return res; }; export const loadApp = async (mainApp: App) => { diff --git a/src/app.ts b/src/app.ts index c0fc093..97a0343 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,10 +2,13 @@ import { App } from '@kevisual/router'; import fs from 'fs'; import { getConfig, getPidList, pidFilePath, checkFileExists } from './module/get-config.ts'; import { sequelize } from './module/sequelize.ts'; -import { spawn } from 'child_process'; export { sequelize }; -export const app = new App(); +export const app = new App({ + serverOptions: { + httpType: 'https', + }, +}); // 处理进程退出或中断信号,确保删除 pid 文件 const cleanUp = () => { @@ -42,7 +45,7 @@ export const createApp = async () => { } fs.writeFileSync(pidFilePath, process.pid.toString()); app.listen(21015, () => { - console.log('Server is running on port 21015', 'http://localhost:21015/api/router', 'server id', process.pid); + console.log('Server is running on port 21015', 'https://localhost:21015/api/router', 'server id', process.pid); }); // import('./route.ts'); }; diff --git a/src/command/login.ts b/src/command/login.ts index dceb8ac..60aed70 100644 --- a/src/command/login.ts +++ b/src/command/login.ts @@ -6,11 +6,40 @@ import inquirer from 'inquirer'; import { runApp } from '../app-run.ts'; import { chalk } from '@/module/chalk.ts'; import { loginInCommand } from '@/module/login/login-by-web.ts'; +export const saveToken = async (token: string) => { + const baseURL = getBaseURL(); + const res = await runApp({ path: 'config', key: 'saveToken', payload: { baseURL, token } }); + if (res.code !== 200) { + console.log('Set token failed', res.message || ''); + } +}; +export const switchToken = async (baseURL: string) => { + const res = await runApp({ path: 'config', key: 'switchToken', payload: { baseURL } }); + if (res.code !== 200 && res.code !== 404) { + console.log('switch token failed', res.message || ''); + } + return res; +}; +export const deleteToken = async (baseURL: string) => { + const res = await runApp({ path: 'config', key: 'deleteToken', payload: { baseURL } }); + if (res.code !== 200) { + console.log('delete token failed', res.message || ''); + } + return res; +}; +export const getTokenList = async () => { + const res = await runApp({ path: 'config', key: 'getTokenList' }); + if (res.code !== 200) { + console.log('get token list failed', res.message || ''); + } + return res; +}; // 定义login命令,支持 `-u` 和 `-p` 参数来输入用户名和密码 const loginCommand = new Command('login') .description('Login to the application') .option('-u, --username ', 'Specify username') .option('-p, --password ', 'Specify password') + .option('-f, --force', 'Force login') .option('-w, --web', 'Login on the web') .action(async (options) => { const config = getConfig(); @@ -54,6 +83,7 @@ const loginCommand = new Command('login') if (res.code === 200) { const { token } = res.data; writeConfig({ ...config, token }); + saveToken(token); console.log('welcome', username); } else { console.log('登录失败', res.message || ''); @@ -100,6 +130,7 @@ const switchOrgCommand = new Command('switch').argument('', 'Switch to const token = res.data.token; writeConfig({ ...config, token }); console.log(`Switch ${username} Success`); + saveToken(token); await showMe(); } else { console.log(`Switch ${username} Failed`, res.message || ''); diff --git a/src/command/ls-token.ts b/src/command/ls-token.ts index 5de099d..9e50bda 100644 --- a/src/command/ls-token.ts +++ b/src/command/ls-token.ts @@ -1,12 +1,44 @@ import { program as app, Command } from '@/program.ts'; import { getConfig, query, writeConfig } from '@/module/index.ts'; import inquirer from 'inquirer'; +import util from 'util'; +import { saveToken, switchToken, deleteToken, getTokenList } from './login.ts'; const token = new Command('token').description('show token').action(async () => { const config = getConfig(); console.log('token', config.token); }); +const tokenList = new Command('list') + .description('show token list') + .option('-r --remove ', 'remove token by number') + .action(async (opts) => { + const res = await getTokenList(); + if (res.code !== 200) { + console.error('get token list failed', res.message || ''); + return; + } + console.log(util.inspect(res.data.value, { colors: true, depth: 4 })); + const list = res.data.value || []; + if (opts.remove) { + const index = Number(opts.remove) - 1; + if (index < 0 || index >= list.length) { + console.log('index out of range'); + return; + } + const removeBase = list.splice(index, 1); + const baseURL = removeBase[0]; + if (baseURL.baseURL) { + const res = await deleteToken(baseURL?.baseURL); + if (res.code !== 200) { + return; + } + } + console.log('delete token success', 'delete', baseURL); + return; + } + }); +token.addCommand(tokenList); app.addCommand(token); const baseURL = new Command('baseURL') @@ -20,7 +52,7 @@ const baseURL = new Command('baseURL') .option('-c, --clear', 'clear baseURL') .action(async (opts) => { const config = getConfig(); - let list = config.baseURLList || []; + let list = (config.baseURLList as Array) || []; const quineList = (list: string[]) => { const newList = new Set(list); return Array.from(newList); @@ -49,20 +81,23 @@ const baseURL = new Command('baseURL') console.log('index out of range'); return; } - list.splice(index, 1); + const removeBase = list.splice(index, 1); list = quineList(list); showList(list); writeConfig({ ...config, baseURLList: list }); + removeBase[0] && deleteToken(removeBase[0]); return; } if (opts.set) { const isNumber = !isNaN(Number(opts.set)); + let baseURL = ''; if (isNumber) { const index = Number(opts.set) - 1; if (index < 0 || index >= list.length) { console.log('index out of range'); return; } + baseURL = list[index]; writeConfig({ ...config, baseURL: list[index] }); console.log('set baseURL success:', list[index]); } else { @@ -72,9 +107,11 @@ const baseURL = new Command('baseURL') console.log('invalid baseURL:', opts.set); return; } + baseURL = opts.set; writeConfig({ ...config, baseURL: opts.set }); console.log('set baseURL success:', opts.set); } + baseURL && switchToken(baseURL); return; } if (opts.list) { @@ -100,6 +137,7 @@ const setBaseURL = new Command('set').description('set baseURL').action(async () ]); const baseURL = answers.baseURL; writeConfig({ ...config, baseURL }); + baseURL && switchToken(baseURL); }); baseURL.addCommand(setBaseURL); diff --git a/src/command/publish.ts b/src/command/publish.ts index 7fb2761..1d0f107 100644 --- a/src/command/publish.ts +++ b/src/command/publish.ts @@ -435,7 +435,7 @@ const servicesCommand = new Command('services') ); }); } else { - console.log(chalk.red(res.message || '获取列表失败')); + console.log('error', chalk.red(res.message || '获取列表失败')); } return; } diff --git a/src/command/router.ts b/src/command/router.ts index 6ff493c..179e840 100644 --- a/src/command/router.ts +++ b/src/command/router.ts @@ -1,49 +1,102 @@ -import { program as app, Command } from '@/program.ts'; +import { program, Command } from '@/program.ts'; import inquirer from 'inquirer'; import { query } from '../module/index.ts'; import chalk from 'chalk'; import util from 'util'; +import { runApp } from '@/app-run.ts'; +const router = new Command('router').description('router get'); +program.addCommand(router); // web 开发模块 -const command = new Command('router') - .description('router get') - .option('-p, --path ', '退出进程') - .option('-k, --key ', '启动进程') +const command = new Command('service') + .description('router services get') + .option('-p, --path ', '第一路径 path') + .option('-k, --key ', '第二路径 key') .action(async (options) => { let { path, key } = options; // 如果没有传递参数,则通过交互式输入 - if (!path || !key) { + if (!path) { const answers = await inquirer.prompt([ { type: 'input', name: 'path', + required: true, message: 'Enter your path:', when: () => !path, // 当 username 为空时,提示用户输入 }, - { - type: 'input', - name: 'key', - message: 'Enter your key:', - when: () => !key, // 当 password 为空时,提示用户输入 - }, ]); path = answers.path || path; + } + if (!key) { + const answers = await inquirer.prompt([ + { + type: 'input', + required: false, + name: 'key', + message: 'Enter your key:', + }, + ]); key = answers.key || key; } - const res = await query.post({ path, key }); if (res?.code === 200) { - const data = res.data.map((item: any) => { - // return `id: ${item.id}, title: ${item.title}`; - - return { - id: item.id, - title: item.title, - }; - }); - console.log(chalk.green(util.inspect(data, { colors: true, depth: 4 }))); + console.log('query success'); + const _list = res.data?.list || res.data; + if (Array.isArray(_list)) { + const data = _list.map((item: any) => { + // return `id: ${item.id}, title: ${item.title}`; + return { + id: item.id, + title: item.title, + }; + }); + console.log(chalk.green(util.inspect(data, { colors: true, depth: 4 }))); + } } else { console.log('error', res.message || ''); } }); -app.addCommand(command); + +router.addCommand(command); + +const localRouter = new Command('local') + .description('router local get') + .option('-p, --path ', '第一路径 path') + .option('-k, --key ', '第二路径 key') + .action(async (options) => { + let { path, key } = options; + // 如果没有传递参数,则通过交互式输入 + if (!path) { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'path', + required: true, + message: 'Enter your path:', + when: () => !path, // 当 username 为空时,提示用户输入 + }, + ]); + path = answers.path || path; + } + if (!key) { + const answers = await inquirer.prompt([ + { + type: 'input', + required: false, + name: 'key', + message: 'Enter your key:', + }, + ]); + key = answers.key || key; + } + const res = await runApp({ path, key }); + if (res?.code === 200) { + console.log('query success'); + + console.log(chalk.green(util.inspect(res, { colors: true, depth: 4 }))); + } else { + console.log('error query', res.code, res.message || ''); + } + }); + +router.addCommand(localRouter); diff --git a/src/module/login/login-by-web.ts b/src/module/login/login-by-web.ts index 1ea92b0..20ec29c 100644 --- a/src/module/login/login-by-web.ts +++ b/src/module/login/login-by-web.ts @@ -4,7 +4,7 @@ import { chalk } from '../chalk.ts'; import jsonwebtoken from 'jsonwebtoken'; import { BaseLoad } from '@kevisual/load'; import { getConfig, writeConfig } from '../get-config.ts'; -export const saveToken = async (token: string) => { +const saveToken = async (token: string) => { const config = await getConfig(); writeConfig({ ...config, diff --git a/src/route/system-config/cache-token.ts b/src/route/system-config/cache-token.ts new file mode 100644 index 0000000..b866d39 --- /dev/null +++ b/src/route/system-config/cache-token.ts @@ -0,0 +1,200 @@ +import { app } from '@/app.ts'; +import { Config } from './model/config.ts'; +import { writeConfig } from '@/module/get-config.ts'; +const cacheToken = 'tokenList'; +export type TokenCacheItem = { + baseURL?: string; + token?: string; + expireTime?: number; +}; + +app + .route({ + path: 'config', + key: 'getTokenList', + description: 'Get token list', + }) + .define(async (ctx) => { + const tokenList = await Config.findOne({ + where: { + key: cacheToken, + }, + logging: false, + }); + if (tokenList) { + ctx.body = tokenList; + return; + } + ctx.body = { + value: [], + }; + }) + .addTo(app); + +app + .route({ + path: 'config', + key: 'setTokenList', + description: 'Set token list', + }) + .define(async (ctx) => { + const { data } = ctx.query; + if (!data) { + ctx.throw(400, 'data is required'); + } + let config = await Config.findOne({ + where: { key: cacheToken }, // 自定义条件 + logging: false, + }); + + if (!config) { + config = await Config.create( + { + key: cacheToken, + value: data, + }, + { logging: false }, + ); + ctx.body = config; + return; + } else { + config.value = data; + await config.save(); + ctx.body = config; + } + }) + .addTo(app); + +app + .route({ + path: 'config', + key: 'clearToken', + description: 'Clear token list', + }) + .define(async (ctx) => { + const config = await Config.findOne({ + where: { key: cacheToken }, + logging: false, + }); + if (config) { + await config.destroy(); + } + ctx.body = 'success'; + }) + .addTo(app); + +app + .route({ + path: 'config', + key: 'saveToken', + description: 'Add token', + validator: { + baseURL: { + type: 'string', + required: true, + message: 'baseURL is required', + }, + token: { + type: 'string', + required: true, + message: 'token is required', + }, + }, + }) + .define(async (ctx) => { + const { baseURL, token } = ctx.query; + if (!baseURL || !token) { + ctx.throw(400, 'baseURL and token are required'); + } + const data: TokenCacheItem = { + baseURL, + token, + }; + const tokenRes = await ctx.call({ path: 'config', key: 'getTokenList' }); + if (tokenRes.code !== 200) { + ctx.throw(tokenRes.code, tokenRes.message || 'Failed to get token list'); + } + const tokenList: TokenCacheItem[] = tokenRes.body?.value || []; + // Check if the token already exists + const index = tokenList.findIndex((item) => item.baseURL === data.baseURL); + if (index > -1) { + tokenList[index] = data; + } else { + tokenList.push(data); + } + const res = await ctx.call({ path: 'config', key: 'setTokenList', payload: { data: tokenList } }); + if (res.code === 200) { + ctx.body = res.body?.value; + } else ctx.throw(res.code, res.message || 'Failed to add token'); + }) + .addTo(app); + +app + .route({ + path: 'config', + key: 'switchToken', + description: 'Switch token user', + validator: { + baseURL: { + type: 'string', + required: true, + message: 'baseURL is required', + }, + }, + }) + .define(async (ctx) => { + const { baseURL } = ctx.query; + const configRes = await ctx.call({ path: 'config', key: 'getTokenList' }); + if (configRes.code !== 200) { + ctx.throw(configRes.code, configRes.message || 'Failed to get token list'); + } + const tokenList: TokenCacheItem[] = configRes.body?.value || []; + const index = tokenList.findIndex((item) => item.baseURL === baseURL); + const token = index > -1 ? tokenList[index].token : ''; + if (index > -1 && token) { + writeConfig({ token: tokenList[index].token }); + ctx.body = { + baseURL: baseURL, + token: tokenList[index].token, + }; + } else { + ctx.throw(404, 'Token not found'); + } + }) + .addTo(app); + +app + .route({ + path: 'config', + key: 'deleteToken', + description: 'Delete token', + validator: { + baseURL: { + type: 'string', + required: true, + message: 'baseURL is required', + }, + }, + }) + .define(async (ctx) => { + const { baseURL } = ctx.query; + const config = await ctx.call({ path: 'config', key: 'getTokenList' }); + if (config.code !== 200) { + ctx.throw(config.code, config.message || 'Failed to get token list'); + } + const tokenList: TokenCacheItem[] = config.body?.value || []; + const index = tokenList.findIndex((item) => item.baseURL === baseURL); + if (index > -1) { + tokenList.splice(index, 1); + const res = await ctx.call({ path: 'config', key: 'setTokenList', payload: { data: tokenList } }); + if (res.code === 200) { + ctx.body = res.body; + } else ctx.throw(res.code, res.message || 'Failed to delete token'); + } else { + console.log('not has token', baseURL); + ctx.body = { + value: tokenList, + }; + } + }) + .addTo(app); diff --git a/src/route/system-config/index.ts b/src/route/system-config/index.ts index 9166f9d..8239e05 100644 --- a/src/route/system-config/index.ts +++ b/src/route/system-config/index.ts @@ -1 +1,2 @@ -import './list.ts' \ No newline at end of file +import './list.ts' +import './cache-token.ts' \ No newline at end of file diff --git a/src/scripts/system-config.ts b/src/scripts/system-config.ts new file mode 100644 index 0000000..4c9811d --- /dev/null +++ b/src/scripts/system-config.ts @@ -0,0 +1,53 @@ +import { getConfig } from '../module/get-config.ts'; +import { runApp } from '../app-run.ts'; + +const getConfigList = async () => { + const res = await runApp({ + path: 'config', + key: 'getTokenList', + }); + console.log(res); +}; + +// getConfigList(); + +const setConfigList = async () => { + const config = getConfig(); + const { baseURL, token } = config; + console.log(baseURL, token); + + const res = await runApp({ + path: 'config', + key: 'saveToken', + payload: { + baseURL: 'abc32', + token, + }, + }); + console.log(res); +}; +// setConfigList(); + +const switchToken = async () => { + const res = await runApp({ + path: 'config', + key: 'switchToken', + payload: { + baseURL: 'abc2', + }, + }); + console.log(res); +}; +// switchToken(); + +const removeToken = async () => { + const res = await runApp({ + path: 'config', + key: 'deleteToken', + payload: { + baseURL: 'abc32', + }, + }); + console.log(res); +}; +removeToken();