Skip to content

Commit f07ca2d

Browse files
author
yangtao
committed
refactor(sse): 重构 SSE 事件处理机制
- 移除仪表盘 SSE 路由,改为使用全局 SSE 事件- 优化隧道管理页面的实时更新逻辑 - 修改 SSE 事件处理函数,提高代码复用性 - 调整前端组件,统一使用实例 terminology - 更新文档,同步版本号
1 parent e59eb80 commit f07ca2d

29 files changed

Lines changed: 800 additions & 1262 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 🚀 NodePass WebUI
22

3-
![Version](https://img.shields.io/badge/version-1.2.0-blue.svg)
3+
![Version](https://img.shields.io/badge/version-1.2.1-blue.svg)
44

55
一个现代化的 NodePass 管理界面,基于 Next.js 14、HeroUI 和 TypeScript 构建。提供实时隧道监控、流量统计和端点管理功能。
66

app/api/sse/dashboard/route.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

app/api/sse/test/route.ts

Lines changed: 40 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,57 @@
1-
import { NextRequest } from 'next/server';
2-
import { getGlobalSSEManager } from '@/lib/server/global-sse';
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { SSEService } from '@/lib/server/sse-service';
3+
import { logger } from '@/lib/logger';
34

4-
export async function GET(request: NextRequest) {
5+
export async function POST(request: NextRequest) {
56
try {
6-
// 使用全局 SSE 管理器
7-
const sseManager = getGlobalSSEManager();
8-
9-
// 获取查询参数
10-
const url = new URL(request.url);
11-
const action = url.searchParams.get('action');
12-
const instanceId = url.searchParams.get('instanceId');
7+
const { url, apiPath, apiKey } = await request.json();
138

14-
if (action === 'stats') {
15-
// 返回 SSE 管理器统计信息
16-
const stats = sseManager.getStats();
17-
console.log('[SSE-Test] 当前SSE管理器统计:', stats);
18-
sseManager.listSubscribers();
19-
20-
return Response.json({
21-
success: true,
22-
stats,
23-
message: '统计信息获取成功,详细信息请查看服务器控制台'
24-
});
9+
if (!url || !apiPath || !apiKey) {
10+
return NextResponse.json({
11+
success: false,
12+
error: '缺少必要参数'
13+
}, { status: 400 });
2514
}
2615

27-
if (action === 'send' && instanceId) {
28-
// 发送测试消息到指定instanceId
29-
const testMessage = {
30-
type: 'log',
31-
logs: `[测试消息] 这是一条发送到 instanceId: ${instanceId} 的测试日志 - ${new Date().toISOString()}`,
32-
time: new Date().toISOString(),
33-
instance: {
34-
id: instanceId,
35-
tcprx: Math.floor(Math.random() * 1000),
36-
tcptx: Math.floor(Math.random() * 1000),
37-
udprx: Math.floor(Math.random() * 100),
38-
udptx: Math.floor(Math.random() * 100)
39-
}
40-
};
16+
// 获取 SSE 服务实例
17+
const sseService = SSEService.getInstance();
4118

42-
console.log(`[SSE-Test] 尝试发送测试消息到 instanceId: ${instanceId}`, testMessage);
43-
44-
// 使用 SSE 管理器发送消息
45-
sseManager.sendTunnelUpdateByInstanceId(instanceId, testMessage);
19+
try {
20+
// 测试连接
21+
await sseService.testEndpointConnection(url, apiPath, apiKey);
4622

47-
return Response.json({
23+
// 返回成功响应
24+
return NextResponse.json({
4825
success: true,
49-
message: `测试消息已发送到 instanceId: ${instanceId}`,
50-
data: testMessage
26+
message: '连接测试成功',
27+
details: {
28+
url: url,
29+
apiPath: apiPath,
30+
isSSLEnabled: url.startsWith('https:')
31+
}
5132
});
52-
}
53-
54-
if (action === 'broadcast') {
55-
// 发送全局广播消息
56-
const broadcastMessage = {
57-
type: 'test_broadcast',
58-
message: `全局广播测试消息 - ${new Date().toISOString()}`,
59-
timestamp: new Date().toISOString()
60-
};
6133

62-
console.log('[SSE-Test] 发送全局广播消息', broadcastMessage);
63-
sseManager.broadcast(broadcastMessage);
64-
65-
return Response.json({
66-
success: true,
67-
message: '全局广播消息已发送',
68-
data: broadcastMessage
69-
});
34+
} catch (error) {
35+
logger.error('SSE连接测试失败:', error);
36+
37+
// 返回详细的错误信息
38+
return NextResponse.json({
39+
success: false,
40+
error: error instanceof Error ? error.message : '连接测试失败',
41+
details: {
42+
url: url,
43+
apiPath: apiPath,
44+
isSSLEnabled: url.startsWith('https:'),
45+
errorType: error instanceof Error ? error.name : '未知错误'
46+
}
47+
}, { status: 500 });
7048
}
7149

72-
// 默认返回帮助信息
73-
return Response.json({
74-
success: true,
75-
message: 'SSE 测试端点',
76-
usage: {
77-
stats: '/api/sse/test?action=stats - 获取SSE管理器统计信息',
78-
send: '/api/sse/test?action=send&instanceId=YOUR_INSTANCE_ID - 发送测试消息到指定隧道',
79-
broadcast: '/api/sse/test?action=broadcast - 发送全局广播消息'
80-
}
81-
});
82-
8350
} catch (error) {
84-
console.error('[SSE-Test] 测试端点错误:', error);
85-
return Response.json({
51+
logger.error('处理SSE测试请求失败:', error);
52+
return NextResponse.json({
8653
success: false,
87-
error: error instanceof Error ? error.message : '未知错误'
54+
error: '处理请求失败'
8855
}, { status: 500 });
8956
}
9057
}

app/api/tunnels/[id]/route.ts

Lines changed: 61 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { prisma } from '@/lib/prisma';
33
import { logTunnelOperation } from '@/lib/operation-log';
44
import { convertBigIntToNumber } from "@/lib/utils";
55
import { fetchWithSSLSupport } from '@/lib/utils/fetch';
6+
import { logger } from '@/lib/server/logger';
67

78
// PATCH /api/tunnels/[instanceId] - 更新隧道状态(启动/停止/重启)
89
export async function PATCH(
@@ -142,86 +143,76 @@ export async function DELETE(
142143
);
143144
}
144145

145-
if (!tunnel.instanceId) {
146-
return NextResponse.json(
147-
{ error: "隧道实例ID不存在" },
148-
{ status: 400 }
149-
);
150-
}
146+
// 如果存在 instanceId,则尝试调用 NodePass API 删除实例
147+
if (tunnel.instanceId) {
148+
try {
149+
// 构建 NodePass API 请求 URL
150+
const apiUrl = `${tunnel.endpoint.url}${tunnel.endpoint.apiPath}/v1/instances/${tunnel.instanceId}`;
151+
152+
// 调用 NodePass API 删除实例
153+
const nodepassResponse = await fetchWithSSLSupport(apiUrl, {
154+
method: 'DELETE',
155+
headers: {
156+
'Content-Type': 'application/json',
157+
'X-API-Key': tunnel.endpoint.apiKey
158+
}
159+
});
151160

152-
try {
153-
// 构建 NodePass API 请求 URL
154-
const apiUrl = `${tunnel.endpoint.url}${tunnel.endpoint.apiPath}/v1/instances/${tunnel.instanceId}`;
155-
156-
// 调用 NodePass API 删除实例
157-
const nodepassResponse = await fetchWithSSLSupport(apiUrl, {
158-
method: 'DELETE',
159-
headers: {
160-
'Content-Type': 'application/json',
161-
'X-API-Key': tunnel.endpoint.apiKey
161+
if (!nodepassResponse.ok) {
162+
const errorText = await nodepassResponse.text();
163+
logger.warn(`NodePass API 响应错误: ${nodepassResponse.status} - ${errorText},继续删除本地记录`);
162164
}
163-
});
164-
165-
if (!nodepassResponse.ok) {
166-
const errorText = await nodepassResponse.text();
167-
throw new Error(`NodePass API 响应错误: ${nodepassResponse.status} - ${errorText}`);
165+
} catch (error: any) {
166+
logger.warn(`调用 NodePass API 删除失败: ${error.message},继续删除本地记录`);
168167
}
168+
}
169169

170-
// NodePass API 调用成功后,删除本地数据库记录
171-
await prisma.tunnel.delete({
172-
where: { id: tunnelId }
173-
});
170+
// 删除本地数据库记录
171+
const result = await prisma.tunnel.deleteMany({
172+
where: { id: tunnelId }
173+
});
174174

175-
// 更新端点的实例数量
176-
await prisma.endpoint.update({
177-
where: { id: tunnel.endpointId },
178-
data: {
179-
tunnelCount: {
180-
decrement: 1
181-
}
182-
}
183-
});
175+
// 如果没有删除任何记录,说明隧道可能已经被删除了
176+
if (result.count === 0) {
177+
logger.warn(`隧道 ${tunnelId} 已经被删除或不存在`);
178+
}
184179

185-
// 记录操作日志
186-
await prisma.tunnelOperationLog.create({
187-
data: {
188-
tunnelId: tunnelId,
189-
tunnelName: tunnel.name,
190-
action: 'delete',
191-
status: "success",
192-
message: `删除隧道成功`
180+
// 更新端点的实例数量
181+
await prisma.endpoint.update({
182+
where: { id: tunnel.endpointId },
183+
data: {
184+
tunnelCount: {
185+
decrement: 1
193186
}
194-
});
195-
196-
return NextResponse.json({
197-
success: true,
198-
message: "隧道已成功删除"
199-
});
187+
}
188+
});
200189

201-
} catch (apiError: any) {
202-
// 记录失败日志
203-
await prisma.tunnelOperationLog.create({
204-
data: {
205-
tunnelId: tunnelId,
206-
tunnelName: tunnel.name,
207-
action: 'delete',
208-
status: "error",
209-
message: `删除隧道失败: ${apiError.message}`
210-
}
211-
});
190+
// 记录操作日志
191+
await prisma.tunnelOperationLog.create({
192+
data: {
193+
tunnelId: tunnelId,
194+
tunnelName: tunnel.name,
195+
action: 'delete',
196+
status: "success",
197+
message: tunnel.instanceId
198+
? `删除隧道及远程实例成功`
199+
: `删除本地隧道记录成功`
200+
}
201+
});
212202

213-
return NextResponse.json({
214-
success: false,
215-
error: "调用 NodePass API 失败",
216-
message: apiError.message
217-
}, { status: 500 });
218-
}
203+
return NextResponse.json({
204+
success: true,
205+
message: tunnel.instanceId
206+
? "隧道及远程实例已成功删除"
207+
: "本地隧道记录已成功删除"
208+
});
219209

220210
} catch (error) {
221-
console.error("删除隧道失败:", error);
222-
return NextResponse.json(
223-
{ error: "删除隧道失败" },
224-
{ status: 500 }
225-
);
211+
logger.error('删除隧道失败:', error);
212+
return NextResponse.json({
213+
success: false,
214+
error: '删除隧道失败',
215+
details: error instanceof Error ? error.message : '未知错误'
216+
}, { status: 500 });
226217
}
227218
}

app/api/tunnels/[id]/status/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ export async function PATCH(
8989
// 更新隧道状态
9090
const updatedTunnel = await prisma.tunnel.update({
9191
where: { id: tunnelId },
92-
data: { status: newStatus }
92+
data: {
93+
status: newStatus,
94+
lastEventTime: new Date() // 添加事件时间
95+
}
9396
});
9497

9598
// 记录成功日志
@@ -99,7 +102,7 @@ export async function PATCH(
99102
tunnelName: tunnel.name,
100103
action: action,
101104
status: "success",
102-
message: `${action === 'start' ? '启动' : action === 'stop' ? '停止' : '重启'}隧道成功`
105+
message: `${action === 'start' ? '启动' : action === 'stop' ? '停止' : '重启'}隧道成功,状态: ${newStatus}`
103106
}
104107
});
105108

0 commit comments

Comments
 (0)