无线网络入侵检测系统实战 | 基于React+Python的可视化安全平台开发详解

news2025/4/20 0:41:24

随着无线网络的普及,网络攻击风险也日益严峻。本项目旨在构建一个实时监测、智能识别、高效防护的无线网络安全平台,通过结合前后端技术与安全算法,实现对常见攻击行为的有效监控和防御。


一、项目简介与功能目的

本系统是一款基于 React 前端 + Python 后端 架构开发的无线网络入侵检测系统 V1.0,专注于无线网络安全领域的可视化平台建设。平台集成了 DDoS 防御、异常流量识别、漏洞扫描、日志分析、黑名单管理等核心功能模块,帮助用户从设备层到数据层全面掌握网络安全状态。

🧾 功能目标:

  • 实时监测无线网络活动与异常行为

  • 提供清晰、可交互的数据可视化展示

  • 辅助用户快速定位安全隐患与潜在攻击源

  • 支持日志溯源与安全报告生成

  • 适用于校园网络、企业内网、实验室网络等应用场景


二、背景分析:为什么要做这类系统?

在现代办公和日常生活中,无线网络已成为信息传输的重要载体。然而其开放性与可被感知的特性,也使其暴露在更多攻击风险下:

  • 🕵️ DDoS洪水攻击、ARP欺骗、端口扫描等日益频繁

  • 🔓 企业常规防火墙难以精准应对新型攻击行为

  • ⚠️ 安全日志分析、漏洞感知能力在传统系统中缺失

因此,本项目以“智能监测 + 可视化感知 + 实时响应”为核心目标,力图构建一个可控、可查、可溯源的无线网络防护系统。


三、项目整体架构说明

项目采用 前后端分离 架构,结合现代 Web 技术与网络安全分析框架:

🖥 前端:React + Chart.js + Recharts

  • 构建响应式界面

  • 实时展示网络流量、入侵事件、设备状态等

  • 使用 Socket.IO 与后端通信,实现实时数据同步

🔧 后端:Python + Flask + Scapy

  • 提供 REST API 接口

  • 网络数据包捕获与解析(基于 scapy)

  • 异常流量检测、黑名单管理、漏洞分析

  • 使用机器学习/规则引擎实现行为识别

🗂 数据存储:MySQL

  • 存储用户信息、网络日志、设备数据等

  • 支持高效查询与日志导出

🔐 安全机制:

  • HTTPS 传输加密

  • 登录认证 + 权限分级控制

  • 日志记录与操作审计


四、项目代码结构

📁 前端目录结构(React)

wireless-security-platform/
├── public/                 # 静态资源
├── src/
│   ├── components/         # 功能组件(Dashboard/NetFilter/PDF等)
│   ├── App.jsx             # 主组件
│   └── styles.css          # 全局样式
├── package.json            # 项目依赖
└── README.md

📁 后端目录结构(Python)

backend/
├── app/                    # 主应用代码
│   └── utils/              # 工具函数
├── run.py                 # 启动入口
├── config.py              # 配置文件
├── requirements.txt       # 第三方依赖
├── traffic.json           # 流量分析数据样本
└── wsgi.py                # WSGI 服务入口

五、核心功能模块详解

1️⃣ 数据包嗅探器 & ARP欺骗检测

  • 实时抓包、统计包数量

  • 支持分钟级、秒级数据可视化展示

  • 检测是否存在 ARP 欺骗行为

import React, { useEffect, useRef, useState } from 'react';
import io from 'socket.io-client';
import { Chart, CategoryScale, LinearScale, LineController, LineElement, PointElement } from 'chart.js';
import './NetFilter.css';

// 注册必要的比例尺、控制器和元素
Chart.register(CategoryScale, LinearScale, LineController, LineElement, PointElement);

