尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

无人机巡维保障系统后端:状态机与轨迹数据处理实践

发布时间:2026/9/23 22:44:38

资讯中心
01
ARTICLE

无人机巡维保障系统后端:状态机与轨迹数据处理实践

无人机巡维保障系统后端:状态机与轨迹数据处理实践
简介基于Java语言的uav-patrol-backend巡维保障系统后端源码面向无人机巡维业务的后端开发人员提供了一套模块化、可扩展的服务端实现可支撑无人机巡维任务管理、设备状态监控与数据上报等核心服务。项目包含51个文件、共93KB以44个Java源文件为主体涵盖核心业务逻辑、数据处理与接口交互另含3个XML及2个YAML配置文件用于数据库连接、环境参数与日志级别等灵活配置并附带版本忽略规则文件与说明文档便于版本管理与快速上手。已有332人学习浏览适合正在搭建无人机巡维管理系统或希望参考Java后端工程结构的中高级开发者。整套代码按模块划分清晰展示了Maven项目构建、多环境配置与源码组织方式通过阅读源码可掌握Spring Boot工程配置、REST接口设计等典型后端实践可直接用于功能扩展、二次开发或学习参考。1. 巡维保障系统后端一套 Java 工程到底在解决什么先还原一个场景一架无人机按计划从机巢起飞沿输电线路巡检拍到疑似隐患点后台要实时看到飞行轨迹、接收照片、判断是否需要派人去现场复核。这个过程里飞手只是“按了启动”真正干活的是一套后端服务——它负责下发任务、接收设备上报、处理海量轨迹点、管理隐患工单、对接前端页面。uav-patrol-backend 这个命名直译就是“无人机巡检后端”所谓巡维保障指的是围绕巡检、维护、保障这三个动作做闭环管理。它是典型的 Java 后端工程承载的是设备接入层、任务调度层、业务处理层和对外 API 层。这类系统最容易让新手误判的地方在于以为核心难点是“调用无人机厂商的 SDK”。真实情况是厂商 SDK 大多只负责最底层的飞行控制业务后端真正要解决的是状态机设计、并发上报处理、轨迹数据存储、任务调度和权限管控。换句话说哪怕你完全不用真实无人机用一个模拟器上报坐标和图片这套后端的前 80% 工作量照样存在。因此这套代码适合两类人一类是在做毕业设计或课程设计、需要一套完整可演示的 Java Web 工程作为基线另一类是已经在做物联网或巡检类项目、想看看别人怎么组织任务状态和设备接入的工程师。下文按我实际搭这类系统的习惯把设计、实现、踩坑一次讲清。2. 选型与工程结构为什么是 Spring Boot 3 MyBatis-Plus而不是别的组合2.1 技术选型的三个约束条件巡维保障后端不是高并发互联网应用它的流量特征非常明确终端设备数量有限几十到几百台无人机和摄像头但单台设备上报频率高、数据格式杂、偶尔有突发批量补传业务操作集中在任务流转和工单处理上事务边界清晰。这个特征决定了选型方向不需要 Spring Cloud 全家桶不需要分布式事务中间件但要求开发效率高、容易扩展设备协议。我一般会选 Spring Boot 3 MyBatis-Plus MySQL RedisJDK 用 17。原因有三条第一Spring Boot 3 已经全面拥抱 Jakarta EE和 JDK 17 的长期支持周期匹配没必要再开 JDK 8 的新项目第二MyBatis-Plus 的代码生成器和 LambdaQueryWrapper 能省掉大量单表 CRUD 的样板代码这类系统的数据表动辄二三十张手写 Mapper 会拖慢进度第三Redis 在这个项目里不是缓存装饰品而是承担设备会话状态、分布式锁、轨迹热数据三个核心职责后面会展开。前端部分不用管你只需要知道接口按前后端分离设计返回统一 JSON 结构即可。2.2 包结构与模块边界按业务域切不按技术层切很多 Java 新手拿到这类项目会问是不是要建 controller / service / mapper / entity 四个包就完事了我的做法是顶层按业务域切分包每个域内部再分 controller / service / mapper。下面是这套工程最常用的包结构com.uav.patrol ├── common # 统一返回体、异常处理、常量、工具类 ├── config # 配置类Redis、MyBatis-Plus、拦截器、CORS ├── module │ ├── device # 设备管理注册、在线状态、协议解析 │ ├── task # 巡检任务创建、调度、执行、状态流转 │ ├── track # 轨迹数据上报接收、存储、回放查询 │ ├── alarm # 隐患告警识别结果接收、工单生成、闭环处理 │ ├── file # 文件服务图片/视频上传、访问鉴权 │ └── system # 用户、角色、菜单、日志 └── UavPatrolApplication.java按业务域切包的好处是一个人开发时心智负担小改任务调度不会误触设备协议代码团队协作时每个人负责一个域合并冲突概率低。体系结构上这套代码里最常见的错误设计是把所有实体类扔进一个 entity 包然后 service 层互相调用一团乱麻最后改一个需求动半套代码。按域隔离后跨域调用统一走对方的 Service 接口不允许直接操作对方的 Mapper这个纪律能保证后期维护不失控。2.3 统一返回体与全局异常的必要性前后端分离项目里前端最怕的不是接口报错而是每个接口返回结构都不一样。这套系统的 common 包里必须定义统一的返回包装类这是第一个要写的类。它的核心设计如下Data public class RT { private int code; // 业务状态码200 成功非 200 失败 private String message; // 提示信息 private T data; // 业务数据 private long timestamp; // 服务端时间戳用于排查时对齐日志 public static T RT ok(T data) { RT r new R(); r.code 200; r.message success; r.data data; r.timestamp System.currentTimeMillis(); return r; } public static T RT fail(int code, String message) { RT r new R(); r.code code; r.message message; r.timestamp System.currentTimeMillis(); return r; } }这段代码的逻辑说明所有接口的返回值统一走 R 包装code 只表示业务成功与否HTTP 状态码仍然按 REST 规范使用404 表示资源不存在、401 表示未认证。前端只需要判断 code 是否为 200不需要针对每个接口单独处理 error 结构。timestamp 字段容易被忽略但它对排查问题非常重要——当设备上报时间和服务器处理时间不一致时对齐时间戳才能定位是网络延迟还是处理堆积。参数说明code 的设计要预留业务码段比如 10001 表示设备离线、10002 表示任务状态冲突不要直接用 HTTP 状态码当业务码。message 要面向使用者可读不能直接抛 SQLException 的原始信息给前端。把全局异常处理器RestControllerAdvice配好后Controller 里的 try-catch 就能彻底清掉业务代码只关注正常流程。2.4 核心数据表任务、轨迹、告警三条主线的 ER 关系先把数据模型立住后面写业务才有据可依。巡维保障系统的表设计有一条隐含的主线设备是资源任务驱动设备工作工作过程中产生轨迹和媒体数据媒体数据经过分析变成告警告警再生成工单。这条链路上最重要的五张表要单独列出来讲。表名核心字段说明patrol_taskid, task_no, device_id, route_id, plan_start_time, plan_end_time, status, created_by巡检任务主表status 是状态机核心task_executionid, task_id, device_id, real_start_time, real_end_time, flight_count, result一次任务的实际执行记录一个任务可能多次执行device_infoid, device_code, device_type, protocol_type, online_status, last_online_time, lat, lng设备台账online_status 用 Redis 缓存辅助判断track_pointid, task_id, device_id, lat, lng, altitude, speed, direction, gps_time, create_time轨迹点表按天分表或冷热分离alarm_infoid, task_id, device_id, alarm_type, lat, lng, media_url, status, handler_id告警信息status 决定是否已生成工单这五张表的关联关系一句话说清patrol_task 关联 device_info 决定谁去执行关联 task_execution 记录真实飞行的开始结束时间飞行过程中设备上报的每个点落到 track_point识别算法产出的异常结果落到 alarm_info。注意 track_point 表不要加唯一索引在 task_id 上因为一次任务会落成千上万个点。3. 任务调度与状态机让巡检任务按计划跑起来还不重不漏3.1 为什么状态字段不能只靠 if-else 硬编码任务状态是整个系统的骨架。一个巡检任务从创建到归档至少要经历待执行 - 已下发 - 执行中 - 已完成 - 已归档中间还可能插入已取消、执行异常两个旁路状态。新手最容易犯的错是在 Service 里写 if (status 1) { doSomething(); } else if (status 2) { doAnother(); }——前两次改动还很爽到第三次加需求时你会发现自己根本记不清哪个数字代表哪个状态而且可能出现“从待执行直接跳到已完成”这种非法流转。正确做法是引入状态机枚举把每个状态允许的流转动作显式建模。这套系统里我会直接定义一个 TaskStatusEnum并且用 Map 预先定义好合法流转路径非法流转直接抛业务异常。代码如下public enum TaskStatusEnum { PENDING(0, 待执行), DISPATCHED(1, 已下发), EXECUTING(2, 执行中), FINISHED(3, 已完成), CANCELED(4, 已取消), EXCEPTION(5, 执行异常), ARCHIVED(6, 已归档); private final int code; private final String desc; private static final MapInteger, SetInteger TRANSITIONS new HashMap(); static { TRANSITIONS.put(PENDING.code, Set.of(DISPATCHED.code, CANCELED.code)); TRANSITIONS.put(DISPATCHED.code, Set.of(EXECUTING.code, CANCELED.code)); TRANSITIONS.put(EXECUTING.code, Set.of(FINISHED.code, EXCEPTION.code)); TRANSITIONS.put(EXCEPTION.code, Set.of(DISPATCHED.code, ARCHIVED.code)); TRANSITIONS.put(FINISHED.code, Set.of(ARCHIVED.code)); } public static void validateTransition(int from, int to) { SetInteger allowed TRANSITIONS.get(from); if (allowed null || !allowed.contains(to)) { throw new BizException(非法状态流转: from - to); } } }逻辑说明这个枚举把状态码与状态描述绑定同时用 Map 定义了每个状态的合法去向。比如执行中只能流转到已完成或执行异常不能在任务还在天上飞的时候直接把它取消。Service 层每次更新状态前必须先调用 validateTransition 做校验校验通过才执行 UPDATE。参数说明状态码不要用 1、2、3 这种裸数字必须用枚举常量引用。desc 字段要给中文描述因为前端下拉框、日志打印、告警消息都要用。实际项目里状态可能还要细分比如任务下发后要区分“等待设备确认”和“设备已确认”这时新增枚举值即可不需要改已有流转逻辑。这套设计还有一个附加价值控制台日志里搜索状态流转时能看到”from0 to1“这类信息配合 traceId 就能还原整个任务的生命周期。3.2 定时巡检任务的两种触发机制调度框架还是延时队列巡检任务有两种触发场景一种是用户手动创建、立即执行另一种是按计划定时执行比如每天上午 9 点飞一遍指定线路。实现定时任务业界最常见两类方案一是用 XXL-Job 这类分布式调度平台把任务模型建在调度中心由调度中心回调业务接口二是用 Spring 自带的 Scheduled 注解配合 Redis 分布式锁。对于这套系统的体量我建议直接上 XXL-Job 或同类框架理由有两条第一调度平台自带失败重试、任务依赖、调度日志这些功能自己写要花不少时间第二任务执行日志是巡维系统的硬需求出了问题要能回看”几点几分触发、执行结果是什么“调度平台天然满足这一点。在业务代码里你的职责是提供一个“接收调度回调并创建巡检任务”的入口。简单示意如下Component public class PatrolTaskTrigger { XxlJob(patrolTaskDispatchJob) public ReturnTString dispatch(String param) { // param 格式约定: taskId调度平台在创建任务时传入 Long taskId Long.parseLong(param); TaskService taskService SpringContextHolder.getBean(TaskService.class); boolean success taskService.dispatchTask(taskId); return success ? ReturnT.SUCCESS : ReturnT.FAIL; } }逻辑说明调度平台负责在指定时间点调用这个入口业务侧只做一件事——根据 taskId 把任务从待执行变成已下发并真正向设备下发命令。这样调度逻辑和业务逻辑彻底解耦调度平台挂了任务只是不触发不会破坏业务数据业务服务升级重启调度平台到点照样回调重启完成自然恢复。参数说明param 的格式必须和调度平台的任务配置约定好。常见做法是传任务 ID但更稳妥的是传任务编号task_no因为任务 ID 在分库分表场景下可能冲突。回调接口要做好幂等同一个回调如果因为网络重试到达两次dispatchTask 里要判断当前状态是否已经是已下发是则直接返回成功避免重复向设备下发指令。3.3 任务下发与设备指令异步还是同步把任务下发给无人机逻辑上就一步——调用设备服务层的接口往 MQTT 或 TCP 通道发一条指令。但这里有个极易踩坑的决策点要不要等待设备回复“收到”如果同步等待一个设备响应慢 5 秒整个任务创建接口就卡 5 秒前端转圈用户烦躁如果完全异步设备没收到指令任务状态已经变成已下发后面就悬空了。常用做法是任务状态先置为“已下发”但下发动作走异步通道同时启动一个延时检查任务。具体实现上用 Redis 存下”待确认指令“的 key15 秒后检查设备是否上报告了“指令已确认”事件。代码如下public void dispatchTask(Long taskId) { PatrolTask task getById(taskId); TaskStatusEnum.validateTransition(task.getStatus(), TaskStatusEnum.DISPATCHED.getCode()); // 1. 状态先落库 updateStatus(taskId, TaskStatusEnum.DISPATCHED); // 2. 发送指令到设备通道不等待结果 DeviceCommand command new DeviceCommand(); command.setDeviceId(task.getDeviceId()); command.setTaskId(taskId); command.setType(START_PATROL); deviceGateway.send(command); // 3. 记录待确认 key15 秒后检查 String confirmKey patrol:task:confirm: taskId; redisTemplate.opsForValue().set(confirmKey, 0, Duration.ofSeconds(15)); }逻辑说明三步走的核心思想是“快速响应前端 最终一致”。用户点创建任务后接口立刻返回成功真正和设备通信的结果由后续确认机制兜底。如果 15 秒内设备上报了确认事件就把 confirmKey 删掉如果没删延时任务发现 key 还在且值还是 0就标记任务为执行异常并在告警表里写一条“任务下发超时”。参数说明15 秒这个超时时间不是拍脑袋定的。无人机从收到指令到完成自检并回复确认通常需要 8 到 12 秒设成 15 秒留了余量又不至于让用户等太久才看到异常。如果是大型固定翼或者复杂航线加载场景要按设备型号调整建议把超时时间做成设备类型的配置项不要硬编码。4. 设备接入与轨迹数据处理高并发上报怎么接得住、存得下4.1 设备协议层统一网关设计别让业务代码依赖厂商 SDK市面上的无人机/摄像头厂家都有自己的 SDK 或协议但是巡维系统的业务逻辑任务、轨迹、告警不关心你的设备是哪个牌子——它只关心三个动作设备上线、上报轨迹点、上报媒体文件。因此设备接入层必须抽象出一个统一网关接口厂商差异全部封在适配器里。这套系统里最常见的抽象如下public interface DeviceGateway { /** * 设备上线 * param deviceCode 设备唯一编码 * param protocolType 协议类型如 mqtt / tcp / http */ void online(String deviceCode, String protocolType); /** * 下发指令 */ void sendCommand(DeviceCommand command); /** * 设备主动上报数据轨迹、状态等 */ void onDataReport(DeviceReport report); /** * 设备离线 */ void offline(String deviceCode); }逻辑说明这个接口放在 module/device 包下所有厂商适配器都实现它。比如大疆的适配器内部把轨迹点转成统一的坐标模型另一个厂商的适配器做同样的转换转换完的数据结构是一致的上层业务根本感知不到厂商差异。这样一个新设备接入时工作量主要集中在适配器里业务层零改动。参数说明DeviceReport 的字段要覆盖设备的通用能力描述至少要包含 deviceCode、reportTypeTRACK / STATUS / MEDIA、latitude、longitude、altitude、speed、direction、timestamp、ext扩展字段。注意 ext 是 JSON 字符串用来兜底厂商特殊字段防止每接入一家新设备就改一次核心表结构。4.2 轨迹上报的接收链路Netty 还是 MQTT轨迹上报是这套系统流量最大的接口。一架飞机每秒上报 1 个点10 架飞机同时飞就是每秒 10 个请求看起来不大问题是上报是脉冲式的——起飞和降落阶段集中上报而且网络抖动时积压的数据会瞬间补传。处理链路我最常用的有两套如果项目要求完全自主可控用 Netty 直接走 TCP 长连接自己解析协议如果允许引入中间件用 EMQX 这类 MQTT BrokerJava 服务通过 MQTT 客户端订阅主题接收数据。这里我推荐 MQTT 方案原因很实际无人机在野外飞行网络质量不稳定MQTT 的 QoS 机制天然处理了消息重传和离线消息保留自研 TCP 协议看起来简单但断线重连、心跳保活、粘包拆包每个细节都要自己填坑周期至少多两周。订阅主题的命名规则建议按设备维度切分patrol/{deviceCode}/track这样业务端订阅 patrol//track 就能收到所有轨迹数据按设备维度隔离也方便排查单台设备的问题。消息进来后第一件事不是落库而是做轻量过滤和转换Component public class TrackReportConsumer { KafkaListener(topics patrol-track-raw, groupId patrol-track-group) public void onTrackReport(ConsumerRecordString, String record) { DeviceReport report JSON.parseObject(record.value(), DeviceReport.class); // 1. 基础校验坐标范围、时间戳合法性 if (!isValidCoordinate(report.getLatitude(), report.getLongitude())) { log.warn(invalid coordinate: {}, record.value()); return; } // 2. 过滤漂移点速度突变、距离突变 String deviceCode report.getDeviceCode(); TrackPoint lastPoint trackCache.getLastPoint(deviceCode); if (lastPoint ! null isDriftPoint(lastPoint, report)) { log.warn(drift point ignored, device{}, distance{}m, deviceCode, calculateDistance(lastPoint, report)); return; } // 3. 落库 更新设备最新位置缓存 trackService.saveTrackPoint(convertToEntity(report)); trackCache.updateLastPoint(deviceCode, report); } }逻辑说明这段代码展示的是消费端的处理策略。先做合法性校验坐标范围不对直接丢弃再做漂移点过滤比如两个相邻轨迹点距离超过了飞机在该时间间隔内的物理可达范围判定为 GPS 漂移丢弃但写入日志。这两步能拦掉大量脏数据降低存储压力和分析误差。最终有效数据才真正落库同时更新 Redis 缓存里的设备最新位置供 Web 端地图实时展示。参数说明isDriftPoint 的判断逻辑是速度阈值和距离阈值的组合我常用单点位移不超过 500 米且速度不超过 150km/h 作为默认值具体要到现场根据飞机型号和采样频率调。Kafka 的 topic 分区数建议按设备量级设置设备少于 50 台时 3 个分区足够这里的关键不是吞吐而是消费顺序——同一台设备的轨迹点如果被分到不同分区会出现乱序所以分区键必须用 deviceCode。4.3 轨迹表的分表策略与冷热数据分离轨迹点表是这套系统里体积膨胀最快的表。假设每架飞机每秒上报 1 个点1 天飞行 2 小时1 台设备一天就是 7200 条50 台设备一个月下来超过 1000 万条。如果全堆在一张表里三个月后查询“某次任务的轨迹回放”会明显变慢因为 MySQL 的普通 B 树索引扛不住千万级数据的范围查询。常见的解法按数据量分三档千万级以内给 track_point 表加联合索引 (task_id, gps_time)单表能撑亿级以内按天分表表名形如 track_point_20240611查询时根据时间范围路由到对应表亿级以上上时序数据库比如 TDengine 或 IoTDB它们对时序数据做了存储和查询层面的专门优化。对于 uav-patrol-backend 这类项目我建议做到第二档就够。分表路由的逻辑写在 MyBatis-Plus 的 DynamicTableNameInnerInterceptor 里代码示意如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); DynamicTableNameInnerInterceptor dynamicTableName new DynamicTableNameInnerInterceptor(); dynamicTableName.setTableNameHandler((sql, tableName) - { if (track_point.equals(tableName)) { // 从上下文中取出当前要写入的时间决定路由到哪张物理表 LocalDate targetDate TrackContextHolder.getCurrentDate(); return track_point_ targetDate.format(DateTimeFormatter.BASIC_ISO_DATE); } return tableName; }); interceptor.addInnerInterceptor(dynamicTableName); return interceptor; } }逻辑说明MyBatis-Plus 的 DynamicTableNameInnerInterceptor 会在 SQL 执行前改写表名。业务代码里写的还是 track_point实际执行时被替换成 track_point_20240611 这类物理表。关键在于路由条件当前日期必须通过 ThreadLocal 或请求参数传入否则你无法确定这条数据该落到哪天。这里我用了 TrackContextHolder它是一个 ThreadLocal 包装类在 MQ 消费入口处设置当前时间即可。参数说明分表不要做得太激进建议按月分而不是按天分。按天分表会导致建表脚本太多管理麻烦按月分表一张表 30 天的数据量通常在百万到千万之间查询性能可控建表 12 张一年也容易维护。历史轨迹的 TTL 一般是 90 天超过这个时间的冷数据要么归档到 OSS要么直接删不需要无限保留。5. 避坑与排查手册这套系统最容易翻车的 5 个环节5.1 时区问题GPS 时间是 UTC业务时间是东八区现象前端地图上显示的轨迹点和实际航线偏差 8 个小时或者任务计划时间明明设置的是 9 点任务却在 1 点被触发了。原因设备上报的 GPS 时间和服务器系统时区不一致。绝大多数 GPS 模块输出的时间是 UTC 标准时间不随设备所在地变化而 MySQL 连接串里如果配置了 serverTimezoneAsia/ShanghaiJDBC 会自动做时区转换。一旦两边的时区信息错位时间就乱了。解决规范三处配置——数据库连接串统一用 serverTimezoneAsia/ShanghaiJackson 序列化 LocalDateTime 时统一格式化并指定时区设备上报的时间戳在协议解析层就明确语义。协议解析处必须加上 UTC 转东八区的逻辑这句话写在代码注释里防止后人“优化”掉。5.2 数据库连接池被打满轨迹批量补传时接口集体超时现象设备断网半小时后恢复积压的轨迹点瞬间补传此时 Web 端用户点任何页面都转圈后台日志报连接获取超时。原因轨迹上报接口是 IO 密集型操作每个请求占用一个数据库连接做 INSERT。积压数据把 HikariCP 默认的 10 个连接全部占满后其他业务请求排队等连接整站瘫痪。解决把轨迹落库改为批量写入——在消费端攒 500 条或 1 秒滑动窗口内攒到的所有数据一次性 batch insert。这个方案能把连接占用降低一到两个数量级同时改造成本极低。另外把 HikariCP 的最大连接数从默认值调到 50并设置 connection-timeout 为 3000ms宁可快速失败也不要无限排队。5.3 设备上下线状态在 Redis 里失效明明在线却下发失败现象设备列表显示某台无人机在线但下发指令时设备没有任何响应后台也没有报错。原因设备在线状态是通过心跳机制维护的设备每 30 秒上报一次心跳服务端收到后刷新 Redis 里的 last_heartbeat_time。但如果 Redis key 的过期时间设置得过短比如设成 30 秒而设备心跳恰好因网络抖动晚到几秒key 就过期了服务端判断设备离线消息队列里的指令被丢弃。解决key 过期时间设置成心跳间隔的 3 倍即 90 秒同时下发指令前不要只查 Redis要主动给设备发一条 ping 探测指令等待 3 秒内是否有 pong 响应。这样能同时处理“状态缓存过期”和“设备假在线”两个问题。5.4 前端跨域问题Vue3 联调时接口能通但带不了 Cookie现象前端用 Vite 开发服务器启动在 5173 端口后端跑在 8080请求发过去总是报跨域错误。解决过程后端加了 CORS 过滤器允许来源 http://localhost:5173结果发现非登录接口正常了但登录接口始终带不上 Session。原因fetch 默认不带 Cookie需要在请求里设置 credentials: include同时后端的 CORS 配置里 allowCredentials 必须为 true且 allowedOrigin 不能是 *。这三者必须同时满足缺一个都不行。另外如果你用了 Spring Security 或 Sa-Token还要放行 OPTIONS 预检请求否则前端每次复杂请求都会先被拦一道。5.5 任务取消后轨迹还在写状态机约束漏掉了数据写入链路现象操作员取消了任务但任务关联的轨迹点数量还在涨地图上还有飞机在飞。原因任务取消只是改了 patrol_task 表的 status但轨迹上报的消费链路并没有校验任务状态。设备不知道任务被取消了还在按原计划上报数据消费端照单全收。解决在 TrackReportConsumer 落库前加一道校验查询任务当前状态只有 EXECUTING 状态才允许写入轨迹如果任务已经被取消或完成将设备上报的数据写入一个单独的 abandoned_track 表同时触发设备停机指令。这个校验不能用查库的方式否则每次上报都多一次数据库查询应该把任务状态缓存在 Redis 里消费时只查缓存。6. 进阶从能跑到能上线Redis 分布式锁与告警闭环的细节打磨巡维保障系统从“功能实现”到“稳定运行”还有两个坎必须过一是定时任务在集群部署时不重复执行二是告警产生后能真正闭环而不是变成一个只读列表。第一个坎靠 Redis 分布式锁解决。假设你有两个后端实例同时运行XXL-Job 的调度是打给某个实例的但如果你自己写了 Scheduled 定时扫描待执行任务就要防止两个实例同时扫到同一条任务。代码套路如下public boolean tryLock(String lockKey, long expireSeconds) { Boolean success redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(expireSeconds)); return Boolean.TRUE.equals(success); } public void clearLock(String lockKey) { redisTemplate.delete(lockKey); }逻辑说明setIfAbsent 就是 Redis 的 SETNX 命令只有 key 不存在时才能设置成功。多个实例同时抢同一把锁只有一个能成功拿到锁的实例执行任务其他实例直接跳过。锁的过期时间要设长一点比如 30 秒保证任务能正常执行完任务执行完后主动删锁不要让锁活到过期。这里有一个细节删除锁之前要校验 value 是不是自己的防止 A 实例的锁过期后被 B 实例拿到A 执行完把 B 的锁删了导致 B 和 C 同时执行。加一个 value 比对再删除即可。第二个坎是告警闭环。巡维系统如果只做到“识别出隐患并记录”运营价值会大打折扣——隐患必须流转到工单工单必须指派给人人处理完必须回填结果。闭环链路在代码上要定义清楚alarm_info 表新增 status 字段0 表示待确认、1 表示已派单、2 表示已处理、3 表示已关闭。PUSH 到前端页面的事件流用 WebSocket 推送实现在线提醒避免前端轮询浪费请求。我的习惯是每周检查一次告警表的历史数据找出超过 7 天没流转的记录沿着时间线重放告警生成时的日志和轨迹定位是算法误报还是流程卡住——这种“复盘日志”的习惯比写再多代码都能更快提升系统质量希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。