// whois.js - 域名信息查询页面功能 // 全局变量 let whoisChart = null; let currentDomain = ''; let isLoading = false; let currentPageSize = 10; // 当前每页显示数量 // IP 地理位置缓存(与 logs.js 共享) if (typeof ipGeolocationCache === 'undefined') { var ipGeolocationCache = {}; var ipGeolocationCacheOrder = []; var GEOLOCATION_CACHE_MAX_SIZE = 1000; var GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; } // LRU 缓存辅助函数:更新缓存顺序,将指定 IP 移到最后(最近使用) function updateCacheOrder(ip) { // 确保变量存在 if (typeof ipGeolocationCacheOrder === 'undefined') { window.ipGeolocationCacheOrder = []; } const index = ipGeolocationCacheOrder.indexOf(ip); if (index > -1) { // 已存在,移除旧位置 ipGeolocationCacheOrder.splice(index, 1); } // 添加到末尾(最近使用) ipGeolocationCacheOrder.push(ip); } // LRU 缓存辅助函数:淘汰最少使用的缓存项 function evictLRUCache() { // 确保变量存在 if (typeof ipGeolocationCacheOrder === 'undefined' || typeof GEOLOCATION_CACHE_MAX_SIZE === 'undefined') { return; // 变量未初始化,直接返回 } while (ipGeolocationCacheOrder.length >= GEOLOCATION_CACHE_MAX_SIZE) { // 淘汰最久未使用的(数组第一个) const oldestIP = ipGeolocationCacheOrder.shift(); if (oldestIP) { delete ipGeolocationCache[oldestIP]; } } } // LRU 缓存辅助函数:清理过期缓存 function cleanupExpiredCache() { // 简化处理:缓存直接存储 API 返回的数据对象,不检查过期时间 // 仅通过 LRU 机制在 evictLRUCache 中控制缓存大小 } // 模拟的 ICP 备案信息(作为备用) function generateMockICPInfo(domain) { const mockICP = [ { company: '华为软件技术有限公司', license: '苏 ICP 备 17040376 号 -24', type: '企业', date: '2025-04-22 09:32:23' }, { company: '腾讯科技(深圳)有限公司', license: '粤 ICP 备 10045678 号 -12', type: '企业', date: '2024-12-15 14:20:10' }, { company: '阿里巴巴云计算(北京)有限公司', license: '京 ICP 备 15023456 号 -8', type: '企业', date: '2025-01-08 11:45:30' }, { company: '百度在线网络技术有限公司', license: '京 ICP 备 12034567 号 -5', type: '企业', date: '2024-11-20 16:15:45' } ]; // 根据域名选择一个 ICP 信息 const index = domain.length % mockICP.length; return mockICP[index]; } // 模拟的 DNS 解析结果(作为备用) function generateMockDNSResults(domain) { const locations = ['中国 - 江苏省', '中国 - 广东省', '中国 - 浙江省', '中国 - 上海市', '中国 - 北京市']; const carriers = ['中国电信', '中国联通', '中国移动', '中国教育网', '中国科技网']; const results = []; const count = 16 + Math.floor(Math.random() * 10); for (let i = 0; i < count; i++) { const ip = `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`; const location = locations[Math.floor(Math.random() * locations.length)]; const carrier = carriers[Math.floor(Math.random() * carriers.length)]; results.push({ ip: ip, location: location, carrier: carrier }); } return results; } // 模拟的 WHOIS 信息(作为备用) function generateMockWhoisInfo(domain) { return { registrant: '该域名已开通隐私保护', created: '2020-01-01 00:00:00', email: '该域名已开通隐私保护', expires: '2027-01-01 00:00:00', registrar: '未知注册商', nameservers: 'ns1.example.com, ns2.example.com' }; } // 模拟的访问趋势数据 function generateMockTrendData() { const labels = []; const totalData = []; const threatData = []; const blockData = []; // 生成最近 30 天的数据 const now = new Date(); for (let i = 29; i >= 0; i--) { const date = new Date(now); date.setDate(date.getDate() - i); labels.push(`${date.getMonth() + 1}-${date.getDate()}`); // 生成随机数据 const total = Math.floor(Math.random() * 100000) + 50000; const threat = Math.floor(Math.random() * 1000); const block = Math.floor(Math.random() * 5000); totalData.push(total); threatData.push(threat); blockData.push(block); } return { labels: labels, totalData: totalData, threatData: threatData, blockData: blockData }; } // 显示加载状态 function showLoading() { isLoading = true; const searchBtn = document.getElementById('whois-search-btn'); if (searchBtn) { searchBtn.disabled = true; searchBtn.innerHTML = '查询中...'; } // 显示加载提示 const loadingDiv = document.getElementById('whois-loading'); if (loadingDiv) { loadingDiv.classList.remove('hidden'); } } // 隐藏加载状态 function hideLoading() { isLoading = false; const searchBtn = document.getElementById('whois-search-btn'); if (searchBtn) { searchBtn.disabled = false; searchBtn.innerHTML = '查询'; } // 隐藏加载提示 const loadingDiv = document.getElementById('whois-loading'); if (loadingDiv) { loadingDiv.classList.add('hidden'); } } // 调用 WHOIS API async function fetchWhoisInfo(domain) { try { const response = await fetch(`https://uapis.cn/api/v1/network/whois?format=json&domain=${encodeURIComponent(domain)}`, { method: 'GET', headers: { 'Authorization': 'Bearer uapi-pnhxbxhkCYUhyS-3r45avJLjTB-qEB8HaNMDzmrT' }, timeout: 10000 // 10秒超时 }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error('获取 WHOIS 信息失败:', error); // 返回 null 而不是抛出错误,让上层处理 return null; } } // 调用 ICP 备案信息 API async function fetchICPInfo(domain) { try { const response = await fetch(`https://uapis.cn/api/v1/network/icp?domain=${encodeURIComponent(domain)}&format=json`, { method: 'GET', headers: { 'Authorization': 'Bearer uapi-pnhxbxhkCYUhyS-3r45avJLjTB-qEB8HaNMDzmrT' }, timeout: 10000 // 10秒超时 }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error('获取 ICP 备案信息失败:', error); // 返回 null 而不是抛出错误,让上层处理 return null; } } // 调用 IP 地理位置 API async function fetchIPInfo(ip) { try { // 确保缓存变量已初始化 if (typeof ipGeolocationCache === 'undefined') { console.log('缓存变量未初始化,正在初始化...'); window.ipGeolocationCache = {}; window.ipGeolocationCacheOrder = []; window.GEOLOCATION_CACHE_MAX_SIZE = 1000; window.GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; } console.log('缓存变量状态:', { cache: typeof ipGeolocationCache, order: typeof ipGeolocationCacheOrder, orderValue: ipGeolocationCacheOrder }); // 清理过期缓存 cleanupExpiredCache(); // 检查缓存(缓存直接存储数据对象) if (ipGeolocationCache[ip]) { console.log('缓存命中:', ip); updateCacheOrder(ip); // 直接返回缓存的数据对象(扁平结构) return ipGeolocationCache[ip]; } console.log('缓存未命中,请求 API:', ip); const response = await fetch(`https://uapis.cn/api/v1/network/ipinfo?ip=${encodeURIComponent(ip)}&source=commercial`, { method: 'GET', headers: { 'Authorization': 'Bearer uapi-pnhxbxhkCYUhyS-3r45avJLjTB-qEB8HaNMDzmrT' }, timeout: 5000 // 5 秒超时 }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('API 返回数据:', data); // 存储到缓存(直接存储数据对象,不嵌套) ipGeolocationCache[ip] = data; console.log('存储缓存前 order:', ipGeolocationCacheOrder); updateCacheOrder(ip); console.log('存储缓存后 order:', ipGeolocationCacheOrder); evictLRUCache(); return data; } catch (error) { console.error(`获取 IP 地理位置信息失败 (${ip}):`, error); // 返回默认值(扁平结构) return { region: '未知', isp: '未知' }; } } // 获取最近 7 天的 DNS 解析记录 async function fetchDNSLogs(domain) { try { // 构建请求 URL const endpoint = `/api/logs/query?search=${encodeURIComponent(domain)}&limit=1000`; console.log('请求 DNS 日志:', endpoint); // 发送请求 const response = await fetch(endpoint, { timeout: 10000 // 10 秒超时 }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const logs = await response.json(); console.log('获取到 DNS 日志:', logs.length, '条'); // 过滤出包含 IP 地址的记录 const ipRecords = []; const seenIPs = new Set(); // 遍历日志记录 for (const log of logs) { // 检查是否包含 answers 字段 if (log.answers && Array.isArray(log.answers)) { // 遍历 answers 数组 for (const answer of log.answers) { // 检查 answer 是否有 value 字段 if (answer.value) { // 提取 IP 地址 const ipMatches = answer.value.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g); if (ipMatches) { console.log(`从 answer.value 提取到 IP:`, ipMatches); for (const ip of ipMatches) { if (!seenIPs.has(ip)) { seenIPs.add(ip); ipRecords.push({ ip: ip, timestamp: log.timestamp }); } } } } } } } console.log('最终提取到 IP 记录:', ipRecords.length, '个'); return ipRecords; } catch (error) { console.error('获取 DNS 解析记录失败:', error); return []; } } // 解析字符串格式的 WHOIS 信息 function parseWhoisString(whoisString) { const result = { registrant: '-', created: '-', email: '-', expires: '-', registrar: '-', nameservers: '-' }; if (!whoisString) { return result; } const lines = whoisString.split('\n'); for (const line of lines) { const trimmedLine = line.trim(); // 注册者 if (trimmedLine.startsWith('Registrant: ')) { const value = trimmedLine.substring('Registrant: '.length).trim(); if (value) { result.registrant = value; } } // 注册邮箱 else if (trimmedLine.startsWith('Registrant Contact Email: ')) { const value = trimmedLine.substring('Registrant Contact Email: '.length).trim(); if (value) { result.email = value; } } // 注册时间(多种格式) else if (trimmedLine.startsWith('Registration Time: ') || trimmedLine.startsWith('Creation Date: ') || trimmedLine.startsWith('Created Date: ')) { let prefix = ''; if (trimmedLine.startsWith('Registration Time: ')) { prefix = 'Registration Time: '; } else if (trimmedLine.startsWith('Creation Date: ')) { prefix = 'Creation Date: '; } else if (trimmedLine.startsWith('Created Date: ')) { prefix = 'Created Date: '; } let value = trimmedLine.substring(prefix.length).trim(); if (value) { // 处理 ISO 格式的时间 (YYYY-MM-DDTHH:MM:SSZ 或 YYYY-MM-DDTHH:MM:SS+0000) if (value.includes('T')) { value = value.replace('T', ' '); if (value.includes('Z')) { value = value.replace('Z', ''); } else if (value.includes('+')) { value = value.split('+')[0]; } } result.created = value; } } // 过期时间(多种格式) else if (trimmedLine.startsWith('Expiration Time: ') || trimmedLine.startsWith('Registry Expiry Date: ') || trimmedLine.startsWith('Registrar Registration Expiration Date: ') || trimmedLine.startsWith('Expiry Date: ')) { let prefix = ''; if (trimmedLine.startsWith('Expiration Time: ')) { prefix = 'Expiration Time: '; } else if (trimmedLine.startsWith('Registry Expiry Date: ')) { prefix = 'Registry Expiry Date: '; } else if (trimmedLine.startsWith('Registrar Registration Expiration Date: ')) { prefix = 'Registrar Registration Expiration Date: '; } else if (trimmedLine.startsWith('Expiry Date: ')) { prefix = 'Expiry Date: '; } let value = trimmedLine.substring(prefix.length).trim(); if (value) { // 处理 ISO 格式的时间 (YYYY-MM-DDTHH:MM:SSZ 或 YYYY-MM-DDTHH:MM:SS+0000) if (value.includes('T')) { value = value.replace('T', ' '); if (value.includes('Z')) { value = value.replace('Z', ''); } else if (value.includes('+')) { value = value.split('+')[0]; } } result.expires = value; } } // 域名服务商 else if (trimmedLine.startsWith('Sponsoring Registrar: ')) { const value = trimmedLine.substring('Sponsoring Registrar: '.length).trim(); if (value) { result.registrar = value; } } // 域名服务器 else if (trimmedLine.startsWith('Name Server: ')) { const value = trimmedLine.substring('Name Server: '.length).trim(); if (value) { if (result.nameservers === '-') { result.nameservers = value; } else { result.nameservers += ', ' + value; } } } } return result; } // 格式化 WHOIS 信息 function formatWhoisInfo(whoisData) { const result = { registrant: '-', created: '-', email: '-', expires: '-', registrar: '-', nameservers: '-' }; // 处理 API 返回字符串的情况 if (!whoisData) { return result; } // 如果 whois 字段是字符串,说明返回的是文本格式的 WHOIS 数据 if (typeof whoisData.whois === 'string') { return parseWhoisString(whoisData.whois); } if (!whoisData.whois) { return result; } const whois = whoisData.whois; const domain = whois.domain || {}; const registrar = whois.registrar || {}; const registrant = whois.registrant || {}; // 处理注册者信息(优先使用 registrant,没有则显示域名服务商) if (registrant.name) { if (registrant.name.includes('REDACTED FOR PRIVACY') || registrant.name === 'REDACTED FOR PRIVACY') { result.registrant = '该域名已开通隐私保护'; } else { result.registrant = registrant.name; } } else if (registrant.organization) { if (registrant.organization.includes('REDACTED FOR PRIVACY') || registrant.organization === 'REDACTED FOR PRIVACY') { result.registrant = '该域名已开通隐私保护'; } else { result.registrant = registrant.organization; } } else { // 如果没有注册者信息,默认显示隐私保护 result.registrant = '该域名已开通隐私保护'; } // 处理注册时间 if (domain.created_date_in_time) { result.created = domain.created_date_in_time.replace('T', ' ').replace('Z', ''); } else if (domain.created_date) { result.created = domain.created_date.replace('T', ' ').replace('Z', ''); } // 处理注册邮箱(优先使用 registrant 的邮箱,没有则使用 registrar 的邮箱) if (registrant.email) { if (registrant.email.includes('REDACTED FOR PRIVACY') || registrant.email === 'REDACTED FOR PRIVACY') { result.email = '该域名已开通隐私保护'; } else { result.email = registrant.email; } } else if (registrar.email) { result.email = registrar.email; } // 处理过期时间 if (domain.expiration_date_in_time) { result.expires = domain.expiration_date_in_time.replace('T', ' ').replace('Z', ''); } else if (domain.expiration_date) { result.expires = domain.expiration_date.replace('T', ' ').replace('Z', ''); } // 处理域名服务商 if (registrar.name) { result.registrar = registrar.name; } // 处理域名服务器 if (domain.name_servers && Array.isArray(domain.name_servers)) { result.nameservers = domain.name_servers.join(', '); } else if (domain.name_servers && typeof domain.name_servers === 'string') { result.nameservers = domain.name_servers; } return result; } // 格式化 ICP 备案信息 function formatICPInfo(icpData) { const result = { company: '-', license: '-', type: '-', date: '-' }; if (!icpData || icpData.code !== '200') { return result; } if (icpData.unitName) { result.company = icpData.unitName; } if (icpData.serviceLicence) { result.license = icpData.serviceLicence; } if (icpData.natureName) { result.type = icpData.natureName; } // API 没有返回审核时间,使用当前时间作为默认值 const now = new Date(); result.date = now.toISOString().slice(0, 19).replace('T', ' '); return result; } // 更新 WHOIS 信息显示 function updateWhoisInfo(info) { document.getElementById('whois-registrant').textContent = info.registrant || '-'; document.getElementById('whois-created').textContent = info.created || '-'; document.getElementById('whois-email').textContent = info.email || '-'; document.getElementById('whois-expires').textContent = info.expires || '-'; document.getElementById('whois-registrar').textContent = info.registrar || '-'; document.getElementById('whois-nameservers').textContent = info.nameservers || '-'; } // 更新 ICP 备案信息 function updateICPInfo(icpInfo) { document.getElementById('icp-company').textContent = icpInfo.company || '-'; document.getElementById('icp-license').textContent = icpInfo.license || '-'; document.getElementById('icp-type').textContent = icpInfo.type || '-'; document.getElementById('icp-date').textContent = icpInfo.date || '-'; } // 更新 DNS 解析结果表格 function updateDNSResults(results, page = 1, pageSize = 10) { const tbody = document.getElementById('dns-results-body'); if (!tbody) return; // 如果没有数据,显示一行"-" if (!results || results.length === 0) { tbody.innerHTML = ` - `; document.getElementById('dns-results-total').textContent = '共计 0 条'; // 清空分页 const pagination = document.getElementById('dns-results-pagination'); if (pagination) { pagination.innerHTML = ''; } return; } const totalPages = Math.ceil(results.length / pageSize); const start = (page - 1) * pageSize; const end = start + pageSize; const pageResults = results.slice(start, end); tbody.innerHTML = ''; pageResults.forEach(result => { const tr = document.createElement('tr'); tr.innerHTML = ` ${result.ip} ${result.location} ${result.carrier} `; tbody.appendChild(tr); }); // 更新总数 document.getElementById('dns-results-total').textContent = `共计 ${results.length} 条`; // 更新分页 updateDNSPagination(results, page, pageSize); } // 更新 DNS 解析结果分页 function updateDNSPagination(results, currentPage, pageSize) { const pagination = document.getElementById('dns-results-pagination'); if (!pagination) return; const totalPages = Math.ceil(results.length / pageSize); pagination.innerHTML = ''; // 创建分页容器(使用 flex 布局) const container = document.createElement('div'); container.className = 'flex items-center space-x-2 flex-wrap gap-2'; // 每页数量筛选器 const pageSizeLabel = document.createElement('span'); pageSizeLabel.className = 'text-sm text-gray-600 dark:text-gray-400'; pageSizeLabel.textContent = '每页:'; container.appendChild(pageSizeLabel); const pageSizeSelect = document.createElement('select'); pageSizeSelect.className = 'px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-primary'; pageSizeSelect.value = pageSize; [3, 5, 10, 20, 50, 100].forEach(size => { const option = document.createElement('option'); option.value = size; option.textContent = size; pageSizeSelect.appendChild(option); }); pageSizeSelect.onchange = () => { const newSize = parseInt(pageSizeSelect.value); console.log('改变每页数量:', currentPageSize, '->', newSize); console.log('当前结果数量:', results ? results.length : 0); currentPageSize = newSize; updateDNSResults(results, 1, currentPageSize); }; container.appendChild(pageSizeSelect); // 分隔符 const separator = document.createElement('span'); separator.className = 'text-sm text-gray-600 dark:text-gray-400 mx-2'; separator.textContent = '|'; container.appendChild(separator); // 上一页按钮 const prevBtn = document.createElement('button'); prevBtn.className = `px-3 py-1 rounded-md ${currentPage === 1 ? 'text-gray-400 cursor-not-allowed' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}`; prevBtn.textContent = '<'; prevBtn.disabled = currentPage === 1; prevBtn.onclick = () => { if (currentPage > 1) { updateDNSResults(results, currentPage - 1, pageSize); } }; container.appendChild(prevBtn); // 页码按钮(智能显示,避免过多) const maxVisiblePages = 5; // 最多显示的页码数 let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); // 调整起始页,确保始终显示 maxVisiblePages 个页码(如果可能) if (endPage - startPage + 1 < maxVisiblePages) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } // 显示第一页 if (startPage > 1) { const firstBtn = document.createElement('button'); firstBtn.className = `px-3 py-1 rounded-md ${1 === currentPage ? 'bg-primary text-white' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}`; firstBtn.textContent = '1'; firstBtn.onclick = () => { updateDNSResults(results, 1, pageSize); }; container.appendChild(firstBtn); // 显示省略号 if (startPage > 2) { const ellipsis = document.createElement('span'); ellipsis.className = 'text-gray-600 dark:text-gray-400 px-2'; ellipsis.textContent = '...'; container.appendChild(ellipsis); } } // 显示中间的页码 for (let i = startPage; i <= endPage; i++) { const pageBtn = document.createElement('button'); pageBtn.className = `px-3 py-1 rounded-md ${i === currentPage ? 'bg-primary text-white' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}`; pageBtn.textContent = i; pageBtn.onclick = () => { updateDNSResults(results, i, pageSize); }; container.appendChild(pageBtn); } // 显示最后页 if (endPage < totalPages) { // 显示省略号 if (endPage < totalPages - 1) { const ellipsis = document.createElement('span'); ellipsis.className = 'text-gray-600 dark:text-gray-400 px-2'; ellipsis.textContent = '...'; container.appendChild(ellipsis); } const lastBtn = document.createElement('button'); lastBtn.className = `px-3 py-1 rounded-md ${totalPages === currentPage ? 'bg-primary text-white' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}`; lastBtn.textContent = totalPages; lastBtn.onclick = () => { updateDNSResults(results, totalPages, pageSize); }; container.appendChild(lastBtn); } // 下一页按钮 const nextBtn = document.createElement('button'); nextBtn.className = `px-3 py-1 rounded-md ${currentPage === totalPages ? 'text-gray-400 cursor-not-allowed' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}`; nextBtn.textContent = '>'; nextBtn.disabled = currentPage === totalPages; nextBtn.onclick = () => { if (currentPage < totalPages) { updateDNSResults(results, currentPage + 1, pageSize); } }; container.appendChild(nextBtn); // 跳转输入框 const jumpLabel = document.createElement('span'); jumpLabel.className = 'text-sm text-gray-600 dark:text-gray-400 mx-2'; jumpLabel.textContent = '跳转:'; container.appendChild(jumpLabel); const jumpInput = document.createElement('input'); jumpInput.type = 'number'; jumpInput.min = '1'; jumpInput.max = totalPages; jumpInput.className = 'w-16 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-primary'; jumpInput.value = currentPage; jumpInput.onkeypress = (e) => { if (e.key === 'Enter') { const pageNum = parseInt(jumpInput.value); if (pageNum >= 1 && pageNum <= totalPages) { updateDNSResults(results, pageNum, pageSize); } else { jumpInput.value = currentPage; } } }; container.appendChild(jumpInput); const jumpBtn = document.createElement('button'); jumpBtn.className = 'px-3 py-1 text-sm bg-primary text-white rounded-md hover:bg-primary/90'; jumpBtn.textContent = '确定'; jumpBtn.onclick = () => { const pageNum = parseInt(jumpInput.value); if (pageNum >= 1 && pageNum <= totalPages) { updateDNSResults(results, pageNum, pageSize); } else { jumpInput.value = currentPage; } }; container.appendChild(jumpBtn); pagination.appendChild(container); } // 更新访问趋势图表 function updateTrendChart(domain) { const ctx = document.getElementById('whois-trend-chart'); if (!ctx) return; const mockData = generateMockTrendData(); if (whoisChart) { whoisChart.destroy(); } whoisChart = new Chart(ctx, { type: 'line', data: { labels: mockData.labels, datasets: [ { label: '全部 DNS 请求', data: mockData.totalData, borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.4, fill: true }, { label: '威胁告警', data: mockData.threatData, borderColor: '#f59e0b', backgroundColor: 'rgba(245, 158, 11, 0.1)', tension: 0.4, fill: false }, { label: '拦截', data: mockData.blockData, borderColor: '#ef4444', backgroundColor: 'rgba(239, 68, 68, 0.1)', tension: 0.4, fill: false } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top' }, tooltip: { mode: 'index', intersect: false } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } } }); } // 显示错误信息 function showWhoisError(message) { const errorDiv = document.getElementById('whois-error'); const errorSpan = errorDiv.querySelector('span'); errorSpan.textContent = message; errorDiv.classList.remove('hidden'); setTimeout(() => { errorDiv.classList.add('hidden'); }, 5000); } // 提取主域名(去除所有子域名) function extractMainDomain(domain) { if (!domain) return domain; const parts = domain.split('.'); // 如果只有两部分(如 example.com),直接返回 if (parts.length <= 2) { return domain; } // 常见的子域前缀(更多类型) const commonSubdomains = [ // 常见子域 'www', 'mail', 'news', 'map', 'image', 'video', 'cdn', 'api', 'blog', 'shop', 'cloud', 'docs', 'help', 'support', 'dev', 'test', 'staging', 'm', 'mobile', 'admin', 'static', 'img', 'assets', 'web', 'app', 'store', 'service', // 日志和监控相关 'log', 'logs', 'event', 'events', 'eventlog', 'beacon', 'beacons', 'tracking', 'track', 'analytics', 'stat', 'stats', 'statistics', 'metric', 'metrics', 'monitor', 'monitoring', 'sentry', 'ingest', 'collector', 'collect', // 服务和功能相关 'auth', 'login', 'sso', 'oauth', 'account', 'user', 'users', 'profile', 'payment', 'pay', 'billing', 'order', 'orders', 'cart', 'checkout', 'search', 'find', 'discovery', 'recommend', 'push', 'notification', // 内容相关 'content', 'file', 'files', 'download', 'upload', 'resource', 'resources', 'data', 'db', 'database', 'cache', 'redis', 'memcache', 'storage', // 网络和基础设施 'proxy', 'gateway', 'router', 'switch', 'lb', 'nlb', 'alb', 'elb', 'edge', 'node', 'server', 'host', 'vm', 'container', 'k8s', 'kubernetes', // 开发相关 'git', 'gitlab', 'github', 'jenkins', 'ci', 'cd', 'build', 'release', 'npm', 'yarn', 'pkg', 'package', 'registry', 'repo', 'repository', // 其他常见子域 'beta', 'alpha', 'preview', 'demo', 'sandbox', 'uat', 'prod', 'production', 'internal', 'external', 'public', 'private', 'secure', 'ssl', 'tls', 'old', 'new', 'v1', 'v2', 'v3', 'api1', 'api2', 'api3' ]; // 处理特殊情况,如 co.uk, co.jp 等(复合顶级域) const compoundTLDs = [ 'co.uk', 'org.uk', 'me.uk', 'ltd.uk', 'plc.uk', 'net.uk', 'co.jp', 'ne.jp', 'or.jp', 'ac.jp', 'go.jp', 'ad.jp', 'ed.jp', 'gr.jp', 'lg.jp', 'co.kr', 'ne.kr', 'or.kr', 'ac.kr', 'go.kr', 'ms.kr', 'co.in', 'co.in', 'firm.in', 'net.in', 'org.in', 'gen.in', 'ind.in', 'co.ca', 'net.ca', 'org.ca', 'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'asn.au', 'id.au', 'co.nz', 'net.nz', 'org.nz', 'geek.nz', 'maori.nz', 'ac.nz', 'school.nz', 'govt.nz', 'mil.nz', 'co.th', 'ac.th', 'go.th', 'or.th', 'net.th', 'in.th', 'com.sg', 'net.sg', 'org.sg', 'gov.sg', 'edu.sg', 'per.sg', 'com.my', 'net.my', 'org.my', 'gov.my', 'edu.my', 'mil.my', 'name.my', 'co.id', 'ac.id', 'go.id', 'mil.id', 'or.id', 'net.id', 'web.id', 'sch.id', 'my.id', 'co.za', 'net.za', 'org.za', 'web.za', 'school.za', 'gov.za', 'mil.za', 'com.cn', 'net.cn', 'org.cn', 'edu.cn', 'gov.cn', 'mil.cn', 'ac.cn', 'org.cn', 'net.cn', 'firm.cn', 'store.cn', 'arts.cn', 'rec.cn', 'info.cn', 'nom.cn', 'com.hk', 'net.hk', 'org.hk', 'edu.hk', 'gov.hk', 'idv.hk', 'com.tw', 'net.tw', 'org.tw', 'edu.tw', 'gov.tw', 'mil.tw', 'idv.tw', 'game.tw', 'ebiz.tw', 'club.tw', 'com.vn', 'net.vn', 'org.vn', 'edu.vn', 'gov.vn', 'int.vn', 'ac.vn', 'biz.vn', 'info.vn', 'name.vn', 'pro.vn', 'health.vn' ]; // 检查是否是复合顶级域 for (const compoundTLD of compoundTLDs) { if (domain.endsWith('.' + compoundTLD)) { const tldParts = compoundTLD.split('.'); // 保留主域名 + 复合顶级域(2 部分) const keepParts = tldParts.length + 1; return parts.slice(-keepParts).join('.'); } } // 检查第一部分是否是常见子域 const firstPart = parts[0].toLowerCase(); if (commonSubdomains.includes(firstPart)) { // 去掉第一个子域,递归处理 return extractMainDomain(parts.slice(1).join('.')); } // 检查最后两部分是否是有效的顶级域组合 const tld = parts[parts.length - 1].toLowerCase(); const sld = parts[parts.length - 2].toLowerCase(); const validTLDs = ['com', 'net', 'org', 'io', 'xyz', 'cn', 'info', 'biz', 'me', 'tv', 'cc', 'top', 'vip', 'pro', 'club', 'site', 'online', 'store', 'tech', 'app', 'dev', 'ai', 'cloud', 'live', 'world', 'today', 'news', 'blog', 'shop', 'life', 'love', 'work', 'design', 'art', 'music', 'game', 'games', 'video', 'photo', 'pics', 'image', 'images', 'media', 'space', 'website', 'link', 'click', 'email', 'mail', 'tel', 'phone', 'mobi', 'mobile', 'asia', 'eu', 'us', 'uk', 'de', 'fr', 'jp', 'ru', 'br', 'in', 'au', 'ca', 'it', 'es', 'nl', 'pl', 'se', 'no', 'fi', 'dk', 'ch', 'at', 'be', 'pt', 'gr', 'cz', 'ie', 'nz', 'za', 'mx', 'ar', 'cl', 'co', 'pe', 've', 'sg', 'hk', 'tw', 'kr', 'th', 'my', 'id', 'ph', 'vn', 'ae', 'sa', 'il', 'tr', 'ua', 'ro', 'hu', 'sk', 'si', 'hr', 'rs', 'bg', 'lt', 'lv', 'ee', 'name', 'cat', 'tel', 'travel', 'pro', 'aero', 'coop', 'museum', 'mil', 'int', 'gov', 'edu']; // 如果最后两部分是有效的顶级域组合,保留它们和前面的主域名 if (validTLDs.includes(tld)) { // 检查二级域名是否是常见的商业/组织前缀 const commonSLD = ['co', 'com', 'net', 'org', 'edu', 'gov', 'mil', 'ac', 'ne', 'or', 'go', 'ad', 'gr', 'lg', 'firm', 'store', 'arts', 'rec', 'info', 'nom', 'idv', 'game', 'ebiz', 'club', 'health', 'web', 'my', 'biz']; if (commonSLD.includes(sld) && sld !== tld) { // 这是一个复合结构,如 example.co.uk,保留最后 3 部分 if (parts.length >= 3) { return parts.slice(-3).join('.'); } } // 普通情况,保留最后 2 部分(主域名 + 顶级域) return parts.slice(-2).join('.'); } // 默认情况:如果超过 2 部分,去掉第一部分,递归处理 if (parts.length > 2) { return extractMainDomain(parts.slice(1).join('.')); } return domain; } // 查询域名信息 async function searchDomainInfo() { const domainInput = document.getElementById('whois-domain-input'); const domain = domainInput.value.trim(); if (!domain) { showWhoisError('请输入要查询的域名'); return; } // 显示加载状态 showLoading(); // 自动裁剪顶级域,提取主域名 const mainDomain = extractMainDomain(domain); currentDomain = mainDomain; console.log('查询域名:', mainDomain); // 隐藏所有信息区域 document.getElementById('whois-info-section').classList.add('hidden'); document.getElementById('icp-info-section').classList.add('hidden'); document.getElementById('dns-results-section').classList.add('hidden'); document.getElementById('trend-section').classList.add('hidden'); try { // 并行获取 WHOIS 和 ICP 信息,以及 DNS 日志 const [whoisData, icpData, dnsLogs] = await Promise.all([ fetchWhoisInfo(mainDomain), fetchICPInfo(mainDomain), fetchDNSLogs(mainDomain) ]); console.log('API 返回数据:', { whoisData, icpData, dnsLogs }); // 处理 WHOIS 信息 let whoisInfo; if (!whoisData || !whoisData.whois) { console.log('未找到 WHOIS 信息,显示为 -'); // 没有数据时显示为 - whoisInfo = { registrant: '-', created: '-', email: '-', expires: '-', registrar: '-', nameservers: '-' }; } else { whoisInfo = formatWhoisInfo(whoisData); console.log('格式化后的 WHOIS 信息:', whoisInfo); } // 处理 ICP 备案信息 let icpInfo; if (!icpData || icpData.code !== '200') { console.log('未找到 ICP 备案信息,显示为 -'); // 没有数据时显示为 - icpInfo = { company: '-', license: '-', type: '-', date: '-' }; } else { icpInfo = formatICPInfo(icpData); console.log('格式化后的 ICP 信息:', icpInfo); } // 如果 WHOIS 信息中没有注册者信息,使用 ICP 的主办单位名称替代 if (whoisInfo.registrant === '该域名已开通隐私保护' || whoisInfo.registrant === '-') { if (icpInfo.company && icpInfo.company !== '-') { whoisInfo.registrant = icpInfo.company; console.log('使用 ICP 主办单位名称作为注册者信息:', icpInfo.company); } } // 处理 DNS 解析结果 let dnsResults; if (dnsLogs && dnsLogs.length > 0) { console.log('找到 DNS 解析记录:', dnsLogs.length, '条'); // 获取每个 IP 的地理位置信息 const ipInfoPromises = dnsLogs.map(async (record) => { const ipInfo = await fetchIPInfo(record.ip); console.log(`IP ${record.ip} 的地理位置信息:`, ipInfo); let location = '-'; let carrier = '-'; // 检查 API 返回的数据 if (ipInfo && ipInfo.region) { // 直接使用 ipInfo 作为数据对象(API 直接返回数据) // 使用 API 返回的 region 字段作为地理位置 location = ipInfo.region; // 使用 API 返回的 isp 字段作为运营商 if (ipInfo.isp) { carrier = ipInfo.isp; } } else { console.log(`IP ${record.ip} 没有 region 字段,ipInfo:`, ipInfo); } return { ip: record.ip, location: location, carrier: carrier }; }); dnsResults = await Promise.all(ipInfoPromises); console.log('处理后的 DNS 解析结果:', dnsResults); } else { // 没有查询到 DNS 解析记录,显示空数组 dnsResults = []; console.log('未找到 DNS 解析记录'); } // 更新显示 updateWhoisInfo(whoisInfo); document.getElementById('whois-info-section').classList.remove('hidden'); updateICPInfo(icpInfo); document.getElementById('icp-info-section').classList.remove('hidden'); // 显示 DNS 解析结果 updateDNSResults(dnsResults, 1, currentPageSize); document.getElementById('dns-results-section').classList.remove('hidden'); // 生成并显示访问趋势图表(模拟数据) updateTrendChart(mainDomain); document.getElementById('trend-section').classList.remove('hidden'); } catch (error) { console.error('查询域名信息失败:', error); showWhoisError('查询失败,请稍后重试'); // 使用模拟数据作为备用 const whoisInfo = generateMockWhoisInfo(mainDomain); const icpInfo = generateMockICPInfo(mainDomain); const dnsResults = generateMockDNSResults(mainDomain); updateWhoisInfo(whoisInfo); document.getElementById('whois-info-section').classList.remove('hidden'); updateICPInfo(icpInfo); document.getElementById('icp-info-section').classList.remove('hidden'); updateDNSResults(dnsResults, 1, currentPageSize); document.getElementById('dns-results-section').classList.remove('hidden'); updateTrendChart(mainDomain); document.getElementById('trend-section').classList.remove('hidden'); } finally { // 隐藏加载状态 hideLoading(); } } // 初始化域名信息查询页面 function initWhoisPage() { // 绑定搜索按钮事件 const searchBtn = document.getElementById('whois-search-btn'); if (searchBtn) { searchBtn.addEventListener('click', searchDomainInfo); } // 绑定输入框回车事件 const domainInput = document.getElementById('whois-domain-input'); if (domainInput) { domainInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { searchDomainInfo(); } }); } // 检查是否有域名参数 const urlParams = new URLSearchParams(window.location.search); const domainParam = urlParams.get('domain'); if (domainParam && domainInput) { domainInput.value = domainParam; // 自动查询 setTimeout(searchDomainInfo, 500); } } // 页面加载时自动初始化 document.addEventListener('DOMContentLoaded', () => { // 检查当前 hash 是否为 whois if (window.location.hash === '#whois') { initWhoisPage(); } }); // 监听 hash 变化 window.addEventListener('hashchange', () => { if (window.location.hash === '#whois') { initWhoisPage(); } });