简介:这是一套基于Spring Boot与Vue.js开发的完整智能家居系统源码,专为计算机、电子信息工程等专业本科生毕设及课程设计打造,解决学生缺乏真实项目经验、难以整合前后端技术栈的实践痛点。资源共444个文件,包含77个核心Java后端模块、46个Vue前端组件、161个SVG图标资源,以及SQL数据库脚本、YML配置、BAT一键启停脚本(如run.bat/build.bat)等工程化支持文件,压缩包仅15.23MB,结构清晰、开箱即用。目前已有54人下载学习,适合从零掌握IoT类Web系统开发全流程的学习者。读者可直接运行调试,获得含设备控制、用户管理、数据可视化等完整功能的可部署系统,同时深入理解RESTful接口设计、Vue组件通信、Spring Security权限控制及前后端联调排错方法。
1. 项目缘起:从零到一,一个全栈智能家居系统的诞生
几年前,我接手了一个家庭智能化的改造需求。客户的要求很明确:要一个能集中控制灯光、窗帘、空调,并能通过手机远程查看室内温湿度的系统,同时希望界面美观、操作流畅。市面上成熟的商业解决方案要么价格昂贵、定制性差,要么数据隐私存疑。作为一个技术出身的从业者,我决定自己动手,用最主流的技术栈——Spring Boot 作为后端基石,Vue.js 作为前端框架,来构建一套轻量、可控、可二次开发的智能家居系统原型。
这个决定背后有几个核心考量。首先,Spring Boot 的“约定大于配置”理念和强大的生态,能让我快速搭建起稳定、可扩展的后端服务,处理设备连接、数据存储和业务逻辑。其次,Vue.js 的渐进式特性和响应式数据绑定,非常适合构建交互复杂、需要实时反馈的前端控制面板。最后,也是最重要的一点,自己掌控源码意味着你可以根据实际硬件(无论是 ESP8266、树莓派还是成熟的 Zigbee 网关)灵活调整通信协议,数据完全私有化,并且可以无限扩展功能模块。
经过几个版本的迭代和实际部署,这套系统已经稳定运行。今天,我就把这套“基于Springboot和Vue的智能家居系统”的核心设计思路、关键技术实现细节,以及开发过程中踩过的那些“坑”和收获的经验,毫无保留地分享出来。无论你是想学习全栈开发实战,还是正计划为自己打造一个智能小家,这篇文章都能为你提供一条清晰的路径和可直接复用的代码骨架。
2. 系统架构全景:前后端分离下的智能核心
在动手写第一行代码之前,清晰的架构设计是避免后期陷入混乱的关键。我们采用的经典前后端分离架构,不仅仅是把前端页面和后端服务拆开,更重要的是职责的清晰划分和数据流的明确设计。
2.1 后端(Spring Boot)核心模块划分
后端是整个系统的大脑,负责设备通信、数据持久化、用户鉴权和业务逻辑调度。我将其划分为以下几个核心模块,每个模块都是一个独立的 Maven 子模块或清晰的包结构,便于维护和团队协作。
设备连接与通信模块:这是与物理世界交互的桥梁。考虑到智能家居设备的多样性,我设计了一个抽象的DeviceConnector接口。对于通过 MQTT 协议上报数据的设备(如 ESP32),我们实现MqttDeviceConnector;对于通过 TCP Socket 直连的硬件,则实现TcpDeviceConnector。所有设备上报的数据(如温湿度、开关状态)都会被统一解析成标准的DeviceData对象,并发布到内部的 Spring Event 事件总线中。这样做的好处是,数据处理逻辑与设备连接逻辑解耦,新增一种设备协议只需实现新的 Connector,无需改动业务代码。
数据持久化模块:使用 MyBatis-Plus 作为 ORM 框架,它强大的 CRUD 封装和条件构造器能极大提升开发效率。数据库表设计上,除了常规的用户、角色表,核心是device(设备元信息表)、device_data(设备上报数据历史表)和device_control_log(设备控制指令日志表)。这里有一个关键点:device_data表需要根据数据量考虑分表或时序数据库方案。对于家庭场景,初期可以按月分表;如果数据量增长迅猛,可以集成 InfluxDB 来专门存储时序数据,MySQL 仅存储元数据和最新状态。
业务逻辑与规则引擎模块:这是实现“智能”的关键。我们定义了一个简单的规则模型:当 [触发器] 满足 [条件] 时,执行 [动作]。例如,“当客厅人体传感器触发且时间在晚上10点后且客厅光照度低于50 lux时,执行打开客厅主灯`”。这个模块监听设备数据事件,匹配用户预设的规则,然后调用设备控制服务执行动作。规则引擎可以做得非常复杂,但初期用一个状态机加条件判断的实现就能覆盖大部分场景。
WebSocket 实时推送模块:为了让前端界面能实时反映设备状态变化(比如灯被物理开关按下了),必须建立双向实时通信。我使用 Spring 提供的WebSocketStomp实现。当设备状态更新或规则触发时,后端会向特定的主题(如/topic/device-status/living-room-light)发送消息,订阅了该主题的前端页面就能立即收到更新并刷新UI。
RESTful API 模块:这是前后端交互的契约。使用 Spring MVC 提供清晰的 API,如/api/device/list获取设备列表,/api/device/{id}/control发送控制指令。所有 API 均需通过 JWT(JSON Web Token)进行鉴权。Swagger 的集成是必须的,它能自动生成 API 文档,前后端开发人员基于此文档并行开发,效率倍增。
2.2 前端(Vue.js)应用结构设计
前端的目标是构建一个直观、响应迅速的控制面板。我采用 Vue CLI 创建项目,并遵循以下结构组织代码。
路由与页面结构:使用 Vue Router 管理路由。主要页面包括:Dashboard.vue(总览仪表盘,显示关键传感器数据和设备聚合状态)、Device.vue(设备列表与详情控制页)、Automation.vue(自动化规则设置页)、History.vue(历史数据图表查看页)。通过路由懒加载来优化首屏性能。
状态管理:虽然对于中小型项目,Vue 的provide/inject或事件总线可能够用,但我强烈推荐使用 Pinia(或 Vuex)进行集中式状态管理。我们将设备列表deviceList、当前用户信息userInfo、全局通知notifications等状态存储在 Store 中。这样做的好处是,任何组件都能方便地获取和修改全局状态,并且状态的变更是可预测、可追踪的。例如,在Dashboard页面和Device页面,它们都引用同一个 Store 中的deviceList,当在Device页面切换某个灯的开关时,Dashboard上的那个灯图标状态也会同步更新。
组件化开发:将 UI 拆分为可复用的组件。例如,SensorCard.vue组件用于展示温湿度传感器的当前值和历史曲线图;SwitchButton.vue是一个带动画效果的开关组件;RuleCard.vue用于展示和编辑一条自动化规则。组件通过props接收参数,通过emits发出事件,保持高内聚、低耦合。
与后端实时通信:使用SockJS-client和webstomp-client库连接后端的 WebSocket 端点。在应用的根组件(如App.vue)中建立连接,并订阅全局的设备状态主题。当收到消息时,调用 Pinia Store 的action来更新对应的设备状态。这样,任何使用了该状态的组件都会自动响应式地更新视图。
UI 框架与图表:为了快速构建美观的界面,我选择了 Element Plus 作为 UI 组件库。它的布局、表单、弹窗等组件非常齐全。对于数据可视化,ECharts 是首选,其丰富的图表类型和灵活的配置项,能够轻松绘制出温湿度变化曲线、设备能耗柱状图等。
3. 核心功能实现拆解:从设备连接到规则自动化
有了清晰的架构,我们来深入几个最核心功能的实现细节。这些部分是系统的筋骨,理解了它们,你就掌握了这套系统的精髓。
3.1 设备接入与数据流:MQTT 的实战应用
在智能家居中,MQTT 协议因其轻量、低功耗、支持发布/订阅模型而成为设备通信的事实标准。我们的系统将 MQTT 服务器(如 EMQX)作为消息中枢。
后端集成 MQTT:在 Spring Boot 中,我们使用org.springframework.integration:spring-integration-mqtt依赖。配置一个MqttPahoClientFactory来设置服务器地址、用户名密码等。然后,定义一个消息通道适配器MqttPahoMessageDrivenChannelAdapter,让它订阅设备上报数据的主题,例如device/data/#。当有消息到达时,适配器会将消息投递到 Spring Integration 的通道中。
// 示例:MQTT 配置与消息处理 @Configuration @EnableIntegration public class MqttConfig { @Bean public MqttPahoClientFactory mqttClientFactory() { DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); MqttConnectOptions options = new MqttConnectOptions(); options.setServerURIs(new String[] {"tcp://your-mqtt-broker:1883"}); options.setUserName("admin"); options.setPassword("password".toCharArray()); factory.setConnectionOptions(options); return factory; } @Bean public MessageChannel mqttInputChannel() { return new DirectChannel(); } @Bean public MessageProducer inbound() { MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("serverClientId", mqttClientFactory(), "device/data/#"); adapter.setCompletionTimeout(5000); adapter.setConverter(new DefaultPahoMessageConverter()); adapter.setQos(1); adapter.setOutputChannel(mqttInputChannel()); return adapter; } @ServiceActivator(inputChannel = "mqttInputChannel") public void handleMessage(Message<?> message) { String topic = (String) message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC); String payload = (String) message.getPayload(); // 解析 payload,转换为 DeviceData 对象 DeviceData data = parseDeviceData(topic, payload); // 发布事件,触发后续处理 applicationEventPublisher.publishEvent(new DeviceDataReceivedEvent(this, data)); } }数据处理与事件驱动:DeviceDataReceivedEvent事件会被多个监听器消费。一个监听器负责将数据存入数据库;另一个监听器负责检查数据是否触发了任何自动化规则;还有一个监听器会通过 WebSocket 将状态更新推送给前端。这种事件驱动模式使得系统各模块之间松耦合,易于扩展。
注意:MQTT 消息的 payload 格式需要提前约定。我推荐使用 JSON,结构清晰且易解析。同时,要为每个设备设计一个唯一的客户端 ID 和主题结构,例如
device/data/{deviceId},便于管理和溯源。
3.2 前后端实时交互:WebSocket + STOMP 详解
HTTP 协议是单向的,前端无法及时获知后端主动发生的变化。WebSocket 解决了这个问题。
后端 WebSocket 配置:Spring 通过@EnableWebSocketMessageBroker注解简化了配置。我们需要配置一个消息代理(这里使用简单的内存代理,生产环境可用 RabbitMQ 或 ActiveMQ 作为外部代理),并定义消息的路由前缀和应用的端点。
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { // 客户端订阅主题的前缀,前端需要订阅 /topic/xxx config.enableSimpleBroker("/topic"); // 客户端发送消息到服务器的路由前缀,前端发送消息到 /app/xxx config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 前端连接 WebSocket 的端点 registry.addEndpoint("/ws-smart-home").setAllowedOriginPatterns("*").withSockJS(); } }后端消息推送:在任何需要通知前端的服务中,注入SimpMessagingTemplate,即可向指定主题发送消息。
@Service public class DeviceStatusService { @Autowired private SimpMessagingTemplate messagingTemplate; public void publishDeviceStatusChange(Device device) { // 当设备状态变更时,推送给所有订阅了该主题的前端 messagingTemplate.convertAndSend("/topic/device-status/" + device.getId(), device.getStatus()); } }前端连接与订阅:在 Vue 项目中,我们通常在main.js或一个独立的websocket.js服务中建立全局连接。
// websocket.js import SockJS from 'sockjs-client'; import webstomp from 'webstomp-client'; let stompClient = null; let connected = false; export function connectWebSocket(store) { const socket = new SockJS('http://your-backend:8080/ws-smart-home'); stompClient = webstomp.over(socket); stompClient.connect({}, frame => { connected = true; console.log('WebSocket Connected: ' + frame); // 订阅设备状态全局主题 stompClient.subscribe('/topic/device-status/+', message => { const statusUpdate = JSON.parse(message.body); // 更新 Pinia Store 中的设备状态 store.updateDeviceStatus(statusUpdate); }); }, error => { console.error('WebSocket连接错误:', error); connected = false; }); } export function sendControlCommand(deviceId, command) { if (connected && stompClient) { stompClient.send(`/app/device/${deviceId}/control`, JSON.stringify(command)); } }这样,一个完整的、低延迟的实时通信链路就建立起来了。前端控制设备后,指令通过 HTTP API 下发;设备状态变化或他人操作后,状态通过 WebSocket 实时推送到所有在线用户的界面。
3.3 自动化规则引擎:让家自己“思考”
自动化是智能家居的灵魂。我们实现一个轻量级但功能完备的规则引擎。
规则数据模型设计:
@Data public class AutomationRule { private Long id; private String name; private Boolean enabled; private List<Trigger> triggers; // 触发器:设备事件、时间点等 private List<Condition> conditions; // 条件:与、或、非组合 private List<Action> actions; // 动作:控制设备、发送通知等 } @Data public class Trigger { private String type; // "DEVICE_EVENT", "TIMER" private String deviceId; // 如果是设备事件 private String event; // "TURN_ON", "TEMPERATURE_ABOVE" private Object value; // 触发值 } @Data public class Condition { private String type; // "AND", "OR", "NOT" private List<Condition> children; // 子条件 private String deviceId; private String operator; // ">", "<", "==", "!=" private Object expectedValue; } @Data public class Action { private String type; // "CONTROL_DEVICE", "SEND_NOTIFICATION" private String target; // 设备ID或通知渠道 private Object command; // 控制指令内容 }规则引擎执行流程:
- 监听事件:规则引擎监听
DeviceDataReceivedEvent和系统定时事件。 - 匹配触发器:当事件到来,遍历所有已启用的规则,检查事件的类型、设备ID、事件值是否匹配规则的任一
Trigger。 - 评估条件:如果触发器匹配,则评估该规则的所有
Condition。条件是一个树形结构,需要递归计算。例如,一个条件可能是(客厅温度 > 28) AND (时间在 9:00-18:00 之间)。 - 执行动作:如果所有条件满足,则顺序执行规则中定义的
Action。例如,先执行“打开客厅空调”,再执行“发送一条手机推送通知”。
前端规则配置界面:这是前端复杂度的体现。我们需要一个可视化的规则编辑器。可以使用动态表单来生成触发器和条件的配置项。例如,当用户选择触发器类型为“设备事件”时,下拉框动态加载设备列表;选择条件运算符时,动态显示对应的值输入框(数字输入框、时间选择器等)。将最终配置的 JSON 对象保存到后端。
实操心得:规则引擎初期不要过度设计,满足“如果-就”这种最常用的场景即可。将规则持久化到数据库时,注意对
conditions这种嵌套结构使用 JSON 类型字段存储(如果 MySQL 版本支持),或者序列化成字符串。另外,规则执行是同步的,要避免执行耗时过长的动作阻塞引擎,对于发送HTTP请求等IO操作,可以考虑异步执行。
4. 开发部署全流程与避坑指南
有了核心代码,如何把它跑起来,并部署到一个稳定的环境中?这里分享从环境准备到上线运维的完整链路。
4.1 本地开发环境搭建
后端环境:
- JDK:建议使用 JDK 11 或 17,这是 Spring Boot 2.x 和 3.x 的推荐版本。在
pom.xml中指定好 Java 版本。 - IDE:IntelliJ IDEA 是首选,其对 Spring Boot 的支持无与伦比。也可以使用 Eclipse 配合 Spring Tools Suite 插件。
- 数据库:本地安装 MySQL 或使用 Docker 运行一个 MySQL 容器。建议使用 Docker,环境一致且干净。
docker run --name mysql-smart-home -e MYSQL_ROOT_PASSWORD=yourpassword -p 3306:3306 -d mysql:8.0 - MQTT Broker:同样使用 Docker 运行 EMQX。
docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 emqx/emqx:latest - 配置文件:在
application.yml中配置数据源、MQTT连接等。使用spring.profiles.active=dev来区分环境。
前端环境:
- Node.js:安装 LTS 版本的 Node.js(如 18.x)。
- 包管理器:使用 npm 或 yarn,建议用 yarn,速度更快。
- 创建项目:
vue create smart-home-frontend,选择手动配置,加入 Router, Pinia, 并选择需要的 CSS 预处理器(如 Sass)。 - 安装依赖:
yarn add element-plus echarts sockjs-client webstomp-client axios。
前后端联调:后端启动后,前端配置一个vue.config.js文件设置开发服务器代理,解决跨域问题。
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true }, '/ws': { target: 'http://localhost:8080', ws: true, // 代理 WebSocket changeOrigin: true } } } }4.2 生产环境部署方案
生产环境追求稳定、可维护和可扩展。
后端部署:
- 打包:使用
mvn clean package -DskipTests生成可执行的 JAR 文件(内嵌 Tomcat)。 - 进程管理:不要直接用
java -jar启动。使用 systemd(Linux)或 NSSM(Windows)将 Spring Boot 应用注册为系统服务,实现开机自启和故障重启。# /etc/systemd/system/smart-home.service 示例 [Unit] Description=Smart Home Backend Service After=network.target [Service] Type=simple User=appuser ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/app/smart-home-backend.jar Restart=on-failure [Install] WantedBy=multi-user.target - 外部化配置:将数据库密码、MQTT 密钥等敏感信息放在 JAR 包外的
application-prod.yml文件中,或使用环境变量注入。 - 日志:配置 Logback 或 Log4j2,将日志按级别和日期滚动输出到文件,便于排查问题。
前端部署:
- 构建:运行
yarn build或npm run build,生成静态文件在dist目录。 - Web 服务器:使用 Nginx 作为静态文件服务器和反向代理。将
dist目录下的文件放到 Nginx 的 HTML 目录。 - Nginx 配置关键点:
这样,用户访问server { listen 80; server_name your-domain.com; root /var/www/smart-home-frontend; index index.html; # 处理前端路由(history模式) location / { try_files $uri $uri/ /index.html; } # 反向代理后端 API location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 代理 WebSocket 连接 location /ws/ { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }your-domain.com,Nginx 服务前端页面;页面中的 API 请求和 WebSocket 连接被 Nginx 转发到后端 Spring Boot 应用。
4.3 实战中踩过的“坑”与解决方案
坑一:WebSocket 连接在 Nginx 代理后频繁断开
- 现象:前端部署后,WebSocket 连接不稳定,几分钟就断开。
- 排查:检查 Nginx 日志和配置。发现是 Nginx 默认的
proxy_read_timeout较短。 - 解决:在代理 WebSocket 的 location 块中增加超时配置。
location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; # 设置长连接超时 proxy_send_timeout 3600s; }
坑二:设备频繁上下线导致 MQTT 连接数暴涨
- 现象:使用 ESP8266 的设备,在网络不稳定时会频繁重连,每个连接都会在 MQTT Broker 上创建一个新会话,导致连接数过多。
- 解决:在设备端代码中,为每个设备设置一个固定的、唯一的 Client ID,并设置
cleanSession为false。这样,设备重连时会复用之前的会话,Broker 端不会创建新的连接记录。同时,在后端 Connector 中实现心跳检测,对于长时间无心跳的“僵尸”连接,主动清理其资源。
坑三:前端复杂表单(规则编辑)的数据绑定与验证混乱
- 现象:规则编辑器包含多个动态嵌套的表单域,使用
v-model直接绑定到复杂对象的嵌套属性,验证和重置逻辑非常难写。 - 解决:采用 Vue 3 的
reactive或ref创建响应式规则对象。对于每个可动态增减的条件行,为其生成一个唯一的 key。使用watch深度监听规则对象的变化,并将变化同步到一个用于提交的纯 JavaScript 对象。验证方面,可以结合使用async-validator库或 Element Plus 表单组件的内置验证规则,为每个字段定义清晰的校验规则。
坑四:Spring Boot 应用内存缓慢增长
- 现象:服务运行几天后,内存占用持续升高。
- 排查:使用
jmap或VisualVM连接进程,生成堆转储文件分析。发现是 WebSocket 会话对象和缓存的设备连接对象没有被及时释放。 - 解决:
- 实现
WebSocketHandler的afterConnectionClosed方法,在连接关闭时,清理与该会话相关的所有缓存。 - 对于设备连接器,实现一个心跳超时机制,定期检查并清理无效连接。
- 检查代码中是否有静态集合类(如
Map、List)持续添加对象而未移除,这是内存泄漏的常见原因。
- 实现
坑五:跨域问题在部署后依然出现
- 现象:本地开发联调正常,部署到服务器后,前端请求 API 出现 CORS 错误。
- 原因:本地开发时,Vue CLI 的代理只在开发服务器生效。部署后,前端直接通过域名访问,请求发到了 Nginx,但 Nginx 配置中可能遗漏了 CORS 头,或者后端 Spring Boot 的 CORS 配置与 Nginx 配置冲突。
- 解决:统一在 Nginx 层处理 CORS,关闭后端 Spring Boot 的 CORS 配置(或将其配置为允许 Nginx 的源)。在 Nginx 的 API 代理 location 块中添加 CORS 头:
location /api/ { proxy_pass http://localhost:8080; ... add_header 'Access-Control-Allow-Origin' '$http_origin' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; if ($request_method = 'OPTIONS') { return 204; } }
这套从零开始的智能家居系统开发之旅,涵盖了从技术选型、架构设计、核心功能实现到部署上线的完整闭环。每个环节的选择和实现,都源于实际项目中的需求和挑战。技术本身不是目的,用技术解决真实问题、创造价值才是。希望这份详尽的拆解,能为你点亮自己动手打造智能家园的道路。
本文还有配套的精品资源,点击获取