尧图精选

基于Spring Boot的小型网络管理系统设计与实现

🕒 发布时间:2026/9/13 16:13:43 📁 来源:尧图网络
简介这是一套基于Spring Boot和Vue技术栈的企业内部网络管理系统毕业设计项目面向计算机专业毕业生或需快速搭建企业级应用的开发者。系统采用前后端分离架构后端使用Java语言与Spring Boot框架前端通过Vue进行组件化开发核心功能涵盖用户登录认证、角色权限控制、网络设备管理、IP地址管理与网络流量监控等模块并提供图形化操作界面。数据库选用MySQL表结构遵循第三范式确保数据一致性与扩展性。资源压缩包大小约11.24MB包含完整项目源代码与配套开发文档文档中详细记录了系统的需求分析、设计思路、开发步骤、测试案例和部署方法可帮助读者快速理解项目架构并完成本地环境搭建便于进行毕业设计答辩与二次开发。目前已有25人浏览学习适合计算机相关专业学生用于毕业设计参考或实际项目演练。1. 为什么企业内部小型网络管理系统还在用 Spring Boot 硬扛如果只是监控三五十台设备、管管内网 IP 和交换机端口很多团队第一反应是上个开源网管平台。但真到落地时会发现Zabbix 和 Prometheus 那套偏向监控告警CMDB 又太重而企业内部的网络管理往往还带着一堆审批流程和资产登记需求。用 Spring Boot 自研一个小型系统反而成了最务实的路径——它不追求替代专业网管软件而是把 Spring Boot 的快速开发能力和网络管理里的常见操作结合起来把设备发现、状态监控、配置备份这些事做进一个内部系统里。这篇文假设你已经能用 IDEA 新建一个 Spring Boot 项目但不确定网络管理模块从哪下手。我会按一套可落地的方案来讲怎么设计设备表、怎么用 JDBC 或 MyBatis-Plus 把扫描结果落库、怎么用定时任务轮询设备存活状态以及怎么把拓扑发现、告警通知这些进阶功能接进去。所有的代码片段都是直接能用的思路参数也给到能跑通的程度。不管是做毕设还是企业内网自用照着这套骨架搭至少不会走偏。2. 小型网络管理系统的模块拆分与 Spring Boot 选型理由2.1 网络管理系统的最小功能集别一上来就做拓扑图企业内部小型网络管理系统最核心的诉求无非三件事摸清网内有什么设备、知道设备当前是否在线、出问题时能快速定位。围绕这三件事系统的功能边界应该收敛在四个模块上——设备台账、状态监控、告警记录、基础报表。设备台账管的是 IP、MAC、厂商、位置这些静态信息状态监控管的是在线离线、响应时延这些动态数据告警记录把异常事件按时间线存下来报表则给运维做月报用。拓扑图、流量分析这些功能不是不能做但小型系统里它们应该排在后置位先保证数据层的完整。从 Spring Boot 的角度看这四个模块恰好都在它的舒适区内。Spring MVC 负责 REST 接口Spring Data JPA 或 MyBatis-Plus 管数据库读写Spring Scheduled 做定时扫描Spring Mail 或者钉钉 Webhook 做告警通知。相比 Python 的 Flask 或者 Node.js 的 ExpressSpring Boot 的优势在于生态整合——你不需要东拼西凑地去选 ORM、任务调度、参数校验的库一套 Spring 全家桶全包了而且事务管理、连接池这些基础设施都是现成的。如果你团队里 Java 工程师占比高后续维护成本会远低于混合技术栈。2.2 为什么放弃 SNMP 而优先选 ICMP Ping 和端口探测网络管理系统的数据采集层常见做法有三种ICMP Ping、SNMP 协议、Agent 主动上报。SNMP 能拿到设备 CPU、内存、接口流量这些深度指标但前提是网络设备得开启 SNMP 服务还得配 community string。小型企业内部往往有大量哑设备——比如 IP 电话、打印机、摄像头——它们要么不支持 SNMP要么配置起来极麻烦。所以这套系统里数据采集层应该做成策略可插拔默认用 ICMP Ping 做存活探测对支持 SNMP 的核心交换机再走 SNMP 补充数据。技术选型落到 Spring Boot 里ICMP Ping 有两条路。一是直接用 Java 的InetAddress.isReachable()它内部会尝试 ICMP ECHO但 JVM 实现里有时会退化成 TCP 探测 7 端口结果不太可靠。二是在项目里引入基于 JNA 的jping或者干脆调系统命令ping -c 1 -W 1。对于 Windows 和 Linux 混合环境我一般建议后者——解析系统ping命令的输出稳定性最好代码量也不大。public Boolean checkAliveByPing(String host, Integer timeoutMs) { try { String os System.getProperty(os.name).toLowerCase(); String cmd; if (os.contains(win)) { cmd ping -n 1 -w timeoutMs host; } else { cmd ping -c 1 -W timeoutMs / 1000 host; } Process process Runtime.getRuntime().exec(cmd); boolean completed process.waitFor(timeoutMs 1000, TimeUnit.MILLISECONDS); if (!completed) { process.destroyForcibly(); return false; } return process.exitValue() 0; } catch (Exception e) { log.error(Ping {} failed, host, e); return false; } }这段代码的核心逻辑是同步等待ping进程结束通过退出码判断主机是否存活。timeoutMs参数控制单次探测的等待时长小型内网建议设 1000 到 2000 毫秒太短容易误报离线太长会拖慢整体扫描节奏。-w参数在 Windows 和 Linux 下的单位不同Windows 是毫秒Linux 是秒代码里做了换算这是在双环境部署时最容易踩的坑。2.3 数据库表设计设备表和监控记录表的主角地位数据模型是整个系统的地基。设备表device和监控记录表monitor_log是两张核心表前者存设备的静态属性后者存每次探测的动态结果。设备表的字段不用太激进但以下这几个必须有id、ip_address、mac_address、device_type、location、status、last_online_time。status字段建议用0表示未知、1表示在线、2表示离线而不是直接存字符串这样写查询条件时索引效率更高。last_online_time是给告警模块用的判断设备离线多久了。监控记录表走的是时间序列数据的路子每轮扫描写入一批记录。字段包括id、device_id、reachable、response_time_ms、scan_time。这张表的数据量会增长得很快小型系统里建议定期清理或者做按月分表。很多人会在这张表上纠结要不要用时序数据库我的建议是——设备量在 500 台以内、每轮扫描间隔在 1 分钟以上时MySQL 完全够用不必为此多维护一套 InfluxDB。CREATE TABLE device ( id bigint(20) NOT NULL AUTO_INCREMENT, ip_address varchar(64) NOT NULL COMMENT IP地址, mac_address varchar(32) DEFAULT NULL COMMENT MAC地址, device_type varchar(32) DEFAULT unknown COMMENT 设备类型, location varchar(255) DEFAULT NULL COMMENT 物理位置, status tinyint(4) DEFAULT 0 COMMENT 0-未知 1-在线 2-离线, last_online_time datetime DEFAULT NULL COMMENT 最后在线时间, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_ip (ip_address) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意ip_address上加了唯一索引这一步很关键。内网扫描时同一个 IP 可能被重复上报唯一索引能兜底避免产生脏数据。device_type字段建议在代码里预定义枚举值router、switch、server、printer、camera这几个够用。如果希望系统后续能自动识别厂商可以再预留一个vendor字段通过 MAC 地址的 OUI 前缀反查这块放到后面扫描逻辑里讲。3. 网络扫描功能的 Spring Boot 实现路径3.1 基于线程池的并发 Ping 扫描别写成单线程死循环网络扫描是系统里最容易做成性能瓶颈的地方。假如内网有 200 个 IP 要探测按单线程每个 Ping 等 1 秒算一轮就要 200 秒这完全不可接受。Spring Boot 里解决这个问题的方式是线程池加按批分发——把 IP 列表切分成若干组每组丢给线程池并发执行等所有任务结束后再汇总结果。JDK 的ExecutorService配CountDownLatch是最直接可控的组合不引入额外的并发框架。线程池的线程数要结合机器配置来设。核心线程数不建议超过 CPU 核数的两倍因为 Ping 操作是 IO 密集型的线程太多会导致上下文切换开销反而拖慢整体速度。队列容量可以设 100 到 500 之间任务数超过队列容量时饱和策略用CallerRunsPolicy让提交任务的线程自己跑这样能天然形成背压避免任务堆积导致内存溢出。Service public class NetworkScanner { private final ExecutorService executor new ThreadPoolExecutor( 8, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(200), new ThreadPoolExecutor.CallerRunsPolicy() ); public ListScanResult scanBatch(ListString ipList, int timeoutMs) throws InterruptedException { ListScanResult results new CopyOnWriteArrayList(); CountDownLatch latch new CountDownLatch(ipList.size()); ListFuture? futures new ArrayList(); for (String ip : ipList) { Future? future executor.submit(() - { try { long start System.currentTimeMillis(); boolean alive checkAliveByPing(ip, timeoutMs); long cost System.currentTimeMillis() - start; results.add(new ScanResult(ip, alive, cost)); } finally { latch.countDown(); } }); futures.add(future); } latch.await(timeoutMs * 2L, TimeUnit.MILLISECONDS); return results; } }这段代码里CopyOnWriteArrayList保证多线程写入results时不会抛并发修改异常虽然写性能差一些但扫描场景下数据量小读多写少正好合适。latch.await设了一个超时时间——timeoutMs * 2——防止个别 IP 的 Ping 进程挂死导致整个批次永远等下去。这个超时时间的单位是毫秒传入前要确认和 Ping 的超时参数一致。3.2 内网 IP 段的配置化管理与自动发现策略扫描范围不能写死在代码里这是系统上线后运维同事会提的第一个需求。配置化的做法是在application.yml里定义一个自定义前缀把内网网段按 CIDR 格式写进去比如10.11.0.0/24和192.168.1.0/28。Spring Boot 的ConfigurationProperties可以直接把这段配置绑定到 Java 对象上这个机制在 springboot 里用得很多也常被面试官问到。CIDR 转 IP 列表的逻辑不要自己造轮子Apache Commons Net 里提供了SubnetUtils传一个10.11.0.0/24进去直接能拿到该子网下所有可用 IP。这个工具类在 Maven 仓库里能找到引入commons-net依赖即可。要注意的是SubnetUtils默认会排除网络地址和广播地址这对扫描目标来说是对的但你得知道它有这个行为免得排查的时候以为丢 IP 了。ConfigurationProperties(prefix network.scan) Component public class ScanConfig { private ListString subnets; private Integer timeoutMs 1000; private String cron 0 */5 * * * ?; // getter / setter 省略 }network: scan: subnets: - 10.11.0.0/24 - 192.168.1.0/28 timeout-ms: 1000 cron: 0 */5 * * * ?配置项里把timeout-ms放在全局意味着所有子网的探测超时都一样。如果内网里有跨地区专线互联的网段可能需要单独调大这个值——那种情况下建议给subnets列表里的每个元素配一个 Map 结构而不是简单的字符串列表这样每个网段都能带独立的超时参数。3.3 扫描结果入库与状态变更的原子性处理扫描完成后结果要写进数据库同时要更新设备表的在线状态。这里如果分两步写中间发生异常就会出现数据不一致——监控记录显示在线设备表里却是离线。Spring 的Transactional注解可以保证这两次 DML 操作在同一个事务里要么全成功要么全回滚。但要注意事务边界应该在 Service 层方法上而不是放在扫描线程的任务里否则每个线程一个事务大批量写入时数据库连接池容易被打满。写入的姿势也有讲究。用 MyBatis-Plus 的话IService.saveOrUpdate会根据主键或者唯一索引判断是插入还是更新。对于扫描结果这种幂等性强的数据直接用saveOrUpdate能省去先查一遍再判断的代码。设备状态更新则需要单独处理——如果是首次发现的设备insert后续扫描再命中则update状态和最后在线时间。MyBatis-Plus 的LambdaUpdateWrapper配合last_online_time NOW()可以一条语句完成不需要在 Java 层传时间参数避免应用服务器和数据库时钟不一致的问题。Transactional(rollbackFor Exception.class) public void processScanResults(ListScanResult results) { ListMonitorLog logs new ArrayList(); for (ScanResult result : results) { Device device deviceMapper.selectOne( new LambdaQueryWrapperDevice() .eq(Device::getIpAddress, result.getIp()) ); if (device null) { device new Device(); device.setIpAddress(result.getIp()); device.setStatus(result.isAlive() ? 1 : 2); device.setLastOnlineTime(result.isAlive() ? LocalDateTime.now() : null); deviceMapper.insert(device); } else { deviceMapper.update(null, new LambdaUpdateWrapperDevice() .eq(Device::getIpAddress, result.getIp()) .set(Device::getStatus, result.isAlive() ? 1 : 2) .set(Device::getLastOnlineTime, result.isAlive() ? LocalDateTime.now() : null) ); } if (!result.isAlive()) { MonitorLog log new MonitorLog(); log.setDeviceId(device.getId()); log.setReachable(false); log.setResponseTimeMs(null); logs.add(log); } } if (!logs.isEmpty()) { monitorLogService.saveBatch(logs, 100); } }这段实现里有一个细节值得注意离线状态的设备它的MonitorLog也会被记录。这样做会让日志表里堆满离线记录但换来的是告警模块可以基于日志表做历史回溯——每次离线事件的持续时长都可以通过相邻两条离线日志的时间差算出来。很多小型系统忽略这个只记在线记录真到排查故障时就发现时间线对不上。4. 设备状态监控的查询优化与告警规则设计4.1 基于定时任务的状态轮询与扫描批次隔离Spring Boot 的Scheduled注解配上 Cron 表达式是最常见的定时任务方案。但直接用会有个隐患如果某次扫描执行时间超过了 Cron 间隔下一次触发会和上一次重叠。解决方式是给任务入口加一个AtomicBoolean做互斥锁扫描开始前尝试 CAS 置位结束后复位。这个 $10 行不到的代码能避免 MySQL 连接池被扫挂。扫描批次隔离是另一个必做的设计。给每次扫描生成一个batch_id格式用时间戳加随机数比如20250207103015_4821。所有设备状态更新和监控记录写入都带上这个批次号。好处有两个——第一报表模块按批次统计时不用GROUP BY时间范围再过滤毛刺数据第二排查问题时可以根据WHERE batch_id ?精确复现某一轮扫描的全部结果不用靠时间去猜。Component public class MonitorTask { private final AtomicBoolean running new AtomicBoolean(false); Scheduled(cron ${network.scan.cron}) public void scheduledScan() { if (!running.compareAndSet(false, true)) { log.warn(Previous scan still running, skip this round); return; } try { String batchId LocalDateTime.now().format( DateTimeFormatter.ofPattern(yyyyMMddHHmmss) ) _ ThreadLocalRandom.current().nextInt(1000, 9999); // 扫描逻辑 } finally { running.set(false); } } }compareAndSet是AtomicBoolean的原子操作两个线程同时进入时只有一个能成功另外一个拿到false直接返回。Spring Boot 默认的Scheduled是单线程执行器按理说不会并发触发同一个方法但如果你后来配置了TaskScheduler的线程池大小这个锁就成刚需了。定时任务的 Cron 走的是 Spring 的CronExpression六段式秒 分 时 日 月 周和 Linux Crontab 略有差别配0 */5 * * * ?表示每 5 分钟跑一次。4.2 离线判定要连续多轮探测避免单次抖动误报网络里的设备没你想的那么稳定。无线设备休眠唤醒时会丢 Ping交换机在高峰期也可能应答变慢如果单次探测失败就判离线运维的告警群里会全是噪音。常见的做法是连续 N 次失败才确认离线N 的建议值是 2 到 3 次。这个逻辑不用写额外的表利用设备表的last_online_time字段就能算——如果当前时间减去last_online_time大于 3 个扫描周期说明这台设备至少经过了 3 轮扫描都没有成功。告警规则应该放在扫描逻辑之外单独写一个评估方法。每轮扫描结束后把当前在线但last_online_time已经过期超过阈值的设备捞出来生成告警记录。这样把探测和告警解耦以后想调整告警阈值不用重新扫描一遍内网。public void generateOfflineAlerts(int maxMissedRounds) { LocalDateTime cutoff LocalDateTime.now() .minusMinutes(maxMissedRounds * 5L); ListDevice suspiciousDevices deviceMapper.selectList( new LambdaQueryWrapperDevice() .eq(Device::getStatus, 1) .lt(Device::getLastOnlineTime, cutoff) ); for (Device device : suspiciousDevices) { Alert alert new Alert(); alert.setDeviceId(device.getId()); alert.setAlertType(OFFLINE); alert.setMessage(Device device.getIpAddress() missed maxMissedRounds scan rounds); alert.setStatus(0); alertMapper.insert(alert); } }生成的告警记录通过一个独立的Alert表存储status字段为 0 表示未处理1 表示已确认。这样运维人员可以在前端页面上标记告警后续报表里也能统计设备的稳定性和告警分布。cutoff的计算隐含了一个假设——扫描周期固定为 5 分钟。如果配置里改了 Cron这里的5L要同步调否则判定逻辑就不准了。更好的做法是把扫描周期也做成配置项直接注入到这个方法里。4.3 告警通知接入钉钉 Webhook 与邮件模板告警不能只写在表里得推给值班的人。钉钉群机器人是最轻的方案——在群里加一个自定义机器人拿到 Webhook 地址用 Spring 的RestTemplatePOST 一个 JSON 过去消息就发出去了。安全方面Webhook 地址里自带access_token参数这个 token 要放在配置中心或者环境变量里别硬编码到代码中。邮件通知适合作为兜底渠道。Spring Boot 的spring-boot-starter-mail依赖配上application.yml里的 SMTP 参数就能用。企业内网一般有自己的邮件服务器host填内网地址port填 25不需要 SSL。模板方面用String.format拼 HTML 就行没必要上 Thymeleaf 模板引擎——告警邮件内容就那几行字引入模板引擎增加了维护成本。public void sendDingTalkAlert(Device device, String alertType) { JSONObject body new JSONObject(); body.put(msgtype, text); JSONObject content new JSONObject(); content.put(content, [告警] 设备 device.getIpAddress() 状态异常 - alertType); body.put(text, content); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityString request new HttpEntity(body.toJSONString(), headers); restTemplate.postForObject(dingTalkWebhook, request, String.class); }钉钉消息的content字段里可以嵌入设备名和 Link 到系统后台的地址但注意postForObject的返回值里包含errcode最好判断一下是否为 0不为 0 说明 Webhook 地址或签名有误。dingTalkWebhook变量从Value注入这样不同环境可以配不同的群测试环境就不会轰炸生产值班群了。5. Spring Boot 集成运维指标的进阶处理5.1 用 Spring Boot Actuator 暴露系统自身的存活状态你写的系统在监控别人别人怎么监控你Spring Boot 自带的 Actuator 模块用起来——加一个spring-boot-starter-actuator依赖再在application.yml里放开health和info端点一个标准的/actuator/health接口就有了。这个接口返回{status:UP}可以被上一层监控系统比如宝兰德自带的监控或者你自己写的另一个 Spring Boot 定时任务定期拉取。Actuator 的端点在 Spring Boot 2.x 之后默认只暴露health这是安全考虑。内网场景里可以把metrics、info、loggers也放出来它们对排查问题很有用。/actuator/metrics能看到 JVM 内存、线程数、GC 次数/actuator/heapdump能生成堆转储文件。注意heapdump这个接口在生产环境要小心——它可能包含敏感信息Spring Boot 的一个已知风险暴露出去后可能被未授权访问上面热词里也有提到 heapdump 敏感信息泄露漏洞所以至少要做访问控制或者只在内网开启。management: endpoints: web: exposure: include: health,info,metrics,loggers endpoint: health: show-details: alwaysshow-details: always会把数据库连接、磁盘空间这些明细信息也放到 health 响应里。如果你的系统接入了前端负载均衡这个配置还是很有用的——通过它就能区分是应用挂掉还是数据库不可达导致节点异常。不过如果系统要暴露到公网记得把show-details改回never。5.2 IP 冲突检测与 MAC 地址漂移监控小型网络管理系统除了设备在线状态最实用的一个功能是 IP-MAC 绑定检测。内网 DHCP 分配混乱或者有人手动改 IP就会造成 IP 冲突表现是某台设备的网络时好时坏。实现的方式是在扫描设备时除了 Ping还要拿到每台设备的 MAC 地址。Linux 下可以用arp -a或者ip neigh showWindows 下用arp -a然后从系统输出里做文本解析。拿到 MAC 地址后和数据库里device表存的mac_address对比。如果同一个 IP 出现了不同的 MAC或者同一个 MAC 出现在另一个 IP 上就说明网内有异常改动。这个逻辑应该写成一个独立的checkMacMigration方法在每轮扫描后执行。MAC 地址解析时注意大小写和分隔符统一——有的系统输出的是AA:BB:CC:DD:EE:FF有的是aa-bb-cc-dd-ee-ff入库前建议先统一成toUpperCase()且用冒号分隔。5.3 小型系统如何平滑升级到 Spring Boot 3.x如果你的项目还在用 Spring Boot 2.5 或者 2.7现在线上跑着没问题但新功能开发时想升级怎么平滑过渡是一个得想清楚的事。Spring Boot 3.x 最大的变化是基准版本从 Java 8 提到了 Java 17这意味着大量依赖也要跟着升级比如 MyBatis-Plus 3.5.3 才支持 Spring Boot 3Swagger 2.9.2 在 3.x 下用不了得换springdoc-openapi。升级策略上建议分三步走。第一步把 Spring Boot 版本升级到当前 2.x 的最新补丁版跑一遍全部测试用例确保没回归。第二步升级 Jakarta EE 相关的依赖Spring Boot 3.x 把javax.*改成了jakarta.*这是兼容性上最大的坑需要全局搜索替换 import。第三步调整配置项server.address这类配置没变但spring.redis.*会变成spring.data.redis.*迁移的时候对照官方迁移清单一项项过。整体预算时间留给两天头一天改依赖和代码编译错误第二天跑功能和压测。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →