Files
JoyD/AutoRobot/Windows/Robot/main.go

520 lines
16 KiB
Go
Raw Normal View History

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