Compare commits

..

No commits in common. "02c505c83a3422a5231b3952d848225f1aec8c0c" and "6b5eec89edcc04b73253ad9c8224124f500be123" have entirely different histories.

21 changed files with 190 additions and 326 deletions

7
.gitignore vendored
View File

@ -5,9 +5,4 @@ coverage
.DS_Store .DS_Store
upload upload
app.config.json5 app.config.json5
release/*
!release/.gitkeep
/*.tgz

5
.npmrc
View File

@ -1,4 +1 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} @abearxiong:registry=https://npm.pkg.github.com
@abearxiong:registry=https://npm.pkg.github.com
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
@kevisual:registry=https://npm.xiongxiao.me

View File

@ -1,14 +0,0 @@
# page proxy
# 部署方案
```sh
envision pack -p -u
envision pack-deploy 330bc5f8-1ae7-4be5-a44c-0ea0b3da184b page-proxy # key和id是人设置的
# 会复制到对应的文件夹里面了,现在。启动时后台启动
# 需要调用类似,但需要token
# token ev token会显示当前登陆的用户的token
# https://kevisual.xiongxiao.me/api/router?path=local-apps&key=updateStatus&appKey=page-proxy&status=start&token=******
```
![alt text](./docs/after-pub-deploy.png)

View File

@ -1,12 +1,10 @@
{ {
port: 3005,
api: { api: {
host: 'localhost:4002', // 后台代理 host: 'localhost:4002', // 后台代理
path: '/api/router', path: '/api/router',
}, },
proxy: { allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'],
port: 3005, domain: 'kevisual.xiongxiao.me',
domain: 'kevisual.xiongxiao.me', resources: 'minio.xiongxiao.me/resources',
resources: 'https://minio.xiongxiao.me/resources',
allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im', 'silkyai.cn'],
},
} }

View File

@ -1,10 +0,0 @@
{
port: 3005,
api: {
host: 'localhost:3000', // 后台代理
path: '/api/router',
},
allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'],
domain: 'demo.kevisual.xiongxiao.me',
resources: 'localhost:9000/resources',
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,49 +1,40 @@
{ {
"name": "page-proxy", "name": "var-proxy",
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"app": {
"key": "page-proxy",
"entry": "dist/app.mjs",
"type": "pm2-system-app",
"files": [
"dist"
]
},
"files": [
"dist"
],
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=development nodemon --ignore upload --exec tsx src/index.ts", "dev": "cross-env NODE_ENV=development nodemon --exec tsx src/index.ts",
"build": "rimraf dist && rollup -c", "build": "rimraf dist && rollup -c",
"start": "pm2 start dist/app.mjs --name page-proxy", "deploy": "rsync -avz dist/ light:~/apps/var-proxy/backend",
"release": "node ./scripts/release/index.mjs", "reload": "ssh light pm2 restart proxy",
"deploy": "envision switch root && envision pack -p -u", "pub": "npm run build && npm run deploy && npm run reload"
"pub": "npm run build && npm run deploy"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-commonjs": "^28.0.0",
"@rollup/plugin-json": "^6.1.0", "@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.2", "@rollup/plugin-typescript": "^12.1.0",
"@types/http-proxy": "^1.17.16", "@types/http-proxy": "^1.17.15",
"@types/node": "^22.13.4", "@types/node": "^22.7.5",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"nodemon": "^3.1.9", "nodemon": "^3.1.7",
"rollup": "^4.34.8", "rollup": "^4.24.0",
"tslib": "^2.8.1", "ts-lib": "^0.0.5",
"typescript": "^5.7.3" "tslib": "^2.7.0",
"typescript": "^5.6.3"
}, },
"dependencies": { "dependencies": {
"@kevisual/router": "0.0.6-alpha-5", "@abearxiong/router": "0.0.1-alpha.43",
"@kevisual/use-config": "^1.0.7", "@abearxiong/use-config": "^0.0.2",
"ioredis": "^5.5.0", "@abearxiong/use-file-store": "^0.0.1",
"nanoid": "^5.1.0" "http-proxy": "^1.18.1",
"ioredis": "^5.4.1",
"nanoid": "^5.0.7"
}, },
"resolutions": { "resolutions": {
"picomatch": "^4.0.2" "picomatch": "^4.0.2"

8
pnpm-lock.yaml generated
View File

@ -14,7 +14,7 @@ importers:
'@abearxiong/router': '@abearxiong/router':
specifier: 0.0.1-alpha.43 specifier: 0.0.1-alpha.43
version: 0.0.1-alpha.43 version: 0.0.1-alpha.43
'@kevisual/use-config': '@abearxiong/use-config':
specifier: ^0.0.2 specifier: ^0.0.2
version: 0.0.2 version: 0.0.2
'@abearxiong/use-file-store': '@abearxiong/use-file-store':
@ -66,8 +66,8 @@ packages:
'@abearxiong/router@0.0.1-alpha.43': '@abearxiong/router@0.0.1-alpha.43':
resolution: {integrity: sha512-umwi4T5s54Zb8ItseGw3uB7PDG8BqnHyAughlXH2dhub4f49fO+rwR+Zth7scUONkmc21Z27/lPgLBHXrYOkYw==, tarball: https://npm.pkg.github.com/download/@abearxiong/router/0.0.1-alpha.43/287c6e2597b36c5e3ed351b473e38f468b5f49ea} resolution: {integrity: sha512-umwi4T5s54Zb8ItseGw3uB7PDG8BqnHyAughlXH2dhub4f49fO+rwR+Zth7scUONkmc21Z27/lPgLBHXrYOkYw==, tarball: https://npm.pkg.github.com/download/@abearxiong/router/0.0.1-alpha.43/287c6e2597b36c5e3ed351b473e38f468b5f49ea}
'@kevisual/use-config@0.0.2': '@abearxiong/use-config@0.0.2':
resolution: {integrity: sha512-IBOmeP46ykbDlkplFS65UsAHjyPDKnvS2oqbkpLWhbSwDbF5zhBnD4ibsFZKPCyc3lMlPeRqYva4x6puX3E/qQ==, tarball: https://npm.pkg.github.com/download/@kevisual/use-config/0.0.2/59fbeec8c8e086ec48e55024fe39020b079e6fa5} resolution: {integrity: sha512-IBOmeP46ykbDlkplFS65UsAHjyPDKnvS2oqbkpLWhbSwDbF5zhBnD4ibsFZKPCyc3lMlPeRqYva4x6puX3E/qQ==, tarball: https://npm.pkg.github.com/download/@abearxiong/use-config/0.0.2/59fbeec8c8e086ec48e55024fe39020b079e6fa5}
'@abearxiong/use-file-store@0.0.1': '@abearxiong/use-file-store@0.0.1':
resolution: {integrity: sha512-65ZQBHxwr76sAFG+Xd4IQstx8dERhkaX5MLqtqJ0f9m+2NnS/klNe0t4q9tgjMWAEWQxHjnPShpHWzkCENaDnQ==, tarball: https://npm.pkg.github.com/download/@abearxiong/use-file-store/0.0.1/f171e398c078d4940c1ddedf5ad529d17b0eec32} resolution: {integrity: sha512-65ZQBHxwr76sAFG+Xd4IQstx8dERhkaX5MLqtqJ0f9m+2NnS/klNe0t4q9tgjMWAEWQxHjnPShpHWzkCENaDnQ==, tarball: https://npm.pkg.github.com/download/@abearxiong/use-file-store/0.0.1/f171e398c078d4940c1ddedf5ad529d17b0eec32}
@ -830,7 +830,7 @@ snapshots:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
'@kevisual/use-config@0.0.2': {} '@abearxiong/use-config@0.0.2': {}
'@abearxiong/use-file-store@0.0.1(typescript@5.6.3)': '@abearxiong/use-file-store@0.0.1(typescript@5.6.3)':
dependencies: dependencies:

View File

View File

@ -11,7 +11,7 @@ import json from '@rollup/plugin-json';
export default { export default {
input: 'src/index.ts', // TypeScript 入口文件 input: 'src/index.ts', // TypeScript 入口文件
output: { output: {
file: 'dist/app.mjs', // 输出文件 file: 'dist/app.js', // 输出文件
format: 'es', // 输出格式设置为 ES 模块 format: 'es', // 输出格式设置为 ES 模块
}, },
plugins: [ plugins: [
@ -25,5 +25,5 @@ export default {
declaration: false, declaration: false,
}), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件 }), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件
], ],
external: ['ioredis', '@kevisual/router', '@kevisual/use-config'], external: ['ws'],
}; };

View File

@ -1,114 +0,0 @@
import fs from 'fs';
import path from 'path';
import archiver from 'archiver';
import { exec } from 'child_process';
import { nanoid } from 'nanoid';
const cwd = process.cwd();
const pkgPath = path.join(cwd, 'package.json');
export const checkFileExistsSync = (filePath) => {
try {
// 使用 F_OK 检查文件或目录是否存在
fs.accessSync(filePath, fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
};
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const releasePath = path.join(cwd, 'release');
const distPath = path.join(cwd, 'dist');
const zip = archiver('zip', {
zlib: { level: 9 },
});
const zipName = `page-proxy-${pkg.version}.zip`;
const zipCache = path.join(releasePath, `page-proxy-${pkg.version}.zip`);
const getZip = async () => {
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(zipCache);
const startTime = (new Date().getTime() / 1000).toFixed(0);
// 监听事件
output.on('close', async () => {
const bytes = zip.pointer();
const size = bytes < 1024 ? `${bytes} bytes` : `${(bytes / 1024).toFixed(2)} KB`;
console.log(`Zip file has been created successfully. Total size: ${size} bytes.`);
let time = (new Date().getTime() / 1000).toFixed(0);
console.log('time', time - startTime);
resolve();
});
output.on('end', () => {
console.log('Data has been drained.'); // 数据已被耗尽
throw new CustomError('Data has been drained.');
});
zip.on('warning', (err) => {
if (err.code === 'ENOENT') {
console.warn('File not found:', err);
} else {
throw err;
}
});
zip.on('error', (err) => {
throw err;
});
// 通过管道将 zip 数据流输出到指定文件
zip.pipe(output);
// 添加 sh 字符串作为文件到 zip 中
const sh = `#!/bin/bash
npm i -g pnpm
pnpm install --prod
`;
zip.append(sh, { name: 'start.sh' });
// 把dist目录下的文件添加到zip中
zip.directory(distPath, 'dist');
// 把README.md添加到zip中
zip.file(path.join(cwd, 'README.md'), { name: 'README.md' });
// 把package.json添加到zip中
zip.file(pkgPath, { name: 'package.json' });
const ecosystemContent = `module.exports = {
apps: [
{
name: 'page-proxy', // 应用名称
script: './dist/app.mjs', // 入口文件
// cwd: '.', // 设置当前工作目录
output: './logs/page-proxy.log',
error: './logs/page-proxy.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
// watch: true, // 自动监控文件变化
watch: ['dist'], // 监控的文件夹
ignore_watch: ['node_modules', 'logs'], // 忽略的文件夹
}
]
}
`;
zip.append(ecosystemContent, { name: 'ecosystem.config.cjs' });
const json5Content = fs.readFileSync(path.join(cwd, 'app.config.json5.example'), 'utf8');
// tokenSecret 是一个随机字符串,用于生成 token
const tokenSecret = 'XX' + nanoid(39);
json5Content.replace('<TOKEN_SECRET>', tokenSecret);
// tokenSecret
// 把app.config.json5.example添加到zip中
// zip.file(path.join(cwd, 'app.config.json5.example'), { name: 'app.config.json5.example' });
zip.append(json5Content, { name: 'app.config.json5.example' });
// 结束归档(必须调用,否则 zip 文件无法完成)
zip.finalize();
});
};
getZip().then(() => {
console.log('zip success');
console.log(`envision switchOrg system && envision deploy ./release/${zipName} -v 1.0.0 -k page-proxy -y y -u`);
console.log(`download zip: https://kevisual.xiongxiao.me/system/page-proxy/${zipName}`);
});

View File

@ -1,4 +1,4 @@
import { App } from '@kevisual/router'; import { App } from '@abearxiong/router';
export const app = new App({ export const app = new App({
serverOptions: { serverOptions: {

View File

@ -1,8 +1,8 @@
import { handleRequest } from './module/index.ts'; import { handleRequest } from './module/index.ts';
import { config } from './module/config.ts'; import { useConfig } from '@abearxiong/use-config';
import { app } from './app.ts'; import { app } from './app.ts';
import './route/route.ts'; import './route/route.ts'
const port = config?.proxy?.port || 3005; const { port } = useConfig<{ port: number }>();
app app
.route({ .route({

View File

@ -1,33 +0,0 @@
import { useConfig } from '@kevisual/use-config';
import { useFileStore } from '@kevisual/use-config/file-store';
export const fileStore = useFileStore('proxy-upload');
type ConfigType = {
api: {
/**
* API host address
*/
host: string;
path?: string;
port?: number;
};
proxy: {
port?: number;
/**
* self domain kevisual.xiongxiao.me
*/
domain: string;
/**
* resources path
* https://minio.xiongxiao.me/resources
*/
resources: string;
/**
* allow origin xiongxiao.me zxj.im silkyai.cn
* 访
*/
allowOrigin: string[];
};
};
export const config = useConfig<ConfigType>();

