This commit is contained in:
xion 2024-10-15 18:05:00 +08:00
parent c92f817d66
commit 6b5eec89ed
11 changed files with 331 additions and 47 deletions

View File

@ -1,7 +1,7 @@
{ {
port: 3005, port: 3005,
api: { api: {
host: 'localhost:4000', // 后台代理 host: 'localhost:4002', // 后台代理
path: '/api/router', path: '/api/router',
}, },
allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'], allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'],

View File

@ -19,6 +19,7 @@
"@rollup/plugin-json": "^6.1.0", "@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.0", "@rollup/plugin-typescript": "^12.1.0",
"@types/http-proxy": "^1.17.15",
"@types/node": "^22.7.5", "@types/node": "^22.7.5",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"nodemon": "^3.1.7", "nodemon": "^3.1.7",
@ -28,9 +29,10 @@
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, },
"dependencies": { "dependencies": {
"@abearxiong/router": "0.0.1-alpha.40", "@abearxiong/router": "0.0.1-alpha.43",
"@abearxiong/use-config": "^0.0.2", "@abearxiong/use-config": "^0.0.2",
"@abearxiong/use-file-store": "^0.0.1", "@abearxiong/use-file-store": "^0.0.1",
"http-proxy": "^1.18.1",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"nanoid": "^5.0.7" "nanoid": "^5.0.7"
}, },

67
pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ importers:
.: .:
dependencies: dependencies:
'@abearxiong/router':
specifier: 0.0.1-alpha.43
version: 0.0.1-alpha.43
'@abearxiong/use-config': '@abearxiong/use-config':
specifier: ^0.0.2 specifier: ^0.0.2
version: 0.0.2 version: 0.0.2
@ -60,6 +63,9 @@ importers:
packages: packages:
'@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}
'@abearxiong/use-config@0.0.2': '@abearxiong/use-config@0.0.2':
resolution: {integrity: sha512-IBOmeP46ykbDlkplFS65UsAHjyPDKnvS2oqbkpLWhbSwDbF5zhBnD4ibsFZKPCyc3lMlPeRqYva4x6puX3E/qQ==, tarball: https://npm.pkg.github.com/download/@abearxiong/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}
@ -231,6 +237,9 @@ packages:
'@types/estree@1.0.6': '@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
'@types/http-proxy@1.17.15':
resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@ -433,6 +442,9 @@ packages:
estree-walker@2.0.2: estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
events@3.3.0: events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'} engines: {node: '>=0.8.x'}
@ -455,6 +467,15 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'} engines: {node: '>=8'}
follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
fsevents@2.3.3: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -485,6 +506,10 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
http-proxy@1.18.1:
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
engines: {node: '>=8.0.0'}
ignore-by-default@1.0.1: ignore-by-default@1.0.1:
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
@ -626,6 +651,9 @@ packages:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'} engines: {node: '>=4'}
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
resolve@1.22.8: resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true hasBin: true
@ -781,8 +809,27 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
hasBin: true hasBin: true
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
snapshots: snapshots:
'@abearxiong/router@0.0.1-alpha.43':
dependencies:
ws: 8.18.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@abearxiong/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)':
@ -916,6 +963,10 @@ snapshots:
'@types/estree@1.0.6': {} '@types/estree@1.0.6': {}
'@types/http-proxy@1.17.15':
dependencies:
'@types/node': 22.7.5
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/node@22.7.5': '@types/node@22.7.5':
@ -1133,6 +1184,8 @@ snapshots:
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
eventemitter3@4.0.7: {}
events@3.3.0: {} events@3.3.0: {}
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
@ -1147,6 +1200,8 @@ snapshots:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
follow-redirects@1.15.9: {}
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@ -1168,6 +1223,14 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
http-proxy@1.18.1:
dependencies:
eventemitter3: 4.0.7
follow-redirects: 1.15.9
requires-port: 1.0.0
transitivePeerDependencies:
- debug
ignore-by-default@1.0.1: {} ignore-by-default@1.0.1: {}
ioredis@5.4.1: ioredis@5.4.1:
@ -1296,6 +1359,8 @@ snapshots:
dependencies: dependencies:
redis-errors: 1.2.0 redis-errors: 1.2.0
requires-port@1.0.0: {}
resolve@1.22.8: resolve@1.22.8:
dependencies: dependencies:
is-core-module: 2.15.1 is-core-module: 2.15.1
@ -1467,3 +1532,5 @@ snapshots:
which@2.0.2: which@2.0.2:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
ws@8.18.0: {}

9
src/app.ts Normal file
View File

@ -0,0 +1,9 @@
import { App } from '@abearxiong/router';
export const app = new App({
serverOptions: {
path: '/api/proxy',
},
});
// app.server.on(callback);

View File

@ -1,14 +1,21 @@
import http from 'http';
import { handleRequest } from './module/index.ts'; import { handleRequest } from './module/index.ts';
import { useConfig } from '@abearxiong/use-config'; import { useConfig } from '@abearxiong/use-config';
import { app } from './app.ts';
import './route/route.ts'
const { port } = useConfig<{ port: number }>(); const { port } = useConfig<{ port: number }>();
const server = http.createServer((req, res) => {
// res.writeHead(200, { 'Content-Type': 'text/plain' }); app
// const pathname = new URL(req.url, `http://${dns.hostName}`).pathname; .route({
handleRequest(req, res); path: 'hello',
// res.write(`Request from ${dns.hostName} with IP: ${dns.ip}\n`); key: '0',
// res.end('Hello World\n'); })
}); .define(async (ctx) => {
server.listen(port, () => { ctx.body = 'hello world';
})
.addTo(app);
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`); console.log(`Server running at http://localhost:${port}/`);
}); });
app.server.on(handleRequest);

View File

@ -8,9 +8,11 @@ import { useConfig } from '@abearxiong/use-config';
import { redis } from './redis/redis.ts'; 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<{ const { api, domain, allowedOrigins } = useConfig<{
api: { api: {
host: string; host: string;
port?: number;
}; };
domain: string; domain: string;
allowedOrigins: string[]; allowedOrigins: string[];
@ -19,6 +21,60 @@ const { api, domain, allowedOrigins } = useConfig<{
const fileStore = useFileStore('upload'); const fileStore = useFileStore('upload');
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') {
return;
}
if (req.url.startsWith('/api/router')) {
// 代理到 http://codeflow.xiongxiao.me/api
const _u = new URL(req.url, `http://${api.host}`);
// 设置代理请求的目标 URL 和请求头
let header: any = {};
if (req.headers?.['Authroization']) {
header.Authorization = req.headers?.['Authroization'];
}
if (req.headers?.['Content-Type']) {
header['Content-Type'] = req.headers?.['Content-Type'];
}
const options = {
host: _u.hostname,
path: req.url,
method: req.method,
headers: {
...header,
},
};
if (_u.port) {
// @ts-ignore
options.port = _u.port;
}
// 创建代理请求
const proxyReq = http.request(options, (proxyRes) => {
// 将代理服务器的响应头和状态码返回给客户端
res.writeHead(proxyRes.statusCode, proxyRes.headers);
// 将代理响应流写入客户端响应
proxyRes.pipe(res, { end: true });
});
// 处理代理请求的错误事件
proxyReq.on('error', (err) => {
console.error(`Proxy request error: ${err.message}`);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.write(`Proxy request error: ${err.message}`);
});
// 处理 POST 请求的请求体(传递数据到目标服务器)
req.pipe(proxyReq, { end: true });
return;
}
if (req.url.startsWith('/api/proxy')) {
return;
}
if (req.url.startsWith('/api')) {
res.end('not catch api');
return;
}
if (req.url.startsWith('/test')) {
handleProxyRequest(req, res);
return;
}
const dns = getDNS(req); const dns = getDNS(req);
// 配置可以跨域 // 配置可以跨域
// 配置可以访问的域名 localhost, xiongxiao.me // 配置可以访问的域名 localhost, xiongxiao.me
@ -87,42 +143,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
user = _user; user = _user;
app = _app; app = _app;
} }
const [_, _api] = req.url.split('/');
if (_api === 'api') {
// 代理到 http://codeflow.xiongxiao.me/api
// 设置代理请求的目标 URL 和请求头
let header: any = {};
if (req.headers?.['Authroization']) {
header.Authorization = req.headers?.['Authroization'];
}
if (req.headers?.['Content-Type']) {
header['Content-Type'] = req.headers?.['Content-Type'];
}
const options = {
host: api.host,
path: req.url,
method: 'POST',
headers: {
...header,
},
};
// 创建代理请求
const proxyReq = http.request(options, (proxyRes) => {
// 将代理服务器的响应头和状态码返回给客户端
res.writeHead(proxyRes.statusCode, proxyRes.headers);
// 将代理响应流写入客户端响应
proxyRes.pipe(res, { end: true });
});
// 处理代理请求的错误事件
proxyReq.on('error', (err) => {
console.error(`Proxy request error: ${err.message}`);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.write(`Proxy request error: ${err.message}`);
});
// 处理 POST 请求的请求体(传递数据到目标服务器)
req.pipe(proxyReq, { end: true });
return;
}
const userApp = new UserApp({ user, app }); const userApp = new UserApp({ user, app });
let isExist = await userApp.getExist(); let isExist = await userApp.getExist();
if (!isExist) { if (!isExist) {

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

@ -0,0 +1,40 @@
import { useConfig } from '@abearxiong/use-config';
const { resources, api } = useConfig<{
resources: string;
api: { host: string };
}>();
export 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;
};
export const fetchDomain = async (domain: string) => {
const fetchUrl = 'http://' + api.host + '/api/router';
const fetchRes = await fetch(fetchUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: 'app',
key: 'getDomainApp',
data: {
domain,
},
}),
}).then((res) => res.json());
return fetchRes;
};

