做企业网络管理这几年我处理过最多的故障工单不是硬件损坏而是IP地址引起的各种破事地址冲突让一整个办公室断网、想查某台服务器的IP记录翻半天台账还对不上、某个网段快耗尽却分不清哪些地址是僵尸。当时公司用的是共享Excel表格开头还有人维护后来直接成了摆设。被逼急了我花业余时间做了一套基于Android的企业网络主机IP地址管理系统把网段规划、IP台账、主机信息、设备发现和审计日志全部收拢到手机端——管理员不需要蹲在电脑前在地下车库、在机房、在分公司现场都能确认地址状态、处理冲突记录。从整理需求到部署上线大概用了两个月这套项目后来整理成了完整的课程设计资料包含源码、论文和部署文档。这篇文章不堆概念就按我实际开发的过程讲为什么选Android、数据库怎么做、设备识别怎么写、最后部署踩了哪些坑。1. 一套IP台账为什么能把企业网络管理员从工单里捞出来1.1 Excel台账是怎么一步步失去参考价值的Excel台账失控通常不是一朝一夕。第一个原因是缺少并发控制管理员A在表格里记了一行管理员B没刷新就改了另一行两个版本在微信里传来传去最后大家手里各有一份最新版。第二个原因是没有强制关联设备下线没人销账、IP重新分配没人更新、私接设备没人发现台账和现实世界的偏差越滚越大。第三个原因是Excel提供不了发现能力你没法自动知道某个地址现在到底通不通、有没有人占着。这三个问题叠加的结果是表格越写越厚可信度越来越低到最后没人看它IP管理重新回到现场打电话猜的状态。我印象很深的一次事故市场部新装修的办公区开不了网排查一上午才发现数据机房里一台测试设备抢占了分配给新电脑的地址而Excel台账上这个地址还标着空闲。整整20天这个地址被一台不干正经事的设备占着没人知道。这类事故单靠管理员责任心是堵不住的需要一个能把台账、真实状态、操作记录绑在一起的系统。1.2 为什么管理员更需要一部随身终端而不是守着电脑网管的工作场景决定了手机优先这条需求。一个企业网管一天的活动半径大概是这样的上午在机房配合服务器上线下午去楼层弱电井查线路中途可能被叫去分公司接新设备。真正坐在工位上查看台账的时间其实很少更常见的是站在机柜前需要马上回答这个IP现在给谁用。背一台笔记本成本高打开又要等启动掏出手机刷一下就能看到地址池状态、占用人、最近在线时间这个体验是质的区别。Android在这个场景下有天然优势它是开放系统允许应用调用底层网络命令做连通性测试而且开发成本低企业也可以直接装APK不需要走应用商店审核。基于这些考虑我把移动端锁定为Android。1.3 系统最终回答的三个问题做这个项目之前我先把需求压缩成三个问题后续所有界面、接口、数据库表设计都围绕它们展开地址现在属于谁——台账查询看占用部门、使用人、设备类型、最近在线时间。地址现在通不通——状态探测看在在线、离线、冲突、未知设备。是谁在什么时候改的——审计追溯每次变更都有记录。凡是回答不了这三个问题的功能砍掉或往后放。这个取舍很重要不然很容易做成一锅乱炖的管理系统。2. 整体架构与技术选型Android端、服务端、数据库怎么分工2.1 为什么这种系统必须有后端一开始我也想过是不是Android端一个App就能搞定数据放SQLite扫描用手机自己跑。但实际用起来会出现一堆问题多台手机数据不统一、换手机数据就丢、扫描大网段耗电发热、手机不在现场就什么都干不了。企业的IP台账是共享数据不是个人数据必须有一个中心化的服务端来保证一致性和并发安全。架构分三层。数据层用MySQL承担持久化和唯一约束服务端用Spring Boot MyBatis专门处理业务规则、地址池生成、定时扫描任务和对外APIAndroid端只负责展示、录入和操作触发。扫描任务放服务端还有一个重要原因服务器7x24在线具备稳定的网络出口和计算资源手机休眠了就没人去探测地址了。2.2 技术选型的几个理由和替代方案组件我选的理由替代选择AndroidJava OkHttp RecyclerView资料多、上手快、课程设计维护成本低Kotlin门槛略高但官方更推荐服务端Spring Boot 2.x MyBatisjar包直接跑内嵌Tomcat省去部署麻烦SSM需要装配一堆XML验证阶段比较折腾数据库MySQL 8企业最常见的数据库索引、事务都够用PostgreSQL也行但多一层学习成本通信HTTP JSON简单直观内网部署不需要复杂协议WebSocket可以推送但项目初期没必要选型的时候我纠结过要不要上HTTPS最终决定内网明文HTTP即可配合Token鉴权演示环境不折腾证书。但这里留了个隐患后面部署章节会讲Android 9对明文HTTP的限制。2.3 接口设计与Token认证服务端接口全部走RESTful风格路径按资源命名POST /api/auth/login 登录GET /api/segment/list 网段列表POST /api/segment/add 新增网段自动生成地址池GET /api/ip/page 分页查询IP台账POST /api/ip/allocate 批量分配POST /api/ip/recycle 批量回收GET /api/scan/report 最近一次扫描结果登录成功后返回一个Token客户端存在本地每次请求放进Header的Authorization字段。服务端用一个拦截器统一校验未登录直接返回401。Token过期设成24小时管理员一般一天用几次这个时长够用如果做商用系统换成JWT加刷新机制更合理。3. 数据库设计把所有IP放进一张状态机里3.1 五张核心表和它们的分工数据库表不在多在语义清晰。我最终保留了五张核心表sys_user管理员、net_segment网段、ip_address地址台账、audit_log审计日志、scan_result扫描结果快照。每张表的定位net_segment一段地址池的根信息包括网段名、网络号、掩码、网关、VLAN、所属物理位置。ip_address每一条IP记录状态、MAC、主机名、使用人、部门、设备类型、最近在线时间都在这张表。audit_log所有状态变更的流水账出了纠纷靠它还原现场。scan_result每次扫描的快照存结果和时间前端展示最近一次扫描报告直接读这张表不重复计算。我没在表之间加外键约束只靠segment_id关联。原因是批量回收和生成地址池时外键校验反而拖慢速度引用完整性由Service层逻辑保证。这一点和教科书主张的外键必须加不太一样但实际运维中省了不少麻烦。3.2 关键建表SQL和状态字段设计状态字段用TINYINT而不是字符串原因很简单程序里做状态机判断时数字比较比字符串比较更不容易出错也省空间。0空闲、1保留、2已分配、3冲突、4异常后面接口全按这套来。CREATE TABLE net_segment ( id INT PRIMARY KEY AUTO_INCREMENT, segment_name VARCHAR(64) NOT NULL COMMENT 网段名称, network VARCHAR(64) NOT NULL COMMENT 网络号, mask_len INT NOT NULL COMMENT 掩码长度如24, gateway VARCHAR(64) DEFAULT NULL COMMENT 网关, vlan INT DEFAULT NULL COMMENT VLAN编号, location_desc VARCHAR(128) COMMENT 物理位置描述, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_network_mask (network, mask_len) ); CREATE TABLE ip_address ( id INT PRIMARY KEY AUTO_INCREMENT, segment_id INT NOT NULL, ip_addr VARCHAR(64) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1保留 2已分配 3冲突 4异常, mac_address VARCHAR(32) DEFAULT NULL, hostname VARCHAR(64) DEFAULT NULL, owner_name VARCHAR(32) DEFAULT NULL, owner_dept VARCHAR(64) DEFAULT NULL, device_type VARCHAR(32) DEFAULT NULL COMMENT PC/服务器/网络设备/打印机等, last_seen_time DATETIME DEFAULT NULL, remark VARCHAR(255), UNIQUE KEY uk_segment_ip (segment_id, ip_addr), KEY idx_status (status) ); CREATE TABLE audit_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, action_type VARCHAR(16) COMMENT allocate/recycle/update/scan, ip_id INT DEFAULT NULL, before_status TINYINT, after_status TINYINT, detail VARCHAR(255), created_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_ip_time (ip_id, created_time) );三个容易忽略的细节net_segment上的networkmask_len唯一索引防止重复录网段ip_address上的status索引因为列表页最高频的查询是按状态筛选audit_log按ip_idcreated_time建联合索引排查单个地址的历史记录时不会全表扫。3.3 状态机的流转规则IP记录不是随便改的每个操作都必须符合状态机规则空闲 - 已分配管理员执行分配填写使用人、部门、设备类型和MAC。已分配 - 空闲回收操作只有已分配或保留状态可以回收。空闲 - 保留预留给特殊设备比如下个月要上线的服务器。已分配 - 冲突扫描发现这台设备在线但MAC对不上台账。冲突 - 已分配管理员核对后更新MAC恢复为正常已分配。这套规则在服务端Service层统一校验Android端只是把请求发过来不能绕过后端直接改数据库。我在后端加了一个兜底任何状态变更操作都先查一次当前状态如果和前端提交的期望状态不一致直接拒绝并提示数据已被他人修改请刷新。3.4 网段地址池的自动生成逻辑手工一个个录IP是体力活也是必然出错的地方。新增网段时后端根据掩码自动生成整个地址池初始化状态全部标成空闲再自动把网络号、广播地址、网关排除掉。生成逻辑的核心就是按掩码长度算出可用主机数量然后逐段填充public ListString generateHostIps(String network, int maskLen, String gateway) { long base ipToLong(network); long count 1L (32 - maskLen); ListString result new ArrayList(); for (long offset 1; offset count - 1; offset) { String ip longToIp(base offset); if (ip.equals(gateway)) continue; result.add(ip); } return result; }这里交代一个计算细节for循环从1开始是为了跳过网络号结束条件是count-2是为了跳过广播地址中间遇到网关地址再单独跳过。对一个/24网段可用地址就是2~254里排除网关之后的所有地址。生成完调用批量插入一次性写入ip_address表。这个过程对用户是异步的接口先返回网段创建成功地址池生成中后台跑完再更新网段状态避免几千条记录在前端等太久。4. 设备发现与主机识别让系统自己回答这个地址谁在用4.1 服务器端扫描任务的设计扫描是整个系统最接近网络运维本质的部分。实现方案分三层Ping探测、ARP解析、信息补全。Ping探测用来判断IP是否在线原理是发ICMP回显请求目标主机回复回显应答就算在线。考虑到防火墙可能屏蔽ICMP我加了一个兜底Ping不通的主机再用TCP连接探测常见端口80、22、443、3389只要任何一个端口能连上就判定主机在线。两轮都失败才标记为离线。探测并发用Java ThreadPoolExecutor线程数控制在200以内扫描一个/24网段大约需要3到6秒。线程数不是越大越好太大反而会因网络栈瓶颈丢包。我实测过200并发对这个场景最稳100并发慢20%500并发错误率明显上升。4.2 ARP表与MAC解析把IP和物理地址对上Ping通一个地址之后必须拿到MAC才能和台账做精确比对。Linux服务器上ARP表就挂在/proc/net/arpPing过之后内核会自动把这个IP解析到MAC并填入表里。读取这个文件按IP过滤出MAC字段就行这个操作不需要root权限特别适合在服务端任务里用。这里有个经验单纯读ARP表如果某个IP刚才没被Ping过表里就没有条目。所以扫描顺序必须是先Ping网段内所有地址再统一读ARP表反了就什么都拿不到。读完ARP表大部分Windows主机和网络设备都能拿到MAC拿不到MAC的历史遗留IP我会在报告里单独标一个MAC未知让管理员人工补录。4.3 Android端能不能自己做扫描这个问题的答案有点微妙。Android上没有root权限应用没法创建原始ICMP socket但系统内置了ping命令应用可以Runtime.exec去调用它。private boolean pingHost(String ip) { try { Process process Runtime.getRuntime().exec(new String[]{ping, -c, 1, -W, 1, ip}); int code process.waitFor(); return code 0; } catch (Exception e) { Log.e(Ping, ping failed, e); return false; } }实际开发中发现三个坑。第一不同厂商Android系统的ping命令路径和参数不完全一致某些国产ROM的ping输出会多几行解析exitCode比解析输出文本更可靠。第二在手机上一个一个Ping一个/24网段排队等结果体验很差全部并发又会发热掉电。第三后台任务在系统休眠时很可能被杀掉。所以我把完整的扫描任务放在服务端Android端只保留单点探测功能给管理员站在现场判断某台设备通不通用。4.4 扫描结果与台账比对的规则扫描只是拿到原始数据真正有价值的是和台账比对后的结论。我实现了三个规则台上登记了这个IP且在线MAC与台账一致标记在线更新last_seen_time。台上登记了这个IP且在线但MAC对不上标记冲突同时把扫描到的MAC写进scan_result作为证据等管理员处理。台上没有这个IP但探测到它在线标记未知设备在报告里单独分组展示。比对逻辑放在服务端的扫描任务里同步执行每次扫描结束生成一张scan_report。Android端进入扫描报告页时直接拉最近一份报告不轮询界面清爽很多。5. Android客户端落地界面、交互与同步的取舍5.1 工程分包与网络层搭建Android工程我用Android Studio创建Gradle版本固定好之后不要随手升级老项目升级Gradle经常连带一堆依赖冲突。包结构我建议按功能模块拆而不是按MVC三层拆维护起来更顺手ui.login登录页ui.dashboard首页仪表盘ui.segment网段管理ui.ipaddressIP台账列表与详情ui.report扫描报告networkRetrofit接口定义、请求体、响应体db本地缓存的Room数据库可选网络层用Retrofit OkHttp Gson接口定义和业务数据解耦。贴一个最核心的接口定义public interface IpApi { POST(api/auth/login) CallLoginResp login(Body LoginReq req); GET(api/ip/page) CallIpPageResp page(Query(keyword) String keyword, Query(status) Integer status, Query(page) int page); POST(api/ip/allocate) CallBaseResp allocate(Body AllocateReq req); GET(api/scan/latest) CallScanReportResp latestReport(); }Retrofit的回调本身就支持线程切换直接在onResponse里更新UI即可。要注意的是请求失败不能只弹一个Toast就完事要区分网络不可达服务端返回错误Token过期三种情况分别给用户下一步提示。5.2 首页仪表盘和IP列表的实现思路首页仪表盘放四个统计卡片空闲、已分配、冲突、未知设备下面接最近扫描结论列表。图表我尝试过MPAndroidChart但后来发现饼图在这种管理工具里价值一般管理员更关心的是现在有没有冲突、有多少未知设备直接用四个带颜色的大数字卡片更直观。IP台账列表用RecyclerView数据源来自服务端分页接口。列表项除了显示IP、使用人、部门之外状态一定要用色块或者Tag区分人眼扫列表时颜色比文字快得多。我用的色卡空闲灰色、已分配绿色、冲突红色、保留蓝色、异常橙色。列表页还需要支持两种筛选按状态筛选状态Tab切换按关键字搜索IP、使用人、部门、MAC任意匹配。搜索建议在服务端做LIKE查询不要把所有数据拉到本地筛几万条记录分页拉全量会卡死手机。列表底部的加载更多用一个简单进度条提示就行没必要做花哨动画。5.3 批量分配与状态流转的交互细节IP列表支持多选选中后底部弹出操作条提供批量分配批量回收批量保留三个动作。批量分配会弹出一个表单填写部门、使用人和设备类型一次提交批量请求。这里我要强调前端校验的一个细节批量提交前要把每个IP的当前状态一起带上去。服务端判断期望状态和实际状态不一致时这条记录会被拒绝但其他记录正常处理。响应体里返回成功数和失败明细前端分两条消息展示成功分配12条失败3条失败明细直接列出IP和原因避免用户瞎猜。这个部分成功的处理逻辑是真实业务里最容易被忽略的。5.4 本地缓存与离线场景虽然是内网系统但管理员走进地下车库时可能完全没有网络。我给列表页加了Room做本地缓存打开页面先显示缓存再静默拉服务端刷新。缓存数据有效期30分钟超过就提示数据可能不是最新的。加载更多分页时只追加新数据不整体重写表。这个功能投入不大但实际使用感受差别很大——管理员在地下室要看地址信息打开的瞬间就有内容和转圈10秒才出数据的体验完全不一样。6. 部署文档不会写的事从开发机搬到生产环境的踩坑记录6.1 服务端部署三个最容易被文档忽略的细节部署本身不复杂JDK装好、MySQL建好库、导入初始化SQL、把Spring Boot打成jar包运行。但我踩过三个坑值得单独说。第一个坑是MySQL 8的认证插件。新装MySQL 8默认用caching_sha2_password而很多课程设计用的JDBC驱动版本比较老连接时报Public Key Retrieval is not allowed。解决方案两个任选其一JDBC连接串里加allowPublicKeyRetrievaltrue或者建账号时指定mysql_native_password。我上线时选的是后者避免在连接串里埋一个不太安全的参数。第二个坑是时区问题。MySQL连接串如果没有serverTimezone会在夜间任务运行时出现时间错乱。我的连接串最终是这样的spring: datasource: url: jdbc:mysql://localhost:3306/ipam?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: ipam_user password: xxxxxx第三个坑是服务器防火墙。CentOS防火墙默认只放行22端口8080端口外部访问不到。需要执行firewall-cmd --add-port8080/tcp --permanent然后firewall-cmd --reload。如果不加这一步你会发现手机连不上服务端但服务端自己用curl测试一切正常。6.2 Android端连不上后端的排查清单真机调试连不上后端是几乎每个做这类项目的人都会遇到的坎。按下面清单排查基本一轮能定位手机和服务器是否在同一局域网能互相Ping通。baseUrl是不是写了http://localhost:8080。Android手机上localhost是手机自己必须写服务器的内网IP。Android 9及以上默认禁止明文HTTPAndroidManifest的application节点需要加android:usesCleartextTraffictrue。服务端进程是否真的在监听0.0.0.0:8080而不是只监听了127.0.0.1。Spring Boot默认监听所有网卡但如果你用了某些IDE的代理配置可能被改成回环地址。如果是Android模拟器访问宿主机要用http://10.0.2.2:8080而不是127.0.0.1。我自己的项目就死在第四和第五点上先在模拟器上开发一切正常换真机全连不上折腾一小时才发现防火墙没放行。这类问题有个共性——开发环境能跑、生产环境不能跑多半是环境差异不是代码逻辑。6.3 上线一个月后的三次翻车和补救系统跑起来只是开始我总结三次真实翻车。第一次是服务器时间漂移。扫描任务记录了last_seen_time但服务器集群时钟没做同步导致某些地址显示的最近在线时间比实际早了几分钟甚至更多管理员误判设备离线。补救方案是给服务器配置NTP同步并在展示逻辑里加了一个容忍窗口当前时间与last_seen_time相差超过阈值才标记为离线。第二次是扫描任务线程池泄漏。早期实现里每轮扫描都new一个ThreadPoolExecutor跑完不shutdown。十天之后服务器的线程数涨到上千个最后系统直接卡死。补救方案是把线程池改成全局单例设置核心线程数和队列上限扫描任务串行执行。第三次是慢查询。地址池从几千条涨到几万条之后按状态筛选的接口响应从200毫秒涨到了3秒。排查发现没有走索引因为status字段的区分度低MySQL优化器有时候会放弃索引。补救方案是加一个(segment_id, status)的联合索引并且把分页从limit offset改成基于上一页最大id的键值分页。6.4 部署文档里应该补充的运维说明我整理部署文档时除了安装步骤还会放进下面这些运维相关内容数据库定期备份命令mysqldump -uipam_user -p ipam /backup/ipam_$(date %F).sql扫描任务日志的位置和查看方式logs/scan.log排查扫描失败先看这个文件。服务端升级包步骤停服、替换jar、启动、查看健康检查接口按这个顺序来别直接覆盖运行中的jar。常见问题表把连接串、防火墙、明文流量这三个问题写进FAQ方便接手人快速定位。这些东西在论文里通常只有运维注意事项一句话但真正接手部署的人依赖的恰恰是这些具体细节。7. 做完这套系统后我给自己总结的经验清单代码写完了部署跑通了回头总结一套经验算不上什么大道理但每条都是真金白银换的。第一先把状态机画清楚再动数据库。我在开发初期改过三次状态字段每次都要连带改一堆SQL和接口浪费的时间比写代码多得多。状态字典、流转规则、谁能触发哪个流转这些在设计文档里写清楚后面开发就是机械填充。第二审计日志从第一天就要写。等系统跑起来再回头补审计几乎不可能因为历史数据已经丢了。所有改状态的操作、扫描结果的重大变化都值得进审计表。第三一定要在真机上测试网络功能。模拟器会掩盖太多问题明文流量、防火墙、真机的休眠策略都是真机环境下才会暴露的。我见过太多课程设计在模拟器上完美运行一装真机立刻打回原形。第四扫描任务别指望手机。手机既不是7x24在线也不适合当网络探测源把扫描放在服务端手机只当遥控器看结果这是这个项目里性价比最高的决定。第五如果后续要商用有四个方向值得优先做对接DHCP租约自动同步给设备贴带二维码的标签扫一扫直接看这个地址的历史记录增加报表功能按月统计每个部门的地址使用率把Android端的Token换成JWT并支持过期刷新。这些都是我在使用过程中真实产生的需求做出来比继续加花哨页面更有价值。