const NetFilter = () => {
  const [isSniffing, setIsSniffing] = useState(false);
  const [filter, setFilter] = useState('All');
  const logDivRef = useRef(null);
  const cumulativeChartRef = useRef(null);
  const unitTimeChartRef = useRef(null);
  const socket = useRef(null);

  useEffect(() => {
    socket.current = io('http://127.0.0.1:5555', {
      transports: ['websocket', 'polling'],
      pingTimeout: 60000,
      pingInterval: 25000,
      withCredentials: true,
      reconnection: true,
      reconnectionAttempts: 5,
      reconnectionDelay: 3000,
      debug: true,  // 启用调试模式
    });

    socket.current.on('connect', () => {
      console.log('已经与后端建立了连接');  // 确保连接成功
    });

    socket.current.on('connect_error', (error) => {
      console.error('Connection error:', error);  // 检查连接错误
    });

    socket.current.on('disconnect', (reason) => {
      console.log('Disconnected:', reason);
    });

    // 确保事件监听器在连接成功后立即注册
    socket.current.on('packet', (log_entry) => {
        console.log('Received packet log:', log_entry);  // 调试信息
        logDivRef.current.innerHTML += `<p>${log_entry}</p>`;
        logDivRef.current.scrollTop = logDivRef.current.scrollHeight;
      } 
    );
    
    const cumulativeCtx = document.getElementById('cumulativeChart')?.getContext('2d');
    const unitTimeCtx = document.getElementById('unitTimeChart')?.getContext('2d');

    if (!cumulativeCtx || !unitTimeCtx) {
      console.error('Canvas context not found');
      return;
    }

    // 销毁旧图表
    if (cumulativeChartRef.current) {
      cumulativeChartRef.current.destroy();
    }
    if (unitTimeChartRef.current) {
      unitTimeChartRef.current.destroy();
    }

    // 初始化新图表
    cumulativeChartRef.current = new Chart(cumulativeCtx, {
      type: 'line',
      data: {
        labels: [],
        datasets: [{
          label: '累积数据包数量',
          data: [],
          borderColor: '#00d2ff',
          fill: false
        }]
      },
      options: {
        scales: {
          x: {
            type: 'category',
            title: { display: true, text: '时间', color: 'white', font: { size: 16 } },
            ticks: { color: 'white', font: { size: 14 } }
          },
          y: {
            type: 'linear',
            title: { display: true, text: '数据包数量', color: 'white', font: { size: 16 } },
            beginAtZero: true,
            ticks: { color: 'white', font: { size: 14 } }
          }
        },
        plugins: {
          legend: { display: false },
          tooltip: { enabled: true }
        }
      }
    });

    unitTimeChartRef.current = new Chart(unitTimeCtx, {
      type: 'line',
      data: {
        labels: [],
        datasets: [{
          label: '单位时间数据包数量',
          data: [],
          borderColor: '#ff6384',
          fill: false
        }]
      },
      options: {
        scales: {
          x: {
            type: 'category',
            title: { display: true, text: '时间', color: 'white', font: { size: 16 } },
            ticks: { color: 'white', font: { size: 14 } }
          },
          y: {
            type: 'linear',
            title: { display: true, text: '数据包数量', color: 'white', font: { size: 16 } },
            beginAtZero: true,
            ticks: { color: 'white', font: { size: 14 } }
          }
        },
        plugins: {
          legend: { display: false },
          tooltip: { enabled: true }
        }
      }
    });

    socket.current.on('arp_alert', (alert) => {
      alert(alert);
    });

    socket.current.on('sniffing_status', (data) => {
      console.log('Sniffing status:', data);
      setIsSniffing(data.status === 'started');
      updateGuiLog(data.status === 'started' ? "[+] 抓包已开始" : "[!] 抓包已停止");
    });

    socket.current.on('packet_stats', (stats) => {
      if (cumulativeChartRef.current && unitTimeChartRef.current) {
        cumulativeChartRef.current.data.labels = stats.labels;
        cumulativeChartRef.current.data.datasets[0].data = stats.cumulative;
        cumulativeChartRef.current.update();

        unitTimeChartRef.current.data.labels = stats.labels;
        unitTimeChartRef.current.data.datasets[0].data = stats.unit_time;
        unitTimeChartRef.current.update();
      }
    });

    return () => {
      // 组件卸载时销毁图表并断开连接
      if (cumulativeChartRef.current) {
        cumulativeChartRef.current.destroy();
      }
      if (unitTimeChartRef.current) {
        unitTimeChartRef.current.destroy();
      }
      if (socket.current) {
        socket.current.disconnect();
      }
      // 组件卸载时移除事件监听器
      if (socket.current) {
        socket.current.off('packet_log');
      }
    };
  }, []);

  const handleStart = () => {
    if (socket.current) {
      console.log('正在开始对:', filter,'数据包信息进行记录');  // 调试日志
      socket.current.emit('start_sniffing', { filter_option: filter }, (response) => {
          console.log('已向发送')
        if (response) {
          console.log('后端的响应为:', response);  // 后端响应日志
        } else {
          console.error('后端无任何响应');  // 无响应日志
        }
      });
      setIsSniffing(true);  // 更新 isSniffing 为 true
    } else {
      console.error('Socket is not connected');
    }
  };

  const handleStop = () => {
    if (socket.current) {
      console.log('已经停止监控');  // 调试日志
      socket.current.emit('stop_sniffing');
      setIsSniffing(false);  // 更新 isSniffing 为 false
    } else {
      console.error('Socket is not connected');
    }
  };

  const updateGuiLog = (message) => {
    if (logDivRef.current) {
      logDivRef.current.innerHTML += `<p>${message}</p>`;
      logDivRef.current.scrollTop = logDivRef.current.scrollHeight;
    }
  };

  const handleFilterChange = (e) => {
    setFilter(e.target.value);
    if (isSniffing) {
      socket.current.emit('update_filter', e.target.value);
      updateGuiLog(`[+] 过滤器已更新为: ${e.target.value}`);
    }
  };

  const handleIntervalChange = (interval) => {
    socket.current.emit('set_interval', interval);
  };

  const handleFullscreen = (chartType) => {
    const chartContainer = chartType === 'cumulative' 
      ? cumulativeChartRef.current.canvas.parentElement
      : unitTimeChartRef.current.canvas.parentElement;
    chartContainer.classList.add('fullscreen');
    addExitButton(chartContainer);
  };

  const addExitButton = (container) => {
    const exitButton = document.createElement('button');
    exitButton.className = 'exit-fullscreen';
    exitButton.innerText = '退出全屏';
    exitButton.addEventListener('click', () => {
      container.classList.remove('fullscreen');
      exitButton.remove();
    });
    container.appendChild(exitButton);
  };

  return (
    <div className="net-filter-container">
      <h1>数据包嗅探器 & ARP欺骗检测</h1>
      <div className="controls">
        <select id="filter" value={filter} onChange={handleFilterChange}>
          <option value="All">全部</option>
          <option value="ARP">ARP</option>
          <option value="DNS">DNS</option>
          <option value="TCP">TCP</option>
          <option value="UDP">UDP</option>
          <option value="HTTP">HTTP</option>
        </select>
        <button id="start" onClick={handleStart} disabled={isSniffing}>开始抓包</button>
        <button id="stop" onClick={handleStop} disabled={!isSniffing}>停止抓包</button>
      </div>
      <div className={`status-indicator ${isSniffing ? 'active' : ''}`}>
        <div className="dot"></div>
        <span id="status-text">{isSniffing ? '抓包中...' : '抓包已停止'}</span>
      </div>
      <div id="log" ref={logDivRef}></div>
      <div className="chart-container">
        <div className="chart-box">
          <h2>累积数据包数量</h2>
          <div className="chart-controls">
            <button onClick={() => handleIntervalChange(60)}>每分钟</button>
            <button onClick={() => handleIntervalChange(300)}>每5分钟</button>
            <button onClick={() => handleFullscreen('cumulative')}>全屏显示</button>
          </div>
          <canvas id="cumulativeChart" key="cumulativeChart"></canvas>
        </div>
        <div className="chart-box">
          <h2>单位时间数据包数量</h2>
          <div className="chart-controls">
            <button onClick={() => handleIntervalChange(60)}>每分钟</button>
            <button onClick={() => handleIntervalChange(300)}>每5分钟</button>
            <button onClick={() => handleFullscreen('unitTime')}>全屏显示</button>
          </div>
          <canvas id="unitTimeChart" key="unitTimeChart"></canvas>
        </div>
      </div>
    </div>
  );
};

export default NetFilter; 

2️⃣ 漏洞扫描模块

  • 一键启动漏洞扫描流程

  • 分类展示高风险/处理中/已修复漏洞

  • 提供 CVE 编号、风险等级、修复建议等技术说明

  • 自动生成漏洞检测报告(PDF导出)

import React, { useState } from 'react';
import { FaBug, FaShieldAlt, FaChartBar, FaSearch, FaExclamationTriangle } from 'react-icons/fa';