View File

@ -1,15 +1,16 @@
import path from 'path'; import path from 'path';
import { redis, subscriber } from './redis/redis.ts'; import { redis, subscriber } from './redis/redis.ts';
import { config, fileStore } from '../module/config.ts'; import { useFileStore } from '@abearxiong/use-file-store';
import { useConfig } from '@abearxiong/use-config';
import fs from 'fs'; import fs from 'fs';
import crypto from 'crypto'; import crypto from 'crypto';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { pipeline } from 'stream'; import { pipeline } from 'stream';
import { promisify } from 'util'; import { promisify } from 'util';
import { fetchApp, fetchDomain, fetchTest } from './query/get-router.ts';
const pipelineAsync = promisify(pipeline); const pipelineAsync = promisify(pipeline);
const { resources } = config?.proxy || { resources: 'https://minio.xiongxiao.me/resources' }; const { resources, api } = useConfig<{ resources: string; api: { host: string; path: string } }>();
const fileStore = useFileStore('upload');
const status: { [key: string]: boolean } = {}; const status: { [key: string]: boolean } = {};
const demoData = { const demoData = {
user: 'root', user: 'root',
@ -43,13 +44,9 @@ type UserAppOptions = {
export class UserApp { export class UserApp {
user: string; user: string;
app: string; app: string;
isTest: boolean;
constructor(options: UserAppOptions) { constructor(options: UserAppOptions) {
this.user = options.user; this.user = options.user;
this.app = options.app; this.app = options.app;
if (this.user === 'test') {
this.isTest = true;
}
} }
async getExist() { async getExist() {
const app = this.app; const app = this.app;
@ -66,7 +63,6 @@ export class UserApp {
if (!value) { if (!value) {
return null; return null;
} }
return JSON.parse(value);
} }
async getFile(appFileUrl: string) { async getFile(appFileUrl: string) {
const app = this.app; const app = this.app;
@ -87,12 +83,29 @@ export class UserApp {
} }
// 获取域名对应的用户和应用 // 获取域名对应的用户和应用
const fetchRes = await fetchDomain(domain).catch((err) => { const fetchUrl = 'http://' + api.host + api.path;
return { const fetchRes = await fetch(fetchUrl, {
code: 500, method: 'POST',
message: err, headers: {
}; 'Content-Type': 'application/json',
}); },
body: JSON.stringify({
path: 'app',
key: 'getDomainApp',
data: {
domain,
},
}),
})
.then((res) => {
return res.json();
})
.catch((err) => {
return {
code: 500,
message: err,
};
});
if (fetchRes?.code !== 200) { if (fetchRes?.code !== 200) {
console.log('fetchRes is error', fetchRes); console.log('fetchRes is error', fetchRes);
return null; return null;
@ -126,16 +139,28 @@ export class UserApp {
async setCacheData() { async setCacheData() {
const app = this.app; const app = this.app;
const user = this.user; const user = this.user;
const isTest = this.isTest;
const key = 'user:app:' + app + ':' + user; const key = 'user:app:' + app + ':' + user;
const fetchUrl = 'http://' + api.host + api.path;
if (status[key]) { if (status[key]) {
return { return {
loading: true, loading: true,
}; };
} }
status[key] = true; status[key] = true;
const fetchRes = await fetch(fetchUrl, {
const fetchRes = isTest ? await fetchTest(app) : await fetchApp({ user, app }); method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: 'app',
key: 'getApp',
data: {
user,
key: app,
},
}),
}).then((res) => res.json());
if (fetchRes?.code !== 200) { if (fetchRes?.code !== 200) {
console.log('fetchRes is error', fetchRes); console.log('fetchRes is error', fetchRes);
this.setLoaded(); this.setLoaded();
@ -253,7 +278,7 @@ export const downloadUserAppFiles = async (user: string, app: string, data: type
}; };
} }
if (data.type === 'oss') { if (data.type === 'oss') {
let serverPath = new URL(resources).href + '/'; const serverPath = 'https://' + resources + '/';
// server download file // server download file
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[i]; const file = files[i];

View File

@ -1,36 +1,36 @@
import { getDNS, isLocalhost } from '@/utils/dns.ts'; import { getDNS, isLocalhost } from '@/utils/dns.ts';
import http from 'http'; import http from 'http';
import { UserApp } from './get-user-app.ts'; import { UserApp } from './get-user-app.ts';
import { config, fileStore } from '../module/config.ts'; import { useFileStore } from '@abearxiong/use-file-store';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { useConfig } from '@abearxiong/use-config';
import { redis } from './redis/redis.ts';
import { getContentType } from './get-content-type.ts'; import { getContentType } from './get-content-type.ts';
import { sleep } from '@/utils/sleep.ts'; import { sleep } from '@/utils/sleep.ts';
import { handleProxyRequest } from './proxy.ts';
const { api, domain, allowedOrigins } = useConfig<{
api: {
host: string;
port?: number;
};
domain: string;
allowedOrigins: string[];
}>();
const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' }; const fileStore = useFileStore('upload');
const domain = config?.proxy?.domain || 'kevisual.xiongxiao.me';
const allowedOrigins = config?.proxy?.allowOrigin || [];
const noProxyUrl = ['/', '/favicon.ico']; const noProxyUrl = ['/', '/favicon.ico'];
export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => { export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
if (req.url === '/favicon.ico') { if (req.url === '/favicon.ico') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('proxy no favicon.ico\n');
return; return;
} }
if (req.url.startsWith('/api/proxy')) { if (req.url.startsWith('/api/router')) {
return;
}
if (req.url.startsWith('/api')) {
// 代理到 http://codeflow.xiongxiao.me/api // 代理到 http://codeflow.xiongxiao.me/api
const _u = new URL(req.url, `http://${api.host}`); const _u = new URL(req.url, `http://${api.host}`);
// 设置代理请求的目标 URL 和请求头 // 设置代理请求的目标 URL 和请求头
let header: any = {}; let header: any = {};
if (req.headers?.['Authorization']) { if (req.headers?.['Authroization']) {
header.authorization = req.headers['Authorization']; header.Authorization = req.headers?.['Authroization'];
} else if (req.headers?.['authorization']) {
header.authorization = req.headers['authorization'];
} }
if (req.headers?.['Content-Type']) { if (req.headers?.['Content-Type']) {
header['Content-Type'] = req.headers?.['Content-Type']; header['Content-Type'] = req.headers?.['Content-Type'];
@ -64,11 +64,17 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
req.pipe(proxyReq, { end: true }); req.pipe(proxyReq, { end: true });
return; return;
} }
if (req.url.startsWith('/api/proxy')) {
return;
}
if (req.url.startsWith('/api')) { if (req.url.startsWith('/api')) {
res.end('not catch api'); res.end('not catch api');
return; return;
} }
if (req.url.startsWith('/test')) {
handleProxyRequest(req, res);
return;
}
const dns = getDNS(req); const dns = getDNS(req);
// 配置可以跨域 // 配置可以跨域
// 配置可以访问的域名 localhost, xiongxiao.me // 配置可以访问的域名 localhost, xiongxiao.me
@ -110,10 +116,8 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
app = data.app; app = data.app;
} }
} }
// const url = req.url; const url = req.url;
const pathname = new URL(req.url, `http://${dns.hostName}`).pathname; if (!domainApp && noProxyUrl.includes(req.url)) {
const url = pathname;
if (!domainApp && noProxyUrl.includes(url)) {
res.write('No proxy for this URL\n'); res.write('No proxy for this URL\n');
return res.end(); return res.end();
} }

64
src/module/proxy.ts Normal file
View File

@ -0,0 +1,64 @@
import http from 'http';
import httpProxy from 'http-proxy';
import { useConfig } from '@abearxiong/use-config';
const { resources, api } = useConfig<{
resources: string;
api: { host: string };
}>();
const proxy = httpProxy.createProxyServer({});
const fetchTest = async (id: string) => {
const fetchUrl = 'http://' + api.host + '/api/router';
const fetchRes = await fetch(fetchUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: 'user-app',
key: 'test',
id: id,
}),
}).then((res) => res.json());
return fetchRes;
};
// 60939f5e-f51b-4563-8c96-7a98ac5ac259
export const handleProxyRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
const url = req.url;
const urls = url.split('/');
const [_, test, id] = urls;
const error = (msg: string) => {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.write(msg);
res.end();
};
if (test !== 'test') {
error('Not Found');
return;
}
if (!id) {
error('Need Test ID');
return;
}
// 判断id是uuid
if (!isUUID(id)) {
error('Need Test ID is UUID');
return;
}
const result = await fetchTest(id);
console.log('data', result);
if (result.code !== 200) {
error('fetch error');
return;
}
const files = result.data?.data?.files;
const appFileUrl = (url + '').replace(`/${test}/${id}/`, '');
const pathFile = files.find((file: any) => file.name === appFileUrl);
const target = `https://${resources}/${pathFile.path}`;
console.log('target', target);
proxy.web(req, res, { target: target, secure: false });
};
function isUUID(id: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(id);
}

View File

@ -1,9 +1,12 @@
import { config } from '../config.ts'; import { useConfig } from '@abearxiong/use-config';
const { resources, api } = useConfig<{
resources: string;
api: { host: string };
}>();
const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' };
const apiPath = api.path || '/api/router';
export const fetchTest = async (id: string) => { export const fetchTest = async (id: string) => {
const fetchUrl = 'http://' + api.host + apiPath; const fetchUrl = 'http://' + api.host + '/api/router';
const fetchRes = await fetch(fetchUrl, { const fetchRes = await fetch(fetchUrl, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -19,7 +22,7 @@ export const fetchTest = async (id: string) => {
}; };
export const fetchDomain = async (domain: string) => { export const fetchDomain = async (domain: string) => {
const fetchUrl = 'http://' + api.host + apiPath; const fetchUrl = 'http://' + api.host + '/api/router';
const fetchRes = await fetch(fetchUrl, { const fetchRes = await fetch(fetchUrl, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -35,22 +38,3 @@ export const fetchDomain = async (domain: string) => {
}).then((res) => res.json()); }).then((res) => res.json());
return fetchRes; return fetchRes;
}; };
export const fetchApp = async ({ user, app }) => {
const fetchUrl = 'http://' + api.host + apiPath;
const fetchRes = await fetch(fetchUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: 'app',
key: 'getApp',
data: {
user,
key: app,
},
}),
}).then((res) => res.json());
return fetchRes;
};

View File

@ -1,5 +1,5 @@
import { Redis } from 'ioredis'; import { Redis } from 'ioredis';
import { useConfig } from '@kevisual/use-config'; import { useConfig } from '@abearxiong/use-config';
const config = useConfig<{ const config = useConfig<{
redis: ConstructorParameters<typeof Redis>; redis: ConstructorParameters<typeof Redis>;

View File

@ -1,8 +1,10 @@
import { UserApp } from '@/module/get-user-app.ts'; import { UserApp } from '@/module/get-user-app.ts';
import { app } from '../../app.ts'; import { app } from '../../app.ts';
import { redis } from '@/module/redis/redis.ts'; import { redis } from '@/module/redis/redis.ts';
import { CustomError } from '@abearxiong/router';
import fs from 'fs'; import fs from 'fs';
import { fileStore } from '../../module/config.ts'; import { useFileStore } from '@abearxiong/use-file-store';
const fileStore = useFileStore('upload');
app app
.route({ .route({
@ -32,7 +34,7 @@ app
await userApp.clearCacheData(); await userApp.clearCacheData();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
ctx.throw('删除失败'); throw new CustomError('删除失败');
} }
ctx.body = 'successfully'; ctx.body = 'successfully';
}) })
@ -57,41 +59,14 @@ app
app app
.route({ .route({
path: 'app', path: 'app',
key: 'clear', key: 'deleteAllForce',
}) })
.define(async (ctx) => { .define(async (ctx) => {
const keys = await redis.keys('user:app:*'); const keys = await redis.keys('user:app:*');
if (keys.length > 0) { await redis.del(...keys);
await redis.del(...keys);
}
fs.rmSync(fileStore, { recursive: true }); fs.rmSync(fileStore, { recursive: true });
ctx.body = { ctx.body = {
keys, keys,
}; };
}) });
.addTo(app);
app
.route({
path: 'app',
key: 'get',
})
.define(async (ctx) => {
const { user, app } = ctx.query.data || {};
if (!user || !app) {
if (!user) {
ctx.throw('user is required');
}
if (!app) {
ctx.throw('app is required');
}
}
const userApp = new UserApp({ user, app });
const cache = await userApp.getCache();
if (!cache) {
ctx.throw('Not Found App');
}
ctx.body = cache;
})
.addTo(app);

View File

@ -1,7 +1,8 @@
import { UserApp, clearAllUserApp } from '../module/get-user-app.ts'; import { UserApp, clearAllUserApp } from '../module/get-user-app.ts';
import { redis } from '../module/redis/redis.ts'; import { redis } from '../module/redis/redis.ts';
import path from 'path'; import path from 'path';
import { config, fileStore } from '../module/config.ts'; import { useFileStore } from '@abearxiong/use-file-store';
const filePath = useFileStore('upload');
const main = async () => { const main = async () => {
const userApp = new UserApp({ user: 'root', app: 'codeflow' }); const userApp = new UserApp({ user: 'root', app: 'codeflow' });
@ -32,6 +33,7 @@ const clearData = async () => {
// clearData(); // clearData();
// clearAllUserApp(); // clearAllUserApp();
const expireData = async () => { const expireData = async () => {
await redis.set('user:app:exist:' + 'codeflow:root', 'value', 'EX', 2); await redis.set('user:app:exist:' + 'codeflow:root', 'value', 'EX', 2);
process.exit(0); process.exit(0);
@ -43,5 +45,5 @@ const keysData = async () => {
const keys = await redis.keys('user:app:exist:*'); const keys = await redis.keys('user:app:exist:*');
console.log('keys', keys); console.log('keys', keys);
process.exit(0); process.exit(0);
}; }
keysData(); keysData();