Compare commits

...

2 Commits

Author SHA1 Message Date
d22be3a840 更新依赖项,优化 WebSocket 处理,添加系统版本路由 2025-12-20 05:13:17 +08:00
76924b008c update packages 2025-12-19 16:13:15 +08:00
7 changed files with 264 additions and 70 deletions

View File

@@ -44,15 +44,16 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@kevisual/ai": "^0.0.19", "@kevisual/ai": "^0.0.19",
"@kevisual/query": "^0.0.32", "@kevisual/query": "^0.0.33",
"@types/busboy": "^1.5.4", "@types/busboy": "^1.5.4",
"@types/send": "^1.2.1", "@types/send": "^1.2.1",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"bullmq": "^5.66.0", "bullmq": "^5.66.2",
"busboy": "^1.6.0", "busboy": "^1.6.0",
"commander": "^14.0.2", "commander": "^14.0.2",
"cookie": "^1.1.1", "cookie": "^1.1.1",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"eventemitter3": "^5.0.1",
"ioredis": "^5.8.2", "ioredis": "^5.8.2",
"minio": "^8.0.6", "minio": "^8.0.6",
"pg": "^8.16.3", "pg": "^8.16.3",
@@ -68,7 +69,7 @@
"@kevisual/logger": "^0.0.4", "@kevisual/logger": "^0.0.4",
"@kevisual/oss": "0.0.13", "@kevisual/oss": "0.0.13",
"@kevisual/permission": "^0.0.3", "@kevisual/permission": "^0.0.3",
"@kevisual/router": "0.0.37", "@kevisual/router": "0.0.42",
"@kevisual/types": "^0.0.10", "@kevisual/types": "^0.0.10",
"@kevisual/use-config": "^1.0.21", "@kevisual/use-config": "^1.0.21",
"@types/archiver": "^7.0.0", "@types/archiver": "^7.0.0",
@@ -113,5 +114,5 @@
"msgpackr-extract" "msgpackr-extract"
] ]
}, },
"packageManager": "pnpm@10.26.0" "packageManager": "pnpm@10.26.1"
} }

107
pnpm-lock.yaml generated
View File

