基于Vue 3 + Spring Boot的物联网生鲜品储运系统设计与实现(源码附有详细的文档讲解)

🚀 从0到1构建高可用物联网生鲜品储运系统:Spring Boot 3.2 + MQTT实战全解析

> 本文深度剖析一个企业级物联网生鲜品储运系统的完整后端实现,涵盖架构设计、核心功能、性能优化和实战经验,提供可直接复用的代码和最佳实践。

源码获取:

Github

前端:

GitHub - CoderDongHuang/Iot-Fresh-System · GitHubhttps://github.com/CoderDongHuang/Iot-Fresh-System

后端:GitHub - CoderDongHuang/Iot-Fresh-System-Server: 这是一个基于物联网的智能鲜品物联网系统 · GitHubhttps://github.com/CoderDongHuang/Iot-Fresh-System-Server
Gitee

前端:

hdh/Iot-Fresh-Sytem - 码云 - 开源中国https://gitee.com/donghuang-C/iot-fresh-sytem

后端:

hdh/Iot-Fresh-System-Server - 码云 - 开源中国https://gitee.com/donghuang-C/iot-fresh-system-server


目录

[🎯 项目背景与痛点分析](#🎯 项目背景与痛点分析)

行业现状与挑战

解决方案价值

智能化物联网监控的效果图

设备状态分布图

设备报警类型统计

仪表盘主界面

设备实时状态(仪表盘主界面,可随时看)

导出数据

设备管理,可看设备详细信息

在线添加设备

实时监控,定时刷新实时数据

设备详情界面,有图表和历史数据

在线控制设备

报警功能(在线提示音和弹窗,后台会自动发送钉钉信息)

数据查询,可查看历史数据

个人界面

[🏗️ 技术架构与选型决策](#🏗️ 技术架构与选型决策)

整体架构设计

前后端技术栈选型

后端技术栈

技术选型表格

前端技术栈

技术组件选型对比表

前后端分离架构优势

前后端项目结构设计

后端项目结构

前端项目结构

[💡 核心功能模块详解](#💡 核心功能模块详解)

[1. 前端架构设计与用户体验优化](#1. 前端架构设计与用户体验优化)

响应式布局设计

数据可视化组件设计

状态管理设计

设备管理模块

核心代码实现

实时数据监控模块

核心代码实现

[🗄️ 数据库设计与优化](#🗄️ 数据库设计与优化)

核心表结构设计

设备表 (devices)

设备数据表 (device_data)

数据库性能优化

索引策略

分区策略(大数据量场景)

[🔌 MQTT通信协议实现](#🔌 MQTT通信协议实现)

MQTT主题设计规范

MQTT配置类详解

[🔐 安全认证与权限控制](#🔐 安全认证与权限控制)

JWT认证实现

[Spring Security配置](#Spring Security配置)

[🚀 性能优化实战经验](#🚀 性能优化实战经验)

前端性能优化策略

代码分割与懒加载

图表组件优化

数据缓存策略

[🔧 深度踩坑与解决方案](#🔧 深度踩坑与解决方案)

前端开发中的常见问题

图表内存泄漏问题

设备状态同步问题

大数据量图表渲染性能问题

前后端联调问题

接口兼容性问题

实时数据推送稳定性

[💎 总结与未来展望](#💎 总结与未来展望)

项目价值总结

技术亮点

未来规划

[📚 参考资料](#📚 参考资料)


🎯 项目背景与痛点分析

行业现状与挑战

在生鲜品储运行业,温湿度控制是决定产品质量的关键因素。传统监控方式存在以下痛点:

  • 🔍 监控盲区: 人工巡查无法实现24小时不间断监控
  • ⚠️ 预警滞后: 环境异常发现时产品已受损
  • 📊 数据孤岛: 历史数据难以有效分析和利用
  • 🔧 维护困难: 设备故障无法及时发现和处理

解决方案价值

我们开发的物联网生鲜品储运系统通过以下方式解决上述痛点:

  1. 🌡️ 实时监控: 秒级数据采集,实时环境监控
  2. 🚨 智能预警: 多维度异常检测,提前预警
  3. 📈 数据分析: 历史数据趋势分析,辅助决策
  4. 🔧 远程维护: 设备状态实时监控,远程诊断

智能化物联网监控的效果图

设备状态分布图

设备报警类型统计

仪表盘主界面

设备实时状态(仪表盘主界面,可随时看)

导出数据

设备管理,可看设备详细信息

在线添加设备

实时监控,定时刷新实时数据

设备详情界面,有图表和历史数据

在线控制设备

报警功能(在线提示音和弹窗,后台会自动发送钉钉信息)

数据查询,可查看历史数据

个人界面


🏗️ 技术架构与选型决策

整体架构设计


前后端技术栈选型

后端技术栈

技术选型表格
技术组件 选型理由 版本 优势
Spring Boot 现代化框架,生态完善 3.2.0 快速开发,自动配置
MySQL 成熟稳定,ACID特性 8.0+ 事务支持,数据一致性
Redis 高性能缓存,丰富数据结构 6.0+ 内存存储,低延迟
MQTT 物联网专用协议 3.1.1 轻量级,低带宽消耗
JWT 无状态认证 最新 跨域支持,易于扩展

前端技术栈

技术组件选型对比表
技术组件 选型理由 版本 优势
Vue 3 + TypeScript 现代化框架,类型安全 3.5.26 组合式API,响应式系统
Element Plus 企业级UI组件库 2.13.0 丰富组件,主题定制
ECharts 专业数据可视化 6.0.0 丰富图表类型,高性能
Vite 下一代构建工具 7.2.4 极速热更新,按需编译
Pinia 现代化状态管理 3.0.4 类型安全,简洁API
Axios HTTP客户端库 1.13.2 拦截器,请求取消

前后端分离架构优势

  1. 开发效率提升:前后端并行开发,互不干扰

  2. 技术栈灵活性:前端可独立选择最适合的技术方案

  3. 性能优化:静态资源CDN加速,API接口独立部署

  4. 维护性增强:前后端职责清晰,易于维护和扩展


前后端项目结构设计

后端项目结构
复制代码
src/main/java/com/iot/fresh/
├── controller/           # 控制器层 - HTTP请求处理
│   ├── DeviceController.java         # 设备管理控制器
│   ├── DashboardController.java      # 仪表盘控制器
│   ├── AlarmController.java          # 报警控制器
│   ├── DataController.java           # 数据控制器
│   ├── DeviceManagementController.java # 设备管理控制器
│   ├── DeviceControlController.java  # 设备控制控制器
│   ├── AuthController.java           # 认证控制器
│   └── UserController.java           # 用户控制器
├── dto/                 # 数据传输对象
│   ├── DeviceDto.java               # 设备数据传输对象
│   ├── DeviceDataDto.java           # 设备数据传输对象
│   ├── AlarmDto.java                # 报警数据传输对象
│   ├── ApiResponse.java             # API统一响应格式
│   ├── PaginatedResponse.java       # 分页响应对象
│   └── LoginRequest.java            # 登录请求对象
├── entity/              # 实体类 - 对应数据库表
│   ├── Device.java                  # 设备实体
│   ├── DeviceData.java              # 设备数据实体
│   ├── Alarm.java                   # 报警实体
│   └── User.java                    # 用户实体
├── service/             # 服务层接口
│   ├── DeviceService.java           # 设备服务接口
│   ├── DataService.java             # 数据服务接口
│   ├── AlarmService.java            # 报警服务接口
│   ├── DashboardService.java        # 仪表盘服务接口
│   └── UserService.java             # 用户服务接口
├── service/impl/        # 服务层实现
│   ├── DeviceServiceImpl.java       # 设备服务实现
│   ├── DataServiceImpl.java         # 数据服务实现
│   ├── AlarmServiceImpl.java        # 报警服务实现
│   ├── DashboardServiceImpl.java    # 仪表盘服务实现
│   └── UserServiceImpl.java         # 用户服务实现
├── repository/          # 数据访问层
│   ├── DeviceRepository.java        # 设备数据访问
│   ├── DeviceDataRepository.java    # 设备数据访问
│   ├── AlarmRepository.java         # 报警数据访问
│   └── UserRepository.java          # 用户数据访问
├── config/              # 配置类
│   ├── MqttConfig.java              # MQTT配置
│   ├── SecurityConfig.java          # 安全配置
│   ├── RedisConfig.java             # Redis配置
│   └── CorsConfig.java              # 跨域配置
├── handler/             # 消息处理器
│   └── MqttMessageHandler.java      # MQTT消息处理器
├── security/            # 安全相关
│   ├── JwtAuthenticationFilter.java # JWT认证过滤器
│   └── JwtUtil.java                 # JWT工具类
├── util/                # 工具类
│   └── DateUtils.java               # 日期工具类
└── IotFreshApplication.java         # 主启动类
前端项目结构
复制代码
📦 iotfreshsystem-vue-2022

└── 📂  src
    ├── 📂  api                 # 🔌 API接口层
    │   ├── device.ts          # 设备相关API
    │   ├── dashboard.ts       # 仪表盘API
    │   ├── alarm.ts          # 报警管理API
    │   ├── data.ts           # 数据查询API
    │   └── http.ts           # HTTP请求封装
    ├── 📂 views               # 🎨 页面组件
    │   ├── dashboard/        # 仪表盘页面
    │   ├── device/           # 设备管理页面
    │   ├── monitor/          # 实时监控页面
    │   ├── alarm/            # 报警管理页面
    │   ├── data/             # 数据查询页面
    │   └── control/           # 设备控制页面
    ├── 📂 components         # 🔧 通用组件
    │   ├── charts/           # 图表组件
    │   │   ├── DevicePieChart.vue    # 设备状态饼图
    │   │   ├── AlarmBarChart.vue     # 报警统计柱状图
    │   │   ├── TemperatureChart.vue  # 温度趋势图
    │   │   └── LightChart.vue        # 光照强度图
    │   ├── device/           # 设备相关组件
    │   │   └── DeviceStatusTable.vue # 设备状态表格
    │   └── layout/           # 布局组件
    │       ├── Sidebar.vue   # 侧边栏
    │       ├── Navbar.vue    # 导航栏
    │       └── Footer.vue    # 页脚
    ├── 📂 store              # 🗃️ 状态管理
    │   ├── device.ts        # 设备状态管理
    │   ├── alarm.ts         # 报警状态管理
    │   └── user.ts          # 用户状态管理
    ├── 📂 router            # 🧭 路由配置
    │   ├── index.ts         # 路由主文件
    │   ├── routes.ts        # 路由定义
    │   └── guard.ts          # 路由守卫
    ├── 📂 types             # 📋 类型定义
    │   ├── api.ts           # API接口类型
    │   ├── device.ts        # 设备相关类型
    │   └── common.ts        # 通用类型
    ├── 📂 utils             # 🛠️ 工具函数
    │   ├── date.ts          # 日期处理
    │   ├── export.ts        # 数据导出
    │   └── auth.ts          # 认证工具
    └── 📂 composables       # 🔄 组合式函数
        ├── useDevice.ts     # 设备相关逻辑
        ├── useCharts.ts     # 图表相关逻辑
        └── useWebSocket.ts  # WebSocket逻辑

💡 核心功能模块详解

1. 前端架构设计与用户体验优化

响应式布局设计

前端采用现代化的响应式设计,确保在不同设备上都能提供优秀的用户体验:

复制代码
<template>

  <div class="dashboard-container">

    <!-- 顶部统计卡片 -->

    <div class="statistics-cards">

      <el-row :gutter="20">

        <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">

          <el-card shadow="hover" class="card-item">

            <div class="card-content">

              <div class="card-icon" style="background: #409EFF;">

                <el-icon><Platform /></el-icon>

              </div>

              <div class="card-info">

                <div class="card-title">在线设备</div>

                <div class="card-value">{{ statistics.onlineDevices }}</div>

              </div>

            </div>

            <div class="card-footer">

              <span>总计 {{ statistics.totalDevices }} 台</span>

              <el-progress

                :percentage="onlinePercentage"

                :color="onlinePercentage > 80 ? '#67c23a' : '#e6a23c'"

                :stroke-width="6"

                :show-text="false"

              />

            </div>

          </el-card>

        </el-col>

        <!-- 更多统计卡片... -->

      </el-row>

    </div>

  </div>

</template>
数据可视化组件设计

系统采用ECharts实现专业级数据可视化,支持多种图表类型:

例如:设备状态分布饼图组件

复制代码
<template>

  <div class="device-pie-chart" ref="chartRef" :style="{ height: height, width: width }" />

</template>



<script setup lang="ts">

import { ref, onMounted, watch } from 'vue'

import * as echarts from 'echarts'



interface Props {

  width?: string

  height?: string

  data?: {

    online: number

    offline: number

    fault: number

    maintenance: number

  }

}



const props = withDefaults(defineProps<Props>(), {

  width: '100%',

  height: '400px',

  data: () => ({ online: 0, offline: 0, fault: 0, maintenance: 0 })

})



const chartRef = ref<HTMLElement>()

let chart: echarts.ECharts | null = null



const initChart = () => {

  if (chartRef.value) {

    chart = echarts.init(chartRef.value)

   

    const option = {

      title: { text: '设备状态分布', left: 'center' },

      tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },

      legend: { orient: 'vertical', left: 'left' },

      series: [{

        name: '设备状态',

        type: 'pie',

        radius: ['15%', '75%'],

        roseType: 'area',

        avoidLabelOverlap: true,

        label: { show: true, position: 'outside', formatter: '{b}: {c} ({d}%)' },

        labelLine: { show: true, length: 25, length2: 35 },

        data: [

          { value: props.data.online, name: '在线', itemStyle: { color: '#67C23A' } },

          { value: props.data.offline, name: '离线', itemStyle: { color: '#909399' } },

          { value: props.data.fault, name: '故障', itemStyle: { color: '#F56C6C' } },

          { value: props.data.maintenance, name: '维护', itemStyle: { color: '#E6A23C' } }

        ]

      }]

    }

   

    chart.setOption(option)

  }

}



// 监听数据变化自动更新图表

watch(() => props.data, () => {

  if (chart) {

    chart.setOption({

      series: [{ data: [

        { value: props.data.online, name: '在线' },

        { value: props.data.offline, name: '离线' },

        { value: props.data.fault, name: '故障' },

        { value: props.data.maintenance, name: '维护' }

      ]}]

    })

  }

}, { deep: true })



onMounted(() => {

  initChart()

})

</script>

状态管理设计

采用Pinia进行现代化状态管理,确保数据流清晰可控:

TypeScript 复制代码
// store/device.ts

import { defineStore } from 'pinia'

import type { DeviceInfo } from '@/types/api'



export const useDeviceStore = defineStore('device', {

  state: () => ({

    deviceList: [] as DeviceInfo[],

    currentDevice: null as DeviceInfo | null,

    deviceStatusStats: {

      online: 0,

      offline: 0,

      fault: 0,

      maintenance: 0

    },

    loading: false

  }),



  getters: {

    onlineDevices: (state) => state.deviceList.filter(d => d.status === 1),

    offlineDevices: (state) => state.deviceList.filter(d => d.status === 0),

    totalDevices: (state) => state.deviceList.length,

    onlinePercentage: (state) => {

      const total = state.deviceList.length

      return total > 0 ? Math.round((state.deviceStatusStats.online / total) * 100) : 0

    }

  },



  actions: {

    async fetchDeviceList(params?: any) {

      this.loading = true

      try {

        const response = await getDeviceList(params)

        this.deviceList = response.data?.list || response.list || []

      } catch (error) {

        console.error('获取设备列表失败:', error)

      } finally {

        this.loading = false

      }

    },

   

    async fetchDeviceStatusStats() {

      try {

        const response = await getDeviceStatusStats()

        this.deviceStatusStats = response.data || response

      } catch (error) {

        console.error('获取设备状态统计失败:', error)

      }

    }

  }

})

设备管理模块

设备状态管理

系统支持四种设备状态,满足不同业务场景:

以下为符合Markdown格式的表格:

状态表

状态 编码 描述 业务场景
🔵 在线 1 设备正常运行 正常监控状态
⚫ 离线 0 设备无法连接 网络故障或设备关机
🔴 故障 2 设备硬件故障 需要维修
🟡 维护 3 设备维护中 定期保养或升级

核心代码实现

设备控制器 - 支持复杂查询和分页

java 复制代码
@RestController

@RequestMapping("/api/device")

@Slf4j

public class DeviceController {

   

    @Autowired

    private DeviceService deviceService;

   

    /**

     * 获取设备列表 - 支持多条件查询和分页

     * @param pageNum 页码(默认1)

     * @param pageSize 每页大小(默认10)

     * @param keyword 搜索关键词(设备名称/VID)

     * @param status 设备状态过滤

     * @param deviceType 设备类型过滤

     * @return 分页设备列表

     */

    @GetMapping("/list")

    @Operation(summary = "获取设备列表", description = "支持分页、搜索和多条件过滤")

    public ApiResponse<PaginatedResponse<DeviceDto>> getDeviceList(

            @RequestParam(defaultValue = "1") @Min(1) Integer pageNum,

            @RequestParam(defaultValue = "10") @Min(1) @Max(100) Integer pageSize,

            @RequestParam(required = false) String keyword,

            @RequestParam(required = false) String status,

            @RequestParam(required = false) String deviceType) {

       

        log.info("查询设备列表 - 页码: {}, 页大小: {}, 关键词: {}, 状态: {}",

                 pageNum, pageSize, keyword, status);

       

        try {

            // 参数验证和转换

            Integer statusInt = convertStatus(status);

           

            // 构建分页请求

            Pageable pageable = PageRequest.of(pageNum - 1, pageSize,

                Sort.by(Sort.Direction.DESC, "createdAt"));

           

            // 执行查询

            ApiResponse<PaginatedResponse<DeviceDto>> response =

                deviceService.getDeviceList(pageable, keyword, statusInt, deviceType);

           

            log.info("设备列表查询成功 - 总数: {}",

                     response.getData().getTotal());

           

            return response;

        } catch (Exception e) {

            log.error("设备列表查询失败", e);

            return ApiResponse.error(500, "查询失败: " + e.getMessage());

        }

    }

   

    /**

     * 新增设备 - 支持批量设备添加

     */

    @PostMapping("/add")

    @Transactional

    public ApiResponse<List<DeviceDto>> addDevices(@Valid @RequestBody List<@Valid DeviceDto> deviceDtos) {

        log.info("批量新增设备 - 数量: {}", deviceDtos.size());

       

        List<DeviceDto> savedDevices = new ArrayList<>();

        for (DeviceDto dto : deviceDtos) {

            try {

                // 设备唯一性验证

                if (deviceService.existsByVid(dto.getVid())) {

                    throw new RuntimeException("设备VID已存在: " + dto.getVid());

                }

               

                DeviceDto savedDevice = deviceService.addDevice(dto);

                savedDevices.add(savedDevice);

               

            } catch (Exception e) {

                log.error("新增设备失败 - VID: {}, 错误: {}", dto.getVid(), e.getMessage());

                // 继续处理其他设备,不中断整个批量操作

            }

        }

       

        return ApiResponse.success("成功新增 " + savedDevices.size() + " 台设备", savedDevices);

    }

}

设备服务实现 - 包含缓存和业务逻辑

java 复制代码
@Service

@Slf4j

public class DeviceServiceImpl implements DeviceService {

   

    @Autowired

    private DeviceRepository deviceRepository;

   

    @Autowired

    private RedisTemplate<String, Object> redisTemplate;

   

    private static final String DEVICE_CACHE_KEY = "device:";

    private static final long CACHE_EXPIRE_TIME = 3600; // 1小时

   

    @Override

    @Cacheable(value = "devices", key = "#vid", unless = "#result == null")

    public Device getDeviceByVid(String vid) {

        log.debug("查询设备信息 - VID: {}", vid);

       

        return deviceRepository.findByVid(vid)

                .orElseThrow(() -> new DeviceNotFoundException("设备不存在: " + vid));

    }

   

    @Override

    @CacheEvict(value = "devices", key = "#deviceDto.vid")

    public DeviceDto updateDevice(DeviceDto deviceDto) {

        log.info("更新设备信息 - VID: {}", deviceDto.getVid());

       

        Device existingDevice = getDeviceByVid(deviceDto.getVid());

       

        // 更新设备信息

        existingDevice.setDeviceName(deviceDto.getDeviceName());

        existingDevice.setDeviceType(deviceDto.getDeviceType());

        existingDevice.setLocation(deviceDto.getLocation());

        existingDevice.setStatus(deviceDto.getStatus());

        existingDevice.setDescription(deviceDto.getDescription());

       

        Device updatedDevice = deviceRepository.save(existingDevice);

       

        // 更新缓存

        redisTemplate.opsForValue().set(

            DEVICE_CACHE_KEY + deviceDto.getVid(),

            updatedDevice,

            CACHE_EXPIRE_TIME,

            TimeUnit.SECONDS

        );

       

        return convertToDto(updatedDevice);

    }

   

    /**

     * 获取设备统计信息

     */

    @Override

    public Map<String, Object> getDeviceStatistics() {

        log.debug("获取设备统计信息");

       

        // 尝试从缓存获取

        String cacheKey = "device:statistics";

        Map<String, Object> cachedStats = (Map<String, Object>) redisTemplate.opsForValue().get(cacheKey);

       

        if (cachedStats != null) {

            log.debug("从缓存获取设备统计信息");

            return cachedStats;

        }

       

        // 缓存未命中,从数据库查询

        List<Device> devices = deviceRepository.findAll();

       

        long totalDevices = devices.size();

        long onlineDevices = devices.stream()

                .filter(d -> d.getStatus() != null && d.getStatus() == 1)

                .count();

        long offlineDevices = devices.stream()

                .filter(d -> d.getStatus() != null && d.getStatus() == 0)

                .count();

        long faultDevices = devices.stream()

                .filter(d -> d.getStatus() != null && d.getStatus() == 2)

                .count();

        long maintenanceDevices = devices.stream()

                .filter(d -> d.getStatus() != null && d.getStatus() == 3)

                .count();

       

        Map<String, Object> statistics = new HashMap<>();

        statistics.put("total", totalDevices);

        statistics.put("online", onlineDevices);

        statistics.put("offline", offlineDevices);

        statistics.put("fault", faultDevices);

        statistics.put("maintenance", maintenanceDevices);

       

        // 存入缓存,有效期5分钟

        redisTemplate.opsForValue().set(cacheKey, statistics, 5, TimeUnit.MINUTES);

       

        return statistics;

    }

}
实时数据监控模块

数据采集流程


核心代码实现

MQTT消息处理器

java 复制代码
@Component

@Slf4j

public class MqttMessageHandler {

   

    @Autowired

    private DataService dataService;

   

    @Autowired

    private AlarmService alarmService;

   

    @Autowired

    private ObjectMapper objectMapper;

   

    /**

     * 处理MQTT消息 - 支持多种消息类型

     */

    @ServiceActivator(inputChannel = "mqttInputChannel")

    public void handleMqttMessage(@Payload String payload, @Headers MessageHeaders headers) {

        long startTime = System.currentTimeMillis();

       

        try {

            String topic = (String) headers.get("mqtt_receivedTopic");

           

            if (topic != null) {

                log.debug("收到MQTT消息 - 主题: {}, 数据大小: {} bytes",

                         topic, payload.length());

               

                String[] topicParts = topic.split("/");

                if (topicParts.length >= 3) {

                    String vid = topicParts[1];

                    String messageType = topicParts[2];

                   

                    switch (messageType) {

                        case "data":

                            handleDeviceData(vid, payload);

                            break;

                        case "alarm":

                            handleAlarmData(vid, payload);

                            break;

                        case "status":

                            handleStatusUpdate(vid, payload);

                            break;

                        case "rfid":

                            handleRfidData(vid, payload);

                            break;

                        default:

                            log.warn("未知消息类型: {}", messageType);

                            break;

                    }

                }

            }

           

            long processingTime = System.currentTimeMillis() - startTime;

            log.debug("MQTT消息处理完成 - 耗时: {}ms", processingTime);

           

        } catch (Exception e) {

            log.error("处理MQTT消息失败", e);

        }

    }

   

    /**

     * 处理设备数据

     */

    private void handleDeviceData(String vid, String payload) {

        try {

            // 解析设备数据

            DeviceDataDto deviceDataDto = parseDeviceData(vid, payload);

            if (deviceDataDto != null) {

                // 异步保存数据

                dataService.processDeviceDataFromMqtt(vid, payload);

               

                // 检查是否需要触发报警

                checkAlarmConditions(deviceDataDto);

               

                log.info("设备数据处理完成 - VID: {}, 温度: {}, 湿度: {}",

                         vid, deviceDataDto.getTin(), deviceDataDto.getHin());

            }

        } catch (Exception e) {

            log.error("处理设备数据失败 - VID: {}, 数据: {}", vid, payload, e);

        }

    }

   

    /**

     * 解析设备数据JSON

     */

    private DeviceDataDto parseDeviceData(String vid, String payload) {

        try {

            Map<String, Object> jsonMap = objectMapper.readValue(payload, Map.class);

           

            DeviceDataDto dto = new DeviceDataDto();

            dto.setVid(vid);

            dto.setTimestamp(LocalDateTime.now());

           

            // 解析传感器数据

            dto.setTin(getDoubleValue(jsonMap, "Tin"));

            dto.setTout(getDoubleValue(jsonMap, "Tout"));

            dto.setHin(getIntegerValue(jsonMap, "Hin"));

            dto.setHout(getIntegerValue(jsonMap, "Hout"));

            dto.setLxin(getIntegerValue(jsonMap, "LXin"));

            dto.setLxout(getIntegerValue(jsonMap, "LXout"));

            dto.setBrightness(getIntegerValue(jsonMap, "brightness"));

            dto.setVstatus(getIntegerValue(jsonMap, "VStatus"));

           

            // 数据有效性验证

            if (!isValidDeviceData(dto)) {

                log.warn("设备数据无效 - VID: {}, 数据: {}", vid, payload);

                return null;

            }

           

            return dto;

           

        } catch (Exception e) {

            log.error("解析设备数据失败", e);

            return null;

        }

    }

   

    /**

     * 检查报警条件

     */

    private void checkAlarmConditions(DeviceDataDto deviceData) {

        // 温度异常检查

        if (deviceData.getTin() != null && deviceData.getTin() > 35.0) {

            alarmService.createTemperatureAlarm(deviceData.getVid(),

                                               deviceData.getTin(), "温度过高");

        }

       

        // 湿度异常检查

        if (deviceData.getHin() != null && deviceData.getHin() < 20) {

            alarmService.createHumidityAlarm(deviceData.getVid(),

                                            deviceData.getHin(), "湿度过低");

        }

    }

}

🗄️ 数据库设计与优化

核心表结构设计

设备表 (devices)

sql 复制代码
CREATE TABLE devices (

    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',

    vid VARCHAR(50) UNIQUE NOT NULL COMMENT '设备唯一标识',

    device_name VARCHAR(100) NOT NULL COMMENT '设备名称',

    device_type VARCHAR(50) NOT NULL COMMENT '设备类型',

    status TINYINT DEFAULT 1 COMMENT '设备状态: 0-离线,1-在线,2-故障,3-维护',

    location VARCHAR(200) COMMENT '设备位置',

    manufacturer VARCHAR(100) COMMENT '制造商',

    model VARCHAR(100) COMMENT '型号',

    firmware_version VARCHAR(50) COMMENT '固件版本',

    contact_phone VARCHAR(20) COMMENT '联系电话',

    description TEXT COMMENT '设备描述',

    ip_address VARCHAR(45) COMMENT 'IP地址',

    mac_address VARCHAR(17) COMMENT 'MAC地址',

    last_heartbeat TIMESTAMP COMMENT '最后心跳时间',

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

   

    INDEX idx_vid (vid),

    INDEX idx_status (status),

    INDEX idx_type (device_type),

    INDEX idx_location (location),

    INDEX idx_heartbeat (last_heartbeat)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备信息表';

设备数据表 (device_data)

sql 复制代码
CREATE TABLE device_data (

    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',

    vid VARCHAR(50) NOT NULL COMMENT '设备ID',

    tin DECIMAL(5,2) COMMENT '内部温度(℃)',

    tout DECIMAL(5,2) COMMENT '外部温度(℃)',

    hin INT COMMENT '内部湿度(%)',

    hout INT COMMENT '外部湿度(%)',

    lxin INT COMMENT '内部光照强度(lux)',

    lxout INT COMMENT '外部光照强度(lux)',

    brightness INT COMMENT '亮度值',

    v_status TINYINT COMMENT '设备运行状态',

    timestamp TIMESTAMP COMMENT '数据时间戳',

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

   

    INDEX idx_vid (vid),

    INDEX idx_timestamp (timestamp),

    INDEX idx_vid_timestamp (vid, timestamp),

    INDEX idx_status (v_status)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备数据表';

数据库性能优化

索引策略

sql 复制代码
-- 复合索引优化查询性能

CREATE INDEX idx_device_composite ON devices(status, device_type, created_at);

CREATE INDEX idx_data_composite ON device_data(vid, timestamp, tin, hin);

-- 全文索引支持搜索

ALTER TABLE devices ADD FULLTEXT INDEX idx_search (device_name, location, description);

分区策略(大数据量场景)

-- 按时间分区,提高查询性能

sql 复制代码
ALTER TABLE device_data

PARTITION BY RANGE (UNIX_TIMESTAMP(timestamp)) (

    PARTITION p2024_q1 VALUES LESS THAN (UNIX_TIMESTAMP('2024-04-01')),

    PARTITION p2024_q2 VALUES LESS THAN (UNIX_TIMESTAMP('2024-07-01')),

    PARTITION p2024_q3 VALUES LESS THAN (UNIX_TIMESTAMP('2024-10-01')),

    PARTITION p2024_q4 VALUES LESS THAN (UNIX_TIMESTAMP('2025-01-01')),

    PARTITION p_future VALUES LESS THAN MAXVALUE

);

🔌 MQTT通信协议实现

MQTT主题设计规范

XML 复制代码
# 数据上报主题(硬件→后端)

device/{vid}/data:        # 实时数据上报

  payload: {

    "Tin": 22.5,         # 内部温度

    "Tout": 18.0,        # 外部温度  

    "Hin": 65,           # 内部湿度

    "VStatus": 1         # 设备状态

  }



device/{vid}/alarm:       # 报警上报

  payload: {

    "type": "temperature",

    "level": "high",

    "message": "温度过高"

  }



device/{vid}/status:      # 状态上报

  payload: {

    "VStatus": 0,

    "battery": 85

  }



# 控制指令主题(后端→硬件)

device/{vid}/control/temperature:  # 温度控制

device/{vid}/control/light:        # 光照控制

device/{vid}/control/fan:          # 风机控制(目前未采用)

MQTT配置类详解

java 复制代码
@Configuration

@EnableIntegration

@IntegrationComponentScan

@Slf4j

public class MqttConfig {

   

    @Value("${mqtt.broker-url:tcp://localhost:1883}")

    private String brokerUrl;

   

    @Value("${mqtt.username:admin}")

    private String username;

   

    @Value("${mqtt.password:password}")

    private String password;

   

    @Value("${mqtt.client-id:iot-fresh-server}")

    private String clientId;

   

    @Value("${mqtt.keep-alive-interval:60}")

    private int keepAliveInterval;

   

    @Value("${mqtt.connection-timeout:30}")

    private int connectionTimeout;

   

    /**

     * MQTT客户端工厂配置

     */

    @Bean

    public MqttPahoClientFactory mqttClientFactory() {

        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();

       

        MqttConnectOptions options = new MqttConnectOptions();

        options.setServerURIs(new String[]{brokerUrl});

        options.setUserName(username);

        options.setPassword(password.toCharArray());

        options.setCleanSession(true);

        options.setAutomaticReconnect(true);

        options.setKeepAliveInterval(keepAliveInterval);

        options.setConnectionTimeout(connectionTimeout);

        options.setMaxInflight(1000);

       

        // 设置遗嘱消息

        options.setWill("server/status", "offline".getBytes(), 1, true);

       

        factory.setConnectionOptions(options);

       

        log.info("MQTT客户端工厂配置完成 - Broker: {}", brokerUrl);

        return factory;

    }

   

    /**

     * MQTT入站通道适配器

     */

    @Bean

    public MessageProducerSupport mqttInbound() {

        String[] topics = {

            "device/+/data",      # 设备数据

            "device/+/alarm",     # 设备报警

            "device/+/status",    # 设备状态

            "device/+/rfid"       # RFID数据

        };

       

        MqttPahoMessageDrivenChannelAdapter adapter =

            new MqttPahoMessageDrivenChannelAdapter(

                clientId + "-inbound",

                mqttClientFactory(),

                topics);

       

        adapter.setCompletionTimeout(5000);

        adapter.setConverter(new DefaultPahoMessageConverter());

        adapter.setQos(1);  # QoS 1: 至少一次送达

        adapter.setOutputChannel(mqttInputChannel());

       

        log.info("MQTT入站适配器配置完成 - 订阅主题: {}", Arrays.toString(topics));

        return adapter;

    }

   

    /**

     * MQTT出站消息处理器

     */

    @Bean

    @ServiceActivator(inputChannel = "mqttOutboundChannel")
j
    public MessageHandler mqttOutbound() {

        MqttPahoMessageHandler messageHandler =

            new MqttPahoMessageHandler(clientId + "-outbound", mqttClientFactory());

       

        messageHandler.setAsync(true);

        messageHandler.setDefaultTopic("device/control");

        messageHandler.setDefaultQos(1);

       

        return messageHandler;

    }

}

🔐 安全认证与权限控制

JWT认证实现

java 复制代码
@Component

@Slf4j

public class JwtUtil {

   

    @Value("${jwt.secret:defaultSecretKey}")

    private String secretKey;

   

    @Value("${jwt.expiration:86400000}") // 24小时

    private long expiration;

   

    /**

     * 生成JWT令牌

     */

    public String generateToken(String username, List<String> roles) {

        Map<String, Object> claims = new HashMap<>();

        claims.put("roles", roles);

        claims.put("created", new Date());

       

        return Jwts.builder()

                .setClaims(claims)

                .setSubject(username)

                .setIssuedAt(new Date())

                .setExpiration(new Date(System.currentTimeMillis() + expiration))

                .signWith(SignatureAlgorithm.HS512, secretKey)

                .compact();

    }

   

    /**

     * 验证JWT令牌

     */

    public boolean validateToken(String token) {

        try {

            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);

            return true;

        } catch (Exception e) {

            log.warn("JWT令牌验证失败: {}", e.getMessage());

            return false;

        }

    }

   

    /**

     * 从令牌获取用户名

     */

    public String getUsernameFromToken(String token) {

        return Jwts.parser()

                .setSigningKey(secretKey)

                .parseClaimsJws(token)

                .getBody()

                .getSubject();

    }

}

Spring Security配置

java 复制代码
@Configuration

@EnableWebSecurity

@EnableMethodSecurity(prePostEnabled = true)

@Slf4j

public class SecurityConfig {

   

    @Autowired

    private JwtAuthenticationFilter jwtAuthenticationFilter;

   

    @Bean

    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http

            .csrf(csrf -> csrf.disable())

            .cors(cors -> cors.configurationSource(corsConfigurationSource()))

            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

            .authorizeHttpRequests(authz -> authz

                .requestMatchers("/api/auth/**").permitAll()

                .requestMatchers("/api/public/**").permitAll()

                .requestMatchers("/api/admin/**").hasRole("ADMIN")

                .requestMatchers("/api/device/control/**").hasAnyRole("ADMIN", "OPERATOR")

                .requestMatchers("/api/**").authenticated()

                .anyRequest().permitAll()

            )

            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

            .exceptionHandling(exceptions -> exceptions

                .authenticationEntryPoint(new JwtAuthenticationEntryPoint())

                .accessDeniedHandler(new JwtAccessDeniedHandler())

            );

       

        log.info("Spring Security配置完成");

        return http.build();

    }

   

    @Bean

    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

    }

   

    @Bean

    public CorsConfigurationSource corsConfigurationSource() {

        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOriginPatterns(List.of("*"));

        configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));

        configuration.setAllowedHeaders(List.of("*"));

        configuration.setAllowCredentials(true);

       

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        source.registerCorsConfiguration("/**", configuration);

        return source;

    }

}

🚀 性能优化实战经验

前端性能优化策略

代码分割与懒加载

TypeScript 复制代码
// 路由懒加载配置

const routes = [

  {

    path: '/dashboard',

    component: () => import('@/

    views/dashboard/index.vue'),

    meta: { title: '仪表盘' }

  },

  {

    path: '/device/list',

    component: () => import('@/

    views/device/list.vue'),

    meta: { title: '设备管理' }

  }

]

图表组件优化

TypeScript 复制代码
// 按需引入ECharts组件

import { use } from 'echarts/core'

import { CanvasRenderer } from 

'echarts/renderers'

import { PieChart, LineChart, 

BarChart } from 'echarts/charts'

import {

  TitleComponent,

  TooltipComponent,

  LegendComponent,

  GridComponent

} from 'echarts/components'

use([

  CanvasRenderer,

  PieChart,

  LineChart,

  BarChart,

  TitleComponent,

  TooltipComponent,

  LegendComponent,

  GridComponent

])

数据缓存策略

TypeScript 复制代码
// 设备数据缓存机制

const deviceCache = new Map()

export const useDeviceCache = () => 

{

  const getDeviceData = async (vid: 

  string) => {

    if (deviceCache.has(vid)) {

      const cached = deviceCache.get

      (vid)

      if (Date.now() - cached.

      timestamp < 30000) { // 30秒缓

      存

        return cached.data

      }

    }

    

    const data = await 

    fetchDeviceData(vid)

    deviceCache.set(vid, { data, 

    timestamp: Date.now() })

    return data

  }

}

🔧 深度踩坑与解决方案

前端开发中的常见问题

图表内存泄漏问题

问题:ECharts图表在组件销毁时未正确清理,导致内存泄漏

解决方案

TypeScript 复制代码
// 正确的图表生命周期管理

const chartRef = ref<HTMLElement>()

let chart: echarts.ECharts | null = 

null

onMounted(() => {

  if (chartRef.value) {

    chart = echarts.init(chartRef.

    value)

  }

})

onUnmounted(() => {

  if (chart) {

    chart.dispose() // 关键:销毁图表

    实例

    chart = null

  }

})

// 监听窗口大小变化

const handleResize = () => {

  if (chart) {

    chart.resize()

  }

}

window.addEventListener('resize', 

handleResize)

onUnmounted(() => {

  window.removeEventListener

  ('resize', handleResize)

})

设备状态同步问题

问题:多标签页打开时设备状态不同步

解决方案

TypeScript 复制代码
// 使用BroadcastChannel实现多标签页通信

const deviceStatusChannel = new 

BroadcastChannel('device-status')

export const useDeviceSync = () => {

  const updateDeviceStatus = (vid: 

  string, status: number) => {

    // 更新本地状态

    updateLocalDeviceStatus(vid, 

    status)

    

    // 广播到其他标签页

    deviceStatusChannel.postMessage

    ({

      type: 'DEVICE_STATUS_UPDATE',

      payload: { vid, status }

    })

  }

  

  // 监听其他标签页的消息

  deviceStatusChannel.onmessage = 

  (event) => {

    if (event.data.type === 

    'DEVICE_STATUS_UPDATE') {

      updateLocalDeviceStatus(event.

      data.payload.vid, event.data.

      payload.status)

    }

  }

}

大数据量图表渲染性能问题

问题:设备历史数据量过大时图表渲染卡顿

解决方案

TypeScript 复制代码
// 数据采样和虚拟滚动

const useDataSampling = 

(originalData: any[], maxPoints: 

number = 1000) => {

  if (originalData.length <= 

  maxPoints) {

    return originalData

  }

  

  const step = Math.ceil

  (originalData.length / maxPoints)

  const sampledData = []

  

  for (let i = 0; i < originalData.

  length; i += step) {

    sampledData.push(originalData

    [i])

  }

  

  return sampledData

}

// 使用Web Worker处理大数据

const processLargeDataInWorker = 

(data: any[]) => {

  return new Promise((resolve) => {

    const worker = new Worker(new 

    URL('@/workers/data-processor.

    worker', import.meta.url))

    worker.postMessage(data)

    worker.onmessage = (event) => {

      resolve(event.data)

      worker.terminate()

    }

  })

}

前后端联调问题

接口兼容性问题

问题:后端返回字段名与前端期望不一致

解决方案

TypeScript 复制代码
// 统一的数据标准化函数

export const normalizeDeviceData = 

(device: any) => ({

  vid: device.vid,

  deviceName: device.device_name || 

  device.deviceName,

  deviceType: device.device_type || 

  device.deviceType,

  status: device.status,

  location: device.location,

  lastHeartbeat: device.

  last_heartbeat || device.

  lastHeartbeat,

  // 支持多种字段名格式

  ...device

})

// API响应拦截器中的标准化处理

service.interceptors.response.use

((response) => {

  if (response.data && Array.isArray

  (response.data.list)) {

    response.data.list = response.

    data.list.map

    (normalizeDeviceData)

  }

  return response

})

实时数据推送稳定性

问题:WebSocket连接不稳定导致数据丢失

解决方案

TypeScript 复制代码
// 带重连机制的WebSocket连接

class StableWebSocket {

  private ws: WebSocket | null = 

  null

  private reconnectAttempts = 0

  private maxReconnectAttempts = 5

  

  connect(url: string) {

    this.ws = new WebSocket(url)

    

    this.ws.onopen = () => {

      console.log('WebSocket连接成功

      ')

      this.reconnectAttempts = 0

    }

    

    this.ws.onclose = () => {

      console.log('WebSocket连接关闭

      ')

      this.attemptReconnect(url)

    }

    

    this.ws.onerror = (error) => {

      console.error('WebSocket错误

      :', error)

    }

  }

  

  private attemptReconnect(url: 

  string) {

    if (this.reconnectAttempts < 

    this.maxReconnectAttempts) {

      setTimeout(() => {

        this.reconnectAttempts++

        this.connect(url)

      }, Math.min(1000 * this.

      reconnectAttempts, 10000)) // 

      指数退避

    }

  }

}

💎 总结与未来展望

项目价值总结

这个物联网生鲜品储运系统成功解决了传统监控方式的痛点,具有以下核心价值:

  • ✅ 实时性: 秒级数据采集,实时环境监控
  • ✅ 智能化: 多维度异常检测,智能预警
  • ✅ 可扩展: 模块化设计,易于功能扩展
  • ✅ 高可用: 完善的错误处理和容错机制
  • ✅ 安全性: 多层次安全防护体系

技术亮点

  1. 现代化架构: Spring Boot 3.2 + Java 17技术栈

  2. 高性能设计: Redis缓存 + 异步处理 + 数据库优化

  3. 标准化接口: RESTful API设计规范

  4. 物联网协议: MQTT专有协议支持

  5. 企业级安全: JWT认证 + 权限控制

未来规划

  • 🚀 边缘计算: 在设备端实现异常检测算法
  • 🤖 AI预测: 基于历史数据的设备故障预测
  • 🌐 多协议支持: 增加HTTP/CoAP等协议
  • 🐳 容器化部署: Docker + Kubernetes部署
  • 📊 大数据分析: 集成数据分析和可视化平台

📚 参考资料

如果这篇文章对您有帮助,欢迎点赞、收藏和分享!如有任何问题,欢迎在评论区交流讨论!

本文基于实际项目经验编写,所有代码均经过生产环境验证。转载请注明出处。

相关推荐
barbyQAQ2 小时前
GitLab CI/CD 基本用法指南
java·ci/cd·gitlab
历程里程碑2 小时前
Linux 38 网络协议:从独立主机到全球互通
java·linux·运维·服务器·网络·c++·职场和发展
A923A2 小时前
【Vue3大事件 | 项目笔记】第三天
前端·vue.js·笔记·vue·前端项目
任子菲阳2 小时前
学JavaWeb第七天——yml配置文件 & 后端实战Tlias案例
java·开发语言·spring
攻城狮7号2 小时前
工业物联网数据架构指南:Apache IoTDB解析与实践
物联网·时序数据库·存储·数据架构·apache iotdb
BUG?不,是彩蛋!2 小时前
AI智慧社区--实现修改密码、退出登录、动态路由
java·spring boot·后端·intellij-idea·mybatis
白狐_7982 小时前
从零构建飞书 × OpenClaw 自动化情报站(二)
java·自动化·飞书
smxgn2 小时前
【SpringBoot整合系列】SpringBoot3.x整合Swagger
java·spring boot·后端
Smoothcloud润云2 小时前
告别 Selenium:Playwright 现代 Web 自动化测试从入门到实战
前端·人工智能·selenium·测试工具·架构·自动化