简介:北邮软件工程专业空调管理系统课程设计源码包,适合高校软件工程学生及Qt/C++开发者参考学习。资源基于Qt框架实现,围绕房间空调设备统一管理展开,涉及多线程服务端指令接收、登录鉴权、Model/View架构刷新列表等典型工程实践,可帮助读者梳理桌面应用从界面设计到业务逻辑的完整流程。压缩包共113个文件、约2.68MB,其中cpp与h源文件构成核心功能,ui文件对应界面布局,docx为配套说明文档,jpg为运行截图与预览,目录结构清晰,便于按模块检索。目前已有392人下载学习,适用于课程设计、期末项目或课后拓展练习。阅读代码可掌握信号槽通信、多线程并发处理、数据读写等关键技能,对后续深入C++桌面开发具有实际参考价值。
1. 软件工程课设的「空调」题:这份资源包解决的不只是代码
软件工程课程设计里,凡是拿到「智能空调控制系统」这种题目的,最容易踩的误区是把它当成一个遥控器界面来写。课设验收真正看的是完整工程链路:需求分析有没有对应到模块、状态机逻辑能不能说清楚、数据库和接口对不对得上、文档和代码是否一致。
这个资源包把「空调」这个题目落成了一套可运行的工程骨架,后端接口、前端页面、模拟温度源、建库脚本和文档模板都齐,适合课程设计周、实训课和毕业设计起步,也适合想补一套完整项目经验照着复现的从业者。下面按拆包顺序讲:先选型和边界,再落到表结构和接口,最后把最容易翻车的现场提前排掉。
2. 需求拆解与技术选型:课设题不是让你造一个空调
2.1 先把「空调」拆成三层验收清单
「智能空调控制系统」这个题目信息量很少,拿到手第一步不是点「新建项目」,而是把题目翻译成能验收的东西。按我拆课设的习惯分三层:功能层回答用户能做什么,数据层回答系统存什么,文档层回答老师按什么评。这三层缺哪层,答辩时大概率会被问住。
| 层次 | 验收点 | 对应实现 |
|---|---|---|
| 功能层 | 开关机、模式切换、温度设定、定时任务 | 后端接口 + 前端页面 |
| 数据层 | 设备信息、运行日志、温度历史 | ac_device / ac_log / temp_record 三张表 |
| 文档层 | 需求规格说明书、设计说明、测试报告 | docs 目录下的模板 |
这张表就是简化版的需求追踪矩阵。答辩时被问「这个需求在哪实现的」,直接说「功能层第二条,对应 /api/mode 接口」,比当场翻代码强太多。注意资源包里的文档模板本来就是按这个三层结构写的,自己写文档时不要打破这个框架。加需求容易,改框架麻烦,而且文档和代码一致是硬性加分项。
2.2 技术栈为什么这么选:课设不是微服务试验场
一套课设最常见的死法,是三五个人用微服务拆了七八个服务,最后演示当天起不来。选型要看你手里的工期和人数。我处理这类题目用的默认方案是:Spring Boot 单体 + REST 接口 + 静态前端页 + 模拟温度源,理由有三。一是单体部署就一个包,不会出现「服务 A 起来了、服务 B 端口被占」的连锁事故;二是状态机、温度上报、日志写入都在同一个进程里,断点调试非常顺;三是课程设计打分里,「能跑且能讲清」的权重远高于「架构花哨」。
| 方案 | 部署难度 | 调试成本 | 课设风险 |
|---|---|---|---|
| Spring Boot 单体 | 低,一个可执行包 | 低,单进程断点 | 低 |
| 前后端分离 | 中,两个工程分别部署 | 中,要处理跨域 | 中 |
| 微服务 | 高,服务编排复杂 | 高,链路难查 | 高,演示容易翻车 |
现在很多人把前后端分离当成标准答案,如果前端页面是自己写的还可以考虑;如果前端是套模板改的,跨域、打包、静态资源路径这三个坑足够吃掉一个周末。我的血泪经验是:课设里能少一个工程就少一个工程,架构上的追求留到正式项目里再卷。还有一个反向踩坑点是追新,课设环境里的 JDK、开发环境和数据库版本经常是机房预装的,我用得最稳的组合是 JDK 8 或 11 配 Spring Boot 2.x,这套组合的问题资料最多,任何一步报错都能搜到答案。版本不是越新越好,能稳定编译和运行才是课设的第一需求。
2.3 没有真硬件:用模拟温度源把链路跑起来
资源包里没有真实空调,答辩现场也搬不来。所以需要一个模拟温度源,负责每隔几秒生成一个室温并通过 HTTP 上报给后端,让前端温度曲线、状态机判定全部按真实数据走一遍。模拟源放在 tools 目录下,单独一个进程运行。
# temp_simulator.py —— 模拟温度采集端,独立运行 import random import time import requests BASE_URL = "http://127.0.0.1:8080/api" # 改成后端实际端口 DEVICE_ID = "AC-DEV-001" # 必须与 ac_device 表里的设备号一致 temp = 26.0 def drift(): # 在 ±0.3 范围内随机游走,模拟室温的缓慢自然变化 return round(temp + random.uniform(-0.3, 0.3), 1) while True: temp = drift() # 上报给后端,入库后前端曲线才会动 resp = requests.post(f"{BASE_URL}/sensor/report", json={"deviceId": DEVICE_ID, "temperature": temp}, timeout=3) print(f"[{time.strftime('%H:%M:%S')}] 上报 {temp}℃ -> {resp.status_code}") time.sleep(5) # 上报周期,演示想加速就改成 1这段脚本里真正需要改的只有两个参数:BASE_URL 要跟后端 application.yml 里的 server.port 对上,DEVICE_ID 要跟数据库 ac_device 表里预置的设备号一致,否则接口会返回「设备不存在」。drift 用随机游走模拟室温,±0.3 的幅度让温度变化平缓,看起来像一个真实房间;sleep(5) 是上报周期,演示时想快点看到曲线变化就改成 1 秒。选择 HTTP 上报而不是写文件或串口,是为了让链路最短,前后端和模拟器三方都只认 HTTP,不依赖任何驱动,这是这个场景下最常见的做法。如果代码里 requests 连不上,多半是代理问题,改用 requests.post(..., proxies={"http": None}) 就能绕开。
2.4 拆包后先做的事:按目录核对交付物
拿到资源包先别急着改代码,对着目录核对一遍,确认交付物和文档描述一致。
ac-controller/ ├── docs/ # 需求分析、设计说明、测试报告模板 ├── db/ │ └── init.sql # 建库建表脚本 ├── backend/ # Spring Boot 后端工程 │ ├── pom.xml │ └── src/main/java/... ├── frontend/ # 静态页面 └── tools/ └── temp_simulator.py # 模拟温度源我会先把 docs 里的题目、作者、日期这些占位符全局替换掉,避免答辩时露出模板痕迹;然后确认 db/init.sql 里的库名和 backend 下 application.yml 里的连接串一致,这两个不一致是启动失败的第一个高频原因。文档模板里的目录结构(引言、需求分析、设计、测试)就是答辩评分表的常见结构,我没见过哪个老师因为模板写得规整扣分,倒见过不少因为缺了测试小节被当作项目不完整。
提示:目录结构不建议动,后面所有启动步骤和文档路径说明都依赖当前骨架,动了就要连带改文档,不是不能改,是没必要。
3. 核心实现:一张状态机加三张表,把空调逻辑落进代码
3.1 空调状态机:把「该不该制冷」变成可测试的规则
空调控制逻辑最常见的写法是 if 套 if:温度高了开制冷,低了开制热,再高开除湿。答辩时这种「现象式代码」最容易被追问,因为说不清规则边界。正确做法是先定义状态机,再把它翻译成代码。资源包里定义了五个状态:待机、制冷、制热、送风、除湿。模式切换不是用户点了就立刻进入,而是由「用户指令 + 当前室温 + 目标温度」三个输入共同决定。
| 状态 | 进入条件 | 退出条件 |
|---|---|---|
| 待机 STANDBY | 开机前、断电后 | 收到 power 命令 |
| 制冷 COOL | 模式=制冷 且 室温 > 目标+回差 | 室温回落 或 切换模式 |
| 制热 HEAT | 模式=制热 且 室温 < 目标-回差 | 室温回升 或 切换模式 |
| 送风 FAN_ONLY | 温差落在回差区间内 | 温差越出区间 |
| 除湿 DRY | 模式=除湿 且 湿度超阈值 | 切换模式 |
// AcStateMachine.java —— 状态机核心,只做状态迁移,不碰数据库 public enum AcState { STANDBY, COOL, HEAT, FAN_ONLY, DRY } public class AcStateMachine { // 回差(滞回区间),防止温度在目标值附近来回横跳 private static final double HYSTERESIS = 1.0; public AcState nextState(AcState current, String cmd, double nowTemp, double targetTemp) { if ("power".equals(cmd)) { // 断电一律回待机,目标温度保留,下次开机继续用 return AcState.STANDBY; } if ("mode".equals(cmd)) { if (nowTemp > targetTemp + HYSTERESIS) { return AcState.COOL; // 室温明显偏高才制冷 } if (nowTemp < targetTemp - HYSTERESIS) { return AcState.HEAT; // 室温明显偏低才制热 } return AcState.FAN_ONLY; // 温差不大的时候只送风 } return current; } }这段代码的关键在 HYSTERESIS = 1.0。这个参数叫回差或滞回区间,它存在的意义是避免系统在目标温度附近反复切换。假设室温 26.5℃、目标 26℃,如果没有回差,一次上报判定制冷、下一次上报判定送风,状态就一直在抖;有了 1℃ 的回差区间,只有室温超过 27℃ 才制冷,低于 25℃ 才制热,中间只送风。判定顺序故意固定为「先 power、再 mode、最后温度」,同一次调用只处理一类命令,这样才能把每个分支写成独立单元测试。我会给这个类补一个边界测试,专门把 26.5℃ / 26℃ 这种贴边数据放进去跑,答辩时还能顺手展示测试报告。
3.2 数据库设计:设备、日志、温度三条表装下所有数据
状态机负责「怎么切」,数据库负责「记下来」。数据库这块在课设里经常被当成黑匣子,其实只要三张表。ac_device 存设备当前状态和设定值,ac_log 存模式切换和开关机事件,temp_record 存传感器每次上报的原始温度。一句话分工:设备表给界面显示当前值,日志表给答辩展示事件流,温度表给曲线图供数据。
-- 设备表:一台空调对应一条记录,界面展示读这张表 CREATE TABLE ac_device ( device_id VARCHAR(32) PRIMARY KEY, -- 设备编号,模拟器上报时使用 room_name VARCHAR(64), -- 房间名,仅用于界面展示 mode TINYINT DEFAULT 0, -- 0待机 1制冷 2制热 3送风 4除湿 target_temp DECIMAL(4,1) DEFAULT 26.0, -- 用户设定的目标温度 current_temp DECIMAL(4,1) DEFAULT 26.0 -- 最近一次上报的室温 ); -- 运行日志:每次模式切换写一条,答辩展示事件流 CREATE TABLE ac_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_id VARCHAR(32) NOT NULL, event_type VARCHAR(16), -- MODE_CHANGE / POWER_ON / POWER_OFF detail VARCHAR(128), -- 切换后的状态名 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 温度历史:传感器原始数据,前端曲线图查这张表 CREATE TABLE temp_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_id VARCHAR(32) NOT NULL, temperature DECIMAL(4,1) NOT NULL, record_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 曲线查询只按设备和时间筛选,一个联合索引足够 ALTER TABLE temp_record ADD INDEX idx_device_time (device_id, record_time);字段类型上,温度用 DECIMAL(4,1) 而不是 FLOAT,是因为浮点存储会在界面上偶尔显示成 26.30000001 这种值,答辩时被看到很尴尬。模式用 TINYINT 存枚举序号,代码里做一次映射转换。target_temp 给了默认值 26.0,这样新插入设备不设目标温度也能正常工作。注意 ac_log 和 temp_record 的 create_time 都加了 DEFAULT CURRENT_TIMESTAMP,写入时不手填时间也不会是 NULL,这两个小细节经常是页面空数据的源头。时间字段统一用 DATETIME,不要混用 TIMESTAMP,虽然都能用,但 DATETIME 没有时间戳上限问题,写起来也不用担心时区换算,课设数据量下性能差异可以忽略。索引只加一个联合索引就够,课设数据量下完全够用,索引加多了纯属自我折腾。
注意:重复执行 init.sql 前先 DROP DATABASE,否则会因表已存在而报错。脚本会先建 ac_db 库再建三张表,导入方式是 mysql -u root -p < db/init.sql。
3.3 接口设计与调用时序:传感器、前端、数据库三端怎么串
接口设计遵循「一个动作一个接口」。资源包后端暴露六个接口:查询设备状态、开关机、切换模式、设定温度、传感器上报、查询温度历史,前四个给前端页面,第五个给模拟温度源,最后一个给曲线图。
| 方法 | 路径 | 入参 | 出参 |
|---|---|---|---|
| GET | /api/device/{deviceId} | 路径参数 | 设备状态、当前温度、目标温度 |
| POST | /api/power | deviceId | 新状态 |
| POST | /api/mode | deviceId、mode | 状态机判定后的新状态 |
| POST | /api/target | deviceId、targetTemp | 新目标温度 |
| POST | /api/sensor/report | deviceId、temperature | 无 |
| GET | /api/temp/history | deviceId、limit | 最近 N 条温度记录 |
// AcController.java —— 只做参数接收和结果返回,状态迁移交给状态机 @RestController @RequestMapping("/api") public class AcController { // 前端点「制冷」时调用:不是点了就制冷,还要看室温够不够条件 @PostMapping("/mode") public Result switchMode(@RequestBody ModeReq req) { AcDevice dev = deviceRepo.findById(req.getDeviceId()).orElseThrow(); AcState next = stateMachine.nextState(dev.getState(), "mode", dev.getCurrentTemp(), dev.getTargetTemp()); dev.setState(next); deviceRepo.save(dev); logRepo.save(new AcLog(dev.getDeviceId(), "MODE_CHANGE", "切换到 " + next.name())); return Result.ok(next); } // 模拟器每隔几秒上报一次室温:写设备表 + 写温度历史,一次事务 @PostMapping("/sensor/report") public Result reportTemp(@RequestBody SensorData data) { AcDevice dev = deviceRepo.findById(data.getDeviceId()).orElseThrow(); dev.setCurrentTemp(data.getTemperature()); deviceRepo.save(dev); tempRepo.save(new TempRecord(dev.getDeviceId(), data.getTemperature())); return Result.ok(); } }这段代码里,controller 刻意不写任何业务判断,真正的状态迁移在 AcStateMachine 里,这让单元测试完全不需要启动服务器。switchMode 里先读了设备表里的当前温度再调状态机,因为「用户点了制冷」只是一个意图,能否进入制冷状态还取决于当前室温是否高于目标温度加回差。sensor/report 同时写两张表,如果其中一次写入失败,事务会整体回滚,不会出现设备表温度已经更新、曲线却少一个点的情况。ModeReq 的两个字段是 deviceId 和 mode,SensorData 两个字段是 deviceId 和 temperature,字段名和前端、模拟器的 JSON 完全对齐,改一个就必须同步改另外两处。Result 是统一返回体,代码、消息、数据三段式,前端页面只认这个结构。
完整调用时序是这样的:前端加载页面先 GET /api/device/{deviceId} 拉当前状态;用户点「制冷」,前端 POST /api/mode;后端从数据库读出当前室温和目标温度,喂给状态机得到 COOL 或 FAN_ONLY;落库后返回新状态,前端据此高亮按钮。这条链路在答辩时用三十秒讲完,老师就能听懂你的数据流,比对着代码讲十分钟有效得多。
4. 常见坑位与排查:把课设跑挂的五个经典现场
下面这五个问题不是猜的,是历年课设演示现场反复出现的五个故障点,每一个我都见过不止一次。按现象、原因、解决三步走,照着排就行。
4.1 数据库连不上:时区、密码、驱动三个连环坑
现象:后端启动报 Communications link failure 或 Access denied,浏览器打开页面全是 500。
原因分三种情况排查:MySQL 8 以上版本对时区参数敏感,连接串里没写时区会直接拒绝连接;账号密码和本地初始化时对不上;依赖里的数据库驱动版本和本地 MySQL 大版本不匹配。这三件事经常同时出问题,所以排查顺序很重要,别把时间耗在玄学式试错上。
解决:先拿数据库客户端单独连一次 ac_db,能连上说明账号和密码没问题,问题在连接串或驱动;然后确认连接串是 jdbc:mysql://127.0.0.1:3306/ac_db?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf8 这种带时区参数的写法;最后核对驱动版本和本地数据库大版本一致。这三步都对了还连不上,再去看本机端口占用,但课设场景下基本到第三步就结束了。
4.2 温度就是不刷新:定时任务和模拟器谁背锅
现象:模拟器窗口一直在打印上报成功,前端页面室温纹丝不动,曲线图也是一条直线。
原因通常是两个方向:一是模拟器把请求发到了另一个端口,比如后端改过 server.port 而模拟器里的 BASE_URL 没改,请求根本没到后端;二是后端收到了但没落库,比如事务没提交或 save 没执行,页面读不到新数据。
解决:不要猜,先看后端控制台有没有 /sensor/report 的请求日志,没有就是 BASE_URL 或端口问题。有日志就查数据库 temp_record 表有没有新行。库里没数据是后端写库问题,库里有数据前端不更新,问题就在前端轮询或刷新逻辑上。最快的隔离办法是用 curl 手动打一条数据:
curl -X POST http://127.0.0.1:8080/api/sensor/report \ -H "Content-Type: application/json" \ -d '{"deviceId":"AC-DEV-001","temperature":26.5}'这条命令返回 200,说明后端接口和数据库链路是通的,问题一定在前端刷新逻辑;返回 404,先查端口和路径;返回 400,查 JSON 字段名是否和接口对齐。按「请求有没有到、数据有没有入库、页面有没有重新查」三步切分,五分钟就能定位。
4.3 状态机「乱跳」:浮点比较和判定顺序的坑
现象:温度 26.4℃、目标 26℃,状态在制冷和送风之间来回切,界面按钮一闪一闪,演示效果很糟。
原因:模拟器温度本来就在 ±0.3 范围波动,温度值会反复越过目标值;如果代码里直接写 nowTemp > targetTemp,那每上报一次条件都可能翻转。另外 DECIMAL 字段转出来是 BigDecimal,直接和 double 比较也会有精度差异,看起来就像同一个温度,结果时好时坏。
解决:按 3.1 的做法加回差,判断改成「目标值 + 回差」和「目标值 - 回差」两个边界,中间状态稳定不跳。温度比较前统一转成 double 再比,避免 BigDecimal 和 double 混比。这是空调控制系统里最基础的滞回控制思想,答辩时主动把这个讲清楚,反而是加分点,因为大多数同学都是在 if 里写死阈值。
4.4 文档和代码对不上:答辩被追问的翻车现场
现象:设计文档里写的接口是 /api/device/list,代码里实际是 /api/ac/list,演示没问题,一翻开文档就被扣分,越解释越乱。
原因:文档是开工前写的,代码是后期改的,接口改名、字段调整都没回写文档。这在课程设计里太常见,本质是把文档当成了应付检查的东西,而不是工程的一部分。
解决:交付前做一次接口一致性核对,把 controller 里所有路径注解和文档里的接口表格逐条比对。我习惯用一段简单的命令先扫出全部路径:
# 找出所有接口映射注解,输出文件位置和注解内容做人工比对 grep -rE "@(Get|Post|Put|Delete)Mapping" backend/src/main/java --include="*.java"注意这段命令只能帮找方法级注解,真正的路径还要拼上类上的 @RequestMapping 前缀,所以核对时先看类级路径再看方法级路径。改代码就同步改文档,养成这个习惯后,再也不会出现两套「事实」。
4.5 演示白屏:把前端打进后端包时的静态资源路径
现象:在开发环境里前后端都能跑,导出可执行包后打开页面全白,但接口直接访问是好的。
原因:前端页面没有被打进后端包的 static 目录,或者页面里引用的脚本、样式用的是绝对路径,部署后路径变了找不到资源。
解决:把前端产物放到 backend 的 src/main/resources/static 下,重新打包;页面里所有资源引用改用相对路径或根路径开头;部署后用 curl -I 访问一下页面,看返回是不是 200。这个问题在答辩前一天出现最折磨人,提前按这条查一遍就稳了。
5. 验收前的最后一遍自查:把资源包变成你自己的交付物
5.1 五分钟演示脚本
资源包能跑只是底线,能演示、能讲清才是加分项。我每次交课设前会固定走一遍下面的脚本,既检查功能,也检查文档一致性,整个过程控制在五分钟以内。
| 时间 | 操作 | 预期现象 | 讲解要点 |
|---|---|---|---|
| 0:00 | 启动数据库和后端 | 日志出现端口监听 | 技术栈与初始化流程 |
| 0:30 | 运行 tools/temp_simulator.py | 温度开始缓慢波动 | 模拟传感器上报机制 |
| 1:00 | 打开前端页面 | 温度曲线实时更新 | sensor/report 接口与 temp_record |
| 2:00 | 切换到制冷模式 | 状态变为制冷,日志多一条 | 状态机判定与回差参数 |
| 3:00 | 把目标温度调高 | 状态从制冷切回送风 | 滞回区间防抖效果 |
演示时我习惯从后端日志和前端页面两个视角同时讲:页面展示结果,日志展示过程。就算功能本身不复杂,也能让老师看到你在用日志定位数据流,这是从业者习惯和纯学生代码的明显区别。
我踩过一个印象很深的坑:答辩前一天发现文档里的接口名和代码不一致,连夜改文档改到下半夜,第二天讲的时候心里还是没底。从那以后,我每次交课设前都强制走一遍这套自查,启动、上报、切模式、对接口、查日志五件事按顺序执行,再对着文档表格核对一遍路径。这套流程也是你拿到资源包后最快的验收路径。把它养成习惯后,再被问「你确定能跑吗」,直接打开页面现场演示就行,不用靠嘴解释。希望帮到你。
本文还有配套的精品资源,点击获取