不知道你有没有过这种经历:好不容易弄到一套看着很完整的管理系统源码,标题写的是 SpringBoot + Vue + MyBatis + MySQL,结构看起来也挺规矩,但导入 IDEA 之后不是后端启动报错,就是前端白屏,折腾一整晚连登录页都见不到。
我之前整理智能家居管理系统时,前前后后下载过好几套同类开源项目,也踩过不少这类坑。后来我发现,问题往往不在代码本身,而是你不清楚这套系统“为什么要这么设计”——不了解业务边界,你就不知道先看哪个模块;不清楚技术选型的理由,你就不知道怎么改;不知道数据表之间的关系,遇到报错也只能瞎猜。
这篇文章我就把一套基于 SpringBoot + Vue + MyBatis + MySQL 的智能家居管理系统,从业务拆解、后端设计、数据库建模、前端联调、本地部署到扩展改造,完整拆给你看。适合正在做课程设计、毕业设计,或者想入门前后端分离项目、又不想只停留在“能跑就行”的人。
1. 先搞清楚系统要管什么,再去碰代码,这是最省时间的一步
我见过不少人拿到源码第一件事就是双击 idea 图标,项目还在加载就急着点运行,结果报错信息刷了一屏,根本不知道从哪下手。其实这类管理系统先别急着跑,先把它要解决的业务边界想清楚,后面读代码会顺畅非常多。
1.1 用户端核心场景:设备、房间、场景联动
智能家居管理系统,面向普通用户的主干功能通常包括这几块:
- 家庭与房间管理:一个用户创建一个家庭,家庭下分出客厅、卧室、厨房等房间。所有的设备都要归属到一个房间,比如“客厅的空调”“厨房的烟感”。
- 设备管理:添加设备、删除设备、查看设备在线状态、查看设备详情。设备本身有类型差异,比如灯、插座、空调、窗帘电机、温湿度传感器、门磁、烟感报警器。
- 实时控制:这是用户感知最强的部分。点一下灯开关,设备状态立刻翻转;调整空调目标温度,冷热模式切换;打开窗帘,电机正转或反转。
- 场景联动:一个按钮触发多个设备动作,比如“离家模式”一键关灯、关空调、关窗帘;“回家模式”自动开灯、开空调。高级一点还有自动触发条件,比如温度高于 28℃ 自动开空调。
- 定时任务:每天 7:30 自动打开窗帘,每周五晚上自动进入影院模式。
- 告警消息:烟感或水浸传感器触发后,给用户生成一条告警。
这就是为什么这类系统要比普通的后台管理系统复杂。普通管理系统大多是“查数据、改数据”,智能家居系统里还有大量“控制指令下发、状态变化回调”的逻辑。
1.2 管理后台:不该省的地方不要省
在非常多毕设或教学项目里,管理后台经常被做得很敷衍,后台里只有对 device 表的增删改查。但实际上,一个可信的智能家居管理系统需要后台支撑的内容不少。
最基本的后台包括用户管理、设备类型字典管理、房间信息管理、操作日志查看。如果多个用户可以协同管理同一个家庭,那还需要家庭成员授权。加上设备控制类接口天然有安全风险,如果后台不做权限控制,任何拿到接口地址的人都能把别人家的空调打开,这在真实场景里是很恐怖的事。
所以你在源码里如果看到用户端 app 接口和后台管理接口分成两个 Controller 包,不要觉得麻烦,这是你应该学习的地方。这也是我常说的,拿到源码后先花 20 分钟把“谁在用这个系统,每个角色能干什么”列出来,之后看代码就相当于拿着地图走路。
1.3 梳理完需求再对号入座看源码,效率能翻一倍
带着这个地图再打开项目,你会有一种豁然开朗的感觉。
后端 package 结构大概是这样:controller 层负责接收请求,service 层处理业务逻辑,mapper 层与数据库打交道,entity(实体)对应表结构,dto 接收前端参数,vo 返回前端页面需要的数据。
前端如果是 Vue 项目,基本都按“页面视图 + 业务组件 + API 请求 + 路由”分层。设备列表是一个页面,但设备卡片、设备控制面板、场景配置表单,这些是组件。API 请求单独放在一个目录里,是为了多个页面都能复用同一套 ajax 封装。
如果源码连这种基本分层都没有,所有逻辑堆在 controller 里,那后面改造会非常痛苦,你要有心理准备。如果结构清晰,那基本只需要关心你要改的那条链路:前端按钮点击 -> 调用 API -> 后端 Controller 接收 -> Service 处理 -> Mapper 写 SQL -> 数据库表改动。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 后端几个关键决策:SpringBoot 版本、MyBatis 动态 SQL、设备控制接口
技术选型不是越新越好。对于学习项目来说,稳定、资料多、踩坑少才是第一原则。
2.1 SpringBoot 版本怎么选,真不是越高越好
现在有 SpringBoot 3.x,底层基于 Spring Framework 6,要求 JDK 17 以上。但很多教学级源码还是用 SpringBoot 2.7.x 配 JDK 8,因为现在大部分公司老项目也都还跑在 2.x 上。你拿着 2.7 的老源码,本地装的是 JDK 8,结果非要把 pom.xml 改成 3.x,那就会出现你很难看懂的启动失败,原因不是代码有问题,是你把运行环境换掉了。
我整理过一套比较稳的组合,供参考:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | SpringBoot 2.7 项目用 8 最省心 |
| SpringBoot | 2.7.x | 能跑通且示例最多 |
| MySQL | 8.0.x | 官方长期支持版本 |
| MySQL 驱动 | 8.0.x | 不需要刻意替换 |
| 前端 Vue | 2.7 或 Vue 3.2+ | 看源码用的哪个,别强行升级 |
| Node.js | 16 或 18 | Vue2 建议 16,Vue3 建议 18 |
有些热搜词总在问“springboot版本太高怎么办”,如果你遇到的就是启动类报错或者依赖冲突,第一步先把 JDK 版本和 SpringBoot 版本对齐,再检查 maven-compiler-plugin 的 source/target 是否还停留在 1.8,如果 JDK 已经 17 而编译目标还是 1.8,编译阶段就会直接失败。
2.2 智能家居系统这类业务,为什么适合用 MyBatis
JPA 写起来看似轻松,但面对智能家居系统里大量“多条件分页查询设备”“房间聚合统计”“按时间段查日志”这类 SQL,MyBatis 的 XML 动态 SQL 要从容得多。这套系统用 MyBatis 而不是 JPA,本质上是想让你在报表、统计、复杂查询上拿到完全可控的 SQL。
其中最关键的是动态 SQL。设备列表页一般会有筛选条件:按房间查、按设备类型查、按在线状态查、按关键字搜索。最简单粗暴的写法是写 if ... else 拼好几条 SQL,但这不可维护。正确做法是使用 <where> + <if> 标签动态拼接。
下面是一段我实际用过的 mapper XML 片段:
xml复制<select id="listDevicesByCondition" resultType="com.smarthome.vo.DeviceVO">
SELECT
d.id,
d.name,
d.device_type,
d.room_id,
r.name AS room_name,
d.status,
d.last_online_time
FROM device d
LEFT JOIN room r ON r.id = d.room_id
<where>
<if test="roomId != null">
AND d.room_id = #{roomId}
</if>
<if test="deviceType != null and deviceType != ''">
AND d.device_type = #{deviceType}
</if>
<if test="online != null">
AND d.status = #{online}
</if>
<if test="keyword != null and keyword != ''">
AND (d.name LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
ORDER BY d.create_time DESC
</select>
如果你在源码里看到 #{} 和 ${} 混用,你就知道 #{} 是预编译占位符,安全;${} 是字符串替换,主要用在动态排序列名这类不能走占位符的地方,使用时必须加白名单校验,否则存在注入风险。
2.3 控制空调和只开关灯,不能都躺在 Controller 里写 if
如果你是照着模块化的思路开发的,设备控制是非常适合体验“设计模式”的地方。否则每增加一种设备类型,Controller 里就多一层 if else,慢慢就变成面条代码。
一个常见的做法是定义设备动作处理接口:
java复制public interface DeviceActionHandler {
// 当前处理器支持哪种设备类型
boolean support(String deviceType);
// 执行控制命令
ActionResult execute(DeviceCommand command);
}
然后为电灯写 LightHandler,为空调写 AirConditionerHandler,为窗帘写 CurtainHandler。每种设备的控制动作、参数校验、命令下发逻辑都放在自己的类里。Service 层通过一个 Map 把所有 Handler 按 deviceType 注册进去,请求进来时根据设备类型路由到对应实现。
这个改造不复杂,但收益很大。新增设备类型时新增一个 Handler 就行,Controller 和 Service 基本不用动。我在实际项目里把原来的 if 链拆掉之后,设备控制模块的测试工作量小了很多,也不怕日志查询串逻辑了。
2.4 一个开关动作背后可能动三张表,事务和并发要提前想好
很多人会把“设备开关”想成一行 update,但实际执行一个控制指令,往往不止改一张表。
举例:用户点击“关闭空调”,后端要做的可能是:
- 更新 device 表里的空调状态字段为关;
- 向 device_log 日志表插入一条“用户关闭空调”的操作记录;
- 如果这个设备绑定了场景(比如“离开模式”要求所有空调关闭),还要触发联动日志;
- 如果设备是插座,还要记录本次用电并产生一条计量数据。
这四步如果中间某一步失败,前面成功的更新就会留下脏数据。所以控制类方法上必须加事务,通常是 Spring 的 @Transactional。但你要知道它默认只在运行时异常下回滚,如果代码里 catch 掉了异常并正常返回,事务是不会回滚的,这个坑我见得太多了。
还要注意并发:用户手速飞快连续点两次“开灯”,第一次是关,第二次可能还是关,但后端如果没做并发保护,就可能出现两次相同的 update,状态倒是没问题,就是日志会记录两条而不是一条。更好的办法是加乐观锁:
sql复制UPDATE device
SET status = #{targetStatus}, version = version + 1
WHERE id = #{deviceId}
AND version = #{oldVersion}
如果 update 影响行数为 0,说明设备状态已经被别人改过了,这时候再返回“操作冲突,请刷新页面”就好。
3. MySQL 建模:设备、房间、日志之间的三层关系怎么设计才顺手
这一节是很多源码最容易出问题,也最容易让你在面试时被问倒的地方。表结构设计得好不好,直接决定业务代码复杂程度。
3.1 要不要把“用户-家庭-房间-设备”做成四级结构
很多简化版项目直接把 device 做成 user_id + room_id,然后就没有家庭概念了。这在你自己的单用户演示场景没问题,但一放到真实多用户环境就露馅。
我更推荐这套结构:
- user:用户表,保存账号密码、昵称、手机号。
- room:房间表,有一个 family_id 外键,还有一个 owner_id 或 family_member 表来关联成员。
- device:设备表,保存设备所属的房间 id、设备类型、型号、名称、状态、电量、最后上线时间等。
- device_log:设备日志表,记录设备上报、控制、告警等事件。
层级查询的核心 SQL 大概是这样的:
sql复制SELECT
u.username,
f.name AS family_name,
r.name AS room_name,
d.name AS device_name,
d.device_type,
d.status
FROM user u
JOIN family_member fm ON fm.user_id = u.id
JOIN family f ON f.id = fm.family_id
JOIN room r ON r.family_id = f.id
JOIN device d ON d.room_id = r.id
WHERE u.id = #{userId}
如果源码里只有 user 表和 room 表,room 表直接存 user_id,也能跑通,只是后续加“家庭成员共享控制权”时会很别扭。所以我建议你拿到源码后先画出表关系图:哪些是一对多、哪些是多对多,心里有数之后处理业务才有底气。
3.2 设备状态和设备日志必须分开,状态字段不要只存一个 status
很多新手设计 device 表时,只会放一个 status 字段(0 表示离线,1 表示在线),再放一个 power(0 关 1 开)。但传感器类设备的实时数据是多种多样的:温湿度计上报的不是开关状态,而是温度和湿度;空气质量检测仪还有 PM2.5 和甲醛数值。
如果每种传感器都建一张表,表数量会爆炸。常见的做法是在 device 表里加一个扩展字段来存当前上报的原始数据,比如叫 current_data_json:
sql复制CREATE TABLE device (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
room_id BIGINT NOT NULL,
device_type VARCHAR(32) NOT NULL COMMENT 'LIGHT/CURTAIN/AC/SENSOR',
name VARCHAR(64) NOT NULL,
status TINYINT DEFAULT 0 COMMENT '0离线 1在线',
power TINYINT DEFAULT 0 COMMENT '0关 1开',
current_data_json VARCHAR(1024) DEFAULT NULL COMMENT '最新上报数据,传感器类设备用',
version INT DEFAULT 0,
last_online_time DATETIME DEFAULT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
这样无论温湿度还是 PM2.5,都放进 current_data_json,页面解析后展示,不会因为设备类型扩展频繁改表结构。MySQL 5.7 以上本身就支持 JSON 类型,但为了兼容老版本,项目里经常用 VARCHAR 存 JSON 字符串,前端再用 JSON.parse 解析。这种字段的索引意义不大,所以不要把查询条件建立在它的内容上。
设备日志表和设备状态是两回事。状态是当前实时值,日志是历史流水。日志表的核心字段要预留这些:设备 id、事件类型(ON/OFF/REPORT/ALARM)、事件内容、创建时间。
sql复制CREATE TABLE device_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
device_id BIGINT NOT NULL,
event_type VARCHAR(32) NOT NULL COMMENT 'ON/OFF/ALARM/REPORT',
content VARCHAR(512) DEFAULT NULL COMMENT '事件描述或原始报文',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_device_time (device_id, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备行为日志表';
这里最容易被忽略的就是联合索引 (device_id, create_time)。没有这个索引,当你按设备详情页拉历史日志的时候,数据量一旦上去,SQL 会非常慢。别拿测试环境几百条数据挑战生产环境十万条日志,索引对查询性能是本质差别。
3.3 按设备ID或者房间ID查列表,永远要想着一次拿全信息
如果你写查询设备列表的 SQL 时只 select 了 device 表的字段,然后发现前端页面还要显示房间名称、设备类型的中文名,于是你在 Java 里 for 循环去查 room 表,这就是典型的 N+1 问题。设备多的时候,循环几十次查询,接口直接卡死。
解决方式是 join 一次查出来:
xml复制<select id="selectDeviceDetail" resultType="com.smarthome.vo.DeviceVO">
SELECT
d.id,
d.name AS deviceName,
d.device_type AS deviceType,
dict.label AS deviceTypeName,
r.name AS roomName,
d.power,
d.status,
d.current_data_json AS currentDataJson,
d.last_online_time AS lastOnlineTime
FROM device d
LEFT JOIN room r ON r.id = d.room_id
LEFT JOIN sys_dict dict ON dict.type_code = 'device_type' AND dict.value = d.device_type
WHERE d.id = #{id}
</select>
另外一个经验是,前端页面展示的字段不一定要全部从数据库查出来。status、power、last_online_time 这些最终要渲染到设备状态卡片上的字段,尽量在一条 SQL 里取全,在组装 VO 时再按视图需要裁剪。能查一次解决的,绝不要 for 循环再查第二次。这是我在做系统重构时最深刻的体会。
4. Vue 端真正的复杂度不在页面 UI,而在状态同步、路由权限与组件复用
页面多不代表复杂,真正复杂的是前端和后端之间数据怎么流动、设备状态变化后页面怎么更新、登录状态失效后请求怎么处理。
4.1 路由结构不要写成大杂烩,先按用户端和后台端分两块
Vue 项目中,路由组织直接影响业务代码的边界维护。
以用户端为例,常见的路由设计是:
text复制/login
/home
/home/dashboard
/home/device/list
/home/device/detail/:deviceId
/home/scene/list
/home/scene/edit/:sceneId
/home/log/list
后台路由和用户端路由隔开,比如都挂在 /admin 下。如果你拿到源码时路由全写在同一个文件里,建议趁早拆成 modules 目录,再在 router.beforeEach 里做登录校验:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path === '/login') {
next()
return
}
if (!token) {
next('/login')
return
}
next()
})
这里要小心:路由守卫里不要只做“有没有 token”的判断,最好加上 token 过期时间的检查。因为 localStorage 里的 token 即使过期了也仍然存在,很多系统的 bug 就是用户停留页面太久,再点操作时被后端返回 401,然后前端报一个“请求失败”,完全没有提示用户重新登录。正确做法是请求拦截器在拿到 401 后,清掉本地 token,并跳转登录页。
4.2 axios 封装不要只写 get/post,要统一处理 token、超时和业务码
我看到太多 Vue 项目里十个页面就有十种发请求的写法,有的用 axios,有的直接用 fetch。这是后续维护的灾难。
推荐的做法是封装一个 request.js,处理三件事:请求头注入 token、响应统一解包、错误统一提示。
javascript复制import axios from 'axios'
import { message } from 'ant-design-vue'
import router from '@/router'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000
})
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
request.interceptors.response.use(
response => {
const res = response.data
// 后端统一返回 { code: 200, data: ..., message: ... }
if (res.code !== 200) {
message.error(res.message || '请求失败')
return Promise.reject(new Error(res.message))
}
return res.data
},
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('token')
router.push('/login')
}
return Promise.reject(error)
}
)
export default request
import.meta.env.VITE_API_BASE_URL 这种写法是 Vite 环境变量的用法,源码里如果用的 Vue CLI,则改成 process.env.VUE_APP_API_BASE_URL。前后端分离项目最怕把后端地址硬编码到每个请求里,换环境就要全网搜索替换,非常痛苦。正确做法是建“.env.development”和“.env.production”两个环境文件,分别写开发环境和部署环境的请求地址。
4.3 设备状态实时刷新:WebSocket 优先,轮询兜底
前端设备列表页最大的痛点是从后端拿到设备状态后,状态是静态的。真实智能家居系统里,设备状态可能在任意时间变化(传感器主动上报、定时触发器动作、另一个家庭成员控制)。如果要求用户手动刷新页面才知道设备到底开没开,体验非常差。
方案一,WebSocket。后端维护一个 WebSocket 服务端,当前端登录后建立连接并携带用户标识。设备状态发生变化时,后端把“设备 xxx 已开启”之类的消息推给该用户的所有在线客户端。前端收到消息后,对列表里的设备卡片做局部更新。
方案二,短轮询。每隔几秒拉一次“获取当前家庭设备状态”接口。好处是简单,不会像 WebSocket 那样有断线重连的问题;坏处是频繁拉接口,后端压力大。小项目或者学生项目用轮询完全可以接受,但轮询间隔不要低于 5 秒。
如果你准备在源码基础上做改造,我的建议是先把轮询跑通,等整体功能稳定后再换成 WebSocket。因为 WebSocket 的消息格式、心跳、断线重连、多端同步,都是独立的一套复杂度,别一开始就塞进项目里。
4.4 设备卡片要做成可配置组件,不要每种设备写一个页面
设备类型有成百上千,但视觉上其实可以抽象成“开关设备”“温控设备”“传感器设备”“电动设备”这么几类。
较好的做法是做一个 DeviceCard 组件,接收一个 deviceConfig 对象,里面描述了这个设备支持哪些能力、用什么控件来控制。设备卡片内部按能力动态渲染:
- 支持开关的显示 Switch 开关;
- 支持亮度调节的显示 Slider 滑条;
- 支持温度调节的显示温度数字框和加减按钮;
- 传感器设备显示最新数值,不显示控制按钮。
这样做的好处是,新增一种传感器设备时前端几乎不用写新代码,只要后端返回的设备类型和组件映射能匹配上,页面会自动渲染出对应控件。设备控制面板同理,把“控制能力”抽成独立组件,后续接入了新设备,前端页面不会崩。
5. 把整套系统在本地跑起来:环境搭配、数据库初始化和启动排错
这部分我积累了不少经验。说实话,跑不起来的项目,大多数不是代码有问题,而是环境版本不对、数据库脚本没导入干净、配置文件里的连接地址不对。
5.1 环境版本搭错是启动失败的第一大原因
先用一张表说明推荐搭配:
| 软件 | 推荐版本 | 备注 |
|---|---|---|
| JDK | 1.8 / 11 | SpringBoot 2.7 必须用 8 以上的版本 |
| Maven | 3.6.3+ | 最好 3.8+ |
| MySQL | 8.0 | 如果源码注释里写的是 5.7 也能跑,但驱动要匹配 |
| Node.js | 16 / 18 | Vue2 配 16 稳妥,Vue3 配 18 稳妥 |
| npm 源 | 国内环境建议设置为镜像源 | 避免依赖装到一半卡住 |
有些源码的 pom.xml 里依赖了 MyBatis、druid、fastjson、jjwt 这些常用库。如果你改了 SpringBoot 版本,这些库的版本也可能需要整体对齐,其中任何一个不兼容都会让你陷入依赖冲突的泥潭。所以我不建议在学习阶段随意升版本。
5.2 导入数据库最常见的三个问题
第一,SQL 文件里没有 CREATE DATABASE,直接就是 CREATE TABLE。这时候你需要自己先建一个库:
sql复制CREATE DATABASE smart_home DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE smart_home;
SOURCE /path/to/schema.sql;
第二,字符集不对。中文在控制台显示成问号,八成是连接字符串里没加 characterEncoding,或者建表语句没用 utf8mb4。数据库连接串建议这样写:
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/smart_home?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: yourpassword
第三,时区报错。MySQL 8 和驱动对时区比较敏感,url 里必须加 serverTimezone=Asia/Shanghai,不然启动后 select 时间字段可能直接抛异常。
5.3 启动顺序有讲究:先验证数据库,再启动后端,最后再跑前端
我的习惯是先连数据库,用 IDEA 的 Database 面板或者命令行工具确认能查到表,再启动后端。后端启动成功且看到“Started Application in x seconds”后,先别急着开前端,先用浏览器访问一下登录接口或任意一个 /api/health 接口,确认接口能通再开前端。
前端启动用命令:
bash复制npm install
npm run dev
如果 npm install 卡住或者报 ERESOLVE 错误,通常是依赖依赖树版本冲突。解决办法是删除 node_modules 和 package-lock.json,然后重新执行 npm install;还不行就换用 npm install --legacy-peer-deps。这个问题在 Vue2 老项目和最新版 Node 之间特别常见,因为新版 npm 对 peerDependencies 的校验更严格了。
5.4 后端返回 500 时,第一件事是打开 MyBatis 的 SQL 日志
后端接口报 500,前端控制台只会显示“Internal Server Error”,光看这个没用,要到后端日志里定位。
如果你在项目里加了 MySQL 连接配置,建议顺手把 MyBatis SQL 日志打开。可以在 application.yml 里加:
yaml复制logging:
level:
com.smarthome.mapper: debug
这里的 com.smarthome.mapper 要换成你项目里 mapper 接口实际所在的包名。打开之后,控制台会打印实际执行的 SQL 语句和参数。一般情况下你一眼就能看出来是不是 SQL 拼接错了,或者参数传成了 null 导致查询结果不对。
如果是 SQL 执行报错,把控制台里的 SQL 复制到数据库客户端里跑一遍,通常就能看到真实的错误信息。这个方法比你在代码里一行一行打断点快得多。我排查项目问题,90% 的数据库相关 bug 都是靠这个方式定位的。
还有一类问题很隐蔽:前端页面能打开,但所有接口都返回 401。这大概率是登录接口没有正确把 token 写入本地缓存,或者请求拦截器里从 localStorage 读 token 时读到了 null。先用浏览器开发者工具的 Network 面板看看登录请求的响应体里有没有 token,再看后续请求的请求头里有没有 Authorization,一步一步过滤,很快就能找到问题在哪。
6. 如果只是把源码跑通,价值不大,建议你从这三个方向动手改造
“跑通”是起点,不是终点。真正让一个源码项目变成你自己的项目,你需要动点手术。
6.1 设备数据接入层:把模拟设备上报改成 MQTT 驱动
很多教学项目的设备控制,实际是前端改了状态,内存里刷新一下,并没有真正连硬件。这是可以理解的,demo 很难给你一套硬件一起跑起来。
但如果你想让系统越来越接近真实可用,可以引入 MQTT 网络层。
基本思路是:
- 智能设备或者设备网关作为 MQTT 客户端,订阅控制主题。
- 后端作为一个 MQTT 客户端,订阅设备上报主题。
- 用户点击“开空调”,后端先修改数据库中的目标状态,再发布一条 MQTT 消息到
/device/{deviceId}/command。 - 设备收到消息后执行动作,并上报一条最新状态到
/device/{deviceId}/report。 - 后端收到上报消息后,更新 device 表中的状态字段,同时通过 WebSocket 推给前端页面。
这样就把“用户操作”和“真实设备执行”解耦了。你可以先在本地用 EMQX 或者 Mosquitto 搭一个 broker,再通过 MQTTX 客户端模拟设备消息,系统就能看到数据的完整流转链路。
6.2 场景联动改造:别把 if else 写进控制器里
教学源码的场景执行逻辑,通常是把场景下的所有动作查出来,然后 for 循环执行设备动作。这在设备量少的时候可以接受,但场景多了之后,规则越来越复杂:什么时候触发、需要满足哪些前置条件、多个动作之间有没有顺序依赖、失败后要不要回滚,都不一样。
进阶一点的方案是,业务里抽象出 Trigger(触发器)、Condition(条件)、Action(动作)三个概念。然后规则引擎统一执行。比如“温度传感器温度大于 28 度且处于在家状态,则打开空调到 25 度并推送提醒”就是一条完整规则。你用数据库表存好规则配置,执行器把规则加载进内存,再通过一个简单状态机控制执行流程。
这个改造在毕设项目里非常能体现设计能力。比起盲目堆功能,这种抽象能力更容易让评审老师眼前一亮。
6.3 给新人的三点实在建议,都是我踩过坑之后总结的
第一,不要把整套源码删掉重写,也不要一字不改直接交作业。最有效率的学习方式是挑三条核心链路精读:用户登录鉴权链路、设备控制链路、设备状态上报链路。把这三条链路的表、接口、前端页面梳理通,这个系统的技术框架你就吃透了。
第二,改造时尽量保持后端接口兼容,不要在前端随意改字段名。前端和后端一旦分开部署,联调成本很高。接口返回结构统一用 { code, message, data },前后端对照着约定来,能省掉一半沟通成本。
第三,一定要亲手建一遍数据库,不要只靠 SQL 文件导入。建表的过程中你会主动思考字段类型、主键、外键、索引,这个“手疼一次”的过程比看十遍教程都管用。如果你能把自己设计的库和源码里的表结构对比一遍,找出差异并搞清楚原因,那这项目才算真正过了你的脑子。
我最近复盘这套智能家居管理系统时最大的感受是:像 SpringBoot、Vue、MyBatis、MySQL 这套组合本身并不罕见,真正决定项目上限的,是你怎么用这些技术解决“设备状态实时同步”“多设备联动控制”这类具体业务问题。这也是为什么同类源码很多,但每个人做完后收获却完全不一样。希望这篇复盘能帮你在动手之前先看清全局,少走点我走过的弯路。