@@ -16,8 +16,8 @@ importers:
specifier: ^0.0.19 specifier: ^0.0.19
version: 0.0.19 version: 0.0.19
'@kevisual/query': '@kevisual/query':
specifier: ^0.0.32 specifier: ^0.0.33
version: 0.0.32 version: 0.0.33
'@types/busboy': '@types/busboy':
specifier: ^1.5.4 specifier: ^1.5.4
version: 1.5.4 version: 1.5.4
@@ -28,8 +28,8 @@ importers:
specifier: ^8.18.1 specifier: ^8.18.1
version: 8.18.1 version: 8.18.1
bullmq: bullmq:
specifier: ^5.66.0 specifier: ^5.66.2
version: 5.66.0 version: 5.66.2
busboy: busboy:
specifier: ^1.6.0 specifier: ^1.6.0
version: 1.6.0 version: 1.6.0
@@ -42,6 +42,9 @@ importers:
drizzle-orm: drizzle-orm:
specifier: ^0.45.1 specifier: ^0.45.1
version: 0.45.1(pg@8.16.3) version: 0.45.1(pg@8.16.3)
eventemitter3:
specifier: ^5.0.1
version: 5.0.1
ioredis: ioredis:
specifier: ^5.8.2 specifier: ^5.8.2
version: 5.8.2 version: 5.8.2
@@ -83,8 +86,8 @@ importers:
specifier: ^0.0.3 specifier: ^0.0.3
version: 0.0.3 version: 0.0.3
'@kevisual/router': '@kevisual/router':
specifier: 0.0.37 specifier: 0.0.42
version: 0.0.37 version: 0.0.42
'@kevisual/types': '@kevisual/types':
specifier: ^0.0.10 specifier: ^0.0.10
version: 0.0.10 version: 0.0.10
@@ -161,6 +164,27 @@ importers:
specifier: ^4.2.1 specifier: ^4.2.1
version: 4.2.1 version: 4.2.1
wxmsg/pack-dist:
dependencies:
'@kevisual/context':
specifier: ^0.0.4
version: 0.0.4
'@kevisual/query':
specifier: ^0.0.29
version: 0.0.29(@kevisual/ws@8.0.0)(zod@3.25.67)
'@kevisual/router':
specifier: 0.0.33
version: 0.0.33
'@types/node':
specifier: ^24.10.1
version: 24.10.4
crypto-js:
specifier: ^4.2.0
version: 4.2.0
xml2js:
specifier: ^0.6.2
version: 0.6.2
packages: packages:
'@ioredis/commands@1.4.0': '@ioredis/commands@1.4.0':
@@ -207,11 +231,14 @@ packages:
'@kevisual/permission@0.0.3': '@kevisual/permission@0.0.3':
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==} resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==}
'@kevisual/query@0.0.29':
resolution: {integrity: sha512-rQZk0J073UuC1QGzuyq+pb4Y0hu8/Qx/xYHs9NbsmslM+RuMnd1zpXmvhXNj7Kn1MdYTH90ng2MlFLBkkQFaIg==}
'@kevisual/query@0.0.31': '@kevisual/query@0.0.31':
resolution: {integrity: sha512-bBdepjmMICLpcj/a9fnn82/0CGGYUZiCV+usWsJZKAwVlZcnj+WtKmbgKT09KpP6g3jjYzYOaXHiNFB8N0bQAQ==} resolution: {integrity: sha512-bBdepjmMICLpcj/a9fnn82/0CGGYUZiCV+usWsJZKAwVlZcnj+WtKmbgKT09KpP6g3jjYzYOaXHiNFB8N0bQAQ==}
'@kevisual/query@0.0.32': '@kevisual/query@0.0.33':
resolution: {integrity: sha512-9WN9cjmwSW8I5A0SqITdts9oxlLBGdPP7kJ8vwrxkaQteHS9FzxKuMBJxZzGKZdyte/zJDvdrE+lMf254BGbbg==} resolution: {integrity: sha512-3w74bcLpwV3z483eg8n0DgkftfjWC6iLONXBvfyjW6IZf6jMOuouFaM4Rk+uEsTgElU6XGMKseNTp6dlQdWYkg==}
'@kevisual/router@0.0.21': '@kevisual/router@0.0.21':
resolution: {integrity: sha512-XKTxbNO924cT18UOAGplWErZ+hMze8Y53F2jYCk18v4jsdsvjRho5uXXjJb6HSVsuITMtQR4R3rG0IcM3jkDKQ==} resolution: {integrity: sha512-XKTxbNO924cT18UOAGplWErZ+hMze8Y53F2jYCk18v4jsdsvjRho5uXXjJb6HSVsuITMtQR4R3rG0IcM3jkDKQ==}
@@ -222,8 +249,11 @@ packages:
'@kevisual/router@0.0.23': '@kevisual/router@0.0.23':
resolution: {integrity: sha512-W6ehlhAzNe58vq4QeQt2XFoO84Qaw34A0PVOByJsJ2ICj4YKBTclAt+rOAoISCvUeSbeNOIuhUE3sLyPfplzUw==} resolution: {integrity: sha512-W6ehlhAzNe58vq4QeQt2XFoO84Qaw34A0PVOByJsJ2ICj4YKBTclAt+rOAoISCvUeSbeNOIuhUE3sLyPfplzUw==}
'@kevisual/router@0.0.37': '@kevisual/router@0.0.33':
resolution: {integrity: sha512-f/siDSqO0g6cQhBrWyPIVv8WMgxjC+olRS8GNxqzkBvAj5M4x3cmfAj1bxTn7neOejTjkGd+ZeoDQbhIpFKDZQ==} resolution: {integrity: sha512-9z7TkSzCIGbXn9SuHPBdZpGwHlAuwA8iN5jNAZBUvbEvBRkBxlrbdCSe9fBYiAHueLm2AceFNrW74uulOiAkqA==}
'@kevisual/router@0.0.42':
resolution: {integrity: sha512-6j254Hl1Q9uM4qKD4v6pcNSXVs7zwHZlyfSxUrNTWrgD7OCt/mrgBpzcNo0TM25/CsdrZCDs21kamienfYQ+lw==}
'@kevisual/types@0.0.10': '@kevisual/types@0.0.10':
resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==} resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==}
@@ -392,6 +422,9 @@ packages:
'@types/node-forge@1.3.11': '@types/node-forge@1.3.11':
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
'@types/node@24.10.4':
resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==}
'@types/node@25.0.3': '@types/node@25.0.3':
resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==}
@@ -547,8 +580,8 @@ packages:
buffer@6.0.3: buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bullmq@5.66.0: bullmq@5.66.2:
resolution: {integrity: sha512-LSe8yEiVTllOOq97Q0C/EhczKS5Yd0AUJleGJCIh0cyJE5nWUqEpGC/uZQuuAYniBSoMT8LqwrxE7N5MZVrLoQ==} resolution: {integrity: sha512-0PrkpIakIntkBcPLltPIRWdLC1FTLUa/VhJkmEfobb5YUQjoUwJdmmf7HX+o/vMonS5048JpP+abf9lVRUFEjA==}
busboy@1.6.0: busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
@@ -1331,6 +1364,18 @@ packages:
once@1.4.0: once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
openai@5.23.2:
resolution: {integrity: sha512-MQBzmTulj+MM5O8SKEk/gL8a7s5mktS9zUtAkU257WjvobGc9nKcBuVwjyEEcb9SI8a8Y2G/mzn3vm9n1Jlleg==}
hasBin: true
peerDependencies:
ws: ^8.18.0
zod: ^3.23.8
peerDependenciesMeta:
ws:
optional: true
zod:
optional: true
p-queue@9.0.1: p-queue@9.0.1:
resolution: {integrity: sha512-RhBdVhSwJb7Ocn3e8ULk4NMwBEuOxe+1zcgphUy9c2e5aR/xbEsdVXxHJ3lynw6Qiqu7OINEyHlZkiblEpaq7w==} resolution: {integrity: sha512-RhBdVhSwJb7Ocn3e8ULk4NMwBEuOxe+1zcgphUy9c2e5aR/xbEsdVXxHJ3lynw6Qiqu7OINEyHlZkiblEpaq7w==}
engines: {node: '>=20'} engines: {node: '>=20'}
@@ -1574,6 +1619,10 @@ packages:
resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
selfsigned@4.0.1:
resolution: {integrity: sha512-QVGzJryYPB7ctpYxoK4DDvH4kFf23wtBl0s6km/wN+JCWLqjutdyWaiXHwSg3B3ftDOFfu0B7FWRVNH0xNn6rw==}
engines: {node: '>=10'}
selfsigned@5.2.0: selfsigned@5.2.0:
resolution: {integrity: sha512-QKF6fsJgdKn5Cy1SekTuwUQ6LOLUdnO4gVne1+TGSAyL/CD1aznNCmBna+bQB9xyon7DiYbDtTR1cLaxDXuaVA==} resolution: {integrity: sha512-QKF6fsJgdKn5Cy1SekTuwUQ6LOLUdnO4gVne1+TGSAyL/CD1aznNCmBna+bQB9xyon7DiYbDtTR1cLaxDXuaVA==}
engines: {node: '>=15.6.0'} engines: {node: '>=15.6.0'}
@@ -2059,9 +2108,16 @@ snapshots:
'@kevisual/permission@0.0.3': {} '@kevisual/permission@0.0.3': {}
'@kevisual/query@0.0.29(@kevisual/ws@8.0.0)(zod@3.25.67)':
dependencies:
openai: 5.23.2(@kevisual/ws@8.0.0)(zod@3.25.67)
transitivePeerDependencies:
- ws
- zod
'@kevisual/query@0.0.31': {} '@kevisual/query@0.0.31': {}
'@kevisual/query@0.0.32': {} '@kevisual/query@0.0.33': {}
'@kevisual/router@0.0.21': '@kevisual/router@0.0.21':
dependencies: dependencies:
@@ -2081,7 +2137,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@kevisual/router@0.0.37': '@kevisual/router@0.0.33':
dependencies:
path-to-regexp: 8.3.0
selfsigned: 4.0.1
send: 1.2.1
transitivePeerDependencies:
- supports-color
'@kevisual/router@0.0.42':
dependencies: dependencies:
path-to-regexp: 8.3.0 path-to-regexp: 8.3.0
selfsigned: 5.2.0 selfsigned: 5.2.0
@@ -2328,6 +2392,10 @@ snapshots:
dependencies: dependencies:
'@types/node': 25.0.3 '@types/node': 25.0.3
'@types/node@24.10.4':
dependencies:
undici-types: 7.16.0
'@types/node@25.0.3': '@types/node@25.0.3':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
@@ -2482,7 +2550,7 @@ snapshots:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
bullmq@5.66.0: bullmq@5.66.2:
dependencies: dependencies:
cron-parser: 4.9.0 cron-parser: 4.9.0
ioredis: 5.8.2 ioredis: 5.8.2
@@ -3166,6 +3234,11 @@ snapshots:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
openai@5.23.2(@kevisual/ws@8.0.0)(zod@3.25.67):
optionalDependencies:
ws: '@kevisual/ws@8.0.0'
zod: 3.25.67
p-queue@9.0.1: p-queue@9.0.1:
dependencies: dependencies:
eventemitter3: 5.0.1 eventemitter3: 5.0.1
@@ -3473,6 +3546,10 @@ snapshots:
'@types/node-forge': 1.3.11 '@types/node-forge': 1.3.11
node-forge: 1.3.1 node-forge: 1.3.1
selfsigned@4.0.1:
dependencies:
node-forge: 1.3.1
selfsigned@5.2.0: selfsigned@5.2.0:
dependencies: dependencies:
'@peculiar/x509': 1.14.2 '@peculiar/x509': 1.14.2

