From d917b7238ec7b7f6704a2a50e82bc4782d42a412 Mon Sep 17 00:00:00 2001 From: MiaoMint <44718819+MiaoMint@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:08:09 +0800 Subject: [PATCH] Enhance error handling and user feedback in the web server's index handler --- cmd/port-manager/main.go | 18 +++++----- internal/scanner/port_scanner.go | 62 ++++++++++++++++++++++---------- internal/web/server.go | 32 +++++++++++++++-- 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/cmd/port-manager/main.go b/cmd/port-manager/main.go index 1343a3c..154d1c7 100644 --- a/cmd/port-manager/main.go +++ b/cmd/port-manager/main.go @@ -85,7 +85,7 @@ func main() { } func showUsage() { - fmt.Println(` + fmt.Print(` 🔌 NAS 端口管理器 ================ @@ -97,17 +97,17 @@ func showUsage() { 选项: -port int - Web服务器端口 (默认: 8080) + Web服务器端口 (默认: 8080) -scan - 仅扫描端口并输出结果,不启动Web服务器 + 仅扫描端口并输出结果,不启动Web服务器 -random - 生成一个随机可用端口 + 生成一个随机可用端口 -min int - 随机端口最小值 (默认: 8000) + 随机端口最小值 (默认: 8000) -max int - 随机端口最大值 (默认: 9999) + 随机端口最大值 (默认: 9999) -help - 显示此帮助信息 + 显示此帮助信息 示例: # 启动Web界面 (默认端口8080) @@ -134,6 +134,6 @@ func showUsage() { ✅ 响应式Web界面设计 更多信息: - 项目地址: https://github.com/miaomint/port-manager -`) + 项目地址: https://github.com/miaomint/port-manager`) + fmt.Println() } diff --git a/internal/scanner/port_scanner.go b/internal/scanner/port_scanner.go index 5500faf..f3aeb6b 100644 --- a/internal/scanner/port_scanner.go +++ b/internal/scanner/port_scanner.go @@ -102,7 +102,7 @@ func (ps *PortScanner) parseNetstatOutput(output, protocol string) ([]types.Port continue } - port, err := ps.parseNetstatLine(line, protocol) + port, err := ps.parseNetstatLineMacOS(line, protocol) if err != nil { continue // 跳过解析失败的行 } @@ -118,31 +118,54 @@ func (ps *PortScanner) parseNetstatOutput(output, protocol string) ([]types.Port return ports, nil } -// parseNetstatLine 解析netstat的单行输出 -func (ps *PortScanner) parseNetstatLine(line, protocol string) (*types.PortInfo, error) { +// parseNetstatLineMacOS 解析macOS netstat的单行输出 +func (ps *PortScanner) parseNetstatLineMacOS(line, protocol string) (*types.PortInfo, error) { fields := regexp.MustCompile(`\s+`).Split(line, -1) - if len(fields) < 3 { + if len(fields) < 4 { return nil, fmt.Errorf("invalid line format") } - // 解析本地地址 - localAddr := fields[0] - parts := strings.Split(localAddr, ":") - if len(parts) < 2 { - return nil, fmt.Errorf("invalid address format") + // macOS netstat格式: Proto Recv-Q Send-Q Local-Address Foreign-Address (state) + localAddr := fields[3] + state := "" + if len(fields) >= 6 { + state = fields[5] + } + + // 解析本地地址,可能包含IPv6地址 + var portStr string + if strings.Contains(localAddr, ".") { + // IPv4 格式: ip.port + parts := strings.Split(localAddr, ".") + if len(parts) >= 2 { + portStr = parts[len(parts)-1] + } + } else if strings.Contains(localAddr, ":") { + // IPv6 格式: [ip]:port 或 ip:port + if strings.Contains(localAddr, "]:") { + // [ip]:port + parts := strings.Split(localAddr, "]:") + if len(parts) == 2 { + portStr = parts[1] + } + } else { + // ip:port (简单情况) + parts := strings.Split(localAddr, ":") + if len(parts) >= 2 { + portStr = parts[len(parts)-1] + } + } + } + + if portStr == "" { + return nil, fmt.Errorf("could not extract port from address: %s", localAddr) } - portStr := parts[len(parts)-1] port, err := strconv.Atoi(portStr) if err != nil { return nil, fmt.Errorf("invalid port: %s", portStr) } - state := "" - if protocol == "tcp" && len(fields) >= 4 { - state = fields[3] - } - return &types.PortInfo{ Port: port, Protocol: protocol, @@ -154,15 +177,18 @@ func (ps *PortScanner) parseNetstatLine(line, protocol string) (*types.PortInfo, // shouldIncludePort 判断是否应该包含此端口 func (ps *PortScanner) shouldIncludePort(port *types.PortInfo) bool { if port.Protocol == "tcp" { - if port.State == "LISTEN" { + // macOS netstat 状态值可能包括: LISTEN, ESTABLISHED, SYN_SENT, etc. + if strings.Contains(port.State, "LISTEN") { return ps.includeListening } - if port.State == "ESTABLISHED" { + if strings.Contains(port.State, "ESTABLISHED") { return ps.includeEstablished } + // 对于其他状态,如果设置了包含监听端口,也包含进来 + return ps.includeListening } - // UDP端口默认包含 + // UDP端口默认包含(如果启用了监听端口) if port.Protocol == "udp" { return ps.includeListening } diff --git a/internal/web/server.go b/internal/web/server.go index f8683f8..7958256 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -178,12 +178,29 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) { try { const response = await fetch('/api/ports'); + + if (!response.ok) { + throw new Error('HTTP error! status: ' + response.status); + } + const data = await response.json(); - displayPorts(data.ports); + // 检查是否有错误 + if (data.error) { + throw new Error(data.error); + } + + // 检查 ports 是否存在 + if (data.ports) { + displayPorts(data.ports); + } else { + displayPorts([]); + } } catch (error) { console.error('扫描失败:', error); alert('扫描失败: ' + error.message); + // 显示空表格 + displayPorts([]); } finally { document.getElementById('loading').style.display = 'none'; } @@ -209,6 +226,15 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) { const tbody = document.getElementById('portsBody'); tbody.innerHTML = ''; + // 检查 ports 是否为空或无效 + if (!ports || !Array.isArray(ports) || ports.length === 0) { + const row = document.createElement('tr'); + row.innerHTML = '暂无发现端口'; + tbody.appendChild(row); + document.getElementById('portsTable').style.display = 'table'; + return; + } + ports.forEach(port => { const row = document.createElement('tr'); @@ -217,8 +243,8 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) { : '-'; row.innerHTML = - '' + port.port + '' + - '' + port.protocol.toUpperCase() + '' + + '' + (port.port || '-') + '' + + '' + (port.protocol ? port.protocol.toUpperCase() : 'UNKNOWN') + '' + '' + (port.state || 'ACTIVE') + '' + '' + (port.serviceName || '未知服务') + '' + '' + serviceCell + '';