2025 实战复盘:物联网 + 数据检索融合项目的核心技术实现与心得

作为深耕Java技术领域多年的开发者,2025年主导的物联网设备管理与智能预警项目是我技术沉淀与突破的关键实践。项目聚焦设备管控、视频同步、智能检索与数据预警四大核心模块,落地了多个高价值技术方案。本文将从实战角度,拆解各模块的核心实现逻辑、关键代码与底层原理,分享项目中的技术选型思考与避坑心得,希望能为大家提供可复用的实践经验。

一、项目背景与核心价值

本项目是面向智慧园区的物联网管控系统,核心目标是实现"设备可控、数据可查、风险可预警"。系统需对接园区内千级物联网设备,支持设备状态实时监控与远程控制;同步第三方视频流实现全天候可视化监管;提供精准的地址检索能力支撑快速定位;整合天气等外部数据实现个性化风险预警。项目最终落地后,将园区设备响应延迟从秒级降至毫秒级,地址检索准确率提升至99.2%,预警响应效率提升60%,有效降低了园区运维成本。

二、核心模块技术实现拆解

(一)设备管理与联动:基于MQTT协议的警铃警灯控制方案

1. 技术选型思考

设备联动的核心需求是"低延迟、高可靠"的设备通信,对比HTTP、CoAP等协议后,最终选择MQTT协议。原因有三:一是MQTT采用发布/订阅模式,支持一对多通信,适配园区多设备联动场景;二是协议头精简(最小2字节),网络传输开销小,适合资源受限的物联网设备;三是支持QoS等级划分,可根据警铃警灯控制的优先级选择合适的消息投递策略。

2. 核心实现逻辑

采用"MQTT Broker + 设备客户端 + 应用服务端"的架构:

  • Broker选型:使用EMQ X,开源且支持高并发,单节点可支撑10万+设备连接;

  • 设备端:警铃警灯控制器内置MQTT客户端,订阅指定主题(如/park/device/alarm/{deviceId});

  • 应用服务端:通过Java客户端(Eclipse Paho)向Broker发布控制消息,指定QoS为2(确保消息仅被投递一次)。

3. 关键代码实现
(1)MQTT客户端工具类
复制代码
public class MqttClientUtil {
    // EMQ X Broker地址
    private static final String BROKER = "tcp://192.168.1.100:1883";
    // 客户端ID(唯一)
    private static final String CLIENT_ID = "java-server-" + System.currentTimeMillis();
    // 用户名密码(Broker配置)
    private static final String USERNAME = "park_admin";
    private static final String PASSWORD = "park@2025";

    private MqttClient mqttClient;

    /**
     * 初始化MQTT客户端
     */
    public void init() throws MqttException {
        MemoryPersistence persistence = new MemoryPersistence();
        mqttClient = new MqttClient(BROKER, CLIENT_ID, persistence);
        MqttConnectOptions connOpts = new MqttConnectOptions();
        connOpts.setUserName(USERNAME);
        connOpts.setPassword(PASSWORD.toCharArray());
        // 自动重连
        connOpts.setAutomaticReconnect(true);
        // 清除会话(避免重连后接收历史消息)
        connOpts.setCleanSession(false);
        // 连接超时时间(3秒)
        connOpts.setConnectionTimeout(3);
        // 保持连接心跳(10秒)
        connOpts.setKeepAliveInterval(10);

        // 连接回调
        mqttClient.setCallback(new MqttCallback() {
            @Override
            public void connectionLost(Throwable throwable) {
                System.out.println("MQTT连接丢失:" + throwable.getMessage());
            }

            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                // 接收设备响应消息(如警铃警灯状态反馈)
                System.out.println("接收消息:主题=" + topic + ", 内容=" + new String(message.getPayload()));
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                System.out.println("消息投递完成:" + token.isComplete());
            }
        });

