智慧消防系统开发实战:从毕设源码到设备告警工单闭环
2026/9/16 14:15:52 网站建设 项目流程

简介:面向计算机毕业设计场景的智慧消防微信小程序完整项目包,基于微信小程序开发工具与Java后端及MySQL实现,适合毕业设计、课程设计以及小程序全栈学习。项目区分管理员与用户两类角色:管理员可管理个人中心、用户信息、新闻信息、商品信息、报修信息及报修结果、房屋信息、留言板、系统设置等;用户支持注册登录、在线报修和商品购买。资源共有1252个文件,约39.79MB,类型覆盖vue/java/js等前后端源码,wxml/wxss小程序页面文件,png/svg界面素材,sql数据库脚本,mp4演示录像,以及bat安装运行脚本,目录结构清晰,便于按模块查阅和二次开发。已有125人学习下载,压缩包内附说明文档与演示视频,可协助快速理解业务流程、完成环境部署,对完成智慧消防课题或积累小程序开发经验很有价值。

1. 源码包不等于能跑:智慧消防系统真正要交付的是闭环

拿到这种毕设源码包,第一件事不是解压跑起来,而是冷静认出它的真实身份:一套带演示录像和说明文档的课程设计作品,不是可以直接部署的生产系统。智慧消防这个题目在校招和课设里出现频率极高,本质要解决的是三件事的闭环:设备把状态报上来,告警能流转到人,巡查结果能回到系统。打开源码后最常见的三个坑也正对应这三件事——接口域名还是写死的局域网地址,点检记录写进内存重启就丢,演示录像里的扫码流程在代码里根本找不到对应页面。所以这篇只讲一件事:把设备、告警、工单和权限这条路走通,其余特效都是加分项。

2. 先立数据模型:设备、告警、工单三张表的设计与接口协议

2.1 消防设备台账为什么必须设计成二维码可扫码?——device 表结构

智慧消防和普通的管理系统最大的差别在设备端。烟感、温感、水压表、手报按钮这些都是物理存在,巡查员要拿着手机到现场扫设备码才能确认“我来过、设备正常”。所以设备表的核心不是 id,而是device_code这个二维码里承载的业务标识。

CREATE TABLE `device` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `device_code` VARCHAR(32) NOT NULL COMMENT '设备编号,二维码扫码的结果', `device_name` VARCHAR(64) NOT NULL COMMENT '设备名称,如3号楼3层东侧烟感', `device_type` TINYINT NOT NULL DEFAULT 1 COMMENT '1烟感 2温感 3水压 4手报 5消防栓', `building` VARCHAR(32) NOT NULL COMMENT '楼栋', `floor` VARCHAR(16) NOT NULL COMMENT '楼层', `room` VARCHAR(32) DEFAULT '' COMMENT '房间或点位', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0离线 1在线 2故障', `install_time` DATETIME DEFAULT NULL, `last_report_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_device_code` (`device_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消防设备台账';

这个结构里device_code建了唯一索引,扫码后直接用字符串匹配,不需要关心二维码内部是 JSON 还是纯编号。buildingfloorroom分开存而不是拼成一个地址字段,是为了后面做楼层平面图和按楼栋统计时不需要写字符串解析。status用 TINYINT 而不是字符串,演示录像里切换“在线/离线”筛选时,下拉框绑定的就是这组枚举值。

2.2 告警状态机:从上报、派单到归档的流转设计

告警不是一条单纯的数据插入,它要经历产生、确认、派单、处置、归档五个环节。很多毕设源码只在告警表里放一个status字段,处理完就改成 2,这会导致演示时说不清“谁在什么时候做了什么”。

CREATE TABLE `alarm` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `device_id` BIGINT UNSIGNED NOT NULL, `alarm_type` TINYINT NOT NULL COMMENT '1烟感 2温感 3水压 4手报', `alarm_level` TINYINT NOT NULL DEFAULT 1 COMMENT '1一般 2严重 3紧急', `content` VARCHAR(255) NOT NULL COMMENT '告警描述', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待确认 1已派单 2处置中 3已归档', `assignee_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '处置人,关联用户表', `create_time` DATETIME NOT NULL, `handle_time` DATETIME DEFAULT NULL, `remark` VARCHAR(255) DEFAULT '', PRIMARY KEY (`id`), KEY `idx_status_time` (`status`, `create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消防告警记录';

