# app/controllers/automation.py from typing import List, Optional, Tuple, Dict, Any from app.core.crud import CRUDBase from app.models.automation import Scenario, ScenarioTriggerIndex, ScenarioScope from app.schemas.automation import ScenarioCreate, ScenarioUpdate from app.utils.event_task import event_manager import logging logger = logging.getLogger(__name__) class ScenarioController(CRUDBase[Scenario, ScenarioCreate, ScenarioUpdate]): def __init__(self): super().__init__(model=Scenario) async def update_trigger_index(self, scenario: Scenario, user_id: str) -> Scenario: """更新场景""" add_list = [] event_set = set() for condition in scenario.trigger.get('conditions') or []: event_name = condition.get('event_name', '') if event_name and event_name in event_set: continue event_set.add(event_name) orm = ScenarioTriggerIndex( owner_user_id=user_id, scenario_id=scenario.id, is_global=scenario.is_global, event_name=condition.get('event_name', ''), enabled=condition.get('enabled', True), scope=condition.get('scope', ScenarioScope.PERSONAL), ) add_list.append(orm) if add_list: await ScenarioTriggerIndex.bulk_create(add_list) return True return False async def new_scenario(self, scenario_in: ScenarioCreate, user_id: str) -> Scenario: """创建新场景""" obj = await self.create(scenario_in) # 更新触发条件 if obj.trigger: await self.update_trigger_index(obj, user_id) return obj async def update_scenario(self, scenario_in: ScenarioUpdate, user_id: str) -> Scenario: """更新场景""" obj = await self.update(id=scenario_in.id, obj_in=scenario_in) # 更新触发条件 if obj.trigger: await ScenarioTriggerIndex.filter(scenario_id=obj.id).delete() await self.update_trigger_index(obj, user_id) return obj async def remove_scenario(self, scenario_id: int) -> None: """删除场景""" await self.model.filter(id=scenario_id).delete() await ScenarioTriggerIndex.filter(scenario_id=scenario_id).delete() return True async def list_automation_events(self) -> List[Dict[str, Any]]: """获取所有自动化事件""" return [a for a in event_manager.automation_event_handlers if a.get("automation_event")] async def get_global_scenarios(self, enabled: bool = True) -> List[Scenario]: """获取所有启用的全局场景""" return await self.model.filter(is_global=True, enabled=enabled).all() async def get_user_scenarios(self, user_id: str, enabled: bool = True) -> List[Scenario]: """获取某用户的启用个人场景""" return await self.model.filter(owner_user_id=user_id, is_global=False, enabled=enabled).all() async def get_applicable_scenarios(self, user_id: str, enabled: bool = True) -> List[Scenario]: """获取对某用户生效的所有场景(全局 + 个人)""" global_scenarios = await self.get_global_scenarios(enabled=enabled) personal_scenarios = await self.get_user_scenarios(user_id, enabled=enabled) return global_scenarios + personal_scenarios class AutomationScenarioController(ScenarioController): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) async def find_active_scenarios(self, event_name: str, event_data: Dict[str, Any]) -> List[Scenario]: """查找所有实际激活的场景配置""" scenario_index_list = await ScenarioTriggerIndex.filter(enabled=True, event_name=event_name).all() scenario_list = [] if scenario_index_list: scenario_list = await self.model.filter(id__in=[s.scenario_id for s in scenario_index_list]).all() logger.info(f'find_active_scenarios: {len(scenario_list)}') active_scenarios = [] for scenario in scenario_list: result, reason = await self.check_is_applicable(scenario, event_data) if result: active_scenarios.append((scenario, reason)) return active_scenarios async def check_is_applicable(self, scenario: Scenario, event_data: Dict[str, Any]) -> tuple[bool, str]: """检查场景是否适用于当前事件""" trigger = scenario.trigger # 如果没有条件,默认返回True,即只要事件发生就会激活场景 if not trigger or not trigger.get('conditions'): logger.info(f'scenario {scenario.id} trigger without conditions, always return True') return True, '没有设置具体的触发条件,事件发生就激活场景' logic = trigger.get('logic', 'and') conditions = trigger.get('conditions', []) if logic == 'or': for condition in conditions: condition = condition.get('condition', {}) result, reason = self.check_condition(condition, event_data) if result: logger.info(f'scenario {scenario.id} trigger condition {condition} return True, reason: {reason}') return True, reason return False, '所有触发条件都不满足' elif logic == 'and': reason_list = [] for condition in conditions: condition = condition.get('condition', {}) result, reason = self.check_condition(condition, event_data) if not result: logger.info(f'scenario {scenario.id} trigger condition {condition} return False, reason: {reason}') return False, f'触发条件 {condition} 不满足, 原因: {reason}' else: reason_list.append(reason) return True, f'{", ".join(reason_list)}' raise ValueError(f"Invalid logic operator: {logic}") def check_condition(self, condition: Dict[str, Any], event_data: Dict[str, Any]) -> tuple[bool, str]: """检查单个条件是否满足""" def find_field_val(field: str, event_data: Dict[str, Any]) -> Any: """递归查找字段值""" if '.' in field: parts = field.split('.') current = event_data for part in parts: if isinstance(current, list): # current = current[int(part)] result = [] for item in current: # find_field_val field_val = find_field_val(part, item) if field_val is not None: result.append(field_val) return result elif isinstance(current, dict): current = current.get(part, None) else: return None return current return event_data.get(field, None) if not condition or not condition.get('field', ''): logger.error(f'scenario check_condition: field is empty, 当做无效条件处理') return True, '没有设置具体的触发条件,事件发生就激活场景' field = condition.get('field', '') value = condition.get('value', '') operator = condition.get('operator', '') field_val = find_field_val(field, event_data) if field_val is None: logger.error(f'scenario check_condition: field {field} value is None, event_data: {event_data}') return False, f'触发条件 {condition} 字段 {field} 不存在' # 字符串比较 if operator in ['contains', 'not_contains']: # if not isinstance(field_val, str): # return False, f'字段 {field} 不是字符串类型' if operator == 'contains' and value in field_val: return True, f'字段 {field} 包含 {value}' if operator == 'not_contains' and value not in field_val: return True, f'字段 {field} 不包含 {value}' return False, f'字段 {field} 不满足 {operator} {value}' # 数字、时间比较 if operator in ['gt', 'gte', 'lt', 'lte', 'eq', 'ne']: if not isinstance(field_val, (int, float, str)): return False, f'字段 {field} 不是数字或时间类型' try: field_val = float(field_val) value = float(value) except ValueError: return False, f'字段 {field} 不是数字或时间类型' if operator == 'gt' and field_val > value: return True, f'字段 {field} 大于 {value}' if operator == 'gte' and field_val >= value: return True, f'字段 {field} 大于等于 {value}' if operator == 'lt' and field_val < value: return True, f'字段 {field} 小于 {value}' if operator == 'lte' and field_val <= value: return True, f'字段 {field} 小于等于 {value}' if operator == 'eq' and field_val == value: return True, f'字段 {field} 等于 {value}' if operator == 'ne' and field_val != value: return True, f'字段 {field} 不等于 {value}' return False, f'字段 {field} 不满足 {operator} {value}' automation_scenario_controller = AutomationScenarioController()