package main import ( "encoding/json" "log" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "sync" "time" "github.com/gorilla/websocket" ) // WebSocket upgrader配置 var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { // 允许所有源访问,生产环境中应限制具体域名 return true }, } // 客户端连接信息存储 type ClientInfo struct { DeviceType string RemoteAddr string Conn *websocket.Conn } // 存储已连接的客户端信息 var ( clients = make(map[string]ClientInfo) clientsMutex = &sync.Mutex{} ) // 保存图片数据到文件 func saveImageToFile(imageData []byte, deviceType string, clientAddr string) error { // 获取当前时间,用于生成唯一的文件名 timestamp := time.Now().Format("20060102_150405_000") // 提取客户端IP地址的一部分作为标识符 clientId := clientAddr if idx := strings.LastIndex(clientAddr, ":"); idx > 0 { clientId = clientAddr[:idx] } // 移除IP地址中的点号,使文件名更友好 clientId = strings.ReplaceAll(clientId, ".", "_") // 构建文件名 fileName := "screenshot_" + deviceType + "_" + clientId + "_" + timestamp + ".jpg" // 确保保存图片的目录存在 imageDir := "images" if err := os.MkdirAll(imageDir, 0755); err != nil { return err } // 构建完整的文件路径 filePath := filepath.Join(imageDir, fileName) // 写入文件 if err := os.WriteFile(filePath, imageData, 0644); err != nil { return err } log.Printf("图片已保存到: %s,文件大小: %d 字节", filePath, len(imageData)) return nil } // WebSocket连接处理函数 func wsHandler(w http.ResponseWriter, r *http.Request) { log.Printf("收到WebSocket连接请求,路径: %s", r.URL.Path) log.Printf("请求头 - Connection: %s, Upgrade: %s", r.Header.Get("Connection"), r.Header.Get("Upgrade")) log.Printf("请求头 - Sec-WebSocket-Protocol: %s", r.Header.Get("Sec-WebSocket-Protocol")) // 从WebSocket子协议中获取设备信息 device := "Unknown" protocols := r.Header.Get("Sec-WebSocket-Protocol") if protocols != "" { // 检查是否包含设备信息格式 if protocols == "device-web" { // 直接匹配子协议名称为device-web的情况 device = "Web" } else if protocols == "device-phone" { // 手机端的子协议(包括安卓和iPhone) device = "Phone" } } // 创建自定义header customHeaders := http.Header{} // customHeaders.Set("Device", "Web") // 创建临时upgrader,设置子协议以匹配客户端请求 tempUpgrader := upgrader if protocols != "" { // 设置服务器支持的子协议,与客户端请求的子协议匹配 log.Printf("接受客户端请求的子协议: %s", protocols) tempUpgrader.Subprotocols = []string{protocols} } // 将HTTP连接升级为WebSocket连接,并添加自定义header conn, err := tempUpgrader.Upgrade(w, r, customHeaders) if err != nil { log.Printf("升级WebSocket连接失败: %v", err) return } defer conn.Close() // 获取客户端地址 clientAddr := conn.RemoteAddr().String() log.Printf("新的WebSocket连接建立,设备类型: %s, 本地地址: %s, 远程地址: %s", device, conn.LocalAddr(), clientAddr) // 存储客户端信息 clientsMutex.Lock() clients[clientAddr] = ClientInfo{ DeviceType: device, RemoteAddr: clientAddr, Conn: conn, } clientsMutex.Unlock() // 连接关闭时从列表中移除 defer func() { clientsMutex.Lock() delete(clients, clientAddr) clientsMutex.Unlock() }() // 设置无超时连接,连接不会因为空闲而自动关闭 conn.SetReadDeadline(time.Time{}) // 发送欢迎消息给客户端,包含设备类型信息 welcomeMsg := []byte("欢迎" + device + "连接到WebSocket服务器") log.Printf("尝试发送欢迎消息") if err := conn.WriteMessage(websocket.TextMessage, welcomeMsg); err != nil { log.Printf("发送欢迎消息失败: %v", err) } else { log.Printf("欢迎消息发送成功") } // 已移除心跳机制,使用无超时连接 // 客户端通过onclose和onerror事件检测连接异常断开 // 应用层消息确认机制和客户端重连策略由客户端实现 // 循环读取客户端发送的消息 for { messageType, p, err := conn.ReadMessage() if err != nil { log.Printf("读取消息失败: %v", err) break } // 处理二进制消息(图片数据) if messageType == websocket.BinaryMessage { log.Printf("收到二进制消息,长度: %d 字节,发送方设备类型: %s", len(p), device) // 如果是来自手机客户端的二进制消息,识别为图片数据并转发给所有Web客户端 if device == "Phone" { // 保存图片到应用程序目录 if err := saveImageToFile(p, device, clientAddr); err != nil { log.Printf("保存图片失败: %v", err) } clientsMutex.Lock() for _, client := range clients { if client.DeviceType == "Web" { // 向Web客户端转发二进制图片数据 if err := client.Conn.WriteMessage(websocket.BinaryMessage, p); err != nil { log.Printf("向Web客户端 %s 转发图片数据失败: %v", client.RemoteAddr, err) } else { log.Printf("成功向Web客户端 %s 转发图片数据,长度: %d 字节", client.RemoteAddr, len(p)) } } } clientsMutex.Unlock() } continue } messageStr := string(p) log.Printf("收到文本消息: %s", messageStr) // 检查消息格式是否为 messageId:content // 这种格式用于支持客户端的消息确认机制 parts := strings.SplitN(messageStr, ":", 2) if len(parts) == 2 { // 提取消息ID和内容 messageId := parts[0] content := parts[1] log.Printf("解析消息 - ID: %s, 内容: %s", messageId, content) // 发送确认消息给客户端 confirmMsg := []byte("CONFIRM:" + messageId) if err := conn.WriteMessage(websocket.TextMessage, confirmMsg); err != nil { log.Printf("发送确认消息失败: %v", err) } // 检查是否为截图命令,如果是则转发给手机客户端 if content == "screenshot" { log.Printf("检测到截图命令") // 获取手机客户端地址 phoneAddr := getPhoneClientAddr() if phoneAddr != "" { // 向手机客户端发送截图命令 clientsMutex.Lock() phoneClient, exists := clients[phoneAddr] clientsMutex.Unlock() if exists { // 直接向手机客户端发送截图命令 log.Printf("正在向手机客户端 %s 发送截图命令", phoneAddr) if err := phoneClient.Conn.WriteMessage(websocket.TextMessage, []byte("screenshot")); err != nil { log.Printf("向手机客户端发送截图命令失败: %v", err) // 向Web客户端发送错误消息 if err := conn.WriteMessage(websocket.TextMessage, []byte("向手机客户端发送截图命令失败")); err != nil { log.Printf("发送错误消息失败: %v", err) } } else { // 向Web客户端发送确认消息 if err := conn.WriteMessage(websocket.TextMessage, []byte("截图命令已发送到手机客户端")); err != nil { log.Printf("发送确认消息失败: %v", err) } } } else { log.Printf("未找到手机客户端连接") if err := conn.WriteMessage(websocket.TextMessage, []byte("未找到手机客户端")); err != nil { log.Printf("发送错误消息失败: %v", err) } } } else { // 将原始内容返回给客户端 if err := conn.WriteMessage(messageType, []byte(content)); err != nil { log.Printf("发送消息失败: %v", err) break } } } else { // 普通消息格式,直接返回 log.Printf("收到普通格式消息") if err := conn.WriteMessage(messageType, p); err != nil { log.Printf("发送消息失败: %v", err) break } } } } log.Println("WebSocket连接已关闭") } // 判断是否为WebSocket升级请求 func isWebSocketRequest(r *http.Request) bool { return r.Header.Get("Connection") == "Upgrade" && r.Header.Get("Upgrade") == "websocket" } // 根处理器,根据请求类型分发 func rootHandler(w http.ResponseWriter, r *http.Request) { if isWebSocketRequest(r) { wsHandler(w, r) return } httpHandler(w, r) } // 获取手机客户端地址 func getPhoneClientAddr() string { clientsMutex.Lock() defer clientsMutex.Unlock() // 注释掉不需要的日志 // log.Printf("当前连接的客户端数量: %d", len(clients)) var phoneAddr string for _, client := range clients { // log.Printf("客户端 [%s] - 设备类型: %s, 远程地址: %s", addr, client.DeviceType, client.RemoteAddr) if client.DeviceType == "Phone" { // log.Printf("找到手机客户端,地址: %s", addr) phoneAddr = client.RemoteAddr break // 找到一个手机客户端就返回 } } // log.Println("未找到手机客户端") return phoneAddr } // HTTP请求处理函数 func httpHandler(w http.ResponseWriter, r *http.Request) { // 获取请求路径 path := r.URL.Path // API请求处理 if strings.HasPrefix(path, "/api/") { handleAPIRequest(w, r, path) return } // 详细记录HTTP请求信息 log.Printf("接收到HTTP请求 - 路径: %s, 方法: %s", path, r.Method) // SPA应用路由处理优化:对于非文件路径(没有扩展名),直接返回index.html // 这确保了所有前端路由路径都能被正确处理 if !strings.Contains(path, ".") && path != "/" { log.Printf("检测到SPA路由路径: %s,直接返回index.html", path) handleSPA(w, r) return } // 静态文件服务 - 优先从dist目录提供构建后的文件 distHandler := http.FileServer(http.Dir("./Web/dist")) distReq, err := http.NewRequest(r.Method, path, r.Body) if err != nil { // 如果创建请求失败,尝试从Web目录提供文件 log.Printf("创建dist请求失败: %v,尝试从Web目录提供文件", err) handleStaticFileFromWeb(w, r, path) return } // 复制原始请求的所有头信息 *distReq = *r // 尝试在dist目录下查找文件 recorder := httptest.NewRecorder() distHandler.ServeHTTP(recorder, distReq) // 检查是否找到了文件 if recorder.Code != http.StatusNotFound { log.Printf("在dist目录找到文件,返回状态码: %d", recorder.Code) // 找到了文件,将响应复制回客户端 for k, v := range recorder.Header() { w.Header()[k] = v } w.WriteHeader(recorder.Code) w.Write(recorder.Body.Bytes()) return } log.Printf("在dist目录未找到文件,尝试从Web目录提供文件") // dist目录下未找到文件,尝试从Web目录提供文件 handleStaticFileFromWeb(w, r, path) } // 处理SPA路由请求 func handleSPA(w http.ResponseWriter, r *http.Request) { log.Printf("执行SPA路由处理 - 请求路径: %s", r.URL.Path) // 获取当前工作目录 cwd, _ := os.Getwd() distDir := filepath.Join(cwd, "Web", "dist") // 检查dist目录是否存在 if _, err := os.Stat(distDir); os.IsNotExist(err) { log.Printf("警告: dist目录不存在: %s,尝试从Web目录提供index.html", distDir) // 如果dist目录不存在,尝试从Web目录提供index.html handleStaticFileFromWeb(w, r, "/index.html") return } // 从dist目录读取index.html indexPath := filepath.Join(distDir, "index.html") content, err := os.ReadFile(indexPath) if err != nil { log.Printf("读取index.html失败: %v", err) // 如果读取失败,尝试从Web目录提供index.html handleStaticFileFromWeb(w, r, "/index.html") return } // 设置响应头 w.Header().Set("Content-Type", "text/html; charset=utf-8") // 确保CORS设置允许所有来源 w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "*") // 写入index.html内容 w.WriteHeader(http.StatusOK) w.Write(content) log.Printf("SPA路由处理成功,返回index.html") } // 处理API请求 func handleAPIRequest(w http.ResponseWriter, r *http.Request, path string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // 处理获取手机客户端地址的API请求 if path == "/api/android-addr" { // 保持向后兼容 phoneAddr := getPhoneClientAddr() w.Write([]byte(`{"status":"ok","androidAddress":"` + phoneAddr + `"}`)) return } // 统一的手机客户端地址API if path == "/api/phone-addr" { phoneAddr := getPhoneClientAddr() w.Write([]byte(`{"status":"ok","phoneAddress":"` + phoneAddr + `"}`)) return } // 其他API请求处理 response := map[string]interface{}{ "status": "ok", "message": "This is an API response", "path": path, "timestamp": time.Now().Unix(), } // 将响应转换为JSON jsonData, err := json.Marshal(response) if err != nil { log.Printf("JSON序列化失败: %v", err) w.Write([]byte(`{"status":"error","message":"Internal server error"}`)) return } w.Write(jsonData) } // 从Web目录提供静态文件 func handleStaticFileFromWeb(w http.ResponseWriter, r *http.Request, path string) { // 详细记录请求路径,帮助诊断SPA路由问题 log.Printf("处理静态文件请求 - 路径: %s, 方法: %s", path, r.Method) // 如果请求根路径,则返回index.html if path == "/" { log.Printf("请求根路径,返回index.html") path = "/index.html" } // 获取当前工作目录,确保路径正确 cwd, _ := os.Getwd() log.Printf("当前工作目录: %s", cwd) // 检查Web目录是否存在 webDir := filepath.Join(cwd, "Web") if _, err := os.Stat(webDir); os.IsNotExist(err) { log.Printf("警告: Web目录不存在: %s", webDir) } webHandler := http.FileServer(http.Dir(webDir)) webReq, err := http.NewRequest(r.Method, path, r.Body) if err != nil { log.Printf("创建Web请求失败: %v", err) // 在错误情况下也返回index.html,确保SPA路由正常工作 path = "/index.html" webReq, _ = http.NewRequest(r.Method, path, r.Body) } // 复制原始请求的所有头信息 *webReq = *r // 尝试在Web目录下查找文件 recorder := httptest.NewRecorder() webHandler.ServeHTTP(recorder, webReq) // 检查是否找到了文件 if recorder.Code != http.StatusNotFound { log.Printf("文件找到,返回状态码: %d", recorder.Code) // 找到了文件,将响应复制回客户端 for k, v := range recorder.Header() { w.Header()[k] = v } w.WriteHeader(recorder.Code) w.Write(recorder.Body.Bytes()) return } // SPA应用处理:当找不到具体文件时,返回index.html,让前端路由处理 log.Printf("文件未找到,启动SPA路由处理 - 路径: %s", path) indexReq, _ := http.NewRequest(r.Method, "/index.html", r.Body) *indexReq = *r indexRecorder := httptest.NewRecorder() webHandler.ServeHTTP(indexRecorder, indexReq) log.Printf("SPA路由处理 - index.html返回状态码: %d", indexRecorder.Code) for k, v := range indexRecorder.Header() { w.Header()[k] = v } w.WriteHeader(indexRecorder.Code) w.Write(indexRecorder.Body.Bytes()) } // 将未找到的请求作为API响应处理 func handleNotFoundAsAPI(w http.ResponseWriter, r *http.Request, path string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) response := map[string]interface{}{ "status": "not_found", "message": "Resource not found", "path": path, "timestamp": time.Now().Unix(), } // 将响应转换为JSON jsonData, err := json.Marshal(response) if err != nil { log.Printf("JSON序列化失败: %v", err) w.Write([]byte(`{"status":"error","message":"Internal server error"}`)) return } w.Write(jsonData) } func main() { // 根路径处理器,根据请求类型分发到WebSocket或HTTP处理器 http.HandleFunc("/", rootHandler) // 启动服务器 port := ":8805" log.Printf("服务器已启动,监听端口 %s...", port) log.Printf("前端页面: http://localhost%s", port) log.Printf("WebSocket连接: ws://localhost%s", port) if err := http.ListenAndServe(port, nil); err != nil { log.Fatalf("服务器启动失败: %v", err) } }