package tools import ( "bytes" "context" "encoding/json" "fmt" "io" "log" "net/http" "os" "sync" "time" ) // IoTDevice 设备结构体(与 IoT 调试服务的结构对应) type IoTDevice struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` Status string `json:"status"` Brightness int `json:"brightness,omitempty"` Color string `json:"color,omitempty"` Temperature float64 `json:"temperature,omitempty"` Mode string `json:"mode,omitempty"` Position int `json:"position,omitempty"` Value float64 `json:"value,omitempty"` Unit string `json:"unit,omitempty"` Battery int `json:"battery,omitempty"` LastUpdated string `json:"last_updated"` } // IoTClient IoT 调试服务 HTTP 客户端 type IoTClient struct { baseURL string client *http.Client // 缓存控制 mu sync.RWMutex cache []IoTDevice cacheTime time.Time cacheTTL time.Duration } // NewIoTClient 创建 IoT 客户端 func NewIoTClient(baseURL string) *IoTClient { if baseURL == "" { baseURL = getEnv("IOT_DEBUG_SERVICE_URL", "http://localhost:8083") } return &IoTClient{ baseURL: baseURL, client: &http.Client{ Timeout: 5 * time.Second, }, cacheTTL: 60 * time.Second, } } // GetAllDevices 获取所有设备列表(带缓存) func (c *IoTClient) GetAllDevices(ctx context.Context) ([]IoTDevice, error) { // 检查缓存 c.mu.RLock() if c.cache != nil && time.Since(c.cacheTime) < c.cacheTTL { devices := make([]IoTDevice, len(c.cache)) copy(devices, c.cache) c.mu.RUnlock() return devices, nil } c.mu.RUnlock() // 请求 API req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/api/v1/devices", nil) if err != nil { log.Printf("[IoT客户端] 创建请求失败: %v", err) return nil, fmt.Errorf("创建请求失败: %w", err) } resp, err := c.client.Do(req) if err != nil { log.Printf("[IoT客户端] 请求失败: %v", err) return nil, fmt.Errorf("获取设备列表失败: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("获取设备列表返回状态码 %d", resp.StatusCode) } var result struct { Devices []IoTDevice `json:"devices"` Total int `json:"total"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("解析设备列表失败: %w", err) } // 更新缓存 c.mu.Lock() c.cache = result.Devices c.cacheTime = time.Now() c.mu.Unlock() return result.Devices, nil } // GetDevice 获取单个设备详情 func (c *IoTClient) GetDevice(ctx context.Context, id string) (*IoTDevice, error) { req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/api/v1/devices/"+id, nil) if err != nil { return nil, fmt.Errorf("创建请求失败: %w", err) } resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("获取设备 %s 失败: %w", id, err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("设备 %s 不存在", id) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("获取设备 %s 返回状态码 %d", id, resp.StatusCode) } var result struct { Device IoTDevice `json:"device"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("解析设备信息失败: %w", err) } return &result.Device, nil } // ToggleDevice 切换设备开关状态 func (c *IoTClient) ToggleDevice(id string) error { req, err := http.NewRequest(http.MethodPost, c.baseURL+"/api/v1/devices/"+id+"/toggle", nil) if err != nil { return fmt.Errorf("创建切换请求失败: %w", err) } resp, err := c.client.Do(req) if err != nil { return fmt.Errorf("切换设备 %s 失败: %w", id, err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return fmt.Errorf("设备 %s 不存在", id) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("切换设备 %s 返回状态码 %d", id, resp.StatusCode) } // 切换后清除缓存,确保下次查询获取最新状态 c.mu.Lock() c.cache = nil c.mu.Unlock() return nil } // SetDeviceProperty 设置设备属性(温度、亮度、位置、模式、颜色等) func (c *IoTClient) SetDeviceProperty(id string, field string, value interface{}) error { body, err := json.Marshal(map[string]interface{}{ "field": field, "value": value, }) if err != nil { return fmt.Errorf("序列化请求失败: %w", err) } req, err := http.NewRequest(http.MethodPost, c.baseURL+"/api/v1/devices/"+id+"/set", nil) if err != nil { return fmt.Errorf("创建设置请求失败: %w", err) } req.Header.Set("Content-Type", "application/json") req.Body = io.NopCloser(bytes.NewReader(body)) resp, err := c.client.Do(req) if err != nil { return fmt.Errorf("设置设备 %s 属性失败: %w", id, err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return fmt.Errorf("设备 %s 不存在", id) } if resp.StatusCode != http.StatusOK { var errResp struct { Error string `json:"error"` } json.NewDecoder(resp.Body).Decode(&errResp) if errResp.Error != "" { return fmt.Errorf("设置设备 %s 属性失败: %s", id, errResp.Error) } return fmt.Errorf("设置设备 %s 属性返回状态码 %d", id, resp.StatusCode) } // 修改后清除缓存 c.mu.Lock() c.cache = nil c.mu.Unlock() return nil } // GetDevicesForContext 获取设备状态摘要(供上下文注入使用,失败不报错) func (c *IoTClient) GetDevicesForContext(ctx context.Context) []IoTDevice { devices, err := c.GetAllDevices(ctx) if err != nil { log.Printf("[IoT客户端] 获取设备状态摘要失败: %v", err) return nil } return devices } // InvalidateCache 使缓存失效 func (c *IoTClient) InvalidateCache() { c.mu.Lock() c.cache = nil c.mu.Unlock() } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }