
作为一个计算机专业的学生尤其是正在准备毕业设计或课程设计的人你大概率在很多资源网站和群里看到过这样一类项目“基于ESP32的物联网智能家居环境检测节点”。这类题目看起来不难但真到自己动手时往往卡在几个非常尴尬的位置ESP32 的程序怎么烧都报错WiFi 连不上传感器读数始终是 0后端接口写好了但前端页面死活拿不到数据。最后明明觉得“这东西原理也不难”却连一个能完整演示的版本都跑不起来。这篇文章要写的就是这类物联网环境检测项目的完整拆解。我会基于这个“A86 基于 ESP32 的物联网智能家居环境检测节点”演示项目把硬件端、后端接口、Vue 前端展示这三层链路分别讲清楚并给出一套可以直接复制运行的示例代码。你读完以后不仅知道每一段代码是什么意思还能自己动手把整个闭环跑通并且懂得怎么把它扩展成一份能通过答辩的毕业设计或课程设计项目。为什么这件事值得专门写一篇文章因为网上关于 ESP32 的教程很多但大多数只停留在“点灯”“读取传感器串口打印”这一步。真正到了要做成“系统”的时候就涉及到前后端联调、数据存储、接口设计、页面展示这些工程问题。这些内容恰恰是学生在校期间最缺、也最需要补的环节。本文的目的就是帮你把这些断层接上。1. 这篇文章真正要解决的问题如果你只是想学“ESP32 怎么用”那你随便搜一个点灯教程就够了。但这篇文章面向的是更具体的一类需求把这个硬件节点做成一个可以让老师或评委看到的完整系统。项目标题里提到了几个关键词ESP32、物联网、python、vue、管理系统、网页制作。把这些词拼起来你就能得到一个非常典型的物联网毕设架构ESP32 作为终端节点负责采集温湿度、光照、空气质量等环境数据。ESP32 通过 WiFi 将数据上报到后端服务。后端服务负责接收、存储和提供查询接口。前端页面Vue负责展示实时数据和历史数据。很多学生卡住的核心问题有三个第一不知道怎么把传感器数据从 ESP32 送到后端。很多人会写 Arduino 代码读取传感器但一到 HTTP POST、JSON 序列化就懵了。实际上这个环节的逻辑很简单难在代码细节和环境配置。第二后端接口不知道怎么设计。有些同学用 Python 写了一个 Flask 接口只写了接收数据没有查询接口前端根本用不了。有些写了接口但没处理跨域前端请求直接失败。第三前端不是不会写而是不知道怎么和真实数据对接。Vue 的基本语法大家都能看懂页面布局也能做但到了用 axios 请求真实接口、处理异步数据、定时刷新这些环节就开始手忙脚乱。这篇文章会围绕这三件事展开。你可以把它当成一套“最小可运行版本”的完整参考实现。代码不追求复杂的架构设计但每一步都能跑通。2. 系统架构与核心概念在写代码之前先把系统架构讲清楚。这不是空谈而是后面所有代码的组织依据。2.1 整体架构整个系统的数据流可以概括为一条链路传感器硬件 - ESP32 开发板 - WiFi 网络 - 后端 API - 数据库 - Vue 前端页面从开发视角看可以拆成三层层级技术选型职责感知层ESP32 DHT11/DHT22 光敏电阻/空气质量传感器采集环境数据通过 WiFi 上报服务层Python Flask 后端 MySQL/SQLite接收数据、存储数据、提供 REST API展示层Vue 3 Vite Element Plus调用后端接口展示实时数据与历史趋势这套架构最大的好处是每一层都可以单独调试。你可以先用串口监视器看 ESP32 的数据再用 Postman 测后端接口最后才让 Vue 页面联动。如果直接一把梭从头写到尾出了问题很难定位。2.2 为什么选这套技术栈先说 ESP32。它和传统 Arduino Uno 相比最大的优势是自带 WiFi 和蓝牙主频也更高。做物联网环境检测节点如果用 Arduino Uno 还要外接 ESP8266 模块既麻烦又容易出接触不良的问题。ESP32 一片芯片就能搞定采集和联网是目前物联网入门和毕设项目的主流选择。再说 Python Flask。很多学生学过 Python用 Flask 写一个轻量后端服务学习成本最低。它不像 Spring Boot 那样需要理解依赖注入和繁琐的配置也不像 Node.js 那样要求你熟悉 JavaScript 生态。Flask 写一个能接收 POST 请求、提供 GET 查询的接口核心代码不超过 50 行。最后是 Vue。Vue 在国内前端开发中占有很高的地位而且对新手相对友好。用 Vue 3 Vite 搭建项目配合 Element Plus 组件库做一个环境数据看板非常快。如果你之前只写过静态 HTML 页面这篇文章里的前端代码会让你看到动态数据渲染是怎么一回事。2.3 技术选型的重要提醒这套技术栈适合的是课程设计和毕业设计不一定适合直接当生产级物联网平台。如果做真实的商用产品你需要考虑 MQTT 协议、设备管理、数据加密、消息队列、时序数据库等问题。但作为学习和演示项目用 HTTP JSON Flask Vue 已经完全足够而且更容易让评委看懂你的代码结构。3. 环境准备与硬件清单这里先把开发环境列清楚。不同版本的软件在配置上可能有差异下面列的是比较通用的方案。3.1 硬件清单设备说明ESP32 开发板推荐 ESP32 DevKitC V4 或 NodeMCU-32S带 USB 接口即可DHT11 或 DHT22温湿度传感器DHT22 精度更高光敏电阻模块检测光照强度输出模拟量MQ-2 或 MQ-135空气质量/烟雾检测可选杜邦线连接传感器与开发板面包板方便接线USB 数据线注意部分数据线只能充电不能传数据3.2 软件环境开发 ESP32 推荐用 Arduino IDE同时需要安装 ESP32 开发板支持包。安装步骤网上有很多这里提醒一个关键点国内网络环境下直接在 Arduino IDE 的“开发板管理器”中搜索 ESP32 可能很慢甚至失败。常见方案是配置国内镜像源或者下载离线安装包手动安装。我建议你按以下顺序准备环境安装 Arduino IDE建议 2.x 版本界面更友好也兼容旧教程。在“文件 - 首选项 - 附加开发板管理器网址”中添加 ESP32 的 JSON 地址。打开“工具 - 开发板 - 开发板管理器”搜索 ESP32 并安装。安装完成后在“工具 - 开发板”中选择你的具体型号。选择正确的 COM 口这个在设备管理器里可以看到。Python 环境方面建议安装 Python 3.9 及以上版本用pip安装 Flask、Flask-CORS、requests 等库。如果你不想把环境搞乱可以用 venv 创建虚拟环境。前端环境需要 Node.js。Node.js 的版本建议使用 18 或 20 的 LTS 版本npm 会随 Node 一起安装。Vue 项目用 Vite 创建具体命令在后面的章节会给出。这里想说一句环境问题占了这个项目 40% 以上的坑。很多同学卡在“IDE 上传报错”“Python 包装不上”“npm install 报错”其实都不是代码问题。遇到环境报错不要慌先看错误信息里的关键字比如port、time out、No module named搜索定位往往比瞎试更高效。4. ESP32 传感器数据采集与上报这一章是硬件端核心。我们的目标不是单纯在串口打印温度湿度而是采集并发送到后端。4.1 引脚接线以 DHT11 和光敏电阻为例典型接线如下传感器引脚ESP32 引脚DHT11 VCC3.3VDHT11 GNDGNDDHT11 DATAGPIO4光敏模块 VCC3.3V光敏模块 GNDGND光敏模块 AOGPIO34ADC注意DHT11 数据引脚需要接一个 10k 欧姆上拉电阻到 VCC部分模块已经集成可以略过。4.2 Arduino 代码实现在 Arduino IDE 中新建一个工程粘贴下面的参考代码。这个代码会读取温度、湿度和光照值通过 WiFi 连接后将这些数据以 JSON 格式 POST 到后端接口。#include WiFi.h #include HTTPClient.h #include ArduinoJson.h #include DHT.h // WiFi 配置 const char* ssid 你的WiFi名称; const char* password 你的WiFi密码; // 后端接口地址注意不要写 localhost const char* serverUrl http://192.168.1.100:5000/api/sensor; // DHT 配置 #define DHTPIN 4 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); // 光敏传感器 #define LIGHT_PIN 34 void setup() { Serial.begin(115200); dht.begin(); WiFi.begin(ssid, password); Serial.print(WiFi 连接中); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(连接成功); Serial.print(IP 地址: ); Serial.println(WiFi.localIP()); } void loop() { float temperature dht.readTemperature(); float humidity dht.readHumidity(); int lightValue analogRead(LIGHT_PIN); if (isnan(temperature) || isnan(humidity)) { Serial.println(DHT 传感器读取失败检查接线); delay(2000); return; } if (WiFi.status() WL_CONNECTED) { HTTPClient http; http.begin(serverUrl); http.addHeader(Content-Type, application/json); StaticJsonDocument256 doc; doc[deviceId] esp32-node-01; doc[temperature] temperature; doc[humidity] humidity; doc[light] lightValue; String requestBody; serializeJson(doc, requestBody); Serial.println(发送数据: requestBody); int httpResponseCode http.POST(requestBody); if (httpResponseCode 0) { Serial.print(服务器响应码: ); Serial.println(httpResponseCode); } else { Serial.print(请求失败: ); Serial.println(http.errorToString(httpResponseCode).c_str()); } http.end(); } else { Serial.println(WiFi 已断开); } delay(10000); }4.3 代码关键点讲解这段代码里最重要的逻辑其实是loop函数中的那几个步骤读取传感器、组装 JSON、发送 HTTP 请求。数据读取部分DHT 传感器读取温湿度要用dht.readTemperature()和dht.readHumidity()这两个函数是 DHT 库提供的。光敏传感器读取的是模拟量 ADC 值ESP32 的 ADC 精度是 12 位所以读数范围是 0 到 4095。光照越强数值越高。JSON 组装部分这里用的是 ArduinoJson 库。StaticJsonDocument256是预分配 256 字节的静态缓冲区对于当前这几个字段足够了。如果未来要加更多字段可以改成DynamicJsonDocument或提高静态大小。HTTP 请求部分http.begin(serverUrl)指定了后端接口地址。注意这里有一个特别容易踩的坑在 ESP32 代码里不能用localhost或127.0.0.1因为那是 ESP32 自己。你要填的是电脑或服务器在局域网中的 IP 地址。比如电脑连了路由器IP 是192.168.1.100那么这里就写http://192.168.1.100:5000/api/sensor。4.4 上传和调试注意事项上传代码之前先确认 Arduino IDE 中选择的开发板型号和端口是正确的。如果上传失败常见原因是没有按住开发板上的 BOOT 键或者驱动没有安装好。上传成功后打开串口监视器波特率选择 115200。你会看到 WiFi 连接信息和传感器数据的打印日志。如果串口输出乱码说明串口监视器右上角的波特率选择不对调到 115200 即可。如果你发现 DHT 读取一直失败优先检查两个地方一是数据线是否插在正确的 GPIO 引脚二是模块供电是否稳定。传感器模块的 VCC 接 3.3V不要接 5V。5. Python Flask 后端接口实现后端是整个系统的数据中枢。ESP32 发数据过来它负责接收存储前端要展示数据它负责查询返回。5.1 创建项目结构建议在后端项目文件夹中按下面的结构组织文件iot-backend/ ├── app.py ├── requirements.txt └── data/ └── sensor.dbsensor.db会在首次运行时由代码自动创建。5.2 安装依赖首先创建一个虚拟环境然后安装依赖。这里用 Flask 提供 API用 Flask-CORS 解决跨域问题用 SQLite 存储数据。python -m venv venvWindows 下激活虚拟环境venv\Scripts\activateMac/Linux 下激活source venv/bin/activate然后安装依赖库pip install flask flask-cors把依赖写入 requirements.txtpip freeze requirements.txt5.3 编写后端代码新建app.py粘贴下面的代码。这是一个最小可运行的实现已经包含了数据接收、历史查询、最新数据查询三个接口。import sqlite3 import time from flask import Flask, request, jsonify from flask_cors import CORS app Flask(__name__) CORS(app) DATABASE data/sensor.db def init_db(): conn sqlite3.connect(DATABASE) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, temperature REAL NOT NULL, humidity REAL NOT NULL, light INTEGER NOT NULL, create_time INTEGER NOT NULL ) ) conn.commit() conn.close() app.route(/api/sensor, methods[POST]) def receive_sensor_data(): data request.get_json() if not data: return jsonify({code: 400, message: 请求体不能为空}), 400 device_id data.get(deviceId, unknown) temperature data.get(temperature) humidity data.get(humidity) light data.get(light) if temperature is None or humidity is None or light is None: return jsonify({code: 400, message: 缺少必要字段}), 400 conn sqlite3.connect(DATABASE) cursor conn.cursor() cursor.execute( INSERT INTO sensor_data (device_id, temperature, humidity, light, create_time) VALUES (?, ?, ?, ?, ?), (device_id, temperature, humidity, light, int(time.time())) ) conn.commit() conn.close() return jsonify({code: 200, message: 数据接收成功}) app.route(/api/sensor/latest, methods[GET]) def get_latest_sensor_data(): conn sqlite3.connect(DATABASE) cursor conn.cursor() cursor.execute( SELECT device_id, temperature, humidity, light, create_time FROM sensor_data ORDER BY id DESC LIMIT 1 ) row cursor.fetchone() conn.close() if row is None: return jsonify({code: 200, data: None}) return jsonify({ code: 200, data: { deviceId: row[0], temperature: row[1], humidity: row[2], light: row[3], createTime: row[4] } }) app.route(/api/sensor/history, methods[GET]) def get_sensor_history(): limit request.args.get(limit, default50, typeint) conn sqlite3.connect(DATABASE) cursor conn.cursor() cursor.execute( SELECT device_id, temperature, humidity, light, create_time FROM sensor_data ORDER BY id DESC LIMIT ?, (limit,) ) rows cursor.fetchall() conn.close() result [] for row in rows: result.append({ deviceId: row[0], temperature: row[1], humidity: row[2], light: row[3], createTime: row[4] }) return jsonify({code: 200, data: result}) if __name__ __main__: init_db() app.run(host0.0.0.0, port5000, debugTrue)5.4 代码关键逻辑解释初始化数据库init_db()函数在应用启动时创建 SQLite 数据表。SQLite 是文件型数据库不需要单独安装服务非常适合轻量级项目。POST /api/sensorESP32 上报数据时调用这个接口。先判断请求体是否为空再逐个检查字段是否存在最后插入数据库。这里使用int(time.time())存储 Unix 时间戳方便后续在有历史数据时做时间范围查询。GET /api/sensor/latest前端展示“当前温湿度”时调用返回最新一条数据。如果没有数据data字段返回None。GET /api/sensor/history前端展示历史记录或趋势图时调用默认返回最近 50 条数据。这里重点说一下跨域问题。如果你的 Vue 页面运行在http://localhost:5173后端运行在http://localhost:5000浏览器会默认拦截跨域请求。CORS(app)这行代码就是为了解决这个问题。如果没有它前端控制台会出现类似的报错Access to XMLHttpRequest at ... has been blocked by CORS policy。5.5 启动后端并验证在项目根目录执行python app.py看到类似下面的输出说明后端启动成功* Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://192.168.1.100:5000此时可以用 Postman 或 curl 模拟 ESP32 上报数据。在终端中执行curl -X POST http://127.0.0.1:5000/api/sensor \ -H Content-Type: application/json \ -d {deviceId:esp32-node-01,temperature:26.5,humidity:65.3,light:2048}预期返回{code: 200, message: 数据接收成功}再用浏览器访问http://127.0.0.1:5000/api/sensor/latest如果能看到刚才插入的数据说明后端接口已经工作正常。6. Vue 前端环境监测看板实现前端是展示层也是评委最容易留下第一印象的部分。这里我们用 Vue 3 Vite 搭建项目写一个简单的环境数据看板展示最新温湿度、光照强度和历史数据列表。6.1 创建 Vue 项目在终端中执行下面的命令npm create vitelatest iot-frontend -- --template vue进入项目目录并安装依赖cd iot-frontend npm install安装 axios用于发送 HTTP 请求npm install axios启动开发服务器npm run devVite 默认会在http://localhost:5173启动页面。此时项目是一个基础模板下面我们改造它。6.2 编写页面组件打开src/App.vue替换为以下内容。这个组件实现了两个核心功能轮询获取最新数据并展示以及获取历史数据列表。template div classdashboard h1智能家居环境检测看板/h1 div classcards div classcard h3温度/h3 p{{ latestData ? latestData.temperature °C : 暂无数据 }}/p /div div classcard h3湿度/h3 p{{ latestData ? latestData.humidity % : 暂无数据 }}/p /div div classcard h3光照强度/h3 p{{ latestData ? latestData.light : 暂无数据 }}/p /div /div h2设备信息/h2 p设备ID{{ latestData ? latestData.deviceId : 未上报 }}/p p最近上报时间{{ latestData ? formatTime(latestData.createTime) : 未上报 }}/p h2历史数据/h2 table thead tr th时间/th th温度 (°C)/th th湿度 (%)/th th光照/th /tr /thead tbody tr v-foritem in historyData :keyitem.createTime td{{ formatTime(item.createTime) }}/td td{{ item.temperature }}/td td{{ item.humidity }}/td td{{ item.light }}/td /tr tr v-ifhistoryData.length 0 td colspan4暂无历史数据/td /tr /tbody /table /div /template script setup import { ref, onMounted, onUnmounted } from vue; import axios from axios; const API_BASE http://localhost:5000/api; const latestData ref(null); const historyData ref([]); let timer null; const fetchLatestData async () { try { const response await axios.get(${API_BASE}/sensor/latest); if (response.data.code 200) { latestData.value response.data.data; } } catch (error) { console.error(获取最新数据失败, error); } }; const fetchHistoryData async () { try { const response await axios.get(${API_BASE}/sensor/history?limit20); if (response.data.code 200) { historyData.value response.data.data; } } catch (error) { console.error(获取历史数据失败, error); } }; const formatTime (timestamp) { const date new Date(timestamp * 1000); const year date.getFullYear(); const month String(date.getMonth() 1).padStart(2, 0); const day String(date.getDate()).padStart(2, 0); const hours String(date.getHours()).padStart(2, 0); const minutes String(date.getMinutes()).padStart(2, 0); const seconds String(date.getSeconds()).padStart(2, 0); return ${year}-${month}-${day} ${hours}:${minutes}:${seconds}; }; onMounted(() { fetchLatestData(); fetchHistoryData(); timer setInterval(() { fetchLatestData(); fetchHistoryData(); }, 5000); }); onUnmounted(() { clearInterval(timer); }); /script style scoped .dashboard { max-width: 900px; margin: 0 auto; padding: 20px; font-family: Segoe UI, Arial, sans-serif; } .cards { display: flex; gap: 20px; margin-bottom: 30px; } .card { flex: 1; background: #f5f7fa; border-radius: 8px; padding: 20px; text-align: center; } .card h3 { margin: 0 0 10px; color: #606266; } .card p { font-size: 28px; font-weight: bold; color: #409eff; margin: 0; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ebeef5; padding: 10px; text-align: center; } th { background-color: #f5f7fa; } /style6.3 前端代码逻辑讲解script setup是 Vue 3 的组合式 API 写法。ref用于创建响应式变量onMounted在组件挂载后执行数据请求onUnmounted在组件销毁时清除定时器。fetchLatestData和fetchHistoryData分别请求后端的最新数据接口和历史数据接口。拿到响应后把response.data.data赋值给对应的响应式变量。轮询逻辑是每隔 5 秒请求一次接口。这样做的好处是 ESP32 每隔 10 秒上报一次数据前端每 5 秒拉取一次基本可以保证页面数据接近实时刷新。实际项目中如果节点数量多可以用 WebSocket 或 MQTT 做更实时的推送但演示项目中轮询已经够用。这里有一个需要留意的点API_BASE我写的是http://localhost:5000/api。如果你的后端跑在别的 IP 或端口需要对应修改。如果用局域网内其他电脑访问这个前端页面这里的localhost会指向访问者自己的机器导致请求失败。这种情况可以把API_BASE改成后端的局域网 IP。7. 运行效果与联调验证到这里三个端口的代码都有了。下面讲怎么把它们串起来做完整的联调。7.1 启动顺序推荐建议按照下面的顺序启动避免出现“不知道是哪个环节出了问题”的情况启动后端python app.py用 curl 或 Postman 测试接口确认数据能写入。启动前端npm run dev在浏览器里打开页面确认历史接口能返回数据。最后给 ESP32 上电观察串口监视器中的上报日志。之所以最后启动 ESP32是因为前两步可以通过模拟数据验证系统链路是否正常。如果前两步已经跑通ESP32 只要上报一次前端页面就会出现新的温度和光照数据。7.2 预期的完整演示流程当三个端都正常运行时整个演示过程应该像下面这样ESP32 上电串口监视器显示 WiFi 连接成功并每 10 秒打印一条 JSON 数据。后端控制台显示POST /api/sensor的访问日志。Vue 页面上的温度、湿度、光照卡片每隔 5 秒自动刷新。历史数据表格新增一条记录时间戳是 ESP32 上报的时间。如果用手靠近光敏电阻遮挡光线光照数值会明显下降下一次刷新页面时数据发生变化。7.3 联调失败的常见表现如果页面一直显示“暂无数据”可以从三个方向排查打开浏览器开发者工具切换到 Network 标签看请求latest接口是否返回 200。如果是 404说明后端接口路径不对。如果请求返回 500查看后端控制台报错信息通常是数据库写入问题。如果请求被 CORS 拦截确认后端是否添加了CORS(app)同时确认浏览器访问的地址是http://localhost:5000而不是http://127.0.0.1:5000。8. 常见问题与排查思路下面整理这个项目中比较常见的问题尽量具体到一个可行的排查路径。问题现象可能原因排查方式解决方案Arduino IDE 上传代码报错未选择正确的开发板型号或 COM 口查看设备管理器中的端口确认板子型号选择正确的 COM 口和开发板型号必要时按住 BOOT 键再上传串口监视器乱码波特率设置错误查看串口监视器右下角波特率将波特率设置为 115200ESP32 连不上 WiFiSSID 或密码错误路由器不开放 2.4G串口打印检查WiFi.begin后的状态确认 WiFi 名称密码尽量不要用纯 5G WiFiDHT 传感器一直返回 NaN接线不良或引脚错误检查 DATA 是否接到 GPIO4上拉电阻是否接好重新拔插杜邦线更换面包板插孔后端接口返回 400JSON 请求体中缺少字段用 Postman 打印请求体查看字段名确认deviceId、temperature、humidity、light字段都存在前端请求浏览器报 CORS 错误后端未启用 CORS查看后端app.py中是否有CORS(app)添加from flask_cors import CORS并实例化前端页面显示“暂无数据”后端数据库为空表用 curl 发一条测试数据先手动 POST 一条数据看前端能否展示ESP32 请求返回 -1后端地址不可达或服务未启动用手机/电脑 ping ESP32 写入的 IP 地址确认后端已启动且 ESP32 填的 IP 是本机局域网 IP9. 最佳实践与工程建议即使这是一个毕设演示项目好的编码习惯和工程组织方式仍然会让你在答辩和后续扩展中受益。9.1 引脚定义统一管理ESP32 代码中把引脚定义放在文件开头统一管理。如果需要换引脚只需要改一处。不要在很多函数里直接写4或34这种魔法数字时间久了很容易维护混乱。9.2 数据上报加上时间戳目前示例代码中时间戳是后端生成的。实际项目中更合理的做法是 ESP32 也把自己的时间信息或设备标识一起上传方便做断网补传和设备区分。如果节点数量多deviceId还可以扩展成具体的房间号或设备位置。9.3 后端接口要统一返回格式我在这里用了{ code: 200, message: ..., data: ... }这种结构。好处是前端可以根据code判断请求是否成功而不是依赖 HTTP 状态码。真实项目中还可以在code中定义业务错误码比如40001表示参数缺失50001表示数据库异常。9.4 配置信息不要硬编码WiFi 的 SSID 和密码后端的 IP 地址这些配置信息不要直接写在代码里。对毕设项目来说可以放到一个头文件中统一管理对更正式的项目可以使用环境变量或配置文件。这样做的好处是方便其他人使用你的代码时修改也避免不小心把密码传到公开仓库。9.5 安全边界与生产环境差异这个项目中的设备上报没有做任何身份认证任何知道接口地址的人都可以伪造数据。这在局域网演示环境中问题不大但如果要部署到公网必须增加 token 或签名机制。同时SQLite 适合数据量较小的场景如果数据量大了或者要支持多设备高频率上报建议换成 MySQL 或时序数据库。9.6 从演示项目到毕业设计的扩展思路如果你的题目是“基于 ESP32 的物联网智能家居环境检测系统”光有环境检测节点可能不够完整。可以按下面几个方向扩展增加执行设备比如检测到温度过高时通过继电器控制风扇或空调。增加报警机制温湿度超限时通过后端发送邮件或钉钉机器人通知。增加用户登录Vue 前端接入一个简单的登录注册功能区分用户查看权限。增加图表展示使用 ECharts 将历史温度湿度数据渲染成折线图视觉冲击力更强。增加设备配置页面在前端页面能够修改 ESP32 的上报频率或传感器阈值。扩展时注意保持现有分层不变每新增一个功能尽量独立到一个模块中不要让后端接口和前端页面耦合得太深。10. 源码获取方式与使用建议这个项目的源码如果从公开渠道获取拿到手后不要急着跑先按下面的顺序做三件事。第一看目录结构。先搞清楚每个文件的作用哪些是后端代码哪些是前端代码哪些是 ESP32 的 Arduino 工程。如果目录混乱说明项目组织得不太好你需要先整理再使用。第二修改配置。重点检查这几处ESP32 代码里的 WiFi 名称和密码、后端接口地址、前端src/App.vue里的API_BASE地址。这三处是项目和环境绑定最深的地方不改必然跑不通。第三分步运行。不要想着“代码拿过来直接一键跑通”。按照前文说的顺序后端先启动用 curl 或 Postman 验证接口再启动前端最后给 ESP32 上电。每一步都验证通过以后再进入整体联调。最后说一句经验之谈这类项目的难点不在于某一个技术点有多深而在于把硬件、后端、前端三个环节串联起来。很多同学答辩时不是说“这个功能我不会”而是“代码能跑但解释不清为什么这么设计”。所以拿到代码后建议你用一个晚上把所有代码读一遍特别是后端接口和前端请求的对应关系。真到答辩的时候能够完整讲清楚数据从传感器到页面展示的整个过程比项目本身多几个炫酷功能更能给评委留下好印象。这篇文章把 ESP32 物联网环境检测项目从架构、硬件、后端到前端做了一个完整拆解。你可以把它当作一份可运行的参考手册也可以当作毕业设计的起步框架。建议收藏备用遇到配置问题随时回来查阅。