first commit
This commit is contained in:
324
internal/web/server.go
Normal file
324
internal/web/server.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/miaomint/port-manager/internal/scanner"
|
||||
"github.com/miaomint/port-manager/internal/service"
|
||||
)
|
||||
|
||||
// Server Web服务器
|
||||
type Server struct {
|
||||
scanner *scanner.PortScanner
|
||||
identifier *service.ServiceIdentifier
|
||||
}
|
||||
|
||||
// NewServer 创建新的Web服务器
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
scanner: scanner.NewPortScanner(true, false), // 只显示监听端口
|
||||
identifier: service.NewServiceIdentifier(),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动Web服务器
|
||||
func (s *Server) Start(port int) error {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// 静态文件路由
|
||||
mux.HandleFunc("/", s.indexHandler)
|
||||
mux.HandleFunc("/api/ports", s.portsAPIHandler)
|
||||
mux.HandleFunc("/api/random-port", s.randomPortHandler)
|
||||
mux.HandleFunc("/api/scan-range", s.scanRangeHandler)
|
||||
|
||||
fmt.Printf("端口管理器启动在: http://localhost:%d\n", port)
|
||||
return http.ListenAndServe(fmt.Sprintf(":%d", port), mux)
|
||||
}
|
||||
|
||||
// indexHandler 主页处理器
|
||||
func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl := `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>端口管理器</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.controls {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
.random-port {
|
||||
background: #28a745;
|
||||
}
|
||||
.random-port:hover {
|
||||
background: #1e7e34;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
.service-link {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.service-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
}
|
||||
.status-active {
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
}
|
||||
.port-number {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
.protocol-badge {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.random-result {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔌 NAS 端口管理器</h1>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="scanPorts()">🔍 扫描端口</button>
|
||||
<button onclick="generateRandomPort()" class="random-port">🎲 生成随机端口</button>
|
||||
<input type="number" id="minPort" placeholder="最小端口" value="8000" style="width: 100px;">
|
||||
<input type="number" id="maxPort" placeholder="最大端口" value="9999" style="width: 100px;">
|
||||
</div>
|
||||
|
||||
<div id="randomResult"></div>
|
||||
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
正在扫描端口...
|
||||
</div>
|
||||
|
||||
<table id="portsTable" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>端口</th>
|
||||
<th>协议</th>
|
||||
<th>状态</th>
|
||||
<th>服务名称</th>
|
||||
<th>快速访问</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="portsBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function scanPorts() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('portsTable').style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ports');
|
||||
const data = await response.json();
|
||||
|
||||
displayPorts(data.ports);
|
||||
} catch (error) {
|
||||
console.error('扫描失败:', error);
|
||||
alert('扫描失败: ' + error.message);
|
||||
} finally {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function generateRandomPort() {
|
||||
const minPort = document.getElementById('minPort').value || 8000;
|
||||
const maxPort = document.getElementById('maxPort').value || 9999;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/random-port?min=' + minPort + '&max=' + maxPort);
|
||||
const data = await response.json();
|
||||
|
||||
document.getElementById('randomResult').innerHTML =
|
||||
'<strong>🎯 可用端口: <span class="port-number">' + data.port + '</span></strong>';
|
||||
} catch (error) {
|
||||
console.error('生成随机端口失败:', error);
|
||||
alert('生成随机端口失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function displayPorts(ports) {
|
||||
const tbody = document.getElementById('portsBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
ports.forEach(port => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const serviceCell = port.serviceURL
|
||||
? '<a href="' + port.serviceURL + '" target="_blank" class="service-link">🚀 打开服务</a>'
|
||||
: '-';
|
||||
|
||||
row.innerHTML =
|
||||
'<td class="port-number">' + port.port + '</td>' +
|
||||
'<td><span class="protocol-badge">' + port.protocol.toUpperCase() + '</span></td>' +
|
||||
'<td class="status-active">' + (port.state || 'ACTIVE') + '</td>' +
|
||||
'<td>' + (port.serviceName || '未知服务') + '</td>' +
|
||||
'<td>' + serviceCell + '</td>';
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
document.getElementById('portsTable').style.display = 'table';
|
||||
}
|
||||
|
||||
// 页面加载完成后自动扫描
|
||||
window.onload = function() {
|
||||
scanPorts();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprint(w, tmpl)
|
||||
}
|
||||
|
||||
// portsAPIHandler 端口API处理器
|
||||
func (s *Server) portsAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
result, err := s.scanner.ScanPorts()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf(`{"error": "%s"}`, err.Error()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 识别服务
|
||||
for i := range result.Ports {
|
||||
identified := s.identifier.IdentifyService(&result.Ports[i])
|
||||
result.Ports[i] = *identified
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
// randomPortHandler 随机端口处理器
|
||||
func (s *Server) randomPortHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
minPort := 8000
|
||||
maxPort := 9999
|
||||
|
||||
if min := r.URL.Query().Get("min"); min != "" {
|
||||
if p, err := strconv.Atoi(min); err == nil {
|
||||
minPort = p
|
||||
}
|
||||
}
|
||||
|
||||
if max := r.URL.Query().Get("max"); max != "" {
|
||||
if p, err := strconv.Atoi(max); err == nil {
|
||||
maxPort = p
|
||||
}
|
||||
}
|
||||
|
||||
port, err := s.scanner.GenerateRandomPort(minPort, maxPort)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf(`{"error": "%s"}`, err.Error()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]int{"port": port}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// scanRangeHandler 扫描端口范围处理器
|
||||
func (s *Server) scanRangeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
startStr := r.URL.Query().Get("start")
|
||||
endStr := r.URL.Query().Get("end")
|
||||
|
||||
if startStr == "" || endStr == "" {
|
||||
http.Error(w, `{"error": "需要提供start和end参数"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
start, err := strconv.Atoi(startStr)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error": "无效的start参数"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
end, err := strconv.Atoi(endStr)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error": "无效的end参数"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
openPorts, err := s.scanner.ScanPortRange(start, end)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf(`{"error": "%s"}`, err.Error()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string][]int{"openPorts": openPorts}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
Reference in New Issue
Block a user