先上代码,这是一段最朴素的 ESP32 上传逻辑,用最少的行数把数据丢到云端:
#include <WiFi.h>
#include <HTTPClient.h>
void setup() {
Serial.begin(115200);
WiFi.begin("你的WIFI名", "你的WIFI密码");
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
HTTPClient http;
http.begin("http://你的域名/api/push.php");
http.addHeader("Content-Type", "application/json");
http.addHeader("X-Api-Key", "你的APIKey");
String body = "{\"dataset\":\"sensor01\",\"data\":{\"temp\":26.5,\"hum\":61}}";
int code = http.POST(body);
Serial.printf("HTTP %d\n", code);
http.end();
}
void loop() {}
能跑,但很多人看到 http:// 就皱眉:这东西传出去会不会被人半路截走?这篇就把「链路加密」和「接口鉴权」两件事讲清楚,顺便把 ESP32 上传数据的完整方案捋一遍。
ESP32 上传数据,到底在怕什么
设备上传数据,怕的无非三件事:被看见、被篡改、被冒充。
「被看见」是最直观的——你的温度、位置、设备编号、业务参数,如果走明文,同一个 Wi-Fi 下的其他人、上游链路上的任何一跳,都可能顺手捞走。「被篡改」更隐蔽,中间人把 {"temp":26.5} 改成 {"temp":99.9},云端照单全收,你的告警逻辑直接跑偏。「被冒充」则是最要命的:别人拿到你的地址和密钥,用脚本往你的数据名里灌垃圾,你的队列就被污染了。
这三件事对应三种手段:链路加密解决「被看见」,完整性校验解决「被篡改」,接口鉴权解决「被冒充」。缺一环都不算完整方案。
另外还有个容易被忽略的点:ESP32 是物理设备,谁拿到板子谁就能读 Flash。所以「密钥写死在固件里」这件事,本身就是个风险面,后面 FAQ 会专门聊。
HTTPS 加密了什么,又没加密什么
HTTPS 的本质是 TLS,它保护的是传输这一段:从 ESP32 的网卡出去,到服务端 TLS 握手结束,中间的报文是密文。抓包工具看到的只有 IP、端口、SNI(目标域名)和流量大小,看不到 JSON 内容。这就是「链路加密」。
但 HTTPS 管不到的地方也很多。它不负责「你是谁」——服务端只知道有个客户端连上来了,不知道你是不是合法设备;它不负责「这条数据该不该收」——只要请求格式对,接口就处理;它也不负责「设备被拆了怎么办」——固件里的密钥被读出来,照样能伪造请求。
所以正确的认知是:HTTPS 解决传输机密性,接口鉴权解决身份合法性,两者是叠加关系,不是替代关系。下面这张表把两件事的分工列清楚:
| 维度 | HTTPS(链路加密) | 接口鉴权(APIKey) |
|---|---|---|
| 防护目标 | 传输内容不被窃听 | 请求来源不被冒充 |
| 作用范围 | 设备到服务端的网络链路 | 服务端对每次请求的校验 |
| 能防抓包看到明文 | 能 | 不能 |
| 能防他人伪造请求 | 不能 | 能(密钥未泄露时) |
| 密钥/凭据是否暴露 | 不暴露内容,但凭据本身在请求里 | 凭据随请求发送,需配合 HTTPS |
| 典型失效场景 | 证书校验被跳过 | 密钥硬编码被反编译读出 |
一句话总结:只上 HTTPS 不上鉴权,等于门锁了但钥匙插在门上;只上鉴权不上 HTTPS,等于钥匙藏好了但门是透明的。
整体方案与数据流:设备—加密链路—云端接口
一个能落地的方案,数据流大致是这样:
- 设备侧:ESP32 采集数据,组装成 JSON,带上
dataset和api_key。 - 链路侧:通过 TLS 把请求发到服务端,证书校验开启,不做「跳过校验」的偷懒操作。
- 接口侧:服务端用 API 密钥鉴权,通过 PDO 预处理写库,内容规范化后计算哈希去重。
- 消费侧:另一端用读取接口按 FIFO 取走,取走即标记已读,不重复下发。
这里的关键设计是「写入」和「读取」分离成两个接口:推送只管把数据放进去,读取只管按顺序消费并标记已读。设备不需要关心谁在消费、消费到哪了,云端天然就是一个队列。
云递数据就是按这个模型做的:推送接口 POST /api/push.php,读取接口 POST /api/pull.php,数据名(dataset)做分类,控制台能看条数、容量和已读状态。它不要求你买服务器和数据库,但接口语义是标准的 HTTP/JSON,任何能发请求的设备都能接。
落地步骤:ESP32 用 HTTPS 推送数据到云递数据
先确认一件事:你的域名是否已经配好 TLS。如果服务端本身是 HTTPS,把地址换成 https:// 即可。ESP32 用 WiFiClientSecure 走加密链路,最简单的写法是校验证书指纹(比直接 setInsecure() 安全得多):
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
// 服务端证书的 SHA-1 指纹,用浏览器或 openssl 获取
const char* ROOT_CA_FP = "AA:BB:CC:DD:..."; // 替换为你的域名证书指纹
void pushData(float temp, float hum) {
WiFiClientSecure client;
client.setCACert(nullptr); // 不用内置 CA 时置空
client.setFingerprint(ROOT_CA_FP); // 校验服务端证书指纹
HTTPClient http;
http.begin(client, "https://你的域名/api/push.php");
http.addHeader("Content-Type", "application/json");
http.addHeader("X-Api-Key", "你的APIKey"); // 密钥走请求头,避免出现在 URL 里
char body[160];
snprintf(body, sizeof(body),
"{\"dataset\":\"sensor01\",\"data\":{\"temp\":%.1f,\"hum\":%.1f}}",
temp, hum);
int code = http.POST((uint8_t*)body, strlen(body));
Serial.printf("push HTTP %d\n", code);
http.end();
}
几个要点:密钥用 X-Api-Key 请求头传,别拼在 URL 里,URL 容易被日志和代理记录;证书指纹要固定校验,一旦服务端换证书,指纹同步更新,否则连接会失败——这是特性不是 bug,说明校验真的在生效。
如果目标地址暂时只有 HTTP 入口,也可以用兼容写法,把参数放 query、正文放纯文本:
HTTPClient http;
http.begin("http://你的域名/api/push.php?api_key=你的APIKey&dataset=sensor01");
http.addHeader("Content-Type", "text/plain");
int code = http.POST("temp=26.5,hum=61"); // 老程序也能直接存一段文本
http.end();
这种写法胜在简单,适合老脚本快速接入。但从安全角度,只要条件允许,还是优先走 HTTPS + 请求头密钥。
扩展能力:读取队列、去重与私有化部署
数据推上去只是第一步,怎么消费同样重要。读取接口 POST /api/pull.php 是队列式消费:按写入顺序 FIFO 下发,取走即标记已读,不会重复下发,还支持分页和按数据名读取。这意味着你在设备端不用自己做「已处理」标记,云端帮你把这条状态管理了。
写入侧还有个实用设计:内容完全相同的数据只保留第一条。ESP32 因为网络抖动重发,或者传感器在稳定期反复上报同一个值,都不会把队列撑爆。系统会对写入内容做规范化并计算哈希,重复的直接丢弃。
再往上一层是部署形态。云递数据支持私有化部署到你自己的内网或公网服务器,技术栈是 Nginx + PHP + MySQL,数据完全自主可控。对数据敏感、或者要求内网闭环的场景,这条路径比公有云更合适。会员方面,新用户注册即赠送 3 天会员免费试用,正式会员月卡 19 元、年卡 99 元,采用卡密充值(YK 开头月卡、NK 开头年卡),多张可叠加、到期顺延;会员到期后数据保留不删除,仅暂停推送和读取,续费即恢复。邀请好友注册并开卡,推荐人还能获得赠送天数,每次续费都自动到账。
效果验证与常见坑
验证方案是否生效,最直接的办法是抓包对比。用同一台设备分别请求 HTTP 和 HTTPS 地址,在抓包工具里看:HTTP 请求的 JSON 明文一览无余,HTTPS 请求只能看到加密载荷。这一对比基本能说明链路加密有没有落地。
接口鉴权则用「错误密钥」验证:把 X-Api-Key 改成一个不存在的值,请求应该被拒绝;换成正确密钥,数据出现在控制台里。能通过这两步,方案的基本盘就稳了。
常见的坑有这么几个:一是图省事用 setInsecure(),等于白上 HTTPS,中间人照样能插进来;二是把密钥拼在 URL 里,日志、浏览器历史、代理缓存都可能留下痕迹;三是忘记处理证书轮换,服务端换证书后设备集体掉线;四是没做重试退避,网络抖动时疯狂重发,虽然云端会去重,但白白浪费流量和电量。
常见问题
ESP32 用 HTTPS 上传数据,中途会被抓包看到明文吗?
不会。只要走的是真正的 TLS 且证书校验开启,抓包工具看到的是加密后的字节流,看不到 JSON 内容。但如果代码里调用了 setInsecure() 跳过证书校验,攻击者可以伪造服务端,这时候「看着像 HTTPS,实际不安全」。所以关键不是协议名,而是校验有没有真的开。
把 APIKey 放在 ESP32 固件里,会不会被反编译泄露?
有风险。ESP32 的固件可以从 Flash 里读出来,字符串大概率能直接找到。缓解手段有三层:一是密钥只授予必要的数据名权限,泄露了损失可控;二是定期在控制台轮换密钥,旧密钥作废;三是对安全要求高的场景,走私有化部署,把接口收敛到内网,外部根本碰不到。
云递数据的接口是 HTTP 地址,那还算安全吗?
接口本身支持 HTTP/HTTPS 两种访问方式,安全性取决于你实际用哪种。文档里给的 http://你的域名/api/push.php 是格式示例,如果你的域名配了 TLS,把协议换成 https:// 就是加密链路。同时别忘了密钥仍然要鉴权——加密和鉴权是两件事,缺一不可。对安全敏感的场景,建议直接私有化部署到内网。
数据推送上去后,怎么保证不被重复消费?
读取接口是队列式设计:按写入顺序 FIFO 下发,取走即标记已读,不会重复下发。配合写入侧的内容哈希去重——内容完全相同的只保留第一条——即便设备因为重试重复推送,实际消费端也只会拿到一份有效数据。控制台里能看到每条数据的已读状态,方便核对。
ESP32 内存有限,跑 HTTPS 会不会很吃力?
TLS 握手确实比明文请求吃内存,握手阶段会占用较多 RAM,但 ESP32 的堆空间通常够用,关键是要复用连接、避免频繁新建 TLS 会话。实践建议是:不要每条数据都重新握手,能批量就批量,能长连接就长连接;如果数据量很大又对延迟不敏感,把上报周期拉长一点,比堆砌并发更划算。实在吃紧,先确认是不是同时开了 Wi-Fi、蓝牙和其他大内存任务,把资源让出来往往比换方案更有效。
开始使用
本文的代码示例都可以直接在云递数据上运行:注册后即可获得专属 APIKey,在 开发文档 中查看推送、读取接口的完整说明;需要长期使用可以在 会员中心 用卡密开通月卡或年卡;更多实战内容可以浏览技术博客的其他文章。