From 9e34f6f0f9b436ebac6164ccd47403de5d569d26 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 18 Jul 2026 10:59:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(ips):=20=E6=89=B9=E9=87=8F=E8=A1=A5?= =?UTF-8?q?=E5=85=A8=E5=8E=82=E5=95=86=E5=8A=9F=E8=83=BD=20+=20MAC?= =?UTF-8?q?=E5=8E=82=E5=95=86=E6=9B=BF=E6=8D=A2=E4=B8=BAIEEE=20OUI?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端: - IPs.vue: 新增「补全厂商」按钮,选中网段后一键补全 - scanApi: 新增 batchFillVendor/batchDiscoverHostname + enable_netbios 参数 后端: - enhanced_scan.py: 新增 POST /scan/batch/vendor 批量补全厂商端点 - enhanced_scan.py: 新增 POST /scan/batch/hostname 批量发现主机名端点 - scan_tasks.py: 透传 enable_netbios 参数 --- backend/app/api/v1/enhanced_scan.py | 76 +++++++++++++++++++++++++++++ frontend/src/api/index.js | 13 ++++- frontend/src/views/IPs.vue | 23 +++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/backend/app/api/v1/enhanced_scan.py b/backend/app/api/v1/enhanced_scan.py index f596692..59307f2 100644 --- a/backend/app/api/v1/enhanced_scan.py +++ b/backend/app/api/v1/enhanced_scan.py @@ -156,6 +156,82 @@ def netbios_lookup(ip_address: str, timeout: int = 3): "hostname": hostname or "Unknown" } +@router.post("/batch/vendor", summary="批量补全厂商信息") +def batch_fill_vendor( + network_id: Optional[int] = None, + db: Session = Depends(get_db) +): + """ + 对已有 MAC 地址但缺少厂商信息的 IP 批量补全厂商(使用 IEEE OUI 数据库) + """ + from app.models.network import IPAddress as IPAddressModel + + query = db.query(IPAddressModel).filter( + IPAddressModel.mac_address.isnot(None), + IPAddressModel.vendor.is_(None) + ) + if network_id: + query = query.filter(IPAddressModel.network_id == network_id) + + items = query.all() + updated_count = 0 + + for ip in items: + vendor = EnhancedScanService.get_mac_vendor(ip.mac_address) + if vendor: + ip.vendor = vendor + updated_count += 1 + + if updated_count > 0: + db.commit() + + return { + "message": f"已补全 {updated_count} 条厂商信息", + "checked": len(items), + "updated": updated_count + } + +@router.post("/batch/hostname", summary="批量发现主机名") +def batch_discover_hostname( + network_id: Optional[int] = None, + enable_dns: bool = True, + enable_netbios: bool = True, + db: Session = Depends(get_db) +): + """ + 对已有 MAC 地址但缺少主机名的 IP 批量执行主机名发现(DNS PTR + NetBIOS) + """ + from app.models.network import IPAddress as IPAddressModel + + query = db.query(IPAddressModel).filter( + IPAddressModel.mac_address.isnot(None), + IPAddressModel.hostname.is_(None) + ) + if network_id: + query = query.filter(IPAddressModel.network_id == network_id) + + items = query.all() + updated_count = 0 + + for ip in items: + hostname = None + if enable_dns: + hostname = EnhancedScanService.reverse_dns_lookup(ip.ip_address, timeout=2) + if not hostname and enable_netbios: + hostname = EnhancedScanService.netbios_lookup(ip.ip_address, timeout=3) + if hostname: + ip.hostname = hostname + updated_count += 1 + + if updated_count > 0: + db.commit() + + return { + "message": f"已发现 {updated_count} 条主机名", + "checked": len(items), + "updated": updated_count + } + @router.get("/online/ips", summary="获取所有在线IP列表") def get_online_ips( diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 97c4944..6ecd2fc 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -63,20 +63,29 @@ export const scanApi = { return api.post(`/scan/ping/${id}`) }, // 综合扫描网段 - // options: { enable_ping, enable_arp, enable_dns, timeout } + // options: { enable_ping, enable_arp, enable_dns, enable_netbios, timeout } // 单次调用覆盖全局 30s 超时,因为 /24 网段扫描要数分钟 comprehensiveScan(id, options = {}) { const { enable_ping = true, enable_arp = true, enable_dns = true, + enable_netbios = true, timeout = 2 } = options return api.post(`/scan/comprehensive/${id}`, {}, { - params: { enable_ping, enable_arp, enable_dns, timeout }, + params: { enable_ping, enable_arp, enable_dns, enable_netbios, timeout }, timeout: 600000 // 10 分钟,足以应付 /16 级别网段 }) }, + // 批量补全厂商信息 + batchFillVendor(params) { + return api.post('/scan/batch/vendor', {}, { params }) + }, + // 批量发现主机名 + batchDiscoverHostname(params) { + return api.post('/scan/batch/hostname', {}, { params }) + }, // 单个IP扫描 scanIP(ipAddress) { return api.post(`/scan/comprehensive/ip/${ipAddress}`) diff --git a/frontend/src/views/IPs.vue b/frontend/src/views/IPs.vue index 38f3a58..c6460dd 100644 --- a/frontend/src/views/IPs.vue +++ b/frontend/src/views/IPs.vue @@ -8,6 +8,10 @@ 批量扫描 + + + 补全厂商 + @@ -176,6 +180,7 @@ const route = useRoute() const loading = ref(false) const submitting = ref(false) const scanning = ref(false) +const vendorLoading = ref(false) const dialogVisible = ref(false) const tableData = ref([]) @@ -278,6 +283,24 @@ const startBatchScan = async () => { } } +const batchFillVendor = async () => { + if (!filters.networkId) { + ElMessage.warning('请先选择网段') + return + } + vendorLoading.value = true + try { + const res = await scanApi.batchFillVendor({ network_id: filters.networkId }) + ElMessage.success(res.message) + loadIPs() + } catch (error) { + ElMessage.error('补全厂商失败') + console.error('补全厂商错误:', error) + } finally { + vendorLoading.value = false + } +} + const editIP = (row) => { ipForm.id = row.id ipForm.ip_address = row.ip_address