package main import ( "crypto/rand" "encoding/json" "fmt" "io" "math/big" "net/http" "os" "strings" "sync" "time" ) // Device 设备信息结构 type Device struct { MAC string `json:"mac"` Username string `json:"username"` DeviceName string `json:"device_name"` Token string `json:"token"` BindTime time.Time `json:"bind_time"` LastSeen time.Time `json:"last_seen"` IsActive bool `json:"is_active"` } // BindingCode 绑定码结构 type BindingCode struct { Code string `json:"code"` Username string `json:"username"` ExpiresAt time.Time `json:"expires_at"` Used bool `json:"used"` } // DeviceManager 设备管理器 type DeviceManager struct { devices map[string]*Device // MAC -> Device bindingCodes map[string]*BindingCode // Code -> BindingCode tokens map[string]string // Token -> MAC mu sync.RWMutex filePath string } var deviceManager *DeviceManager var deviceManagerOnce sync.Once // GetDeviceManager 获取设备管理器单例 func GetDeviceManager() *DeviceManager { deviceManagerOnce.Do(func() { deviceManager = &DeviceManager{ devices: make(map[string]*Device), bindingCodes: make(map[string]*BindingCode), tokens: make(map[string]string), filePath: "./devices.json", } deviceManager.LoadFromFile() }) return deviceManager } // GenerateBindingCode 生成6位数字绑定码 func (dm *DeviceManager) GenerateBindingCode(username string) (string, error) { dm.mu.Lock() defer dm.mu.Unlock() // 生成6位随机数字 var code string for i := 0; i < 6; i++ { n, err := rand.Int(rand.Reader, big.NewInt(10)) if err != nil { return "", err } code += fmt.Sprintf("%d", n.Int64()) } // 检查是否已存在(小概率) if _, exists := dm.bindingCodes[code]; exists { // 递归重新生成 return dm.GenerateBindingCode(username) } // 创建绑定码,5分钟有效 bindingCode := &BindingCode{ Code: code, Username: username, ExpiresAt: time.Now().Add(5 * time.Minute), Used: false, } dm.bindingCodes[code] = bindingCode fmt.Printf("[Device] Generated binding code %s for user %s\n", code, username) return code, nil } // generateDeviceToken 生成设备Token func generateDeviceToken() string { b := make([]byte, 32) rand.Read(b) return fmt.Sprintf("%x", b) } // BindDevice 绑定设备 func (dm *DeviceManager) BindDevice(mac, bindingCode, deviceName string) (*Device, error) { dm.mu.Lock() defer dm.mu.Unlock() // 验证绑定码 code, exists := dm.bindingCodes[bindingCode] if !exists { return nil, fmt.Errorf("绑定码不存在") } if code.Used { return nil, fmt.Errorf("绑定码已使用") } if time.Now().After(code.ExpiresAt) { return nil, fmt.Errorf("绑定码已过期") } // 检查设备是否已绑定 if existingDevice, exists := dm.devices[mac]; exists { return nil, fmt.Errorf("设备已绑定到用户 %s", existingDevice.Username) } // 生成Token token := generateDeviceToken() // 创建设备 device := &Device{ MAC: mac, Username: code.Username, DeviceName: deviceName, Token: token, BindTime: time.Now(), LastSeen: time.Now(), IsActive: true, } // 保存设备信息 dm.devices[mac] = device dm.tokens[token] = mac // 标记绑定码已使用 code.Used = true // 保存到文件 dm.SaveToFile() fmt.Printf("[Device] Device %s bound to user %s\n", mac, code.Username) return device, nil } // VerifyToken 验证设备Token func (dm *DeviceManager) VerifyToken(token string) (*Device, error) { dm.mu.RLock() defer dm.mu.RUnlock() mac, exists := dm.tokens[token] if !exists { return nil, fmt.Errorf("无效的Token") } device, exists := dm.devices[mac] if !exists { return nil, fmt.Errorf("设备不存在") } if !device.IsActive { return nil, fmt.Errorf("设备已停用") } return device, nil } // GetDeviceByMAC 根据MAC地址获取设备 func (dm *DeviceManager) GetDeviceByMAC(mac string) (*Device, error) { dm.mu.RLock() defer dm.mu.RUnlock() device, exists := dm.devices[mac] if !exists { return nil, fmt.Errorf("设备未绑定") } return device, nil } // UpdateLastSeen 更新设备最后在线时间 func (dm *DeviceManager) UpdateLastSeen(mac string) { dm.mu.Lock() defer dm.mu.Unlock() if device, exists := dm.devices[mac]; exists { device.LastSeen = time.Now() dm.SaveToFile() } } // DirectBindDevice 直接绑定设备(不需要绑定码) func (dm *DeviceManager) DirectBindDevice(mac, username, deviceName string) (*Device, error) { dm.mu.Lock() defer dm.mu.Unlock() // 检查设备是否已绑定 if existingDevice, exists := dm.devices[mac]; exists { return nil, fmt.Errorf("设备已绑定到用户 %s", existingDevice.Username) } // 生成Token token := generateDeviceToken() // 创建设备 device := &Device{ MAC: mac, Username: username, DeviceName: deviceName, Token: token, BindTime: time.Now(), LastSeen: time.Now(), IsActive: true, } dm.devices[mac] = device dm.tokens[token] = mac dm.SaveToFile() fmt.Printf("[Device] Device %s directly bound to user %s\n", mac, username) return device, nil } // UnbindDevice 解绑设备 func (dm *DeviceManager) UnbindDevice(mac string) error { dm.mu.Lock() defer dm.mu.Unlock() device, exists := dm.devices[mac] if !exists { return fmt.Errorf("设备不存在") } // 删除Token映射 delete(dm.tokens, device.Token) // 删除设备 delete(dm.devices, mac) dm.SaveToFile() fmt.Printf("[Device] Device %s unbound\n", mac) return nil } // GetDevice 获取单个设备信息 func (dm *DeviceManager) GetDevice(mac string) (*Device, error) { dm.mu.RLock() defer dm.mu.RUnlock() device, exists := dm.devices[mac] if !exists { return nil, fmt.Errorf("设备不存在") } return device, nil } // GetUserDevices 获取用户的所有设备 func (dm *DeviceManager) GetUserDevices(username string) []*Device { dm.mu.RLock() defer dm.mu.RUnlock() var devices []*Device for _, device := range dm.devices { if device.Username == username { devices = append(devices, device) } } return devices } // SaveToFile 保存设备信息到文件 func (dm *DeviceManager) SaveToFile() error { data := struct { Devices map[string]*Device `json:"devices"` }{ Devices: dm.devices, } jsonData, err := json.MarshalIndent(data, "", " ") if err != nil { fmt.Println("[Error] Failed to marshal devices:", err) return err } err = os.WriteFile(dm.filePath, jsonData, 0644) if err != nil { fmt.Println("[Error] Failed to write devices.json:", err) return err } return nil } // LoadFromFile 从文件加载设备信息 func (dm *DeviceManager) LoadFromFile() error { file, err := os.Open(dm.filePath) if err != nil { if os.IsNotExist(err) { fmt.Println("[Info] devices.json not found, creating new file") return nil } return err } defer file.Close() data, err := io.ReadAll(file) if err != nil { return err } var fileData struct { Devices map[string]*Device `json:"devices"` } err = json.Unmarshal(data, &fileData) if err != nil { return err } dm.devices = fileData.Devices if dm.devices == nil { dm.devices = make(map[string]*Device) } // 重建Token索引 for mac, device := range dm.devices { dm.tokens[device.Token] = mac } fmt.Printf("[Info] Loaded %d devices from file\n", len(dm.devices)) return nil } // HTTP处理器 // GenerateBindingCodeHandler 生成绑定码 func GenerateBindingCodeHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // 获取当前登录用户 username := GetCurrentUser(r) // 需要从session获取 if username == "" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } dm := GetDeviceManager() code, err := dm.GenerateBindingCode(username) if err != nil { http.Error(w, "Failed to generate binding code", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "code": code, "expires_in": 300, // 5分钟 }) } // BindDeviceHandler ESP32设备绑定接口 func BindDeviceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } fmt.Println("[API] ESP32 device bind request received from", r.RemoteAddr) // 解析请求 var req struct { MAC string `json:"mac"` BindingCode string `json:"binding_code"` DeviceName string `json:"device_name"` } err := json.NewDecoder(r.Body).Decode(&req) if err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if req.MAC == "" || req.BindingCode == "" { http.Error(w, "Missing required fields", http.StatusBadRequest) return } // 如果没有提供设备名称,使用默认名称 if req.DeviceName == "" { req.DeviceName = "ESP32音乐播放器" } dm := GetDeviceManager() device, err := dm.BindDevice(req.MAC, req.BindingCode, req.DeviceName) if err != nil { fmt.Printf("[Error] Device bind failed: %v\n", err) http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "message": "设备绑定成功", "token": device.Token, "username": device.Username, }) } // VerifyDeviceHandler 验证设备Token func VerifyDeviceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } token := r.Header.Get("X-Device-Token") if token == "" { token = r.URL.Query().Get("token") } if token == "" { http.Error(w, "Missing token", http.StatusBadRequest) return } dm := GetDeviceManager() device, err := dm.VerifyToken(token) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // 更新最后在线时间 dm.UpdateLastSeen(device.MAC) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "device": map[string]interface{}{ "mac": device.MAC, "username": device.Username, "device_name": device.DeviceName, "bind_time": device.BindTime, "last_seen": device.LastSeen, }, }) } // GetCurrentUser 从请求中获取当前登录用户 func GetCurrentUser(r *http.Request) string { userStore := GetUserStore() // 1. 尝试从 Authorization Header 获取 (Bearer Token) authHeader := r.Header.Get("Authorization") if authHeader != "" { token := strings.TrimPrefix(authHeader, "Bearer ") // 直接从UserStore验证Token user, err := userStore.GetUserByToken(token) if err == nil && user != nil { return user.Username } } // 2. 尝试从 Cookie 获取 session_token cookie, err := r.Cookie("session_token") if err == nil { username := userStore.GetUsernameByToken(cookie.Value) if username != "" { return username } } // 3. 尝试从 X-Device-Token Header 获取 (用于ESP32设备) deviceToken := r.Header.Get("X-Device-Token") if deviceToken != "" { dm := GetDeviceManager() device, err := dm.VerifyToken(deviceToken) if err == nil && device != nil { // 更新最后在线时间 dm.UpdateLastSeen(device.MAC) return device.Username } } return "" } // DirectBindDeviceHandler Web端直接绑定设备(不需要绑定码) func DirectBindDeviceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // 获取当前用户 username := GetCurrentUser(r) if username == "" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "未登录", }) return } // 解析请求 var req struct { MAC string `json:"mac"` DeviceName string `json:"device_name"` } err := json.NewDecoder(r.Body).Decode(&req) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "请求格式错误", }) return } if req.MAC == "" { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "MAC地址不能为空", }) return } // 如果没有提供设备名称,使用默认名称 if req.DeviceName == "" { req.DeviceName = "ESP32音乐播放器" } dm := GetDeviceManager() device, err := dm.DirectBindDevice(req.MAC, username, req.DeviceName) if err != nil { fmt.Printf("[Error] Direct bind failed: %v\n", err) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": err.Error(), }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "message": "设备绑定成功", "device": map[string]interface{}{ "mac": device.MAC, "device_name": device.DeviceName, "bind_time": device.BindTime, }, }) } // ListUserDevicesHandler 列出用户的所有设备 func ListUserDevicesHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // 获取当前用户 username := GetCurrentUser(r) if username == "" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "未登录", }) return } dm := GetDeviceManager() devices := dm.GetUserDevices(username) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "devices": devices, }) } // UnbindDeviceHandler 解绑设备 func UnbindDeviceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // 获取当前用户 username := GetCurrentUser(r) if username == "" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "未登录", }) return } // 解析请求 var req struct { MAC string `json:"mac"` } err := json.NewDecoder(r.Body).Decode(&req) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "请求格式错误", }) return } if req.MAC == "" { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "MAC地址不能为空", }) return } dm := GetDeviceManager() // 检查设备是否属于当前用户 device, err := dm.GetDevice(req.MAC) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "设备不存在", }) return } if device.Username != username { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "无权解绑此设备", }) return } // 解绑设备 err = dm.UnbindDevice(req.MAC) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": err.Error(), }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "message": "设备已解绑", }) } // SyncDeviceHandler ESP32用MAC地址同步Token(用于网页端绑定后自动获取token) func SyncDeviceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { MAC string `json:"mac"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if req.MAC == "" { http.Error(w, "Missing MAC address", http.StatusBadRequest) return } dm := GetDeviceManager() device, err := dm.GetDevice(req.MAC) if err != nil || device == nil { // 设备未绑定 w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "设备未绑定", }) return } // 更新最后在线时间 dm.UpdateLastSeen(device.MAC) // 返回 token 和用户名 w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "token": device.Token, "username": device.Username, "message": "同步成功", }) fmt.Printf("[Info] Device %s synced token for user: %s\n", device.MAC, device.Username) }