520 lines
16 KiB
Go
520 lines
16 KiB
Go
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)
|
||
}
|
||
}
|