function VulnerabilityScan() {
  const [scanResults, setScanResults] = useState([
    { id: 1, name: 'CVE-2023-1234', severity: 'high', description: '远程代码执行漏洞', status: '未修复' },
    { id: 2, name: 'CVE-2023-5678', severity: 'medium', description: '权限提升漏洞', status: '已修复' },
    { id: 3, name: 'CVE-2023-9101', severity: 'low', description: '信息泄露漏洞', status: '未修复' }
  ]);

  const [selectedVulnerability, setSelectedVulnerability] = useState(null);

  const handleScan = () => {
    // 模拟扫描结果
    setScanResults([
      ...scanResults,
      { id: 4, name: 'CVE-2023-1122', severity: 'high', description: 'SQL注入漏洞', status: '未修复' }
    ]);
  };

  return (
    <section className="vulnerability-scan-section">
      <h2><FaBug /> 漏洞扫描</h2>
      
      <div className="scan-controls">
        <button className="scan-btn" onClick={handleScan}>
          <FaSearch />
          <span>开始扫描</span>
        </button>
      </div>

      <div className="scan-results">
        <div className="results-overview">
          <div className="overview-card">
            <div className="card-header">
              <FaExclamationTriangle />
              <h3>高风险漏洞</h3>
            </div>
            <div className="card-content">
              <span className="count">2</span>
              <p>需要立即处理</p>
            </div>
          </div>
          
          <div className="overview-card">
            <div className="card-header">
              <FaShieldAlt />
              <h3>已修复漏洞</h3>
            </div>
            <div className="card-content">
              <span className="count">1</span>
              <p>已处理完成</p>
            </div>
          </div>
          
          <div className="overview-card">
            <div className="card-header">
              <FaChartBar />
              <h3>总漏洞数</h3>
            </div>
            <div className="card-content">
              <span className="count">3</span>
              <p>已检测到</p>
            </div>
          </div>
        </div>

        <div className="results-details">
          <h3>漏洞详情</h3>
          <table className="vulnerabilities-table">
            <thead>
              <tr>
                <th>漏洞编号</th>
                <th>严重程度</th>
                <th>描述</th>
                <th>状态</th>
              </tr>
            </thead>
            <tbody>
              {scanResults.map(vuln => (
                <tr 
                  key={vuln.id}
                  className={`${vuln.severity} ${selectedVulnerability?.id === vuln.id ? 'selected' : ''}`}
                  onClick={() => setSelectedVulnerability(vuln)}
                >
                  <td>{vuln.name}</td>
                  <td>
                    <span className={`severity-badge ${vuln.severity}`}>
                      {vuln.severity === 'high' ? '高' : vuln.severity === 'medium' ? '中' : '低'}
                    </span>
                  </td>
                  <td>{vuln.description}</td>
                  <td>{vuln.status}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {selectedVulnerability && (
        <div className="vulnerability-details">
          <h4>漏洞详情:{selectedVulnerability.name}</h4>
          <div className="details-content">
            <p><strong>严重程度:</strong>
              <span className={`severity-badge ${selectedVulnerability.severity}`}>
                {selectedVulnerability.severity === 'high' ? '高' : 
                 selectedVulnerability.severity === 'medium' ? '中' : '低'}
              </span>
            </p>
            <p><strong>描述:</strong>{selectedVulnerability.description}</p>
            <p><strong>状态:</strong>{selectedVulnerability.status}</p>
            <p><strong>建议:</strong>请及时更新相关软件版本或应用安全补丁。</p>
          </div>
        </div>
      )}
    </section>
  );
}

export default VulnerabilityScan; 

3️⃣ 黑名单管理与网络状态可视化

  • 支持手动/自动添加恶意IP

  • 核心网络设备状态实时监控(如路由器/交换机/服务器)

  • 动态折线图展示网络流量趋势

import React from 'react';
import { FaDesktop, FaBug, FaKey } from 'react-icons/fa';

function HostVulnerability() {
  return (
    <section className="host-vulnerability-section">
      <h2>主机与漏洞概览</h2>
      <div className="overview-cards">
        <div className="card">
          <div className="card-header">
            <FaDesktop />
            <h3>主机发现</h3>
          </div>
          <div className="card-content">
            <div className="stat">24</div>
            <p>最近扫描到的主机数量</p>
            <button className="view-details-btn">查看详情</button>
          </div>
        </div>
        
        <div className="card">
          <div className="card-header">
            <FaBug />
            <h3>漏洞扫描</h3>
          </div>
          <div className="card-content">
            <div className="stat">7</div>
            <p>最近检测到的漏洞数量</p>
            <div className="vulnerability-levels">
              <span className="level high">高: 2</span>
              <span className="level medium">中: 3</span>
              <span className="level low">低: 2</span>
            </div>
            <button className="view-details-btn">查看详情</button>
          </div>
        </div>
        
        <div className="card">
          <div className="card-header">
            <FaKey />
            <h3>弱密码检测</h3>
          </div>
          <div className="card-content">
            <div className="stat">5</div>
            <p>检测到的弱密码设备数量</p>
            <button className="view-details-btn">查看详情</button>
          </div>
        </div>
      </div>
    </section>
  );
}

export default HostVulnerability;

4️⃣ 日志分析模块

  • 支持多主机日志对比(主机A / 主机B)

  • 日志来源支持 MySQL / Nginx / Tomcat 等

  • 时间范围筛选、日志等级过滤、关键词搜索

  • 保留6个月历史记录,满足安全审计需求

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import './Knowledge.css'; // 引入CSS文件

// 使用styled-components定义样式
const Loading = styled.div`
  margin-top: 10px;
  padding: 10px;
  border-radius: 4px;
  font-size: 14px;
  text-align: center;
  background-color: #f8f9fa;
  color: #666;
`;

// 新增卡片样式
const Card = styled.div`
  background: rgba(255, 255, 255, 0.8);
  border-radius: 20px;
  padding: 20px;
  margin: 15px 0;
  box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  transition: all 0.3s ease;

  &:hover {
    transform: translateY(-5px);
    box-shadow: 0 12px 40px 0 rgba(31, 38, 135, 0.25);
  }
`;

// 日志解析器
const getLogParser = (source) => {
  const parsers = {
    mysql: (log) => ({
      timestamp: new Date(log.timestamp),
      message: log.message,
      level: log.level || 'info',
      source: 'MySQL'
    }),
    mongodb: (log) => ({
      timestamp: new Date(log.timestamp),
      message: log.message,
      level: log.level || 'info',
      source: 'MongoDB'
    }),
    tomcat: (log) => ({
      timestamp: new Date(log.timestamp),
      message: log.message,
      level: log.level || 'info',
      source: 'Tomcat'
    }),
    nginx: (log) => ({
      timestamp: new Date(log.timestamp),
      message: log.message,
      level: log.level || 'info',
      source: 'Nginx'
    }),
    kafka: (log) => ({
      timestamp: new Date(log.timestamp),
      message: log.message,
      level: log.level || 'info',
      source: 'Kafka'
    }),
    redis: (log) => ({
      timestamp: new Date(log.timestamp),
      message: log.message,
      level: log.level || 'info',
      source: 'Redis'
    })
  };
  return parsers[source] || (log => log);
};

// 封装模块组件
const LogModule = ({ hostName, apiEndpoint }) => {
  const [logData, setLogData] = useState([]);
  const [filteredData, setFilteredData] = useState([]);
  const [searchTerm, setSearchTerm] = useState('');
  const [dateRange, setDateRange] = useState([null, null]);
  const [timeInterval, setTimeInterval] = useState('1h');
  const [isLoading, setIsLoading] = useState(false);
  const [selectedSources, setSelectedSources] = useState([]);

  // 中间件选项
  const middlewareOptions = [
    { value: 'mysql', label: 'MySQL' },
    { value: 'nginx', label: 'Nginx' },
    { value: 'tomcat', label: 'Tomcat' },
    { value: 'kafka', label: 'Kafka' },
    { value: 'redis', label: 'Redis' }
  ];

  // 根据时间间隔更新日期范围
  const updateDateRange = (interval) => {
    const now = new Date();
    let startDate = new Date(now);

    switch (interval) {
      case '5m':
        startDate.setMinutes(now.getMinutes() - 5);
        break;
      case '15m':
        startDate.setMinutes(now.getMinutes() - 15);
        break;
      case '1h':
        startDate.setHours(now.getHours() - 1);
        break;
      case '6h':
        startDate.setHours(now.getHours() - 6);
        break;
      case '1d':
        startDate.setDate(now.getDate() - 1);
        break;
      case '1w':
        startDate.setDate(now.getDate() - 7);
        break;
      case '1mo':
        startDate.setMonth(now.getMonth() - 1);
        break;
      default:
        startDate = null;
    }

    setDateRange([startDate, now]);
  };

  // 处理时间间隔变化
  const handleTimeIntervalChange = (e) => {
    const newInterval = e.target.value;
    setTimeInterval(newInterval);
    updateDateRange(newInterval);
  };

  // 获取日志数据
  useEffect(() => {
    const mockLogs = {
      mysql: [
        { timestamp: new Date().toISOString(), message: '数据库连接成功', level: 'info' },
        { timestamp: new Date(Date.now() - 300000).toISOString(), message: '查询执行时间过长', level: 'warning' },
        { timestamp: new Date(Date.now() - 600000).toISOString(), message: '数据库备份完成', level: 'info' }
      ],
      nginx: [
        { timestamp: new Date().toISOString(), message: '200 GET /index.html', level: 'info' },
        { timestamp: new Date(Date.now() - 120000).toISOString(), message: '404 GET /nonexistent', level: 'error' },
        { timestamp: new Date(Date.now() - 300000).toISOString(), message: '502 Bad Gateway', level: 'error' }
      ],
      tomcat: [
        { timestamp: new Date().toISOString(), message: '服务器启动完成', level: 'info' },
        { timestamp: new Date(Date.now() - 180000).toISOString(), message: '内存使用率过高', level: 'warning' },
        { timestamp: new Date(Date.now() - 600000).toISOString(), message: '部署新应用成功', level: 'info' }
      ],
      kafka: [
        { timestamp: new Date().toISOString(), message: '新消息到达 topic: test', level: 'info' },
        { timestamp: new Date(Date.now() - 240000).toISOString(), message: '消费者组 rebalance', level: 'warning' },
        { timestamp: new Date(Date.now() - 500000).toISOString(), message: '创建新 topic: logs', level: 'info' }
      ],
      redis: [
        { timestamp: new Date().toISOString(), message: '内存使用量: 512MB', level: 'info' },
        { timestamp: new Date(Date.now() - 300000).toISOString(), message: '连接数达到上限', level: 'warning' },
        { timestamp: new Date(Date.now() - 600000).toISOString(), message: 'RDB 持久化完成', level: 'info' }
      ]
    };

    const fetchLogs = async () => {
      if (selectedSources.length === 0) {
        setLogData([]);
        return;
      }

      setIsLoading(true);
      
      try {
        // 模拟API请求延迟
        await new Promise(resolve => setTimeout(resolve, 500));
        
        const allLogs = selectedSources.map(source => {
          const parser = getLogParser(source);
          return mockLogs[source].map(parser);
        });

        setLogData(allLogs.flat());
      } catch (err) {
        console.error('获取日志失败:', err);
      } finally {
        setIsLoading(false);
      }
    };

    fetchLogs();
  }, [hostName, selectedSources, apiEndpoint, timeInterval]);

  // 过滤日志
  useEffect(() => {
    let filtered = logData.filter(log => {
      const matchesSearch = log.message.toLowerCase().includes(searchTerm.toLowerCase());
      const matchesDate = (!dateRange[0] || log.timestamp >= dateRange[0]) &&
                         (!dateRange[1] || log.timestamp <= dateRange[1]);
      return matchesSearch && matchesDate;
    });
    setFilteredData(filtered);
  }, [searchTerm, dateRange, logData]);

  return (
    <Card>
      {/* 主机名称和时间范围 */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '20px' }}>
        <h3 style={{ margin: 0 }}>{hostName}</h3>
        <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
            <input
              type="date"
              value={dateRange[0] ? dateRange[0].toISOString().split('T')[0] : ''}
              onChange={(e) => setDateRange([new Date(e.target.value), dateRange[1]])}
              disabled={isLoading}
              style={{
                padding: '8px',
                borderRadius: '8px',
                border: '1px solid #ddd',
                color: '#000',
                backgroundColor: '#fff',
                fontSize: '14px'
              }}
            />
            <span style={{ color: '#666' }}>至</span>
            <input
              type="date"
              value={dateRange[1] ? dateRange[1].toISOString().split('T')[0] : ''}
              onChange={(e) => setDateRange([dateRange[0], new Date(e.target.value)])}
              disabled={isLoading}
              style={{
                padding: '8px',
                borderRadius: '8px',
                border: '1px solid #ddd',
                color: '#000',
                backgroundColor: '#fff',
                fontSize: '14px'
              }}
            />
          </div>
          <select 
            value={timeInterval} 
            onChange={handleTimeIntervalChange}
            disabled={isLoading}
            style={{
              padding: '8px 16px',
              borderRadius: '8px',
              border: '1px solid #ddd',
              backgroundColor: '#fff',
              color: '#000',
              fontSize: '14px',
              cursor: 'pointer',
              appearance: 'none',
              backgroundImage: `url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e")`,
              backgroundRepeat: 'no-repeat',
              backgroundPosition: 'right 8px center',
              backgroundSize: '16px',
              minWidth: '120px'
            }}
          >
            <option value="5m">最近5分钟</option>
            <option value="15m">最近15分钟</option>
            <option value="1h">最近1小时</option>
            <option value="6h">最近6小时</option>
            <option value="1d">最近1天</option>
            <option value="1w">最近1周</option>
            <option value="1mo">最近1个月</option>
          </select>
        </div>
      </div>

      {/* 搜索日志 */}
      <div style={{ marginBottom: '20px' }}>
        <input
          type="text"
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          placeholder="搜索日志..."
          disabled={isLoading}
          style={{ width: '100%', padding: '8px', borderRadius: '8px', border: '1px solid #ddd' }}
        />
      </div>

      {/* 选择日志来源 */}
      <div style={{ marginBottom: '20px' }}>
        <h4 style={{ marginBottom: '10px' }}>选择日志来源</h4>
        <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
          {middlewareOptions.map(option => (
            <button
              key={option.value}
              style={{
                padding: '8px 16px',
                borderRadius: '8px',
                border: 'none',
                background: selectedSources.includes(option.value) ? '#007bff' : '#f0f0f0',
                color: selectedSources.includes(option.value) ? '#fff' : '#333',
                cursor: 'pointer',
                transition: 'all 0.2s ease'
              }}
              onClick={() => setSelectedSources(prev =>
                prev.includes(option.value)
                  ? prev.filter(item => item !== option.value)
                  : [...prev, option.value]
              )}
            >
              {option.label}
            </button>
          ))}
        </div>
      </div>

      {/* 日志显示区域 */}
      <div style={{ maxHeight: '400px', overflowY: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ background: '#f8f9fa' }}>
              <th style={{ padding: '12px', textAlign: 'left' }}>时间</th>
              <th style={{ padding: '12px', textAlign: 'left' }}>来源</th>
              <th style={{ padding: '12px', textAlign: 'left' }}>级别</th>
              <th style={{ padding: '12px', textAlign: 'left' }}>消息</th>
            </tr>
          </thead>
          <tbody>
            {filteredData.map((log, index) => (
              <tr key={index} style={{ borderBottom: '1px solid #eee' }}>
                <td style={{ padding: '12px' }}>{new Date(log.timestamp).toLocaleString()}</td>
                <td style={{ padding: '12px' }}>{log.source}</td>
                <td style={{ padding: '12px' }}>{log.level}</td>
                <td style={{ padding: '12px' }}>{log.message}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {isLoading && <Loading>加载中...</Loading>}
    </Card>
  );
};

// 主组件
const Knowledge = () => {
  const hosts = [
    { name: '主机A' },
    { name: '主机B' },
    { name: '主机C' }
  ];

  return (
    <div style={{ padding: '20px' }}>
      {hosts.map(host => (
        <LogModule
          key={host.name}
          hostName={host.name}
          apiEndpoint="/api/logs"
        />
      ))}
    </div>
  );
};

export default Knowledge;

5️⃣ 报告导出功能

  • 一键生成完整安全分析报告

  • 导出格式:PDF / CSV

  • 内容包括:设备状态、漏洞信息、黑名单记录、日志摘要等

import React from 'react';
import { PDFDownloadLink, Document, Page, Text, View, StyleSheet, Font } from '@react-pdf/renderer';

// 注册中文字体
Font.register({
  family: 'SimSun',
  src: '/fonts/SimSun.ttf', // 确保路径正确
});

// 定义PDF样式
const styles = StyleSheet.create({
  page: {
    padding: 30,
    fontFamily: 'SimSun', // 使用中文字体
  },
  section: {
    marginBottom: 20,
    borderBottom: '1px solid #eee',
    paddingBottom: 10,
  },
  title: {
    fontSize: 24,
    marginBottom: 20,
    color: '#333',
    fontWeight: 'bold',
  },
  subtitle: {
    fontSize: 18,
    marginBottom: 15,
    color: '#555',
  },
  text: {
    fontSize: 14,
    marginBottom: 8,
    color: '#666',
  },
  table: {
    display: 'table',
    width: '100%',
    marginTop: 20,
  },
  tableRow: {
    display: 'table-row',
  },
  tableCell: {
    display: 'table-cell',
    padding: '8px 12px',
    border: '1px solid #ddd',
  },
  tableHeader: {
    backgroundColor: '#f5f5f5',
    fontWeight: 'bold',
  }
});

// 主机信息组件
const HostInfo = ({ host, selected, onChange }) => {
  // 根据操作系统类型获取图标
  const getOSIcon = (os) => {
    const iconStyle = {
      fontSize: 48,
      color: '#666'
    };

    if (os.includes('Windows')) {
      return <span style={iconStyle}>🪟</span>; // Windows 图标
    } else if (os.includes('Android')) {
      return <span style={iconStyle}>🤖</span>; // Android 图标
    } else if (os.includes('macOS') || os.includes('iOS')) {
      return <span style={iconStyle}>🍎</span>; // 苹果系统图标
    } else if (os.includes('Linux') || os.includes('Ubuntu')) {
      return <span style={iconStyle}>🐧</span>; // Linux 图标
    } else {
      return <span style={iconStyle}>💻</span>; // 默认图标
    }
  };

  return (
    <div style={{ 
      position: 'relative',
      padding: 20,
      backgroundColor: selected ? '#e6f7ff' : '#fafafa',
      borderRadius: 12,
      border: `1px solid ${selected ? '#40a9ff' : '#e8e8e8'}`,
      cursor: 'pointer',
      transition: 'all 0.3s ease',
      boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
      ':hover': {
        boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
        transform: 'translateY(-2px)'
      }
    }} onClick={() => onChange()}>
      {/* 选择按钮 */}
      <div style={{
        position: 'absolute',
        top: 12,
        right: 12,
        width: 20,
        height: 20,
        borderRadius: '50%',
        border: `2px solid ${selected ? '#40a9ff' : '#d9d9d9'}`,
        backgroundColor: selected ? '#40a9ff' : '#fff',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        transition: 'all 0.3s ease'
      }}>
        {selected && (
          <span style={{ 
            color: '#fff',
            fontSize: 12,
            lineHeight: 1
          }}>✓</span>
        )}
      </div>

      {/* 系统图标 */}
      <div style={{ 
        textAlign: 'center',
        marginBottom: 16
      }}>
        <div style={{ 
          display: 'flex',
          justifyContent: 'center',
          marginBottom: 8
        }}>
          {getOSIcon(host.os)}
        </div>
        <div style={{ 
          fontSize: 14,
          color: '#666',
          fontWeight: 500
        }}>
          {host.os}
        </div>
      </div>

      {/* 主机信息 */}
      <div style={{ 
        borderTop: '1px solid #eee',
        paddingTop: 16
      }}>
        <div style={{ 
          display: 'flex',
          justifyContent: 'space-between',
          marginBottom: 8
        }}>
          <div style={{ fontSize: 14, color: '#666' }}>主机名</div>
          <div style={{ fontSize: 14, fontWeight: 500 }}>{host.name}</div>
        </div>
        <div style={{ 
          display: 'flex',
          justifyContent: 'space-between',
          marginBottom: 8
        }}>
          <div style={{ fontSize: 14, color: '#666' }}>IP地址</div>
          <div style={{ fontSize: 14, fontWeight: 500 }}>{host.ip}</div>
        </div>
        <div style={{ 
          display: 'flex',
          justifyContent: 'space-between',
          marginBottom: 8
        }}>
          <div style={{ fontSize: 14, color: '##666' }}>状态</div>
          <div style={{ 
            fontSize: 14,
            fontWeight: 500,
            color: host.status === '在线' ? '#52c41a' : '#f5222d'
          }}>
            {host.status}
          </div>
        </div>
        <div style={{ 
          display: 'flex',
          justifyContent: 'space-between',
          marginBottom: 8
        }}>
          <div style={{ fontSize: 14, color: '#666' }}>开放端口</div>
          <div style={{ fontSize: 14, fontWeight: 500 }}>{host.openPorts.join(', ')}</div>
        </div>
        <div style={{ 
          display: 'flex',
          justifyContent: 'space-between'
        }}>
          <div style={{ fontSize: 14, color: '#666' }}>漏洞数量</div>
          <div style={{ 
            fontSize: 14,
            fontWeight: 500,
            color: host.vulnerabilities.length > 0 ? '#f5222d' : '#52c41a'
          }}>
            {host.vulnerabilities.length}
          </div>
        </div>
      </div>
    </div>
  );
};

// 漏洞分析卡片组件
const VulnerabilityCard = ({ host, vuln, selected, onChange }) => {
  return (
    <div style={{ 
      position: 'relative',
      padding: 16,
      backgroundColor: selected ? '#e6f7ff' : vuln.severity === '高危' ? '#fff1f0' : '#fff7e6',
      borderRadius: 8,
      boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
      cursor: 'pointer',
      transition: 'all 0.3s ease',
      ':hover': {
        boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
        transform: 'translateY(-2px)'
      }
    }} onClick={() => onChange()}>
      {/* 选择按钮 */}
      <div style={{
        position: 'absolute',
        top: 12,
        right: 12,
        width: 20,
        height: 20,
        borderRadius: '50%',
        border: `2px solid ${selected ? '#40a9ff' : '#d9d9d9'}`,
        backgroundColor: selected ? '#40a9ff' : '#fff',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        transition: 'all 0.3s ease'
      }}>
        {selected && (
          <span style={{ 
            color: '#fff',
            fontSize: 12,
            lineHeight: 1
          }}>✓</span>
        )}
      </div>

      <div style={{ fontSize: 16, fontWeight: 500, marginBottom: 8 }}>
        {host.name} ({host.ip})
      </div>
      <div style={{ color: '#666', marginBottom: 4 }}>漏洞类型: {vuln.type}</div>
      <div style={{ color: '#666', marginBottom: 4 }}>风险等级: {vuln.severity}</div>
      <div style={{ color: '#666', marginBottom: 4 }}>描述: {vuln.description || '暂无详细描述'}</div>
      <div style={{ color: '#666' }}>建议措施: {vuln.recommendation}</div>
    </div>
  );
};

// PDF内容组件
const MyDocument = ({ 
  wifiSignal, 
  vulnerabilities, 
  hostCount, 
  selectedHosts,
  selectedVulnerabilities,
  networkInfo,
  securityIssues
}) => {
  console.log('Selected Hosts:', selectedHosts);
  console.log('Selected Vulnerabilities:', selectedVulnerabilities);

  // PDF中的图标映射
  const getOSIcon = (os) => {
    if (os.includes('Windows')) {
      return '🪟';
    } else if (os.includes('Android')) {
      return '🤖';
    } else if (os.includes('macOS') || os.includes('iOS')) {
      return '🍎';
    } else if (os.includes('Linux') || os.includes('Ubuntu')) {
      return '🐧';
    } else {
      return '💻';
    }
  };

  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <View style={styles.section}>
          <Text style={styles.title}>网络安全测试报告</Text>
          <Text style={styles.subtitle}>网络概况</Text>
          <Text style={styles.text}>• 当前WiFi信号强度: {wifiSignal}</Text>
          <Text style={styles.text}>• 网络类型: {networkInfo.type}</Text>
          <Text style={styles.text}>• 局域网内部主机数量: {hostCount}</Text>
        </View>

        <View style={styles.section}>
          <Text style={styles.subtitle}>主机信息</Text>
          <View style={styles.table}>
            <View style={[styles.tableRow, styles.tableHeader]}>
              <Text style={styles.tableCell}>图标</Text>
              <Text style={styles.tableCell}>主机名</Text>
              <Text style={styles.tableCell}>IP地址</Text>
              <Text style={styles.tableCell}>状态</Text>
              <Text style={styles.tableCell}>操作系统</Text>
              <Text style={styles.tableCell}>开放端口</Text>
              <Text style={styles.tableCell}>漏洞数量</Text>
            </View>
            {selectedHosts.map((host, index) => (
              <View key={index} style={styles.tableRow}>
                <Text style={styles.tableCell}>{getOSIcon(host.os)}</Text>
                <Text style={styles.tableCell}>{host.name}</Text>
                <Text style={styles.tableCell}>{host.ip}</Text>
                <Text style={styles.tableCell}>{host.status}</Text>
                <Text style={styles.tableCell}>{host.os}</Text>
                <Text style={styles.tableCell}>{host.openPorts.join(', ')}</Text>
                <Text style={styles.tableCell}>{host.vulnerabilities.length}</Text>
              </View>
            ))}
          </View>
        </View>

        <View style={styles.section}>
          <Text style={styles.subtitle}>漏洞分析</Text>
          <View style={styles.table}>
            <View style={[styles.tableRow, styles.tableHeader]}>
              <Text style={styles.tableCell}>主机名</Text>
              <Text style={styles.tableCell}>漏洞类型</Text>
              <Text style={styles.tableCell}>风险等级</Text>
              <Text style={styles.tableCell}>建议措施</Text>
            </View>
            {selectedVulnerabilities.map((vuln, index) => (
              <View key={index} style={styles.tableRow}>
                <Text style={styles.tableCell}>{vuln.host.name}</Text>
                <Text style={styles.tableCell}>{vuln.type}</Text>
                <Text style={styles.tableCell}>{vuln.severity}</Text>
                <Text style={styles.tableCell}>{vuln.recommendation}</Text>
              </View>
            ))}
          </View>
        </View>
      </Page>
    </Document>
  );
};

// 主组件
const MakePDF = ({ wifiSignal = "强", vulnerabilities = 3, hostCount = 5 }) => {
  const [hosts, setHosts] = React.useState([
    { 
      name: 'PC-01', 
      ip: '192.168.1.101', 
      status: '在线', 
      os: 'Windows 10',
      openPorts: [80, 443, 3389],
      vulnerabilities: [
        { type: 'SMB漏洞', severity: '高危', recommendation: '更新系统补丁' },
        { type: '弱密码', severity: '中危', recommendation: '加强密码策略' }
      ],
      selected: false 
    },
    { 
      name: 'PC-02', 
      ip: '192.168.1.102', 
      status: '离线', 
      os: 'Ubuntu 20.04',
      openPorts: [22, 80],
      vulnerabilities: [
        { type: 'SSH弱密码', severity: '高危', recommendation: '使用密钥认证' }
      ],
      selected: false 
    },
    { 
      name: 'PC-03', 
      ip: '192.168.1.103', 
      status: '在线', 
      os: 'macOS 12',
      openPorts: [22, 5900],
      vulnerabilities: [
        { type: 'VNC未加密', severity: '中危', recommendation: '启用加密' }
      ],
      selected: false 
    },
    { 
      name: 'Phone-01', 
      ip: '192.168.1.104', 
      status: '在线', 
      os: 'Android 12',
      openPorts: [8080],
      vulnerabilities: [
        { type: '未加密通信', severity: '中危', recommendation: '启用HTTPS' }
      ],
      selected: false 
    },
    { 
      name: 'Phone-02', 
      ip: '192.168.1.105', 
      status: '在线', 
      os: 'iOS 15',
      openPorts: [443],
      vulnerabilities: [
        { type: '越狱检测', severity: '低危', recommendation: '检查设备完整性' }
      ],
      selected: false 
    }
  ]);

  const [selectedVulnerabilities, setSelectedVulnerabilities] = React.useState([]);

  const handleHostSelect = (index) => {
    const newHosts = [...hosts];
    newHosts[index].selected = !newHosts[index].selected;
    setHosts(newHosts);
  };

  const handleVulnerabilitySelect = (hostIndex, vulnIndex) => {
    const host = hosts[hostIndex];
    const vuln = host.vulnerabilities[vulnIndex];
    const isSelected = selectedVulnerabilities.some(
      v => v.host.name === host.name && v.type === vuln.type
    );

    if (isSelected) {
      setSelectedVulnerabilities(prev => 
        prev.filter(v => !(v.host.name === host.name && v.type === vuln.type))
      );
    } else {
      setSelectedVulnerabilities(prev => [...prev, { ...vuln, host }]);
    }
  };

  const selectedHosts = hosts.filter(host => host.selected);

  const networkInfo = {
    type: '有线网络',
    ip: '192.168.1.1',
    subnetMask: '255.255.255.0',
    gateway: '192.168.1.1'
  };

  const securityIssues = {
    openPorts: [80, 443, 22],
    threats: ['未加密的WiFi', '未更新的设备', '未配置的安全策略']
  };

  return (
    <div style={{ 
      padding: 24,
      maxWidth: 1200,
      margin: '0 auto',
      backgroundColor: '#fff',
      borderRadius: 12,
      boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
    }}>
      <h2 style={{ 
        color: '#333',
        marginBottom: 24,
        fontSize: 24,
        fontWeight: 600,
        textAlign: 'center'
      }}>网络安全测试报告</h2>

      <div style={{ marginBottom: 32 }}>
        <h3 style={{ 
          color: '#555',
          marginBottom: 16,
          fontSize: 20,
          fontWeight: 500
        }}>主机信息</h3>
        <div style={{ 
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
          gap: 24
        }}>
          {hosts.map((host, index) => (
            <HostInfo
              key={index}
              host={host}
              selected={host.selected}
              onChange={() => handleHostSelect(index)}
            />
          ))}
        </div>
      </div>

      <div style={{ marginBottom: 32 }}>
        <h3 style={{ 
          color: '#555',
          marginBottom: 16,
          fontSize: 20
        }}>漏洞分析</h3>
        <div style={{ 
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
          gap: 16
        }}>
          {hosts.map((host, hostIndex) => (
            host.vulnerabilities.map((vuln, vulnIndex) => {
              const isSelected = selectedVulnerabilities.some(
                v => v.host.name === host.name && v.type === vuln.type
              );
              return (
                <VulnerabilityCard
                  key={`${hostIndex}-${vulnIndex}`}
                  host={host}
                  vuln={vuln}
                  selected={isSelected}
                  onChange={() => handleVulnerabilitySelect(hostIndex, vulnIndex)}
                />
              );
            })
          ))}
        </div>
      </div>

      <div style={{ 
        textAlign: 'center',
        marginTop: 32,
        paddingTop: 24,
        borderTop: '1px solid #eee'
      }}>
        <PDFDownloadLink
          document={<MyDocument
            wifiSignal={wifiSignal}
            vulnerabilities={vulnerabilities}
            hostCount={hostCount}
            selectedHosts={selectedHosts}
            selectedVulnerabilities={selectedVulnerabilities}
            networkInfo={networkInfo}
            securityIssues={securityIssues}
          />}
          fileName="security_report.pdf"
        >
          {({ loading }) => (
            <button style={{
              padding: '12px 32px',
              backgroundColor: '#1890ff',
              color: '#fff',
              border: 'none',
              borderRadius: 8,
              cursor: 'pointer',
              fontSize: 16,
              fontWeight: 500,
              transition: 'all 0.3s ease',
              ':hover': {
                backgroundColor: '#40a9ff',
                transform: 'translateY(-2px)',
                boxShadow: '0 4px 12px rgba(24,144,255,0.3)'
              }
            }}>
              {loading ? '生成PDF中...' : '生成PDF'}
            </button>
          )}
        </PDFDownloadLink>
      </div>
    </div>
  );
};

export default MakePDF;

六、使用方式简要说明

👤 登录流程

  • 注册+登录验证(用户名/密码)

  • 权限控制区分不同用户视图

🔍 功能入口导航

  • 仪表盘:首页实时状态概览

  • 数据包检测:开始/停止抓包

  • 漏洞扫描:点击一键检测

  • 黑名单管理:IP添加/删除

  • 日志中心:按主机分类分析日志

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2338368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[经验总结]Linux双机双网卡Keepalived高可用配置及验证细节

1. 前言 这种配置需求比较少见&#xff0c;在网上也很少有相关文章&#xff0c;于是记录在此&#xff0c;供有需要的朋友参考。 本篇重点介绍配置的关键点&#xff0c;基础部分简单提及&#xff0c;不赘述。 2. 需求描述 如上图&#xff0c;即给两个主机配置两对高可用主从配…

2025.04.17【Dendrogram】生信数据可视化:Dendrogram图表详解

Dendrogram customization Go further with ggraph: edge style, general layout, node features, adding labels, and more. Customized circular dendrogram Learn how to build a circular dendrogram with proper labels. 文章目录 Dendrogram customizationCustomized c…

Linux下的网络管理

一、ipv4原理 网络接口是指网络中的计算机或网络设备与其他设备实现通讯的进出口&#xff0c;一般是指计算机的网络接口即网卡设备 从RHEL7开始引入了一种新的“一致网络设备命名”的方式为网络接口命名&#xff0c;该方式可以根据固件、设备拓扑、设备类型和位置信息分配固…

Zookeeper介绍与安装配置

1.综述 1.1.Zookeeper介绍 Zookeeper 是一个分布式协调服务&#xff0c;由 Apache 开发&#xff0c;主要用于管理分布式应用中的配置信息、命名服务、分布式同步和组服务。它通过简单的接口提供高性能、高可用性和严格的顺序访问控制&#xff0c;广泛应用于分布式系统的协调与…

实验五 内存管理实验

实验五 内存管理实验 一、实验目的 1、了解操作系统动态分区存储管理过程和方法。 2、掌握动态分区存储管理的主要数据结构--空闲表区。 3、加深理解动态分区存储管理中内存的分配和回收。 4、掌握空闲区表中空闲区3种不同放置策略的基本思想和实现过程。 5、通过模拟程…

用Webpack 基础配置快速搭建项目开发环境

Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具&#xff0c;但是Webpack有大量的配置项&#xff0c;对新手不太友好&#xff0c;但是我们可以根据webpack-cli的init命令根据项目需求快速生成webpack的配置文件&#xff0c;本文将手把手教你如何用 Webpack 和 npm 快…

Axios 介绍及使用指南

本文将基于 Axios 原理&#xff0c;安装及封装方面展开描述&#xff0c;话不多说&#xff0c;现在发车&#xff01; 一、原理 Axios 中文文档&#xff1a;起步 | Axios中文文档 | Axios中文网 赛前科普&#xff1a; 下文将涉及到三个关键词&#xff1a;Axios&#xff0c;Ajax…

接口自动化测试(二)

一、接口测试流程&#xff1a;接口文档、用例编写 拿到接口文档——编写接口用例以及评审——进行接口测试——工具/自动化框架进行自动化用例覆盖(70%)——输出测试报告 自动化的目的一般是为了回归 第一件事情&#xff1a;理解需求&#xff0c;学会看接口文档 只需要找到我…

Arduino+ESP826601s模块连接阿里云并实现温湿度数据上报

ArduinoESP826601s模块连接阿里云并实现温湿度数据上报 一、前言二、准备工作三、程序代码1. Arduino的程序2. ESP826601的程序3. 上面程序需要注意的地方 四、运行结果五、结束语 一、前言 看完我这三篇文章&#xff0c;相信各位朋友对于阿里云物联网平台的使用都有了一定的认…

本地生活服务信息分类信息系统

最近在找分类信息系统&#xff0c;看了很多市面上常见的分类信息系统&#xff1a; 1&#xff0c;私集分类信息系统 2&#xff0c;火鸟分类信息系统 3&#xff0c;觅分类信息系统 4&#xff0c;框分类信息系统 5&#xff0c;蚂蚁分类信息系统 发现很多分类信息系统&#xff0c;…

【Dify应用】连接数据库生成Echarts图表

这里写自定义目录标题 需求文档内容测试环境实际效果工作流内容工具安装B工作流详解A工作流详解优化建议 需求 甲方要求。根据自然语言生成对应Echarts图表&#xff0c;并且数据来源于私有数据库。 文档内容 本文档内容主要展示使用Dify&#xff08;本地源码&#xff09;进行…

RAG 实战|用 StarRocks + DeepSeek 构建智能问答与企业知识库

文章作者&#xff1a; 石强&#xff0c;镜舟科技解决方案架构师 赵恒&#xff0c;StarRocks TSC Member &#x1f449; 加入 StarRocks x AI 技术讨论社区 https://mp.weixin.qq.com/s/61WKxjHiB-pIwdItbRPnPA RAG 和向量索引简介 RAG&#xff08;Retrieval-Augmented Gen…

ubuntu 22.04 使用ssh-keygen创建ssh互信账户

现有两台ubuntu 22.04服务器&#xff0c;ip分别为192.168.66.88和192.168.88.66。需要将两台服务器创建新用户并将新用户做互信。 创建账户 adduser user1 # 如果此用户不想使用密码&#xff0c;直接一直回车就行&#xff0c;创建的用户是没法使用用户密码进行登陆的 su - …

【Linux网络】Socket 编程TCP

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12891150.html 目录 TCP socket API 详解 socket(): bind(): listen(): accept(): connect V0…

ESP32-idf学习(二)esp32C3作服务端与电脑蓝牙数据交互

一、当前需求 目前是想利用蓝牙来传输命令&#xff0c;或者一些数据&#xff0c;包括电脑、手机与板子的数据传输&#xff0c;板子与板子之间的数据传输。构思是一个板子是数据接收终端&#xff0c;在电脑或手机下发指令后&#xff0c;再给其他板子相应指令&#xff0c;也需要…

NHANES指标推荐:CMI

文章题目&#xff1a;Association between cardiometabolic index and biological ageing among adults: a population-based study DOI&#xff1a;10.1186/s12889-025-22053-3 中文标题&#xff1a;成年人心脏代谢指数与生物衰老之间的关系&#xff1a;一项基于人群的研究 发…

前端单元测试实战:如何开始?

实战&#xff1a;如何开始单元测试 1.安装依赖 npm install --save-dev jest2.简单的例子 首先&#xff0c;创建一个 sum.js 文件 ./sum.js function sum(a, b) {return a b; }module.exports sum;创建一个名为 sum.test.js 的文件&#xff0c;这个文件包含了实际测试内…

react-native搭建开发环境过程记录

主要参考&#xff1a;官网的教程 https://reactnative.cn/docs/environment-setup 环境介绍&#xff1a;macos ios npm - 已装node18 - 已装&#xff0c;通过nvm进行版本控制Homebrew- 已装yarn - 已装ruby - macos系统自带的2.2版本。watchman - 正常安装Xcode - 正常安装和…

观察者模式详解与C++实现

1. 模式定义 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;定义了对象间的一对多依赖关系。当一个对象&#xff08;被观察者/主题&#xff09;状态改变时&#xff0c;所有依赖它的对象&#xff08;观察者&#xff09;都会自动收到通知…

UE5 关卡序列

文章目录 介绍创建一个关卡序列编辑动画添加一个物体编辑动画时间轴显示秒而不是帧时间轴跳转到一个确定的时间时间轴的显示范围更改关键帧的动画插值方式操作多个关键帧 播放动画 介绍 类似于Unity的Animation动画&#xff0c;可以用来录制场景中物体的动画 创建一个关卡序列…