9.3 KiB
天璇 v4 修复计划
TL;DR (For humans)
7个问题,24个todo。核心改动:IP子网实体聚合、多列实体、地球手动缩放、异步聚类修复、列类型值优先检测。
Problem & Approach
Intent: CLEAR — 用户列出7个具体问题,方案可从前端/后端代码推导。无需高精度审查。
Issue 1 — 聚类选非数值列报错
根因: _run_analysis_worker (views.py:502) 默认取前10非_开头的列;手动向导步骤2未过滤非数值列。_handle_run_clustering (tool_registry.py:787-791) 虽有数值过滤,但无列给用户时返回"无可用维度"且不提示哪些可用。
方案:
- manual.html 步骤2特征列只展示数值列(标注dtype)
_handle_run_clustering报错时返回可用数值列名清单- views.py 默认特征列选择排除实体列自身
Issue 2 — 地球问题 (4个子问题)
根因:
- TLS流不显示 → globe_view 只用GeoIP,未用数据列经纬度
- 经纬线 → Three.js未画辅助线
- 滚轮缩放 → 无wheel事件处理
- 复选框状态丢失 → GET表单→刷新页面→选中状态丢失 方案:
- globe_view 优先用数据列
src_latitude/src_longitude/dst_latitude/dst_longitude,回退GeoIP - 增强现有手动拖拽JS + 添加滚轮事件(不引入OrbitControls——three.min.js是UMD构建,OrbitControls需要ESM,不兼容)
- 用 LineLoop 绘10度间隔经纬线
- 模板端将
?runs=1,2参数回填到checkbox checked属性
Issue 3 — Django异步聚类失败
根因: _run_analysis_worker (views.py:503) 在线程中调 asyncio.run()。_handle_run_clustering 本质是同步代码(polars/numpy/sklearn,无await),_handle_extract_features 内有 sync_to_async 用于ORM。
方案:
_handle_run_clustering提取同步核心函数_cluster_sync(),worker直接调同步版_handle_extract_features用asgiref.sync.async_to_sync包装
Issue 4 — 列类型靠列名而非数值
根因: type_classifier.py 优先级: config→名称匹配→值检测。名称匹配(如 _ip→IPv4)过于宽泛。
方案: 改为 config→值检测→名称匹配(仅值检测返回STRING时作为二次提示)。增加MAC地址、端口值模式。
Issue 5 — 数据列初步分析
方案: scripts/column_survey.py——standalone脚本,输出每列名称/dtype/唯一值数/空值率/前5非空样本。
Issue 6 — src_ip和dst_ip都作为实体
根因: entity_detector.py 只返回单 recommended。_background_process (views.py:367) 只用单列。
实际状态: manual.html line 46 <select multiple> 已支持多选!_run_analysis_worker (views.py:464) 已处理 entity_columns 列表。gap仅在于 _background_process 和 entity_detector。
方案:
entity_detector增加recommended_multi(前N候选)_background_process支持多列实体聚合- manual.html 确认页(步骤3)修复多选显示
Issue 7 — IP子网实体聚合 (/24 + /28)
根因: 无子网处理。IP直接作为实体标识。 方案:
- Config新增
Entity类,含subnet_masks和ip_columns entity_aggregator.py新增ip_to_subnet()和apply_subnet_aggregation()- 在 group_by 之前将IP转换为子网前缀
- 支持 /24 (0-255) 和 /28 (240-255)
Architecture & Design Decisions
Decision 1: IP子网聚合设计
- 位置: entity_aggregator.py,
aggregate_by_entity之前 - mask从 config 读取,支持 [24, 28]
- 转换后的子网前缀替代原始IP:
ip_to_subnet('10.0.1.15', 24)→'10.0.1.0/24'ip_to_subnet('10.0.1.250', 28)→'10.0.1.240/28'- 无效IP/None → 原值
# IP → subnet prefix
def ip_to_subnet(ip_str: str, mask: int) -> str:
"""Convert IP to CIDR subnet prefix."""
if not ip_str or not isinstance(ip_str, str):
return ip_str
try:
parts = [int(o) for o in ip_str.strip().split('.')]
if len(parts) != 4:
return ip_str
if any(p < 0 or p > 255 for p in parts):
return ip_str
ip_int = (parts[0]<<24) + (parts[1]<<16) + (parts[2]<<8) + parts[3]
subnet_int = ip_int & (0xFFFFFFFF << (32 - mask))
return f"{(subnet_int>>24)&255}.{(subnet_int>>16)&255}.{(subnet_int>>8)&255}.{subnet_int&255}/{mask}"
except (ValueError, IndexError, TypeError):
return ip_str
Decision 2: 多列实体
EntityProfile.entity_value(max_length=512) 存储多列值:"src_ip=10.0.0.1||dst_ip=203.0.113.5"AnalysisRun.entity_column(max_length=256) 存储逗号分隔:"src_ip,dst_ip"unique_together = ['run', 'entity_value']自然支持多列唯一性
Decision 3: 值优先类型检测
优先级:
- Config override → 立即返回
- 值采样 → 按顺序检测: IPv4→MAC→HEX→URL→Port→BOOL_ENUM→ENUM→Float→STRING
- 名称匹配 → 仅值检测返回STRING时作为二次提示(如
_ip后缀提示IPv4) - STRING作为最终兜底
新增模式:
- MAC:
r'^([0-9A-Fa-f]{2}[:.-]){5}[0-9A-Fa-f]{2}$' - Port: 非空值全部在 1-65535 且全为整数 → ENUM
- Float: >80% 样本可
float()解析 → FLOAT
Decision 4: 地球缩放+旋转 (不引入OrbitControls)
重要: three.min.js (603KB) 是 UMD 独立构建,OrbitControls 是 three/examples/jsm/ 的 ESM 格式, 无法直接加载。两个可行方案已评估:
a) ❌ 下载 OrbitControls 到 static/ - 需要处理模块依赖链(Controls→Euler→...),不适用于离线场景 b) ✅ 增强现有手动拖拽JS + 添加滚轮缩放事件 (globe.html lines 101-110 已有拖拽骨架)
实施:
- 保留并增强现有 mousedown/mousemove/mouseup 逻辑(已验证可用)
- 添加
wheel事件:camera.position.z += event.deltaY * 0.01+ clamp(3, 30) - 鼠标中键重置视角
Dependency Graph
独立阶段(可并行):
Todo 1-2: 列分析脚本 ──── (无依赖)
Todo 3-4: 值类型检测 ──── (无依赖)
Todo 5-8: 地球修复 ────── (无依赖)
Todo 9-10: 异步修复 ───── (无依赖)
顺序阶段:
Todo 11-13: 子网聚合 ──── (无依赖,但避免与类型检测冲突,需确保IP列识别的正确性)
Todo 14-17: 多列实体 ──── (依赖子网聚合的 IP→subnet 转换,因为多列中的IP列需要被子网处理)
Todo 18-20: 聚类列过滤 ── (依赖多列实体,因为 entity 列需要从 feature 列中排除)
最终:
Todo 21-24: 测试+文档 ─── (依赖所有)
F1-F3: 最终验证 ───────── (依赖所有)
Todos
- 1. 创建
scripts/column_survey.py: 对每列输出名称/dtype/唯一值数/空值率/前5样本 - 2. QA column_survey: 在现有测试数据上有合理输出
- 3.
type_classifier.py: 值优先检测(IPv4→MAC→HEX→URL→Port→BOOL→ENUM→Float→STRING);名称降级为二次提示 - 4. QA type_classifier:
pytest tests/test_type_classifier.py - 5.
globe.html: 添加wheel滚轮缩放事件 + 增强现有mousedown/move/up拖拽(不引入OrbitControls) - 6.
globe.html: 添加经纬线网格(10度间隔 LineLoop 绘制纬线圆+经线弧) - 7.
globe.html+globe_view修复: (a) 复选框提交后保留选中状态 (b) globe_view 优先用数据列经纬度 - 8. QA globe: 打开
/globe/交互验证滚轮缩放+经纬线+复选框 - 9.
views.py+tool_registry.py: 提取_cluster_sync()同步核心 +async_to_sync(_handle_extract_features) - 10. QA async: 手动分析流程聚类步骤不报
asyncio.run()错误 - 11.
config/loader.py: 新增Config.Entity类含subnet_masks(list[int]) 和ip_columns - 12.
entity_aggregator.py: 新增ip_to_subnet()函数(含空值/无效IP处理)+apply_subnet_aggregation() - 13. QA subnet:
ip_to_subnet('10.0.1.15', 24) == '10.0.1.0/24'+ip_to_subnet(None, 24) == None - 14.
entity_detector.py: 返回recommended_multi(前3候选list);对IP列自动推荐多列 - 15.
views.py:_background_process支持多列实体聚合;AnalysisRun.entity_column存逗号分隔 - 16.
manual.html: 步骤3确认页展示多选实体列(entity_columns|join:", ") - 17. QA multi-col: 选 src_ip+dst_ip → 确认页显示两个 → 聚合 → 聚类 → 成功
- 18.
manual.html: 步骤2特征列只展示数值列(标注dtype如"bytes_sent (Int64)");排除entity列 - 19.
views.py+tool_registry.py: 聚类报错时返回可用数值列清单;views.py默认排除entity列 - 20. QA cluster-col: 手动向导验证特征列仅数值列
- 21.
tests/test_type_classifier.py: 添加 MAC/端口/值优先测试 - 22.
tests/test_entity.py: 添加多列实体+IP子网测试 - 23.
tests/test_e2e.py: 添加多列实体→聚类端到端 - 24. 更新 AGENTS.md 和 docs/工作流.md: 新增架构决策+已知问题表
Final Verification Wave
- F1. 全量测试:
pytest tests -q→ 全部通过 - F2. 解除占用: 清理临时文件
- F3. 更新所有 MD 文件到最终状态
Approval Gate
状态: 等待批准 待执行: 24个todo,预计500-800行变更 关键变更: 10个文件(views.py/tool_registry.py/type_classifier.py/entity_aggregator.py/ entity_detector.py/globe.html/manual.html/config.yaml/loader.py/models.py)
同意后开始执行?