        mqttClient.connect(connOpts);
        System.out.println("MQTT客户端连接成功");
    }

    /**
     * 发布控制消息
     * @param topic 主题
     * @param content 消息内容(如"ON"开启,"OFF"关闭)
     * @param qos QoS等级
     */
    public void publish(String topic, String content, int qos) throws MqttException {
        if (mqttClient == null || !mqttClient.isConnected()) {
            init();
        }
        MqttMessage message = new MqttMessage(content.getBytes());
        message.setQos(qos);
        mqttClient.publish(topic, message);
    }

    /**
     * 关闭客户端
     */
    public void close() throws MqttException {
        if (mqttClient != null && mqttClient.isConnected()) {
            mqttClient.disconnect();
            mqttClient.close();
        }
    }
}
(2)警铃警灯控制服务
复制代码
public class AlarmDeviceService {
    private MqttClientUtil mqttClientUtil = new MqttClientUtil();

    /**
     * 控制警铃警灯
     * @param deviceId 设备ID
     * @param status 控制状态(ON/OFF)
     */
    public void controlAlarmDevice(String deviceId, String status) {
        if (!"ON".equals(status) && !"OFF".equals(status)) {
            throw new IllegalArgumentException("状态参数错误,仅支持ON/OFF");
        }
        String topic = "/park/device/alarm/" + deviceId;
        try {
            // QoS=2,确保消息仅投递一次
            mqttClientUtil.publish(topic, status, 2);
        } catch (MqttException e) {
            System.err.println("控制警铃警灯失败:" + e.getMessage());
            // 失败重试(最多3次)
            retryPublish(topic, status, 2, 3);
        }
    }

    /**
     * 消息发布重试
     */
    private void retryPublish(String topic, String content, int qos, int retryCount) {
        int count = 0;
        while (count < retryCount) {
            try {
                Thread.sleep(1000 * (count + 1)); // 指数退避重试
                mqttClientUtil.publish(topic, content, qos);
                return;
            } catch (Exception e) {
                count++;
                System.err.println("第" + count + "次重试失败:" + e.getMessage());
            }
        }
        throw new RuntimeException("重试" + retryCount + "次后仍失败");
    }

    // 测试方法
    public static void main(String[] args) {
        AlarmDeviceService service = new AlarmDeviceService();
        service.controlAlarmDevice("alarm_001", "ON");
    }
}
4. 底层逻辑与避坑点
  • 底层逻辑:MQTT的发布/订阅模式通过Broker中转消息,服务端发布消息后,Broker会将消息推送给所有订阅该主题的设备;QoS=2通过"发布-确认-接收-确认"四步流程确保消息仅投递一次,避免警铃警灯重复触发。

  • 避坑点:① 客户端ID必须唯一,否则会导致连接冲突;② 需设置合理的心跳间隔,避免Broker误判连接失效;③ 控制消息需携带设备ID,确保精准控制,避免广播风暴。

(二)第三方视频同步方案:基于RTSP协议的流转发实现

1. 需求分析

项目需同步第三方园区的实时视频流,核心痛点是"跨平台兼容""低延迟转发""异常恢复"。第三方提供的视频流采用RTSP协议,而系统前端需支持Web端播放(主流支持HTTP-FLV/RTMP协议),因此需要实现"RTSP转HTTP-FLV"的流转发。

2. 技术选型与架构
  • 转码工具:选用FFmpeg,开源且支持多种协议转换;

  • 流服务:使用Nginx-rtmp-module搭建流服务器,接收转码后的HTTP-FLV流并提供给前端;

  • 同步策略:采用"拉流+定时检测"机制,服务端主动拉取第三方RTSP流,转码后推送到Nginx,同时定时检测流状态,异常时自动重连。

