简介:这份源码是面向无人机智慧巡检场景的后端系统完整实现,基于轻量级、易扩展的若依框架与Java语言构建,适合具备一定Java与Spring开发基础、希望研究行业级后端架构的开发者参考。系统围绕实时视频流检测报警、报表数据查看与大屏可视化展示三大核心功能展开,可应用于电力巡检、农业监测、城市规划等无人机作业领域。压缩包共405个文件,约11.3MB,以333个Java源文件为主体承载业务逻辑,辅以29个XML与3个YAML配置文件、14个VM模板文件,另有PNG、JPG、JPEG等界面与大屏视觉素材,以及数据库脚本、proto协议文件与readme说明,整体呈现前后端分离、模块化与可配置的设计思路。目前已有363人学习下载。通过研读其目录结构与代码组织,读者可掌握若依框架下巡检后端的搭建方式、实时数据处理与可视化展示的实现路径,并获得一套可直接借鉴的行业后端解决方案。
1. 若依框架做无人机智慧巡检后端:这套源码到底解决什么问题
拿到「基于若依框架的无人机智慧巡检后端设计源码」这个题目时,我第一反应不是去翻代码,而是先想清楚一件事:无人机巡检这条链路里,后端到底卡在哪。真实场景是这样的——一架四轴无人机按航线飞完一段输电线路或光伏区,机载相机拍回几百张带 GPS 坐标的图,落地后这些图要入库、要按航段归档、要跑缺陷识别、要生成巡检报告、要能在地图上回放飞行轨迹。如果每个环节都靠人工拷卡、Excel 登记、微信传图,一个班组一天处理不完一个架次。这套后端要干的就是把「飞行任务下发 → 媒体回传归档 → AI 识别 → 缺陷工单 → 报告导出」串成一条可查询、可追溯的数据流水线。
若依(RuoYi)在这里的价值不是它多先进,而是它把 RBAC 权限、菜单、部门、字典、操作日志、代码生成器这些「每个管理系统都要写一遍」的东西提前给好了。巡检系统天然是多角色协作:飞手、审核员、检修班组、管理员,权限颗粒度要求高,用若依能省掉至少两周的权限模块开发。这套源码适合两类人:一是手里有巡检业务、想快速搭出可用后端的团队;二是想拿一个真实业务练若依二次开发的 Java 后端。下面我按「先立住架构、再动手复现、最后讲坑」的顺序拆开讲。
2. 巡检后端的分层架构与若依二次开发边界
2.1 为什么选若依而不是从零搭 Spring Boot
从零搭一个 Spring Boot 后端,权限、日志、代码生成这些基础设施至少要写三到五周,而且很容易在 RBAC 的数据模型上反复返工。若依已经把用户、角色、菜单、部门、岗位、字典、参数配置、操作日志、登录日志这套做完了,还带一个能根据数据库表反向生成 Controller/Service/Mapper/Vue 页面的代码生成器。对巡检这种「业务表多、CRUD 密集」的系统,代码生成器能省掉大量重复劳动。
但要注意边界:若依是「后台管理框架」,不是「业务中台」。它擅长的是管理端 CRUD 和权限,不擅长高并发媒体处理、实时消息、大文件流式上传。所以我的做法是——把若依当管理后台的底座,把巡检核心业务(任务调度、媒体处理、AI 推理调用)拆成独立模块,通过接口和若依主应用解耦。这样既吃到若依的权限红利,又不会被它的单体结构拖住。
2.2 分层结构与模块划分
我一般把巡检后端分成四层,对应到若依的多模块结构里:
| 层 | 职责 | 对应若依模块 | 典型类 |
|---|---|---|---|
| 接入层 | 鉴权、参数校验、限流 | ruoyi-admin | Controller |
| 业务层 | 任务、航线、媒体、缺陷、工单 | ruoyi-inspection | Service |
| 领域层 | 巡检领域模型、状态机 | ruoyi-inspection-domain | Entity/Enum |
| 基础设施层 | 存储、消息、AI 调用 | ruoyi-common + 自建 | Mapper/Client |
关键设计点是巡检任务的状态机。一个任务从「待下发」到「已完成」要经过:待下发 → 已下发 → 飞行中 → 待归档 → 识别中 → 待审核 → 已完成,中间还可能进「异常」「已取消」。这个状态机不要写在 Controller 里,要单独抽一个InspectionTaskStateMachine,用枚举 + 转移表控制,否则后面加一个「复飞」状态就会到处改 if-else。
2.3 数据库表设计的最小集合
巡检业务的核心表我一般先建这几张,其余用代码生成器补:
-- 巡检任务表 CREATE TABLE inspection_task ( task_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '任务ID', task_code VARCHAR(64) NOT NULL COMMENT '任务编号', route_id BIGINT NOT NULL COMMENT '航线ID', drone_sn VARCHAR(64) DEFAULT NULL COMMENT '无人机序列号', pilot_id BIGINT DEFAULT NULL COMMENT '飞手用户ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待下发 1已下发 2飞行中 3待归档 4识别中 5待审核 6已完成 9异常', plan_start DATETIME DEFAULT NULL COMMENT '计划开始时间', actual_start DATETIME DEFAULT NULL COMMENT '实际开始时间', actual_end DATETIME DEFAULT NULL COMMENT '实际结束时间', del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志', create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (task_id), UNIQUE KEY uk_task_code (task_code), KEY idx_status (status) ) ENGINE=InnoDB COMMENT='巡检任务表'; -- 巡检媒体表 CREATE TABLE inspection_media ( media_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '媒体ID', task_id BIGINT NOT NULL COMMENT '任务ID', media_type TINYINT NOT NULL COMMENT '1图片 2视频', file_path VARCHAR(512) NOT NULL COMMENT '存储路径', longitude DECIMAL(10,7) DEFAULT NULL COMMENT '经度', latitude DECIMAL(10,7) DEFAULT NULL COMMENT '纬度', altitude DECIMAL(8,2) DEFAULT NULL COMMENT '海拔', shoot_time DATETIME DEFAULT NULL COMMENT '拍摄时间', ai_status TINYINT NOT NULL DEFAULT 0 COMMENT '0未识别 1识别中 2已识别 3识别失败', PRIMARY KEY (media_id), KEY idx_task (task_id) ) ENGINE=InnoDB COMMENT='巡检媒体表';inspection_task的status用 TINYINT 而不是字符串,是为了索引效率和状态机判断;inspection_media把经纬度单独存字段而不是塞进 JSON,是因为后面要按地理范围查「某段线路的所有缺陷图」,JSON 字段没法走空间索引。ai_status独立于任务状态,是因为一个任务下几百张图,识别是逐张推进的,任务级状态和媒体级状态必须分开。
3. 从若依代码生成器到巡检业务接口的落地步骤
3.1 用代码生成器生成基础 CRUD
若依的代码生成器在「系统工具 → 代码生成」里,导入上面建好的表,配置好字段类型和查询方式,就能生成一套 Controller/Service/Mapper/XML 和前端页面。生成前有几个配置必须改对:
- 实体类名:
InspectionTask,不要用默认的InspectionTask加表前缀。 - 字段的「查询方式」:
task_code选LIKE,status选EQ,plan_start选BETWEEN。 - 「显示类型」:
status选下拉框,字典类型填inspection_task_status,这样前端自动渲染成状态标签。
生成后先别急着改,跑一遍确认增删改查通,再动业务逻辑。这一步的产物是「能跑的骨架」,不是「能用的系统」。
3.2 任务下发接口的实现
任务下发不是简单改个状态,它要校验航线是否存在、无人机是否可用、飞手是否有资质,然后推一条消息给调度服务。核心代码如下:
@PreAuthorize("@ss.hasPermi('inspection:task:dispatch')") @Log(title = "巡检任务", businessType = BusinessType.UPDATE) @PostMapping("/dispatch/{taskId}") public AjaxResult dispatch(@PathVariable Long taskId) { InspectionTask task = inspectionTaskService.selectById(taskId); // 1. 状态校验:只有待下发状态能下发 if (!TaskStatusEnum.PENDING_DISPATCH.getCode().equals(task.getStatus())) { return AjaxResult.error("当前任务状态不允许下发"); } // 2. 航线与无人机可用性校验 Route route = routeService.selectById(task.getRouteId()); if (route == null || !"0".equals(route.getStatus())) { return AjaxResult.error("航线不存在或已停用"); } Drone drone = droneService.selectBySn(task.getDroneSn()); if (drone == null || drone.getStatus() != 1) { return AjaxResult.error("无人机不可用"); } // 3. 状态机推进 + 推送调度消息 taskStateMachine.fire(task, TaskEvent.DISPATCH); inspectionTaskService.updateById(task); mqProducer.send("inspection.dispatch", buildDispatchMsg(task, route)); return AjaxResult.success("下发成功"); }@PreAuthorize用的是若依内置的权限校验表达式,@ss.hasPermi会去查当前用户的权限集合,这是若依权限体系的标准用法,不要自己另写一套。taskStateMachine.fire是前面说的状态机,把「校验通过后该转到哪个状态」这件事收敛到一处。mqProducer.send是异步推送,下发接口本身不等待无人机响应,避免接口超时。
3.3 媒体回传与归档
媒体回传有两种模式:无人机落地后批量上传,或飞行中通过图传链路实时回传。批量上传用若依的文件上传组件改造即可,关键是上传后要解析 EXIF 拿到经纬度和拍摄时间,写入inspection_media。实时回传则要走独立的 WebSocket 或 MQTT 通道,不要塞进若依的 HTTP 接口。
public void handleMediaUpload(MultipartFile file, Long taskId) { String path = fileService.upload(file); MediaMeta meta = exifParser.parse(file); // 解析EXIF InspectionMedia media = new InspectionMedia(); media.setTaskId(taskId); media.setFilePath(path); media.setLongitude(meta.getLongitude()); media.setLatitude(meta.getLatitude()); media.setShootTime(meta.getShootTime()); media.setAiStatus(0); inspectionMediaService.insert(media); // 触发异步识别 aiTaskExecutor.submit(() -> aiClient.recognize(media.getMediaId(), path)); }exifParser要处理一个现实问题:部分无人机导出的图 EXIF 里 GPS 是度分秒格式,有的是十进制,解析时要统一转成十进制存储,否则地图上打点会偏。aiTaskExecutor用独立线程池,不要用若依默认的ThreadPoolTaskExecutor,因为识别任务耗时长,会拖垮其他异步任务。
4. 巡检后端避坑:5 个我踩过的真实问题
4.1 现象:任务列表查询越来越慢,翻到第 10 页要 3 秒
原因:inspection_task表数据量到几十万后,若依默认的分页查询在status和create_time上没有合适索引,全表扫描。解决:给status、create_time、task_code建组合索引,并且把「已删除」的数据用del_flag过滤掉后单独归档到历史表,主表只留近半年的活跃任务。
4.2 现象:媒体上传到 200MB 以上就失败
原因:若依默认的spring.servlet.multipart.max-file-size是 10MB,且 Nginx 的client_max_body_size默认 1MB。解决:两处都要改,Spring 侧调到 500MB,Nginx 侧调到 512MB,同时把上传接口改成流式写入而不是先读进内存,否则大文件会把 JVM 堆打满。
4.3 现象:AI 识别结果和图片对不上,张冠李戴
原因:异步识别任务用了共享的mediaId变量,多线程下被覆盖。解决:识别任务提交时把mediaId和filePath作为不可变参数传入,不要在 lambda 里引用外部可变变量。这个坑很隐蔽,因为单张测试永远正常,批量跑才暴露。
4.4 现象:飞手只能看到自己的任务,但审核员看不到任何任务
原因:若依的数据权限默认按部门隔离,审核员和飞手不在同一部门时,审核员查不到数据。解决:在@DataScope注解上给审核员角色配置「全部数据权限」,或者自定义数据权限切面,按「任务参与人」而不是「部门」过滤。巡检业务的数据权限维度是「人」不是「部门」,这点和若依默认假设不同。
4.5 现象:任务状态偶尔卡在「识别中」不动
原因:AI 识别服务超时或抛异常后,没有回写ai_status,媒体永远停在「识别中」。解决:识别任务加超时兜底,超过 5 分钟未回调的,定时任务扫描出来标记为「识别失败」并告警。任何异步链路都要有「失败回写」和「超时兜底」,这是血泪经验。
5. 让巡检后端真正可用的两个进阶技巧
5.1 用状态机 + 事件表做全链路可追溯
巡检系统最怕的是「这张图到底是谁在什么时候识别出缺陷的」说不清。我的做法是在状态机每次fire时,往inspection_task_event表写一条事件记录:任务ID、事件类型、操作人、时间、备注。这样任何一个任务的全生命周期都能查出来,出问题时有「后悔药」。这张表只增不改,数据量大了按月分表。
public void fire(InspectionTask task, TaskEvent event) { TaskStatusEnum from = TaskStatusEnum.of(task.getStatus()); TaskStatusEnum to = transitionTable.get(from, event); if (to == null) { throw new BizException("非法状态转移: " + from + " -> " + event); } task.setStatus(to.getCode()); eventService.record(task.getTaskId(), event, SecurityUtils.getUsername()); }transitionTable用Map<Pair<Status,Event>, Status>初始化,所有合法转移集中定义,新增状态只改这一处。eventService.record里拿当前登录用户用若依的SecurityUtils,不要自己从 ThreadLocal 取。
5.2 巡检报告的异步导出与断点续传
巡检报告通常要汇总一个任务下所有媒体、缺陷、工单,生成 PDF 或 Excel。同步生成会超时,我的做法是:导出请求先落一条export_job记录,返回 jobId,前端轮询进度;后端用独立线程池生成,生成过程中每完成 10% 更新一次进度;文件生成后存到对象存储,返回下载链接。如果生成中途服务重启,job 状态停在「生成中」,启动时扫描超过 30 分钟的 job 标记为失败并允许重试。这套机制不复杂,但没有它,运维会被「报告导不出来」的电话打爆。
最后说个习惯:我做完任何一个若依二次开发项目,都会先把代码生成器生成的原始代码和手写业务代码用不同包名隔开,比如com.xxx.inspection.generated和com.xxx.inspection.biz。这样下次若依升级或重新生成代码时,不会覆盖掉手写的业务逻辑。这个习惯帮我省过至少两次大返工。希望帮到你。
本文还有配套的精品资源,点击获取