更新依赖项,优化 WebSocket 处理,添加系统版本路由

This commit is contained in:
2025-12-20 05:13:17 +08:00
parent 76924b008c
commit d22be3a840
7 changed files with 240 additions and 57 deletions

View File

@@ -5,7 +5,7 @@ import { getLoginUser } from '@/modules/auth.ts';
import { logger } from '../logger.ts';
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 url = new URL(req.url, 'http://localhost');
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);
wss.handleUpgrade(req, socket, head, (ws) => {
// 这里手动触发 connection 事件
console.log('emitting connection event');
// @ts-ignore
wss.emit('connection', ws, req);
});
@@ -28,15 +29,21 @@ export const wss = new WebSocketServer({
wss.on('connection', async (ws, req) => {
console.log('connected', req.url);
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);
if (!loginUser) {
ws.send(JSON.stringify({ code: 401, message: 'No Login' }));
ws.close();
console.log('未登录,断开连接');
ws.send(JSON.stringify({ code: 401, message: '未登录' }));
setTimeout(() => {
ws.close();
}, 1000);
return;
}
const user = loginUser.tokenUser?.username;
wsProxyManager.register(id, { user, ws });
const user = loginUser.tokenUser.username;
const userApp = user + '-' + id;
console.log('注册 ws 连接', userApp);
wsProxyManager.register(userApp, { user, ws });
ws.send(
JSON.stringify({
type: 'connected',
@@ -54,16 +61,103 @@ wss.on('connection', async (ws, req) => {
});
ws.on('close', () => {
logger.debug('ws closed');
wsProxyManager.unregister(id, user);
wsProxyManager.unregister(userApp);
});
});
export class WssApp {
wss: WebSocketServer;
bunWSS = websocket;
constructor() {
this.wss = wss;
}
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 { WebSocket } from 'ws';
import { logger } from '../logger.ts';
import { EventEmitter } from 'eventemitter3';
class WsMessage {
ws: WebSocket;
user?: string;
emitter: EventEmitter;;
constructor({ ws, user }: WssMessageOptions) {
this.ws = ws;
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 }) {
if (this.ws.readyState !== WebSocket.OPEN) {
@@ -21,23 +36,17 @@ class WsMessage {
});
logger.info('ws-proxy sendData', message);
this.ws.send(message);
const msg = { path: data?.path, key: data?.key, id: data?.id };
return new Promise((resolve) => {
const timer = setTimeout(() => {
resolve({
code: 500,
message: 'timeout',
message: `运行超时执行的id: ${id},参数是${JSON.stringify(msg)}`,
});
}, timeout);
this.ws.once('message', (event: Buffer) => {
const eventData = event.toString();
if (!eventData) {
return;
}
const data = JSON.parse(eventData);
if (data.id === id) {
resolve(data.data);
clearTimeout(timer);
}
this.emitter.once(id, (data: any) => {
resolve(data);
clearTimeout(timer);
});
});
}
@@ -48,37 +57,28 @@ type WssMessageOptions = {
};
export class WsProxyManager {
wssMap: Map<string, WsMessage> = new Map();
constructor() {}
getId(id: string, user?: string) {
return id + '/' + user;
}
constructor() { }
register(id: string, opts?: { ws: WebSocket; user: string }) {
const _id = this.getId(id, opts?.user || '');
if (this.wssMap.has(_id)) {
const value = this.wssMap.get(_id);
if (this.wssMap.has(id)) {
const value = this.wssMap.get(id);
if (value) {
value.ws.close();
}
}
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) {
const _id = this.getId(id, user || '');
const value = this.wssMap.get(_id);
unregister(id: string) {
const value = this.wssMap.get(id);
if (value) {
value.ws.close();
}
this.wssMap.delete(_id);
this.wssMap.delete(id);
}
getIds() {
return Array.from(this.wssMap.keys());
}
get(id: string, user?: string) {
if (user) {
const _id = this.getId(id, user);
return this.wssMap.get(_id);
}
get(id: string) {
return this.wssMap.get(id);
}
}

View File

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