# 天璇 v5 — 18 Issues Full Fix Plan ## TL;DR (For humans) 修复上传管理、列检测、大规模数据处理、Globe可视化、聚类可视化等18个问题。 --- ## Workstream A: 上传管理修复 (Issues 1, 2, 3, 10, 11) ### A1 重启后端后旧数据可分析 **Files**: `analysis/views.py`, `analysis/session_store.py` **Root Cause**: 旧分析的 `AnalysisRun` 记录在 DB 中,但 `csv_glob` 指向的目录是 `%APPDATA%/TianXuan/data/uploads/{timestamp}/`。重启后 session_store 内存清空,但上传文件仍在磁盘。`globe_view` 和 `run_detail` 尝试加载旧运行时会调用 `load_csv_directory(run.csv_glob)` 重新读取 CSV。 **Fix**: 确保 `load_csv_directory` 能处理已删除的上传目录 → 优雅降级。在 `globe_view` 和 `_background_process` 中添加 `os.path.isdir()` 检查,目录不存在时将 run 标记为 `failed` 并设置友好的错误消息。 ### A2 Web图标在所有页面显示 **Files**: `templates/base.html`, `static/tianxuan/` **Root Cause**: `base.html` 没有 ``。浏览器自动请求 `/favicon.ico` 但项目无此文件。 **Fix**: 1. 创建 `static/tianxuan/favicon.svg` 或使用基于主题的 HTML favicon 2. `base.html` 的 `` 中添加 `` ### A3 上传按钮在页面顶部 **File**: `templates/tianxuan/upload.html` **Root Cause**: 上传 `
` 在 "已上传数据集" 表格之前,但上传按钮 (`#submitBtn`) 初始 `display:none`,只有在选择文件后才显示。 **Fix**: 将上传按钮移到 dropZone 下方、紧跟在选择文件后显示。或者将上传区域移到页面顶部的固定/粘性位置。 ### A10 Django文件上传无限制 **Files**: `tianxuan/settings.py`, `analysis/views.py` **Root Cause**: 当前限制:`DATA_UPLOAD_MAX_NUMBER_FIELDS=10000`、`DATA_UPLOAD_MAX_MEMORY_SIZE=50MB`、`views.py` 中硬编码 2000 文件/10GB 总大小限制。用户说"只能上传100文件"可能是由于 `FILE_UPLOAD_MAX_MEMORY_SIZE=10MB` 导致大文件被写入临时文件时出现问题。 **Fix**: - 移除 `DATA_UPLOAD_MAX_NUMBER_FIELDS` 限制(设为 0 或大幅增加) - 增大 `DATA_UPLOAD_MAX_MEMORY_SIZE` 到 256MB - 移除 `views.py` 中的 2000 文件限制(改为仅警告) ### A11 上传页删除功能完善 **Files**: `templates/tianxuan/upload.html`, `analysis/views.py` **Root Cause**: 删除按钮只显示在 `status` 为 `ready/completed/failed` 的运行上。`loading/profiling/aggregating` 状态的运行没有删除按钮(可能是为了安全)。 **Fix**: - 修改模板,在所有状态下显示删除按钮(`loading` 状态时弹出确认:"后台处理中,删除将中断分析") - 确保 `delete_upload` 视图能安全处理正在处理的运行(`shutil.rmtree` + ORM delete) --- ## Workstream B: 列检测与类型分类修复 (Issues 7, 15, 18) ### B7 TLS 0ver hex格式识别 **Files**: `analysis/type_classifier.py`, `analysis/views.py`, `templates/tianxuan/globe.html` **Root Cause**: `0ver` 列名不在名称启发式规则中(需要 `^ver_` 前缀)。TLS版本检测在 `views.py` 中硬编码为 `'1.3' in tls` / `'1.2' in tls`,不知道 hex 格式如 `03 03` (TLS 1.2) / `03 04` (TLS 1.3)。 **Fix**: 1. `type_classifier.py` 的 `_NAME_TYPE_MAP` 添加 `0ver` → ENUM 映射 2. `views.py` 的 `globe_view` 中添加 hex→TLS版本映射: ```python TLS_HEX_MAP = {'0303': 'TLSv1.2', '0304': 'TLSv1.3', '0302': 'TLSv1.1', '0301': 'TLSv1.0'} hex_val = tls.replace(' ', '') # "03 03" → "0303" tls_label = TLS_HEX_MAP.get(hex_val, 'other') ``` 3. Globe legend 添加对 hex 版本的说明 ### B15 "+"字符串被识别为数字 **Files**: `analysis/type_classifier.py`, `analysis/data_loader.py` **Root Cause**: 独立 `+` 在 `_coerce_to_float` 中被列为 artifact(正常),但值如 `+5`、`+0.5` 等前导加号的值会通过 `float("+5")` 解析。在 `_values_to_type` 中,LAT_LON 检测会过滤独立 `+`,但列中混有 `+` 和数字的列可能被分类为 FLOAT。 **Fix**: 1. 在 `_values_to_type` 的 FLOAT 检测步骤中,检查采样中是否有 `+` 或 `-` 开头的字符串值 2. 如果存在前导 `+`/`-` 且并非所有值都是有效数字,降级为 STRING 3. 在 `_coerce_to_float` 中添加对 `+` 前缀值的更严格检查 ### B18 根据真实列名配置实体检测 **Files**: `analysis/entity_detector.py`, `analysis/entity_aggregator.py`, `config/config.yaml` **Changes**: 1. `entity_detector.py` 的 `ENTITY_KEYWORDS` 添加用户全部列名 2. `entity_aggregator.py` 的关键词集合添加用户列名 3. `config.yaml` 更新 `entity.ip_columns` 和 column 覆盖 4. `type_classifier.py` 的 `_NAME_TYPE_MAP` 添加全部列名映射 **用户列名映射表**: | 列名 | 类型 | 聚合用途 | 检测关键词 | |------|------|---------|-----------| | `:ips` | IPv4 (已存在) | src IP | `:ips` | | `:ipd` | IPv4 (已存在) | dst IP | `:ipd` | | `:prs` | ENUM/端口 | src port | `:prs` | | `:prd` | ENUM/端口 | dst port | `:prd` | | `scnt` | STRING/ENUM | source country | `scnt` | | `dcnt` | STRING/ENUM | dest country | `dcnt` | | `server-ip` | IPv4 | server IP | `server_ip` | | `client-ip` | IPv4 | client IP | `client_ip` | | `0ver` | ENUM | TLS version | `0ver` | | `cnam` | STRING | cert common name | `cnam` | | `snam` | STRING | server name | `snam` | | `4dur` | FLOAT | duration | `4dur` | | `8ses` | FLOAT | session offset | `8ses` | | `2tmo` | FLOAT | timeout offset | `2tmo` | | `4ksz` | INT | key size | `4ksz` | | `cnrs` | BOOL_ENUM | recoverable | `cnrs` | | `isrs` | BOOL_ENUM | is recovered | `isrs` | | `8ack` | INT | backward bytes | `8ack` | | `8ppk` | INT | payload packets | `8ppk` | | `8dbd` | TIMESTAMP | db timestamp | `8dbd` | | `1ipp` | ENUM | IP protocol | `1ipp` | | `4dbn` | ENUM | database number | `4dbn` | | `tabl` | STRING | table name | `tabl` | | `name` | STRING | link name | `name` | | `source-node` | STRING | source node | `source_node` | | `cipher-suite` | HEX | TLS cipher | `cipher_suite` | | `ecdhe-named-curve` | STRING | ECDHE curve | `ecdhe_named_curve` | | `0cph` | HEX | TLS cipher hex | `0cph` | | `0crv` | HEX | TLS curve hex | `0crv` | | `0rnd` | STRING | TLS random | `0rnd` | | `0rnt` | FLOAT/TIMESTAMP | TLS random time | `0rnt` | | `4dbn` | STRING | db number | `4dbn` | | `time` | TIMESTAMP | time | `time` | | `timestamp` | TIMESTAMP | timestamp | `timestamp` | | `row` | INT | row number | `row` | | `+.latd` | LAT_LON | latitude (entity_aggregator 子串匹配) | `latd` | | `+.lond` | LAT_LON | longitude | `lond` | | `+.ispn` | STRING | ISP name | `ispn` | | `+.orgn` | STRING | org name | `orgn` | | `+.city` | STRING | city | `city` | **关键的 `_find_column` 改进**: `entity_aggregator.py` 中的 `_find_column` 使用严格匹配。对于 `:prs` 需要匹配 dst_port keywords → 不匹配。需要在 `_find_column` 中添加 `+` 和 `:` 前缀剥离: ```python normalised = name.lower().lstrip(':+').replace('-', '_').replace(' ', '_') ``` **Lat/Lon 列检测修复**: `+.latd` / `+.lond` 列: - `type_classifier._values_to_type` 中如果值 max_abs ≤ 10 则 LAT_LON 检测失败 - 需要在 `_NAME_TYPE_MAP` 中添加 `latd|lond` 模式 → LAT_LON - 或在 `_name_to_type` 中添加对 `+.` 前缀的感知 --- ## Workstream C: Traceback截断修复 (Issue 4) ### C4 清理所有 traceback 截断 **File**: `analysis/tool_registry.py` line 1138 **Current**: `traceback.format_exc()[:200]` 截断 PCA 错误到 200 字符 **Fix**: 改为完整 traceback。同时审计整个项目: - `tianxuan/llm_orchestrator.py` line 71: `json.dumps(...)[:1200]` — 这是工具结果截断,合理,保留 - `analysis/tool_registry.py` line 43: `_truncate_response()` — 工具响应截断,合理,保留 - **只修复** line 1138 的 `[:200]` --- ## Workstream D: 大规模数据处理 (Issues 5, 6, 14, 16) ### D5 大数据量卡顿优化 (1M-20M行) **Root Cause**: 当前 `MAX_ROWS_PER_RUN = 300` 但分析流水线是一次性 collect 所有数据到内存。 **Fix**: 1. `data_loader.py` 的 `load_csv_directory` 添加 `head=N` 参数用于预览场景 2. 聚类分析使用 Polars streaming (`engine='streaming'`) 3. 在 `tool_registry.py` 的 `_cluster_sync` 中添加数据降采样:>50K 行时随机采样 4. `views.py` 中 globe_view 的 `MAX_ROWS_PER_RUN` 从 300 改为 500(对于 20M 数据来说 300 行太稀疏了) ### D6 进度条 + 静默处理 **Root Cause**: 当前状态机只有 `loading→profiling→aggregating→ready→clustering→extracting→completed/failed`,无百分比。 **Fix**: 1. 在 `AnalysisRun` model 添加 `progress_pct` (IntegerField, default=0) 和 `progress_msg` (CharField) 2. 后台线程在每一步更新 `progress_pct`(loading=10, profiling=30, aggregating=50, clustering=70, extracting=90, completed=100) 3. `run_status_api` 返回 `progress_pct` 和 `progress_msg` 4. `upload.html` 中的进度条显示实际百分比 5. **无前端静默处理**:添加 `?background=true` 参数,后端仍全速分析但前端不轮询 ### D14 低内存设备聚类崩溃 **Root Cause**: 8GB 设备上,20M 行 × 30 列的数据在聚类时会产生巨大 float64 ndarray。 **Fix**: 1. `_cluster_sync` 中添加内存检查:`psutil.virtual_memory().available < 2GB` 时自动启用流式模式 2. 自动降采样:>100K 行时使用 `sklearn.utils.resample` 采样到 50K 行 3. 使用 `MiniBatchKMeans` 替代 `KMeans`(内存效率更高) 4. 在聚类前使用 `sklearn.decomposition.PCA(n_components=min(50, n_features))` 降维 ### D16 空闲时高磁盘IO **Root Cause**: `session_store.py` 是纯内存的,不应有磁盘 IO。但 Django 日志 `RotatingFileHandler` 可能在后台写入。另外 SQLite 使用 DELETE 模式(非 WAL)可能导致写入阻塞。 **Fix**: 1. 审计后台线程是否有不必要的 ORM 轮询 2. 如果 `settings.py` 中的 `LOGGING` 配置了低级别(如 DEBUG),改为 INFO+ 3. 检查是否有任何定时任务或 watchdog --- ## Workstream E: Globe可视化修复 (Issues 8, 9) ### E8 Globe响应式缩放 **File**: `templates/tianxuan/globe.html` **Root Cause**: 当前高度固定为 700px(`H = 700`),resize 只更新宽度和 aspect。缩放(wheel)只移动 `camera.position.z`(zoom),不按比例调整数据点大小。 **Fix**: 1. resize handler 中根据容器尺寸动态计算高度:`H = container.clientHeight || window.innerHeight * 0.8` 2. 添加设备像素比处理:`renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))` 3. 弧线粗细使用相对比例(根据 zoom level 调整 `TubeGeometry.radius` 或 `LineBasicMaterial.linewidth`) ### E9 `?data=` 访问拒绝支持 **File**: `analysis/views.py` (globe_view), `templates/tianxuan/globe.html` **Root Cause**: globe_view 只识别 `?runs=1&runs=2` 格式,没有 `?data=` 参数的处理逻辑。 **Fix**: 1. `globe_view` 添加 `?data=dataset_id` 参数支持 2. 当提供 `data=` 时,直接从 `SessionStore` 按 dataset_id 读取数据 3. Globe 模板更新 JavaScript 以支持 dataset 模式 --- ## Workstream F: LLM日志 (Issue 12) ### F12 LLM运行过程打印到前端 **Files**: `tianxuan/llm_orchestrator.py`, `analysis/views.py`, `templates/tianxuan/auto.html` **Root Cause**: LLM 在 thread 中运行,`logger.info` 写入日志文件,但前端 `auto.html` 通过轮询 `/logs/?pos=N` 显示日志。问题是日志缓冲可能延迟写入。 **Fix**: 1. 添加 `llm_status_api` 视图返回当前 LLM 运行状态 2. `llm_orchestrator` 使用回调函数报告进度(步骤/工具调用/中间结果) 3. `_run_llm_analysis` worker 写入 `AnalysisRun.run_log` 字段 --- ## Workstream G: 聚类可视化修复 (Issue 13) ### G13 聚类图表完善 **Files**: `templates/analysis/cluster_overview.html`, `analysis/views.py` **Root Cause**: 当前只有 PCA 散点图 + 地理散点图(两个 Canvas)。用户要求更丰富的图表。 **Fix**: 1. 添加簇大小饼图/柱状图(Canvas) 2. 添加 Silhouette 得分的簇间对比图 3. 添加特征重要性水平条形图(top 10 features per cluster) 4. 确保 PCA 坐标在 web 分析流程中正确保存(当前 `_handle_extract_features` 在 tool_registry.py 中保存,但异常被 `[:200]` 截断 → 先修 C4) --- ## Workstream H: 聚类质量改进 (Issue 17) ### H17 预处理降维+特征工程后聚类 **Files**: `analysis/tool_registry.py`, `analysis/entity_aggregator.py` **Root Cause**: 当前聚类直接对原始聚合特征(flow_count, total_bytes, unique_dst_ips 等)做 StandardScaler + HDBSCAN。没有特征选择、PCA/UMAP 预处理。 **Fix**: 1. 在 `_cluster_sync` 中添加可选的 PCA 预处理步骤 2. 添加方差过滤:去除方差为 0 或接近 0 的特征 3. 添加相关性过滤:去除相关性 >0.95 的特征对中的冗余特征 4. HDBSCAN 超参数根据数据规模自动调整 --- ## Dependencies - C4 (traceback fix) is prerequisite for G13 (PCA fix won't be debuggable without full traceback) - B18 (column mapping) is prerequisite for B7 (0ver hex detection needs 0ver in column map) and G17 (proper features need proper column detection) - D5 (optimization) is partially prerequisite for D14 (memory handling both affect clustering path) ## Must-Not-Have - 不修改 `runtime/` 目录下的任何文件 - 不删除或重命名现有数据库表 - 不添加新的 Python 包依赖(除非绝对必要且用户批准) - 不修改 Polars 版本锁定