package handler import ( "crypto/rand" "encoding/hex" "encoding/json" "git.yeij.top/AskaEth/Cyrene/pkg/logger" "net/http" "github.com/gin-gonic/gin" "git.yeij.top/AskaEth/Cyrene/gateway/internal/engine" "git.yeij.top/AskaEth/Cyrene/gateway/internal/middleware" "git.yeij.top/AskaEth/Cyrene/gateway/internal/store" ) // AutomationHandler 自动化处理器 type AutomationHandler struct { store *store.AutomationStore engine *engine.RuleEngine } // NewAutomationHandler 创建自动化处理器 func NewAutomationHandler(s *store.AutomationStore, e *engine.RuleEngine) *AutomationHandler { return &AutomationHandler{store: s, engine: e} } // ========== 请求/响应体 ========== // CreateRuleRequest 创建规则请求 type CreateRuleRequest struct { Name string `json:"name" binding:"required"` Description string `json:"description"` TriggerType string `json:"trigger_type" binding:"required"` TriggerConfig json.RawMessage `json:"trigger_config"` Conditions json.RawMessage `json:"conditions"` Actions json.RawMessage `json:"actions" binding:"required"` Enabled *bool `json:"enabled"` } // UpdateRuleRequest 更新规则请求 type UpdateRuleRequest struct { Name *string `json:"name"` Description *string `json:"description"` TriggerType *string `json:"trigger_type"` TriggerConfig *json.RawMessage `json:"trigger_config"` Conditions *json.RawMessage `json:"conditions"` Actions *json.RawMessage `json:"actions"` Enabled *bool `json:"enabled"` } // CreateSceneRequest 创建场景请求 type CreateSceneRequest struct { Name string `json:"name" binding:"required"` Icon string `json:"icon"` RuleIDs json.RawMessage `json:"rule_ids"` } // UpdateSceneRequest 更新场景请求 type UpdateSceneRequest struct { Name *string `json:"name"` Icon *string `json:"icon"` RuleIDs *json.RawMessage `json:"rule_ids"` } // ========== Rule Handlers ========== // ListRules 获取用户的所有规则 // GET /api/v1/automation/rules func (h *AutomationHandler) ListRules(c *gin.Context) { userID := middleware.GetUserID(c) if userID == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"}) return } rules, err := h.store.GetRulesByUser(userID) if err != nil { logger.Printf("[automation] 获取规则列表失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取规则列表失败"}) return } c.JSON(http.StatusOK, gin.H{ "rules": rules, "count": len(rules), }) } // CreateRule 创建规则 // POST /api/v1/automation/rules func (h *AutomationHandler) CreateRule(c *gin.Context) { userID := middleware.GetUserID(c) if userID == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"}) return } var req CreateRuleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效: " + err.Error()}) return } triggerConfig := ensureRawMessage(req.TriggerConfig) conditions := ensureRawMessage(req.Conditions) actions := ensureRawMessage(req.Actions) enabled := true if req.Enabled != nil { enabled = *req.Enabled } rule := &store.AutomationRule{ ID: randomHexID(), UserID: userID, Name: req.Name, Description: req.Description, TriggerType: req.TriggerType, TriggerConfig: triggerConfig, Conditions: conditions, Actions: actions, Enabled: enabled, } if err := h.store.CreateRule(rule); err != nil { logger.Printf("[automation] 创建规则失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "创建规则失败"}) return } c.JSON(http.StatusCreated, gin.H{ "success": true, "rule": rule, }) } // GetRule 获取单条规则 func (h *AutomationHandler) GetRule(c *gin.Context) { id := c.Param("id") rule, err := h.store.GetRule(id) if err != nil { logger.Printf("[automation] 获取规则失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取规则失败"}) return } if rule == nil { c.JSON(http.StatusNotFound, gin.H{"error": "规则不存在"}) return } c.JSON(http.StatusOK, gin.H{"rule": rule}) } // UpdateRule 更新规则 // PUT /api/v1/automation/rules/:id func (h *AutomationHandler) UpdateRule(c *gin.Context) { userID := middleware.GetUserID(c) id := c.Param("id") // 先获取规则验证所有权 existing, err := h.store.GetRule(id) if err != nil { logger.Printf("[automation] 获取规则失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取规则失败"}) return } if existing == nil { c.JSON(http.StatusNotFound, gin.H{"error": "规则不存在"}) return } if existing.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "无权修改此规则"}) return } var req UpdateRuleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效: " + err.Error()}) return } // 只更新提供的字段 if req.Name != nil { existing.Name = *req.Name } if req.Description != nil { existing.Description = *req.Description } if req.TriggerType != nil { existing.TriggerType = *req.TriggerType } if req.TriggerConfig != nil { existing.TriggerConfig = req.TriggerConfig } if req.Conditions != nil { existing.Conditions = req.Conditions } if req.Actions != nil { existing.Actions = req.Actions } if req.Enabled != nil { existing.Enabled = *req.Enabled } if err := h.store.UpdateRule(existing); err != nil { logger.Printf("[automation] 更新规则失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "更新规则失败"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "rule": existing, }) } // DeleteRule 删除规则 // DELETE /api/v1/automation/rules/:id func (h *AutomationHandler) DeleteRule(c *gin.Context) { userID := middleware.GetUserID(c) id := c.Param("id") existing, err := h.store.GetRule(id) if err != nil { logger.Printf("[automation] 获取规则失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取规则失败"}) return } if existing == nil { c.JSON(http.StatusNotFound, gin.H{"error": "规则不存在"}) return } if existing.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "无权删除此规则"}) return } if err := h.store.DeleteRule(id); err != nil { logger.Printf("[automation] 删除规则失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "删除规则失败"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // TriggerRule 手动触发单条规则 // POST /api/v1/automation/rules/:id/trigger func (h *AutomationHandler) TriggerRule(c *gin.Context) { userID := middleware.GetUserID(c) id := c.Param("id") rule, err := h.store.GetRule(id) if err != nil { logger.Printf("[automation] 获取规则失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取规则失败"}) return } if rule == nil { c.JSON(http.StatusNotFound, gin.H{"error": "规则不存在"}) return } if rule.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "无权触发此规则"}) return } h.engine.ExecuteRuleActions(rule) h.store.MarkRuleTriggered(id) c.JSON(http.StatusOK, gin.H{ "success": true, "message": "规则已触发", }) } // ========== Scene Handlers ========== // ListScenes 获取所有场景 // GET /api/v1/automation/scenes func (h *AutomationHandler) ListScenes(c *gin.Context) { userID := middleware.GetUserID(c) if userID == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"}) return } scenes, err := h.store.GetScenesByUser(userID) if err != nil { logger.Printf("[automation] 获取场景列表失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取场景列表失败"}) return } c.JSON(http.StatusOK, gin.H{ "scenes": scenes, "count": len(scenes), }) } // CreateScene 创建场景 // POST /api/v1/automation/scenes func (h *AutomationHandler) CreateScene(c *gin.Context) { userID := middleware.GetUserID(c) if userID == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"}) return } var req CreateSceneRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效: " + err.Error()}) return } ruleIDs := ensureRawMessage(req.RuleIDs) scene := &store.AutomationScene{ ID: randomHexID(), UserID: userID, Name: req.Name, Icon: req.Icon, RuleIDs: ruleIDs, } if err := h.store.CreateScene(scene); err != nil { logger.Printf("[automation] 创建场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "创建场景失败"}) return } c.JSON(http.StatusCreated, gin.H{ "success": true, "scene": scene, }) } // GetScene 获取单个场景 func (h *AutomationHandler) GetScene(c *gin.Context) { id := c.Param("id") scene, err := h.store.GetScene(id) if err != nil { logger.Printf("[automation] 获取场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取场景失败"}) return } if scene == nil { c.JSON(http.StatusNotFound, gin.H{"error": "场景不存在"}) return } c.JSON(http.StatusOK, gin.H{"scene": scene}) } // UpdateScene 更新场景 // PUT /api/v1/automation/scenes/:id func (h *AutomationHandler) UpdateScene(c *gin.Context) { userID := middleware.GetUserID(c) id := c.Param("id") existing, err := h.store.GetScene(id) if err != nil { logger.Printf("[automation] 获取场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取场景失败"}) return } if existing == nil { c.JSON(http.StatusNotFound, gin.H{"error": "场景不存在"}) return } if existing.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "无权修改此场景"}) return } var req UpdateSceneRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效: " + err.Error()}) return } if req.Name != nil { existing.Name = *req.Name } if req.Icon != nil { existing.Icon = *req.Icon } if req.RuleIDs != nil { existing.RuleIDs = req.RuleIDs } if err := h.store.UpdateScene(existing); err != nil { logger.Printf("[automation] 更新场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "更新场景失败"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "scene": existing, }) } // DeleteScene 删除场景 // DELETE /api/v1/automation/scenes/:id func (h *AutomationHandler) DeleteScene(c *gin.Context) { userID := middleware.GetUserID(c) id := c.Param("id") existing, err := h.store.GetScene(id) if err != nil { logger.Printf("[automation] 获取场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取场景失败"}) return } if existing == nil { c.JSON(http.StatusNotFound, gin.H{"error": "场景不存在"}) return } if existing.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "无权删除此场景"}) return } if err := h.store.DeleteScene(id); err != nil { logger.Printf("[automation] 删除场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "删除场景失败"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // ExecuteScene 手动执行场景 // POST /api/v1/automation/scenes/:id/execute func (h *AutomationHandler) ExecuteScene(c *gin.Context) { id := c.Param("id") // 验证场景存在 scene, err := h.store.GetScene(id) if err != nil { logger.Printf("[automation] 获取场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取场景失败"}) return } if scene == nil { c.JSON(http.StatusNotFound, gin.H{"error": "场景不存在"}) return } userID := middleware.GetUserID(c) if err := h.engine.ExecuteScene(id, userID); err != nil { logger.Printf("[automation] 执行场景失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "执行场景失败"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "场景已执行", }) } // ========== 辅助函数 ========== // randomHexID 使用 crypto/rand 生成 16 字节 hex ID func randomHexID() string { b := make([]byte, 16) rand.Read(b) return hex.EncodeToString(b) } // ensureRawMessage 确保 json.RawMessage 非空 func ensureRawMessage(raw json.RawMessage) *json.RawMessage { if len(raw) == 0 { return nil } result := make(json.RawMessage, len(raw)) copy(result, raw) return &result }