From 6b5eec89edcc04b73253ad9c8224124f500be123 Mon Sep 17 00:00:00 2001 From: xion Date: Tue, 15 Oct 2024 18:05:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.config.json5 | 2 +- package.json | 4 +- pnpm-lock.yaml | 67 ++++++++++++++++++++++++ src/app.ts | 9 ++++ src/index.ts | 25 +++++---- src/module/index.ts | 93 +++++++++++++++++++++------------- src/module/proxy.ts | 64 +++++++++++++++++++++++ src/module/query/get-router.ts | 40 +++++++++++++++ src/route/app/index.ts | 1 + src/route/app/list.ts | 72 ++++++++++++++++++++++++++ src/route/route.ts | 1 + 11 files changed, 331 insertions(+), 47 deletions(-) create mode 100644 src/app.ts create mode 100644 src/module/proxy.ts create mode 100644 src/module/query/get-router.ts create mode 100644 src/route/app/index.ts create mode 100644 src/route/app/list.ts create mode 100644 src/route/route.ts diff --git a/app.config.json5 b/app.config.json5 index f13b51f..e04958a 100644 --- a/app.config.json5 +++ b/app.config.json5 @@ -1,7 +1,7 @@ { port: 3005, api: { - host: 'localhost:4000', // 后台代理 + host: 'localhost:4002', // 后台代理 path: '/api/router', }, allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'], diff --git a/package.json b/package.json index dcd80af..4865f44 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-typescript": "^12.1.0", + "@types/http-proxy": "^1.17.15", "@types/node": "^22.7.5", "cross-env": "^7.0.3", "nodemon": "^3.1.7", @@ -28,9 +29,10 @@ "typescript": "^5.6.3" }, "dependencies": { - "@abearxiong/router": "0.0.1-alpha.40", + "@abearxiong/router": "0.0.1-alpha.43", "@abearxiong/use-config": "^0.0.2", "@abearxiong/use-file-store": "^0.0.1", + "http-proxy": "^1.18.1", "ioredis": "^5.4.1", "nanoid": "^5.0.7" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdf2328..0dece01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: .: dependencies: + '@abearxiong/router': + specifier: 0.0.1-alpha.43 + version: 0.0.1-alpha.43 '@abearxiong/use-config': specifier: ^0.0.2 version: 0.0.2 @@ -60,6 +63,9 @@ importers: 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': 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': 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': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -433,6 +442,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -455,6 +467,15 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 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: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -485,6 +506,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 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: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} @@ -626,6 +651,9 @@ packages: resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} engines: {node: '>=4'} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -781,8 +809,27 @@ packages: engines: {node: '>= 8'} 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: + '@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-file-store@0.0.1(typescript@5.6.3)': @@ -916,6 +963,10 @@ snapshots: '@types/estree@1.0.6': {} + '@types/http-proxy@1.17.15': + dependencies: + '@types/node': 22.7.5 + '@types/json-schema@7.0.15': {} '@types/node@22.7.5': @@ -1133,6 +1184,8 @@ snapshots: estree-walker@2.0.2: {} + eventemitter3@4.0.7: {} + events@3.3.0: {} fast-deep-equal@3.1.3: {} @@ -1147,6 +1200,8 @@ snapshots: dependencies: to-regex-range: 5.0.1 + follow-redirects@1.15.9: {} + fsevents@2.3.3: optional: true @@ -1168,6 +1223,14 @@ snapshots: dependencies: 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: {} ioredis@5.4.1: @@ -1296,6 +1359,8 @@ snapshots: dependencies: redis-errors: 1.2.0 + requires-port@1.0.0: {} + resolve@1.22.8: dependencies: is-core-module: 2.15.1 @@ -1467,3 +1532,5 @@ snapshots: which@2.0.2: dependencies: isexe: 2.0.0 + + ws@8.18.0: {} diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..3ec3f24 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,9 @@ +import { App } from '@abearxiong/router'; + +export const app = new App({ + serverOptions: { + path: '/api/proxy', + }, +}); + +// app.server.on(callback); diff --git a/src/index.ts b/src/index.ts index e7f3d2f..d69a418 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,21 @@ -import http from 'http'; import { handleRequest } from './module/index.ts'; import { useConfig } from '@abearxiong/use-config'; +import { app } from './app.ts'; +import './route/route.ts' const { port } = useConfig<{ port: number }>(); -const server = http.createServer((req, res) => { - // res.writeHead(200, { 'Content-Type': 'text/plain' }); - // const pathname = new URL(req.url, `http://${dns.hostName}`).pathname; - handleRequest(req, res); - // res.write(`Request from ${dns.hostName} with IP: ${dns.ip}\n`); - // res.end('Hello World\n'); -}); -server.listen(port, () => { + +app + .route({ + path: 'hello', + key: '0', + }) + .define(async (ctx) => { + ctx.body = 'hello world'; + }) + .addTo(app); + +app.listen(port, () => { console.log(`Server running at http://localhost:${port}/`); }); + +app.server.on(handleRequest); diff --git a/src/module/index.ts b/src/module/index.ts index dcd6b00..72602d1 100644 --- a/src/module/index.ts +++ b/src/module/index.ts @@ -8,9 +8,11 @@ import { useConfig } from '@abearxiong/use-config'; import { redis } from './redis/redis.ts'; import { getContentType } from './get-content-type.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[]; @@ -19,6 +21,60 @@ const { api, domain, allowedOrigins } = useConfig<{ const fileStore = useFileStore('upload'); const noProxyUrl = ['/', '/favicon.ico']; 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); // 配置可以跨域 // 配置可以访问的域名 localhost, xiongxiao.me @@ -87,42 +143,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR user = _user; 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 }); let isExist = await userApp.getExist(); if (!isExist) { diff --git a/src/module/proxy.ts b/src/module/proxy.ts new file mode 100644 index 0000000..e987ced --- /dev/null +++ b/src/module/proxy.ts @@ -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); +} diff --git a/src/module/query/get-router.ts b/src/module/query/get-router.ts new file mode 100644 index 0000000..30e2809 --- /dev/null +++ b/src/module/query/get-router.ts @@ -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; +}; diff --git a/src/route/app/index.ts b/src/route/app/index.ts new file mode 100644 index 0000000..9166f9d --- /dev/null +++ b/src/route/app/index.ts @@ -0,0 +1 @@ +import './list.ts' \ No newline at end of file diff --git a/src/route/app/list.ts b/src/route/app/list.ts new file mode 100644 index 0000000..1d76531 --- /dev/null +++ b/src/route/app/list.ts @@ -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, + }; + }); diff --git a/src/route/route.ts b/src/route/route.ts new file mode 100644 index 0000000..a2b5028 --- /dev/null +++ b/src/route/route.ts @@ -0,0 +1 @@ +import './app/index.ts' \ No newline at end of file