first commit
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
# 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()
|
||||
Reference in New Issue
Block a user