1
src/route/app/index.ts Normal file
View File

@ -0,0 +1 @@
import './list.ts'

72
src/route/app/list.ts Normal file
View File

@ -0,0 +1,72 @@
import { UserApp } from '@/module/get-user-app.ts';
import { app } from '../../app.ts';
import { redis } from '@/module/redis/redis.ts';
import { CustomError } from '@abearxiong/router';
import fs from 'fs';
import { useFileStore } from '@abearxiong/use-file-store';
const fileStore = useFileStore('upload');
app
.route({
path: 'app',
key: 'list',
})
.define(async (ctx) => {
const keys = await redis.keys('user:app:*');
// const keys = await redis.keys('user:app:exist:*');
// const data = await redis.mget(...keys);
ctx.body = {
// data: data,
keys,
};
})
.addTo(app);
app
.route({
path: 'app',
key: 'delete',
})
.define(async (ctx) => {
const { user, app } = ctx.query;
try {
const userApp = new UserApp({ user, app });
await userApp.clearCacheData();
} catch (error) {
console.error(error);
throw new CustomError('删除失败');
}
ctx.body = 'successfully';
})
.addTo(app);
app
.route({
path: 'app',
key: 'deleteAll',
})
.define(async (ctx) => {
const keys = await redis.keys('user:app:*');
for (const key of keys) {
await redis.set(key, '', 'EX', 1);
}
ctx.body = {
keys,
};
})
.addTo(app);
app
.route({
path: 'app',
key: 'deleteAllForce',
})
.define(async (ctx) => {
const keys = await redis.keys('user:app:*');
await redis.del(...keys);
fs.rmSync(fileStore, { recursive: true });
ctx.body = {
keys,
};
});

1
src/route/route.ts Normal file
View File

@ -0,0 +1 @@
import './app/index.ts'