3. 核心实现
(1)FFmpeg转码命令
复制代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class VideoSyncService {
    // FFmpeg路径
    private static final String FFMPEG_PATH = "D:/ffmpeg/bin/ffmpeg.exe";
    // Nginx流服务器地址
    private static final String NGINX_FLV_URL = "rtmp://192.168.1.101/live/";

    /**
     * 同步第三方RTSP视频流
     * @param rtspUrl 第三方RTSP地址
     * @param streamName 流名称(唯一标识)
     */
    public void syncRtspStream(String rtspUrl, String streamName) {
        // FFmpeg转码命令:RTSP转HTTP-FLV推送到Nginx
        String[] cmd = {
                FFMPEG_PATH,
                "-rtsp_transport", "tcp", // 使用TCP传输,避免丢包
                "-i", rtspUrl,
                "-c:v", "h264", // 视频编码保持h264
                "-c:a", "aac", // 音频编码aac
                "-f", "flv", // 输出格式FLV
                "-flvflags", "no_duration_filesize",
                NGINX_FLV_URL + streamName
        };

        try {
            Process process = new ProcessBuilder(cmd).redirectErrorStream(true).start();
            // 监听FFmpeg输出,判断流状态
            new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.out.println("FFmpeg日志:" + line);
                        // 检测流断开关键字
                        if (line.contains("Input/output error") || line.contains("Connection refused")) {
                            System.err.println("视频流断开,准备重连");
                            process.destroy();
                            // 延迟3秒重连
                            Thread.sleep(3000);
                            syncRtspStream(rtspUrl, streamName);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 测试方法
    public static void main(String[] args) {
        VideoSyncService service = new VideoSyncService();
        // 第三方RTSP地址示例
        String rtspUrl = "rtsp://admin:123456@192.168.2.100:554/stream1";
        service.syncRtspStream(rtspUrl, "park_entrance");
    }
}
(2)Nginx配置(支持HTTP-FLV)
复制代码
rtmp {
    server {
        listen 1935;
        chunk_size 4096;

        application live {
            live on;
            record off;
            # 推送流时验证(可选)
            push_auth on;
            push_auth_user admin 123456;
        }
    }
}

http {
    server {
        listen 8080;

        location /flv {
            # 映射RTMP流到HTTP-FLV
            flv_live on;
            chunked_transfer_encoding on;
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        }
    }
}
4. 前端播放示例(Vue)
复制代码
<template>
  <div>
    <video id="videoPlayer" controls autoplay width="100%"></video>
  </div>
</template>

<script>
import flvjs from 'flv.js';

export default {
  mounted() {
    this.initPlayer();
  },
  methods: {
    initPlayer() {
      const videoElement = document.getElementById('videoPlayer');
      if (flvjs.isSupported()) {
        const flvPlayer = flvjs.createPlayer({
          type: 'flv',
          url: 'http://192.168.1.101:8080/flv?port=1935&app=live&stream=park_entrance'
        });
        flvPlayer.attachMediaElement(videoElement);
        flvPlayer.load();
        flvPlayer.play();
      }
    }
  }
};
</script>
5. 核心优化点
  • 采用TCP传输RTSP流,避免UDP传输的丢包问题;

  • 转码时复用原始视频编码(h264),减少CPU占用;

  • 增加流状态监听与自动重连机制,确保同步稳定性;

  • Nginx配置跨域支持,解决前端播放跨域问题。

(三)复杂地址检索:基于Elasticsearch的精准检索实现

1. 需求分析

园区地址检索需支持"模糊匹配""层级检索""多条件组合检索"(如"1号楼3层东侧设备"),传统数据库(MySQL)的模糊查询性能差、准确率低,因此引入Elasticsearch(ES)实现高效检索。

2. 技术选型
  • ES版本:7.17.0(支持IK分词器);

  • 分词器:IK分词器(支持中文分词,适配地址检索场景);

  • Java客户端:Elasticsearch High Level REST Client。

3. 核心实现
(1)索引设计与映射创建
复制代码
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import java.io.IOException;

public class EsIndexService {
    private RestHighLevelClient esClient;

    // 构造方法注入esClient(Spring Boot环境)
    public EsIndexService(RestHighLevelClient esClient) {
        this.esClient = esClient;
    }

    /**
     * 创建地址检索索引
     * 索引名:park_address_index
     */
    public void createAddressIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("park_address_index");

        // 设置索引参数
        request.settings(Settings.builder()
                .put("number_of_shards", 3) // 分片数
                .put("number_of_replicas", 1) // 副本数
        );

        // 构建映射(mapping)
        XContentBuilder mapping = XContentFactory.jsonBuilder()
                .startObject()
                    .startObject("properties")
                        // 地址ID
                        .startObject("addressId")
                            .field("type", "keyword")
                        .endObject()
                        // 完整地址
                        .startObject("fullAddress")
                            .field("type", "text")
                            .field("analyzer", "ik_max_word") // 细粒度分词
                            .field("search_analyzer", "ik_smart") // 粗粒度检索
                        .endObject()
                        // 层级地址(省/市/园区/楼栋/楼层)
                        .startObject("levelAddress")
                            .field("type", "nested") // 嵌套类型
                            .startObject("properties")
                                .startObject("province")
                                    .field("type", "keyword")
                                .endObject()
                                .startObject("city")
                                    .field("type", "keyword")
                                .endObject()
                                .startObject("park")
                                    .field("type", "keyword")
                                .endObject()
                                .startObject("building")
                                    .field("type", "keyword")
                                .endObject()
                                .startObject("floor")
                                    .field("type", "keyword")
                                .endObject()
                            .endObject()
                        .endObject()
                        // 关联设备ID
                        .startObject("deviceIds")
                            .field("type", "keyword")
                        .endObject()
                    .endObject()
                .endObject();

        request.mapping(mapping);

        // 执行创建索引
        CreateIndexResponse response = esClient.indices().create(request, RequestOptions.DEFAULT);
        System.out.println("索引创建结果:" + response.isAcknowledged());
    }
}
(2)地址数据CRUD与检索
复制代码
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class AddressSearchService {
    private RestHighLevelClient esClient;
    private static final String INDEX_NAME = "park_address_index";

    public AddressSearchService(RestHighLevelClient esClient) {
        this.esClient = esClient;
    }

    /**
     * 新增地址数据
     */
    public String addAddress(Map<String, Object> addressData) throws IOException {
        IndexRequest request = new IndexRequest(INDEX_NAME);
        // 自动生成文档ID
        request.source(addressData, XContentType.JSON);
        IndexResponse response = esClient.index(request, RequestOptions.DEFAULT);
        return response.getId();
    }

    /**
     * 复杂地址检索(支持模糊匹配+层级筛选)
     * @param keyword 检索关键字
     * @param levelParams 层级参数(如{park:"智慧园区", building:"1号楼"})
     * @return 检索结果
     */
    public List<Map<String, Object>> searchAddress(String keyword, Map<String, String> levelParams) throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 模糊匹配完整地址
        if (keyword != null && !keyword.isEmpty()) {
            boolQuery.should(QueryBuilders.matchQuery("fullAddress", keyword));
        }

        // 层级筛选(嵌套查询)
        if (levelParams != null && !levelParams.isEmpty()) {
            for (Map.Entry<String, String> entry : levelParams.entrySet()) {
                boolQuery.filter(QueryBuilders.nestedQuery(
                        "levelAddress",
                        QueryBuilders.termQuery("levelAddress." + entry.getKey(), entry.getValue()),
                        org.elasticsearch.index.query.NestedQueryBuilder.RewriteMethod.CONSTANT_SCORE_AFTER_FILTER
                ));
            }
        }

        sourceBuilder.query(boolQuery);
        sourceBuilder.size(50); // 最多返回50条结果
        request.source(sourceBuilder);

        // 执行检索
        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
        List<Map<String, Object>> result = new ArrayList<>();
        for (SearchHit hit : response.getHits().getHits()) {
            result.add(hit.getSourceAsMap());
        }
        return result;
    }

    // 测试方法
    public static void main(String[] args) {
        // 模拟Spring Boot注入esClient
        RestHighLevelClient esClient = EsClientUtil.getClient();
        AddressSearchService service = new AddressSearchService(esClient);

        // 新增地址数据
        try {
            Map<String, Object> addressData = Map.of(
                    "addressId", "addr_001",
                    "fullAddress", "XXX智慧园区1号楼3层东侧",
                    "levelAddress", Map.of(
                            "province", "江苏省",
                            "city", "南京市",
                            "park", "智慧园区",
                            "building", "1号楼",
                            "floor", "3层"
                    ),
                    "deviceIds", List.of("alarm_001", "camera_001")
            );
            service.addAddress(addressData);

            // 检索测试:关键字"1号楼3层",筛选园区"智慧园区"
            Map<String, String> levelParams = Map.of("park", "智慧园区");
            List<Map<String, Object>> searchResult = service.searchAddress("1号楼3层", levelParams);
            System.out.println("检索结果:" + searchResult);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            EsClientUtil.closeClient(esClient);
        }
    }
}

// ES客户端工具类
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestClient;
import java.io.IOException;

class EsClientUtil {
    public static RestHighLevelClient getClient() {
        RestClientBuilder builder = RestClient.builder(
                new HttpHost("192.168.1.102", 9200, "http")
        );
        return new RestHighLevelClient(builder);
    }

    public static void closeClient(RestHighLevelClient client) {
        try {
            if (client != null) {
                client.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
4. 底层逻辑与性能优化
  • 底层逻辑:ES通过倒排索引实现高效检索,将"完整地址"字段分词后,建立"词条-文档ID"的映射关系;嵌套类型用于存储层级地址,支持精准的层级筛选;

  • 性能优化:① 合理设置分片与副本数,兼顾检索性能与可用性;② 检索时使用"ik_smart"分词器(粗粒度),减少匹配次数;③ 层级筛选使用filter查询(不计算评分),提升检索速度;④ 限制返回结果数量,避免大数据量传输。

(四)第三方数据预警:基于配置驱动的智能预警实现

1. 需求分析

系统需整合第三方天气,根据用户配置的预警规则(如"暴雨天气触发户外设备预警")自动触发预警,核心需求是"配置灵活""实时性强""可扩展"。

2. 技术架构

采用"数据拉取-规则解析-预警触发"的流程:

  • 数据拉取:通过定时任务(XXLjob)周期性调用第三方API获取数据;

  • 规则配置:预警规则存储在数据库,支持用户动态配置(如规则类型、触发条件、关联设备);

  • 预警触发:将拉取的数据与规则匹配,匹配成功则通过MQTT发送预警指令控制设备。

3. 核心实现
(1)第三方API调用工具类
复制代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;

public class ThirdPartyApiUtil {
    private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();

    /**
     * 调用第三方GET API
     * @param url API地址
     * @param params 请求参数
     * @return 响应结果(JSON)
     */
    public static JSONObject callGetApi(String url, Map<String, String> params) throws Exception {
        // 拼接参数
        StringBuilder urlBuilder = new StringBuilder(url);
        if (params != null && !params.isEmpty()) {
            urlBuilder.append("?");
            for (Map.Entry<String, String> entry : params.entrySet()) {
                urlBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
            url = urlBuilder.substring(0, urlBuilder.length() - 1);
        }

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Content-Type", "application/json")
                .GET()
                .build();

        HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != 200) {
            throw new RuntimeException("API调用失败,状态码:" + response.statusCode());
        }
        return JSON.parseObject(response.body());
    }
}
(2)预警规则实体与数据库映射(MyBatis)
复制代码
// 预警规则实体
public class AlarmRule {
    private Long id;
    private String ruleName; // 规则名称
    private String ruleType; // 规则类型:WEATHER(天气)、FLIGHT(航班)
    private String triggerCondition; // 触发条件(JSON格式,如{"weatherType":"暴雨","level":"蓝色"})
    private List<String> deviceIds; // 关联设备ID
    private String alarmContent; // 预警内容
    private Integer status; // 规则状态:0(禁用)、1(启用)

    // getter/setter省略
}

// MyBatis Mapper接口
public interface AlarmRuleMapper {
    // 查询所有启用的规则
    @Select("SELECT * FROM alarm_rule WHERE status = 1")
    @Results({
            @Result(column = "trigger_condition", property = "triggerCondition"),
            @Result(column = "device_ids", property = "deviceIds", typeHandler = StringListTypeHandler.class)
    })
    List<AlarmRule> selectEnabledRules();
}

// 字符串列表类型处理器(适配MySQL的VARCHAR存储逗号分隔的设备ID)
public class StringListTypeHandler extends BaseTypeHandler<List<String>> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        String value = String.join(",", parameter);
        ps.setString(i, value);
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String value = rs.getString(columnName);
        return parseValue(value);
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String value = rs.getString(columnIndex);
        return parseValue(value);
    }

    @Override
    public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String value = cs.getString(columnIndex);
        return parseValue(value);
    }

    private List<String> parseValue(String value) {
        if (value == null || value.isEmpty()) {
            return new ArrayList<>();
        }
        return Arrays.asList(value.split(","));
    }
}
(3)预警任务与规则匹配
复制代码
import com.alibaba.fastjson.JSONObject;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

public class AlarmJob implements Job {
    @Resource
    private AlarmRuleMapper alarmRuleMapper;
    @Resource
    private AlarmDeviceService alarmDeviceService;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            // 1. 查询所有启用的预警规则
            List<AlarmRule> enabledRules = alarmRuleMapper.selectEnabledRules();
            if (enabledRules.isEmpty()) {
                return;
            }

            // 2. 拉取第三方数据
            JSONObject weatherData = pullWeatherData(); // 拉取天气数据
            JSONObject flightData = pullFlightData(); // 拉取航班数据

            // 3. 规则匹配与预警触发
            for (AlarmRule rule : enabledRules) {
                boolean isTrigger = false;
                switch (rule.getRuleType()) {
                    case "WEATHER":
                        isTrigger = matchWeatherRule(rule, weatherData);
                        break;
                    case "FLIGHT":
                        isTrigger = matchFlightRule(rule, flightData);
                        break;
                }
                // 触发预警:控制关联设备+推送预警消息
                if (isTrigger) {
                    triggerAlarm(rule);
                }
            }
        } catch (Exception e) {
            System.err.println("预警任务执行失败:" + e.getMessage());
        }
    }

    /**
     * 拉取天气数据(高德天气API示例)
     */
    private JSONObject pullWeatherData() throws Exception {
        String url = "https://restapi.amap.com/v3/weather/weatherInfo";
        Map<String, String> params = Map.of(
                "key", "你的高德API密钥",
                "city", "320100", // 南京行政区划代码
                "extensions", "all"
        );
        return ThirdPartyApiUtil.callGetApi(url, params);
    }

    /**
     * 拉取航班数据(飞常准API示例)
     */
    private JSONObject pullFlightData() throws Exception {
        String url = "https://api.variflight.com/flight/find";
        Map<String, String> params = Map.of(
                "key", "你的飞常准API密钥",
                "flightNo", "MU2875" // 航班号
        );
        return ThirdPartyApiUtil.callGetApi(url, params);
    }

    /**
     * 匹配天气预警规则
     */
    private boolean matchWeatherRule(AlarmRule rule, JSONObject weatherData) {
        if (weatherData == null) {
            return false;
        }
        // 解析触发条件
        JSONObject triggerCondition = JSON.parseObject(rule.getTriggerCondition());
        String targetWeatherType = triggerCondition.getString("weatherType");
        String targetLevel = triggerCondition.getString("level");

        // 解析天气数据(简化逻辑,实际需根据API返回格式调整)
        JSONObject liveWeather = weatherData.getJSONArray("lives").getJSONObject(0);
        String currentWeatherType = liveWeather.getString("weather");
        String currentLevel = liveWeather.getString("level");

        // 匹配条件:天气类型一致且预警等级符合
        return targetWeatherType.equals(currentWeatherType) && targetLevel.equals(currentLevel);
    }

    /**
     * 匹配航班预警规则
     */
    private boolean matchFlightRule(AlarmRule rule, JSONObject flightData) {
        if (flightData == null) {
            return false;
        }
        JSONObject triggerCondition = JSON.parseObject(rule.getTriggerCondition());
        int targetDelayMin = triggerCondition.getIntValue("delayMin"); // 目标延误时间(分钟)

        // 解析航班数据
        int currentDelayMin = flightData.getIntValue("delayMin");

        // 匹配条件:当前延误时间≥目标延误时间
        return currentDelayMin >= targetDelayMin;
    }

    /**
     * 触发预警
     */
    private void triggerAlarm(AlarmRule rule) {
        // 1. 控制关联设备(如开启警灯警铃)
        for (String deviceId : rule.getDeviceIds()) {
            alarmDeviceService.controlAlarmDevice(deviceId, "ON");
        }
        // 2. 推送预警消息(可对接短信、钉钉等,此处省略)
        System.out.println("触发预警:" + rule.getAlarmContent());
    }
}
(4)XXLJob配置
4. 核心设计亮点
  • 配置驱动:预警规则存储在数据库,支持动态增删改查,无需修改代码即可调整预警策略;

  • 解耦设计:数据拉取、规则匹配、预警触发模块分离,便于扩展新的数据源(如交通数据)和预警类型;

  • 容错机制:API调用失败时会抛出异常,任务框架会记录日志并在下一次周期重试,确保数据获取的可靠性。