注意status的注释不是简单粗暴的“0未处理 1已处理”,而是拆成四个可操作的阶段。idx_status_time这个联合索引专门服务告警列表页最常见的查询:打开小程序先按状态分组显示,再按时间倒序排列。处置人字段放在告警表而不是单独的工单表,是课设体量下的合理简化;如果导师要求必须体现“派单”,就再建一张work_order表关联alarm_id,两张表的关系是 1 对 1。

2.3 接口协议里的时间、经纬度与权限字段

设备端和后端约定接口协议时,最容易出问题的是三个字段:时间、位置、权限。时间统一用yyyy-MM-dd HH:mm:ss的字符串传输,小程序端可以直接渲染,省掉new Date的兼容性处理。经纬度传到后端用 DECIMAL(10,6),别用前端算好的距离值,电子围栏的判定必须在后端做,否则抓包改个坐标就能绕过巡检。

权限字段推荐用role字符串而不是 level 数字。原因很直接:演示答辩时评委可能随口问“这个 user 为什么进不了管理页”,这时回答“代码判断 role 等于 admin”比解释“level 大于等于 2”直观得多。接口统一返回这个外壳:

{ "code": 0, "message": "success", "data": {} }

code为 0 表示成功,非 0 是业务错误码。前端请求封装里只判断code,不判断 HTTP 200,因为登录过期、设备离线这类业务异常在 HTTP 层也是 200。

2.4 演示项目最不该省的维度:角色与权限

很多毕设源码为了演示流畅,把权限做成了摆设——小程序端靠隐藏按钮控制入口,后端接口不做校验。抓包工具一开就能直接调管理接口删除数据,答辩现场非常尴尬。

