Files
xingrin/backend/apps/scan/utils/config_merger.py
yyhuni f076c682b6 feat(scan): add multi-engine support and config merging with enhanced indexing
- Add multi-engine support to Scan model with engine_ids and engine_names fields
- Implement config_merger utility for merging multiple engine configurations
- Add merged_configuration property to Scan model for unified config access
- Update scan creation and scheduling services to handle multiple engines
- Add pg_trgm GIN indexes to asset and snapshot models for fuzzy search on url, title, and name fields
- Update scan views and serializers to support multi-engine selection and display
- Enhance frontend components for multi-engine scan initiation and scheduling
- Update test data generation script for multi-engine scan scenarios
- Add internationalization strings for multi-engine UI elements
- Refactor scan flow to use merged configuration instead of single engine config
- Update Docker compose files with latest configuration
2026-01-01 22:35:05 +08:00

81 lines
2.1 KiB
Python

"""
配置合并工具模块
提供多引擎 YAML 配置的冲突检测和合并功能。
"""
from typing import List, Tuple
import yaml
class ConfigConflictError(Exception):
"""配置冲突异常
当两个或多个引擎定义相同的顶层扫描类型键时抛出。
"""
def __init__(self, conflicts: List[Tuple[str, str, str]]):
"""
参数:
conflicts: (键, 引擎1名称, 引擎2名称) 元组列表
"""
self.conflicts = conflicts
msg = "; ".join([f"{k} 同时存在于「{e1}」和「{e2}" for k, e1, e2 in conflicts])
super().__init__(f"扫描类型冲突: {msg}")
def merge_engine_configs(engines: List[Tuple[str, str]]) -> str:
"""
合并多个引擎的 YAML 配置。
参数:
engines: (引擎名称, 配置YAML) 元组列表
返回:
合并后的 YAML 字符串
异常:
ConfigConflictError: 当顶层键冲突时
"""
if not engines:
return ""
if len(engines) == 1:
return engines[0][1]
# 追踪每个顶层键属于哪个引擎
key_to_engine: dict[str, str] = {}
conflicts: List[Tuple[str, str, str]] = []
for engine_name, config_yaml in engines:
if not config_yaml or not config_yaml.strip():
continue
try:
parsed = yaml.safe_load(config_yaml)
except yaml.YAMLError:
# 无效 YAML 跳过
continue
if not isinstance(parsed, dict):
continue
# 检查顶层键冲突
for key in parsed.keys():
if key in key_to_engine:
conflicts.append((key, key_to_engine[key], engine_name))
else:
key_to_engine[key] = engine_name
if conflicts:
raise ConfigConflictError(conflicts)
# 无冲突,用双换行符连接配置
configs = []
for _, config_yaml in engines:
if config_yaml and config_yaml.strip():
configs.append(config_yaml.strip())
return "\n\n".join(configs)