| 
						
						
							
								
							
						
						
					 | 
					@ -6,32 +6,32 @@ import ( | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"encoding/json" | 
					 | 
					 | 
						"encoding/json" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"errors" | 
					 | 
					 | 
						"errors" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"fmt" | 
					 | 
					 | 
						"fmt" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						"github.com/redis/go-redis/v9" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"log" | 
					 | 
					 | 
						"log" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						"memobus_relay_server/config" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						"memobus_relay_server/registry" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						"memobus_relay_server/storage" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"net" | 
					 | 
					 | 
						"net" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"net/http" | 
					 | 
					 | 
						"net/http" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"net/http/httputil" | 
					 | 
					 | 
						"net/http/httputil" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"os" | 
					 | 
					 | 
						"os" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						"os/signal" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"strings" | 
					 | 
					 | 
						"strings" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"sync" | 
					 | 
					 | 
						"sync" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						"syscall" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"time" | 
					 | 
					 | 
						"time" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"github.com/golang-jwt/jwt/v5" | 
					 | 
					 | 
						"github.com/golang-jwt/jwt/v5" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						"github.com/hashicorp/yamux" | 
					 | 
					 | 
						"github.com/hashicorp/yamux" | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					) | 
					 | 
					 | 
					) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// 1. 密钥配置
 | 
					 | 
					 | 
					// 1. 密钥配置
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					var ( | 
					 | 
					 | 
					var ( | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
						// 用于验证 App 请求的密钥,必须和 ibserver 的 AppAccessSecret 一致
 | 
					 | 
					 | 
						appAccessSecret   []byte | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
						appAccessSecret = []byte(os.Getenv("APP_ACCESS_SECRET")) | 
					 | 
					 | 
						deviceRelaySecret []byte | 
				
			
			
				
				
			
		
	
		
		
			
				
					 | 
					 | 
						// 用于验证设备连接的密钥,必须和旧中继服务的 RelaySecret 一致
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						deviceRelaySecret = []byte(os.Getenv("RELAY_SECRET")) | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					) | 
					 | 
					 | 
					) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// 2. 结构体定义
 | 
					 | 
					 | 
					// 2. 结构体定义
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					type AuthPayload struct { | 
					 | 
					 | 
					type AuthPayload struct { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						DeviceSN string `json:"device_sn"` | 
					 | 
					 | 
						DeviceSN string `json:"device_sn"` | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						Token    string `json:"token"` | 
					 | 
					 | 
						Token    string `json:"token"` | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -53,20 +53,62 @@ var ( | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						sessionMutex   = &sync.RWMutex{} | 
					 | 
					 | 
						sessionMutex   = &sync.RWMutex{} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					) | 
					 | 
					 | 
					) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// 3. Main & 服务器启动逻辑
 | 
					 | 
					 | 
					// 3. Main & 服务器启动逻辑
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					func main() { | 
					 | 
					 | 
					func main() { | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
						if len(appAccessSecret) == 0 || len(deviceRelaySecret) == 0 { | 
					 | 
					 | 
						// 1. 加载配置
 | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
							log.Println("WARNING: APP_ACCESS_SECRET or RELAY_SECRET environment variable not set.") | 
					 | 
					 | 
						config.LoadConfig() | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						appAccessSecret = []byte(config.Cfg.Auth.AppAccessSecret) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						deviceRelaySecret = []byte(config.Cfg.Auth.DeviceRelaySecret) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						// 2. 初始化存储层 (Redis)
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						if err := storage.InitRedis(); err != nil { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							log.Fatalf("Failed to initialize storage: %v", err) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						} | 
					 | 
					 | 
						} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						go listenForDevices(":7002") | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
						log.Println("Starting App HTTP server on :8089") | 
					 | 
					 | 
						// 3. 启动注册与心跳 (它会自己检查 Redis 是否启用)
 | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
						http.HandleFunc("/tunnel/", handleAppRequest) // 统一入口
 | 
					 | 
					 | 
						registry.StartHeartbeat(func() int { | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
						if err := http.ListenAndServe(":8089", nil); err != nil { | 
					 | 
					 | 
							sessionMutex.RLock() | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
							log.Fatalf("Failed to start App server: %v", err) | 
					 | 
					 | 
							defer sessionMutex.RUnlock() | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							return len(deviceSessions) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						}) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						// 4. 启动核心服务 (放入后台 goroutine)
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						go listenForDevices(config.Cfg.Server.DeviceListenPort) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						mux := http.NewServeMux() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						mux.HandleFunc("/tunnel/", handleAppRequest) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						httpServer := &http.Server{ | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							Addr:    config.Cfg.Server.AppListenPort, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							Handler: mux, | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						} | 
					 | 
					 | 
						} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						go func() { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							log.Printf("Starting App HTTP server on %s", config.Cfg.Server.AppListenPort) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								log.Fatalf("App server ListenAndServe error: %v", err) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						}() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						// 5. 设置并等待优雅停机
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						shutdownChan := make(chan os.Signal, 1) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						signal.Notify(shutdownChan, syscall.SIGINT, syscall.SIGTERM) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						sig := <-shutdownChan | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						log.Printf("Shutdown signal received (%s), starting graceful shutdown...", sig) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						// 6. 执行清理操作
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						defer cancel() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						// a. 向调度服务(Redis)注销自己
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						registry.Unregister() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						// b. 优雅地关闭 HTTP 服务器
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						if err := httpServer.Shutdown(shutdownCtx); err != nil { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							log.Printf("HTTP server shutdown error: %v", err) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						} else { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							log.Println("HTTP server gracefully stopped.") | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						log.Println("Graceful shutdown complete.") | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					} | 
					 | 
					 | 
					} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					func listenForDevices(addr string) { | 
					 | 
					 | 
					func listenForDevices(addr string) { | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -87,9 +129,7 @@ func listenForDevices(addr string) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						} | 
					 | 
					 | 
						} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					} | 
					 | 
					 | 
					} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// 4. 设备端认证与会话管理
 | 
					 | 
					 | 
					// 4. 设备端认证与会话管理
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					func handleDeviceSession(conn net.Conn) { | 
					 | 
					 | 
					func handleDeviceSession(conn net.Conn) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						defer conn.Close() | 
					 | 
					 | 
						defer conn.Close() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						log.Printf("New device connected from %s, awaiting authentication...\n", conn.RemoteAddr()) | 
					 | 
					 | 
						log.Printf("New device connected from %s, awaiting authentication...\n", conn.RemoteAddr()) | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					@ -117,11 +157,11 @@ func handleDeviceSession(conn net.Conn) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						userID := claims.UserID | 
					 | 
					 | 
						userID := claims.UserID | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						log.Printf("Device '%s' (user: %s) authenticated successfully.\n", deviceSN, userID) | 
					 | 
					 | 
						log.Printf("Device '%s' (user: %s) authenticated successfully.\n", deviceSN, userID) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
						config := yamux.DefaultConfig() | 
					 | 
					 | 
						yamuxConfig := yamux.DefaultConfig() | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
						config.EnableKeepAlive = true | 
					 | 
					 | 
						yamuxConfig.EnableKeepAlive = true | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
						config.KeepAliveInterval = 30 * time.Second | 
					 | 
					 | 
						yamuxConfig.KeepAliveInterval = 30 * time.Second | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
						session, err := yamux.Server(conn, config) | 
					 | 
					 | 
						session, err := yamux.Server(conn, yamuxConfig) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					 | 
					 | 
						if err != nil { | 
					 | 
					 | 
						if err != nil { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
							log.Printf("Failed to start yamux session for device '%s': %v", deviceSN, err) | 
					 | 
					 | 
							log.Printf("Failed to start yamux session for device '%s': %v", deviceSN, err) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
							return | 
					 | 
					 | 
							return | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -138,6 +178,15 @@ func handleDeviceSession(conn net.Conn) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						sessionMutex.Unlock() | 
					 | 
					 | 
						sessionMutex.Unlock() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						log.Printf("Yamux session started for device '%s'\n", deviceSN) | 
					 | 
					 | 
						log.Printf("Yamux session started for device '%s'\n", deviceSN) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						if storage.RedisClient != nil { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							instanceID := config.Cfg.Server.InstanceID | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							if err := storage.RedisClient.HSet(context.Background(), config.Cfg.Redis.DeviceRelayMappingKey, deviceSN, instanceID).Err(); err != nil { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								log.Printf("ERROR: Failed to update device-relay mapping for %s: %v", deviceSN, err) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							} else { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								log.Printf("Device %s is now mapped to instance %s in Redis.", deviceSN, instanceID) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
						} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						defer func() { | 
					 | 
					 | 
						defer func() { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
							sessionMutex.Lock() | 
					 | 
					 | 
							sessionMutex.Lock() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
							if currentInfo, exists := deviceSessions[deviceSN]; exists && currentInfo.Session == session { | 
					 | 
					 | 
							if currentInfo, exists := deviceSessions[deviceSN]; exists && currentInfo.Session == session { | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					@ -145,6 +194,20 @@ func handleDeviceSession(conn net.Conn) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
							} | 
					 | 
					 | 
							} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
							sessionMutex.Unlock() | 
					 | 
					 | 
							sessionMutex.Unlock() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
							log.Printf("Device '%s' session closed\n", deviceSN) | 
					 | 
					 | 
							log.Printf("Device '%s' session closed\n", deviceSN) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							// b. 再清理 Redis 映射
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							if storage.RedisClient != nil { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								instanceID := config.Cfg.Server.InstanceID | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								// [健壮性优化] 在删除前,先检查一下 Redis 里的值是不是还是自己。
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								// 这可以防止因为竞态条件,错误地删除了一个刚刚重连到本机的、更新的会话映射。
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								currentInstanceID, err := storage.RedisClient.HGet(context.Background(), config.Cfg.Redis.DeviceRelayMappingKey, deviceSN).Result() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								if err == nil && currentInstanceID == instanceID { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
									storage.RedisClient.HDel(context.Background(), config.Cfg.Redis.DeviceRelayMappingKey, deviceSN) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
									log.Printf("Removed device-relay mapping for %s.", deviceSN) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								} else if err != nil && err != redis.Nil { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
									log.Printf("ERROR: Could not verify mapping for %s before deleting: %v", deviceSN, err) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
								} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					 | 
					 | 
					 | 
							} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						}() | 
					 | 
					 | 
						}() | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						<-session.CloseChan() | 
					 | 
					 | 
						<-session.CloseChan() | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					@ -172,9 +235,7 @@ func validateDeviceToken(tokenString string) (*DeviceJWTClaims, error) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						return claims, nil | 
					 | 
					 | 
						return claims, nil | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					} | 
					 | 
					 | 
					} | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// 5. App 端认证与请求处理
 | 
					 | 
					 | 
					// 5. App 端认证与请求处理
 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					// ==============================================================================
 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					func handleAppRequest(w http.ResponseWriter, r *http.Request) { | 
					 | 
					 | 
					func handleAppRequest(w http.ResponseWriter, r *http.Request) { | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						pathParts := strings.SplitN(strings.TrimPrefix(r.URL.Path, "/"), "/", 3) | 
					 | 
					 | 
						pathParts := strings.SplitN(strings.TrimPrefix(r.URL.Path, "/"), "/", 3) | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
						if len(pathParts) < 2 || pathParts[0] != "tunnel" { | 
					 | 
					 | 
						if len(pathParts) < 2 || pathParts[0] != "tunnel" { | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					
  |