View File

@@ -57,6 +57,6 @@ const main = () => {
}); });
}; };
setTimeout(() => { // setTimeout(() => {
main(); // main();
}, 1200); // }, 1200);

View File

@@ -5,7 +5,7 @@ import { getLoginUser } from '@/modules/auth.ts';
import { logger } from '../logger.ts'; import { logger } from '../logger.ts';
export const wsProxyManager = new WsProxyManager(); export const wsProxyManager = new WsProxyManager();
export const upgrade = async (request: any, socket: any, head: any) => { export const upgrade = (request: any, socket: any, head: any) => {
const req = request as any; const req = request as any;
const url = new URL(req.url, 'http://localhost'); const url = new URL(req.url, 'http://localhost');
const id = url.searchParams.get('id'); const id = url.searchParams.get('id');
@@ -13,6 +13,7 @@ export const upgrade = async (request: any, socket: any, head: any) => {
console.log('upgrade', request.url, id); console.log('upgrade', request.url, id);
wss.handleUpgrade(req, socket, head, (ws) => { wss.handleUpgrade(req, socket, head, (ws) => {
// 这里手动触发 connection 事件 // 这里手动触发 connection 事件
console.log('emitting connection event');
// @ts-ignore // @ts-ignore
wss.emit('connection', ws, req); wss.emit('connection', ws, req);
}); });
@@ -28,15 +29,21 @@ export const wss = new WebSocketServer({
wss.on('connection', async (ws, req) => { wss.on('connection', async (ws, req) => {
console.log('connected', req.url); console.log('connected', req.url);
const url = new URL(req.url, 'http://localhost'); const url = new URL(req.url, 'http://localhost');
const id = url?.searchParams?.get('id') || nanoid(); const _id = url?.searchParams?.get('id');
const id = _id || nanoid();
const loginUser = await getLoginUser(req); const loginUser = await getLoginUser(req);
if (!loginUser) { if (!loginUser) {
ws.send(JSON.stringify({ code: 401, message: 'No Login' })); console.log('未登录,断开连接');
ws.send(JSON.stringify({ code: 401, message: '未登录' }));
setTimeout(() => {
ws.close(); ws.close();
}, 1000);
return; return;
} }
const user = loginUser.tokenUser?.username; const user = loginUser.tokenUser.username;
wsProxyManager.register(id, { user, ws }); const userApp = user + '-' + id;
console.log('注册 ws 连接', userApp);
wsProxyManager.register(userApp, { user, ws });
ws.send( ws.send(
JSON.stringify({ JSON.stringify({
type: 'connected', type: 'connected',
@@ -54,16 +61,103 @@ wss.on('connection', async (ws, req) => {
}); });
ws.on('close', () => { ws.on('close', () => {
logger.debug('ws closed'); logger.debug('ws closed');
wsProxyManager.unregister(id, user); wsProxyManager.unregister(userApp);
}); });
}); });
export class WssApp { export class WssApp {
wss: WebSocketServer; wss: WebSocketServer;
bunWSS = websocket;
constructor() { constructor() {
this.wss = wss; this.wss = wss;
} }
upgrade(request: any, socket: any, head: any) { upgrade(request: any, socket: any, head: any) {
return upgrade(request, socket, head); // return upgrade(request, socket, head);
return bunUpgrade(request);
} }
} }
export const bunUpgrade = (request: Request) => {
const url = new URL(request.url, 'http://localhost');
const isUpgrade = url.pathname === '/ws/proxy';
if (isUpgrade) {
console.log('upgrade', request.url);
// 使用 Bun 原生 WebSocket
new Response(null, {
status: 101,
headers: {
'Upgrade': 'websocket',
},
});
return true;
}
return false;
};
// Bun WebSocket 处理器
export const websocket = {
async open(ws: any) {
console.log('WebSocket opened');
const { url, token } = ws.data;
const urlObj = new URL(url, 'http://localhost');
const _id = urlObj.searchParams.get('id');
const id = _id || nanoid();
// 创建一个模拟的 request 对象用于认证
const mockReq: any = {
url: url,
headers: {
authorization: token ? `Bearer ${token}` : undefined,
},
};
const loginUser = await getLoginUser(mockReq);
if (!loginUser) {
console.log('未登录,断开连接');
ws.send(JSON.stringify({ code: 401, message: '未登录' }));
ws.close();
return;
}
const user = loginUser.tokenUser.username;
const userApp = user + '-' + id;
console.log('注册 ws 连接', userApp);
ws.data.userApp = userApp;
ws.data.user = user;
wsProxyManager.register(userApp, { user, ws });
ws.send(
JSON.stringify({
type: 'connected',
user: user,
id,
}),
);
},
async message(ws: any, message: string) {
try {
const data = JSON.parse(message);
logger.debug('message', data);
} catch (error) {
logger.error('Failed to parse message', error);
}
},
close(ws: any) {
const { userApp } = ws.data;
logger.debug('ws closed', userApp);
if (userApp) {
wsProxyManager.unregister(userApp);
}
},
error(ws: any, error: Error) {
console.error('WebSocket error:', error);
},
};

View File

@@ -1,12 +1,27 @@
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { WebSocket } from 'ws'; import { WebSocket } from 'ws';
import { logger } from '../logger.ts'; import { logger } from '../logger.ts';
import { EventEmitter } from 'eventemitter3';
class WsMessage { class WsMessage {
ws: WebSocket; ws: WebSocket;
user?: string; user?: string;
emitter: EventEmitter;;
constructor({ ws, user }: WssMessageOptions) { constructor({ ws, user }: WssMessageOptions) {
this.ws = ws; this.ws = ws;
this.user = user; this.user = user;
this.emitter = new EventEmitter();
this.listenMessage();
}
async listenMessage() {
this.ws.on('message', (event: Buffer) => {
const eventData = event.toString();
if (!eventData) {
return;
}
const data = JSON.parse(eventData);
logger.debug('ws-proxy listenMessage', data);
this.emitter.emit(data.id, data.data);
});
} }
async sendData(data: any, opts?: { timeout?: number }) { async sendData(data: any, opts?: { timeout?: number }) {
if (this.ws.readyState !== WebSocket.OPEN) { if (this.ws.readyState !== WebSocket.OPEN) {
@@ -21,23 +36,17 @@ class WsMessage {
}); });
logger.info('ws-proxy sendData', message); logger.info('ws-proxy sendData', message);
this.ws.send(message); this.ws.send(message);
const msg = { path: data?.path, key: data?.key, id: data?.id };
return new Promise((resolve) => { return new Promise((resolve) => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
resolve({ resolve({
code: 500, code: 500,
message: 'timeout', message: `运行超时执行的id: ${id},参数是${JSON.stringify(msg)}`,
}); });
}, timeout); }, timeout);
this.ws.once('message', (event: Buffer) => { this.emitter.once(id, (data: any) => {
const eventData = event.toString(); resolve(data);
if (!eventData) {
return;
}
const data = JSON.parse(eventData);
if (data.id === id) {
resolve(data.data);
clearTimeout(timer); clearTimeout(timer);
}
}); });
}); });
} }
@@ -48,37 +57,28 @@ type WssMessageOptions = {
}; };
export class WsProxyManager { export class WsProxyManager {
wssMap: Map<string, WsMessage> = new Map(); wssMap: Map<string, WsMessage> = new Map();
constructor() {} constructor() { }
getId(id: string, user?: string) {
return id + '/' + user;
}
register(id: string, opts?: { ws: WebSocket; user: string }) { register(id: string, opts?: { ws: WebSocket; user: string }) {
const _id = this.getId(id, opts?.user || ''); if (this.wssMap.has(id)) {
if (this.wssMap.has(_id)) { const value = this.wssMap.get(id);
const value = this.wssMap.get(_id);
if (value) { if (value) {
value.ws.close(); value.ws.close();
} }
} }
const value = new WsMessage({ ws: opts?.ws, user: opts?.user }); const value = new WsMessage({ ws: opts?.ws, user: opts?.user });
this.wssMap.set(_id, value); this.wssMap.set(id, value);
} }
unregister(id: string, user?: string) { unregister(id: string) {
const _id = this.getId(id, user || ''); const value = this.wssMap.get(id);
const value = this.wssMap.get(_id);
if (value) { if (value) {
value.ws.close(); value.ws.close();
} }
this.wssMap.delete(_id); this.wssMap.delete(id);
} }
getIds() { getIds() {
return Array.from(this.wssMap.keys()); return Array.from(this.wssMap.keys());
} }
get(id: string, user?: string) { get(id: string) {
if (user) {
const _id = this.getId(id, user);
return this.wssMap.get(_id);
}
return this.wssMap.get(id); return this.wssMap.get(id);
} }
} }

View File

@@ -27,7 +27,7 @@ export const UserV1Proxy = async (req: IncomingMessage, res: ServerResponse, opt
return false; return false;
} }
logger.debug('data', data); logger.debug('data', data);
const client = wsProxyManager.get(userAppKey, user); const client = wsProxyManager.get(userAppKey);
const ids = wsProxyManager.getIds(); const ids = wsProxyManager.getIds();
if (!client) { if (!client) {
opts?.createNotFoundPage?.(`未找到应用, ${userAppKey}, ${ids.join(',')}`); opts?.createNotFoundPage?.(`未找到应用, ${userAppKey}, ${ids.join(',')}`);

View File

@@ -17,6 +17,7 @@ export const addAuth = (app: App) => {
.route({ .route({
path: 'auth', path: 'auth',
id: 'auth', id: 'auth',
description: '验证token必须成功, 错误返回401正确赋值到ctx.state.tokenUser',
}) })
.define(async (ctx) => { .define(async (ctx) => {
const token = ctx.query.token; const token = ctx.query.token;
@@ -40,6 +41,7 @@ export const addAuth = (app: App) => {
path: 'auth', path: 'auth',
key: 'can', key: 'can',
id: 'auth-can', id: 'auth-can',
description: '验证token可以不成功错误不返回401正确赋值到ctx.state.tokenUser失败赋值null',
}) })
.define(async (ctx) => { .define(async (ctx) => {
if (ctx.query?.token) { if (ctx.query?.token) {
@@ -67,6 +69,7 @@ app
id: 'auth-admin', id: 'auth-admin',
isDebug: true, isDebug: true,
middleware: ['auth'], middleware: ['auth'],
description: '验证token必须是admin用户, 错误返回403正确赋值到ctx.state.tokenAdmin',
}) })
.define(async (ctx) => { .define(async (ctx) => {
const tokenUser = ctx.state.tokenUser; const tokenUser = ctx.state.tokenUser;
@@ -140,18 +143,37 @@ app
app app
.route({ .route({
path: 'test', path: 'router',
key: 'test', key: 'list',
description: '列出所有的当前的可请求的路由信息',
middleware: ['auth-can']
}) })
.define(async (ctx) => { .define(async (ctx) => {
ctx.body = app.router.routes.map((item) => { const tokenUser = ctx.state.tokenUser;
let isUser = !!tokenUser;
ctx.body = {
list: app.router.routes.map((item) => {
return { return {
id: item.id,
path: item.path, path: item.path,
key: item.key, key: item.key,
description: item.description, description: item.description,
validator: item.validator, middeleware: item.middleware,
// schema: item.schema, metadata: item.metadata,
}; };
}); }),
isUser
}
}) })
.addTo(app); .addTo(app);
app.route({
path: 'system',
key: 'version'
}).define(async (ctx) => {
ctx.body = {
version: '0.0.1',
name: 'KeVisual Backend System',
}
}).addTo(app);