最少要分三个角色:管理员、值班员、巡查员。管理员看全部告警和统计数据,值班员负责确认告警并派单,巡查员只能看到分配给自己的点检任务。表设计上不需要单独建权限表,在用户表加一个role字段,后端写一个拦截器校验请求路径前缀,/admin/**只允许管理员访问。

3. 小程序端三个高频改造点:加载页、导航栏高度与点检表单

3.1 修改刚进入的加载页面,避免启动白屏

毕设源码自带的加载页往往是一张静态图,过两秒自动跳转。真正的智慧消防小程序启动时要拉取设备汇总、今日告警、未完成工单三个数据,如果都在onLoad里串行请求,点击图标后至少白屏两秒。常见做法是先渲染一个带进度状态的启动页,数据到位后再switchTab进主页面。

Page({ data: { progress: 0, loadingText: '正在同步消防设备状态...' }, onLoad() { this.tickProgress() this.fetchBootstrapData() }, tickProgress() { this.timer = setInterval(() => { if (this.data.progress >= 90) { clearInterval(this.timer) return } this.setData({ progress: this.data.progress + 10 }) }, 100) }, fetchBootstrapData() { wx.request({ url: `${app.globalData.baseUrl}/api/fire/bootstrap`, success: (res) => { if (res.data.code === 0) { app.globalData.summary = res.data.data wx.switchTab({ url: '/pages/overview/index' }) } } }) } })

进度条最多走到 90%,剩下 10% 留给接口返回后瞬间拉满,避免用户看到进度条卡在 100% 页面还没跳。这种做法比“固定延时跳转”可靠——接口慢时不会误跳,接口快时不用白等。

3.2 顶部导航栏高度适配:胶囊按钮和状态栏的换算

微信小程序顶部导航栏高度不是固定的。刘海屏、灵动岛、普通安卓机,状态栏高度都不一样。写死44px48px的页面,换台手机就上下错位。只要是自定义导航栏,都靠wx.getMenuButtonBoundingClientRect取值来算高度。

const getNavBarInfo = () => { const menuRect = wx.getMenuButtonBoundingClientRect() const systemInfo = wx.getSystemInfoSync() const statusBarHeight = systemInfo.statusBarHeight const navBarHeight = (menuRect.top - statusBarHeight) * 2 + menuRect.height return { statusBarHeight, navBarHeight, menuRect } }

这段代码的核心逻辑:胶囊按钮(右上角那个圆角矩形)垂直居中于导航栏,所以导航栏总高度等于「胶囊到状态栏的距离 × 2 + 胶囊自身高度」。拿到结果后在app.globalData里存一份,所有页面的自定义导航栏组件统一读取。注意wx.getSystemInfoSync在部分新版本基础库上已标记废弃,但兼容性仍最稳,毕设项目用这个没毛病。

3.3 用 radio-group 实现点检单,处理单选框回填

点检任务是小程序端最核心的交互界面。巡查员到现场后逐项确认“设备外观正常、指示灯正常、压力表读数正常”,每一项对应一个单选框组。这里容易踩的坑是回填:任务详情接口返回的是 JSON 数组,而不是每个选项的布尔值。

<radio-group bindchange="onCheckChange"> <label wx:for="{{checkPoints}}" wx:key="id" class="check-item {{item.result === 1 ? 'checked' : ''}}" > <radio value="{{item.id}}" checked="{{item.result === 1}}" color="#e64340" /> <text>{{item.name}}</text> </label> </radio-group>

checkPoints中每项包含idnameresult三个字段,result为 0 表示异常,为 1 表示正常。后端返回的历史点检记录直接赋值给data.checkPoints,radio 的checked属性会自动回填,不需要额外写 setData 逻辑。提交时遍历数组收集结果,这一步通常和拍照上传绑在同一个按钮上。

3.4 长按拖拽排序与图片上传

巡查点检顺序有时需要现场调整,比如先查烟感再查手报。微信小程序没有原生长按拖拽组件,常见方案是movable-area配合movable-view实现,但代码量大;课设更推荐用现成思路:长按进入排序模式,监听touchmove计算交换位置。

onItemLongPress(e) { this.setData({ dragIndex: e.currentTarget.dataset.index, isDragging: true }) }, onItemTouchMove(e) { if (!this.data.isDragging) return const touch = e.touches[0] const items = this.data.checkPoints const currentIndex = this.data.dragIndex // 根据 touch.clientY 与各项累计高度判断移动到哪个位置 }

实现里最关键的参数是touch.clientY和每个check-itemoffsetTop对比,超过中间线就交换数组顺序。拖完记得把isDragging置回 false,否则松手后还会继续响应 touchmove,屏幕会跟着手指乱跳。图片上传用wx.chooseMedia,一次最多选 9 张,上传前压缩到宽度 1280 以内,能省不少流量和上传时间。

3.5 uniapp 与原生小程序:HBuilderX 跑同一套代码的取舍

如果源码是用 uniapp 写的,项目结构里会多出pages.json而不是app.json,HBuilderX 里可以直接运行到微信开发者工具。这种技术选型的好处是一套代码同时出小程序和 App,但要注意 uniapp 的 radio-group 事件名是@change,原生小程序是bindchange,改造页面时经常在这里翻车。

我一般会先看根目录有没有manifest.json,有就是 uniapp 工程,直接 HBuilderX 打开。原生的微信小程序项目没有这个文件,用微信开发者工具导入。两者在演示上没本质差别,选哪个取决于源码本身,不必为了技术栈高低去重写。

4. 后端接口与联调:登录、定位、告警推送与微信支付 v3

4.1 微信登录换取 token,演示项目常见的绕过方案

小程序调用wx.login拿到 code,后端拿 code 到微信接口换 openid,再签发自己的 token。这套流程本身五分钟能写完,但很多毕设源码为了演示省事,写了个“一键登录”按钮直接带一个user_id参数请求接口,跳过整个登录流程。

@PostMapping("/api/auth/login") public Result login(@RequestBody LoginDTO dto) { String openid = wechatService.code2Session(dto.getCode()); if (openid == null) { return Result.fail("登录凭证已失效"); } User user = userMapper.selectByOpenid(openid); if (user == null) { user = userMapper.createDefaultUser(openid, dto.getRole()); } String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok(token); }

这段代码里有三个点值得展开。第一,code2Session请求微信接口必须后端发起,不能在前端直接把 code 发给微信服务器,否则appsecret会暴露。第二,首次登录自动建用户的写法适合演示场景,但生产上应该弹窗让用户补全姓名和手机号。第三,token 里只放idrole,不要把整个 user 对象塞进去,JWT 是有体积上限的。

注意最新的微信基础库已经收紧头像昵称获取方式,wx.getUserProfile返回的头像昵称不再是真实信息。毕设里如果需要显示用户姓名,建议用个人中心里的昵称输入框,后端存到用户表,不要尝试调getUserInfo

4.2 定位上报与电子围栏判定

消防巡查必须要求巡查员在设备附近才能提交点检,否则坐在宿舍里刷任务也能完成。后端的判定逻辑不能只比对“距离小于 50 米”,要考虑设备坐标的存储和小程序端定位误差。

SELECT * FROM device WHERE ( 6371000 * acos( cos(radians(#{lat})) * cos(radians(latitude)) * cos(radians(longitude) - radians(#{lng})) + sin(radians(#{lat})) * sin(radians(latitude)) ) ) < 50;

这条 SQL 直接查出 50 米内的所有设备,Haversine 公式算的是球面距离,单位是米。业务逻辑上先提交定位,后端返回距离最近的三台设备,小程序端让巡查员点选“我就在这台设备前”,而不是硬性限制成必须匹配唯一设备,因为室内 GPS 漂移十几米是常态。

4.3 告警订阅消息与定时轮询的配合

小程序不像 App 能常驻后台收推送,订阅消息每条都要用户点击授权同意,且一次性订阅只能收到一次推送。智慧消防这个场景里,告警推送的可行做法是:用户在小程序里点击“开启告警提醒”时请求订阅授权,后端在设备上报告警时调用subscribeMessage.send发送。

轮询作为兜底。小程序onShow和下拉刷新时拉取最近告警列表,两套机制配合,既能实时提醒,又不至于在用户没授权订阅时完全失联。演示录像里最常看到的问题是:开发者工具里订阅消息功能默认关闭,必须到详情设置的「本地设置」里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,还要在公众平台里配置request合法域名。

4.4 扩展功能:小程序微信支付 v3 对接的关键参数

不是每个智慧消防项目都要做支付,但如果设备年检、消防培训课程收费这个模块出现在源码里,对接的就是微信支付 v3。先看三个关键参数是否配齐:mchid(商户号)、serial_no(商户证书序列号)、api_v3_key(API v3 密钥)。

v3 和 v2 最大的区别是签名方式。v3 用 RSA-SHA256,请求头里要带Authorization: WECHATPAY2-SHA256-RSA2048,签名串由请求方法、路径、时间戳、随机串、请求体拼接而成。毕设里最常见的错误是时间戳和随机串每次请求都重新生成,但验签用的商户私钥没加载对。把这三个值放到配置文件里,appidmchid分开放,不要写死在代码里。支付成功后微信会往回调地址推通知,注意回调地址必须用 HTTPS 域名,本地调试时用内网穿透工具转到 8080 端口。

5. 演示录像怎么录:抓包验接口、反编译验代码、场景串流程

5.1 录像脚本编排:看板→告警→处置三段式

演示录像不是操作录屏,是按脚本表演的系统验收。从多个毕业设计答辩现场回来看,得分高的录像都遵循同一个结构:先用看板页的统计数字交代系统全貌,再触发一条真实告警展示流转,最后以巡查员扫码提交点检收尾。三段各留 15 秒给评委看清页面跳转,不要一镜到底乱点。

触发告警不要用假按钮。常见做法是后端提供一个测试接口POST /api/fire/alarm/test,传设备编号和告警类型,模拟真实上报链路。录像里能看到这条请求进 Redis 或数据库,告警列表 5 秒内出现新纪录,比前端写死一条数据有说服力得多。

5.2 用抓包工具核对每个请求是否走通

录完像后我会用抓包工具把小程序流量完整过一遍。启动小程序,操作点检、告警处理、工单归档三个核心流程,然后逐个检查请求路径和响应体。重点看三处:code是否都是 0、时间字段格式是否统一、经纬度是不是真实值。

抓包前需要做两个准备:HTTP 请求在调试工具里能直接看到,但 HTTPS 的要装证书,并且要把证书安装到系统信任区,否则小程序请求会报证书校验失败。另一个是关闭小程序的域名校验,这个在开发者工具里勾选“不校验合法域名”即可,不影响代码逻辑。

5.3 反编译检查代码里有没有该删的东西

毕设源码包里带反编译出来的代码不奇怪,关键是自己要会用反编译工具检查有没有“不能见人”的东西。把小程序包解包后,第一眼看app.js里的globalData,第二眼看配置文件里的appid,第三眼看接口地址。

如果发现源码里带真实的appsecret、商户私钥、数据库密码,答辩前必须换掉。尤其注意小程序端代码里出现mchid和私钥内容的,这是把服务端配置写到前端的大忌。反编译的代码结构可以在答辩时间接展示自己对微信小程序运行机制的理解,但没必要主动提;评委如果问了,就说“我用反编译工具分析过官方示例小程序,学习它的分包结构”。

5.4 答辩前必改的三个演示破绽

改完这三处再去录最终的演示录像:第一,全局搜索代码里所有http://localhosthttp://192.16810.0.2.2这类地址,统一换成已备案的 HTTPS 域名或演示用的线上接口;第二,检查数据库脚本里有没有DROP DATABASE,有的话去掉,避免评委在自行运行项目时误操作;第三,把测试账号的密码从123456改成可展示的强密码,并在说明文档里标注出来。这三处是答辩现场翻车概率最高的位置,改完再绿录屏,基本就不会被追问“这个系统是不是只能演示”。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询