三、项目总结与2026年技术规划

(一)项目核心收获

2025年的这个物联网项目,是对Java技术在物联网领域应用的一次全面实践。通过项目落地,我不仅深化了MQTT、ES等技术的底层理解,更积累了"跨协议整合""数据检索优化""配置驱动架构设计"的实战经验。项目中形成的MQTT客户端工具类、ES检索模板、第三方API调用框架等,已沉淀为团队可复用的技术组件,提升了后续项目的开发效率。

(二)2026年技术规划

  1. 技术深化:深入研究物联网通信协议(如CoAP、LwM2M),探索低功耗设备的接入方案;

  2. 架构优化:将项目重构为微服务架构,拆分设备管理、视频同步、预警服务等模块,提升系统可扩展性;

  3. 智能化升级:引入AI算法(如设备故障预测),实现从"被动预警"到"主动预测"的升级;

  4. 知识输出:将项目中的技术难点与解决方案整理为系列文章,持续分享实战经验,助力技术社区发展。

四、结语

技术的价值在于解决实际问题,2025年的这个项目让我深刻体会到"技术选型需适配场景""架构设计要兼顾灵活与稳定"的重要性。未来,我将继续聚焦Java技术在物联网、大数据领域的应用,在深耕技术的同时,通过博客分享更多实战干货,与大家共同成长。

相关推荐
码农水水2 小时前
阿里Java面试被问:慢查询的优化方案
java·adb·面试
222you2 小时前
RuoYi-Vue3的项目搭建
java
C++业余爱好者2 小时前
Hibernate 框架超详细说明
java·开发语言
零度@2 小时前
30条Java性能优化清单
java·开发语言
期待のcode3 小时前
Java的包装类
java·开发语言
李少兄3 小时前
从一篇IDEA笔记开始,我走出了自己的技术创作路
java·笔记·intellij-idea
鹿角片ljp3 小时前
力扣26.有序数组去重:HashSet vs 双指针法
java·算法
SweetCode3 小时前
汉诺塔问题
android·java·数据库
p&f°3 小时前
Java面试题(全)自用
java·开发语言