Files

520 lines
16 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}