news 2026/9/8 8:27:14

ESP8266入门笔记:从选型到MQTT上云,一篇文章搞定

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP8266入门笔记:从选型到MQTT上云,一篇文章搞定

ESP8266 大概是这几年里我玩过性价比最离谱的 Wi-Fi 芯片之一。它把一颗 32 位处理器、完整的 802.11 b/g/n 协议栈和常用的 GPIO 全部塞进指甲盖大小的板子里,价格只要几块钱,社区资料还多到看不完。当年我靠它一口气做了远程插线板、室内温湿度上报和一台能跟着手机走的遥控小车,踩过的坑能写满一页纸。这篇 esp8266 学习笔记是入门系列的第一部分,重点解决三件事:选对开发板、搭好开发环境、跑通第一次联网,然后顺带把 MQTT 连接 OneNet 的完整思路讲清楚。看完这篇,你应该能独立把一个小数据点从板子上传到云平台,后面的项目也就能真正落地了。

1. 拿到 ESP8266 之后,先搞明白手上的是哪一块板子

1.1 我建议新手优先选 NodeMCU

很多教程会直接让你“买一块 NodeMCU”,但新手拿到手发现型号五花八门:ESP-01、ESP-12F、NodeMCU、WeMos D1 mini、LOLIN D1……第一次确实容易懵。

从学习角度说,我最推荐的是带 USB 口的 NodeMCU 或者 WeMos D1 mini 这类成品开发板。它们的核心差别在下面几点:

  • ESP-01:最便宜,只有两个 GPIO,体积很小,但没有 USB 口,必须外接一个 USB 转 TTL 模块才能烧录。串口通信和供电都要自己搭线,对新手不友好。
  • ESP-12F 模块:本身只是一个模组,要把 GPIO0、EN、RST、VCC、GND 这些引脚引到合适的地方,再接 USB-TTL 工具,才能当开发板用。
  • NodeMCU / WeMos D1 mini:板载 USB 口、串口芯片、3.3V 稳压电路、复位按键,有些还带了 Flash 按键,插上 USB 线就能直接烧录和调试。

也就是说,如果目标是快速验证 MCU 能力、跑通代码流程,NodeMCU 这类成品板是性价比和效率最平衡的选择。尤其是板载串口芯片,能少接错一半线,不夸张。

实际购买时注意区分 ESP-12E 和 ESP-12F。两者引脚基本兼容,F 比 E 多了 PCB 天线的调整,无线性能略有差异,但学习阶段不用太纠结。真正要关注的是 Flash 容量,尽量选 4MB 的版本,后面存固件、文件系统都要用到。

1.2 ESP-12F 原理图和启动引脚

等到你开始看原理图或者自己画板,就要认识 ESP-12F 必拉的几个引脚。很多人第一块 ESP8266 烧不进程序,就是因为这几个引脚状态不对。

引脚正常运行模式下载模式(烧录固件)
GPIO0高电平或悬空低电平
GPIO2高电平高电平
GPIO15低电平低电平
CH_PD / EN高电平高电平
RST高电平上电后自动复位

这里最核心的就是 GPIO0。芯片上电时如果检测到 GPIO0 是低电平,就会进入 UART 下载模式;如果 GPIO0 是高电平或者悬空,就正常跑应用固件。这也是为什么 NodeMCU 板子上会有 Flash 按键:按住 Flash 再按 Reset,其实就是把 GPIO0 拉低并重新上电,让板子进入可烧录状态。

自己画板用 ESP-12F 时,EN 脚必须接一个 10k 电阻上拉到 3.3V,RST 同样上拉,GPIO15 需要下拉到 GND。GPIO16 比较特殊,它内部没有上拉,如果要用到深度睡眠唤醒,必须自己接一个上拉电阻。

电源部分要格外注意。ESP8266 工作时瞬时电流经常冲到 300mA 以上,Wi-Fi 发射瞬间可能更高。如果用电荷泵或低压差线性稳压器给模块供电,建议在 VCC 和 GND 之间并一个 10uF 钽电容或 100uF 电解电容,避免电压跌落导致模块随机重启或丢包。很多“代码没问题但就是不稳定”的情况,根源都是供电不过关。

2. 开发环境搭建与固件烧录,别在这一步放弃

2.1 Arduino IDE 添加 ESP8266 板卡,五步搞定

我第一次给 ESP8266 写程序用的是 Arduino IDE,因为资料最多、上手最快。哪怕你之前只写过一点点 C 语言,也能在一个晚上跑通点灯和联网。具体步骤如下:

  1. 安装 Arduino IDE,建议使用 1.8.x 或 2.x 版本。
  2. 打开「文件」→「首选项」,在「附加开发板管理器网址」里填入:
    https://arduino.esp8266.com/stable/package_esp8266com_index.json
  3. 打开「工具」→「开发板」→「开发板管理器」,搜索esp8266,找到esp8266 by ESP8266 Community后点击安装。
  4. 等待下载完成后,在「工具」→「开发板」里选择NodeMCU 1.0 (ESP-12E Module)LOLIN(WEMOS) D1 mini (clone)
  5. 在「工具」里设置 Flash Size 为4M (3M SPIFFS),Upload Speed 可以先用115200,稳定之后再尝试921600

这里有个很容易踩的坑:安装完板卡支持包后,如果你用的是 NodeMCU,但开发板选错了型号,编译和上传可能能过,可串口打印或引脚定义会和实际不一致。先用板子上丝印写的型号去选,选不到就搜同芯片方案,大体上不会错。

如果开发板管理器下载非常慢,也可以手动去 GitHub 下载 esp8266/Arduino 的 release 包,解压到 Arduino 的hardware/esp8266com/esp8266目录下。这里就不展开细节,遇到再单独处理。

安装完开发板支持包后,还要确认串口驱动。NodeMCU 常见的是 CH340,WeMos D1 部分版本是 CP2102,Windows 10 以上一般能自动识别,识别不了就到官网下对应驱动。检查方法是在「设备管理器」里看有没有出现 COM 口,如果只有 USB 设备而没有 COM 口,大概率是驱动没装好。

2.2 flasher 工具和固件烧录步骤

有些场景下我们不写代码,需要直接烧现成的固件,比如 AT 固件或者 MicroPython。这里说的烧录不只是 Arduino IDE 的自动上传,而是更底层的 Flash 烧录。

常用的工具有三类:

  • 乐鑫官方 Flash Download Tools:Windows 图形化工具,支持组合选择多个 bin 文件。
  • esptool.py:Python 脚本,功能最底层,支持绝大多数 ESP 芯片,也是很多工具的底层核心。
  • NodeMCU PyFlasher:专门为 NodeMCU 设计的图形化烧录工具,适合烧现成 bin 文件。

以 AT 固件为例,解压官方固件包后,里面有多个 bin 文件和一个读我文档,里面有推荐的烧录地址。以常见 4MB Flash 版本为例,地址大致如下:

地址文件
0x00000boot.bin
0x01000user1.bin 或 user2.bin
0x3FC000blank.bin
0x3FE000blank.bin

实际操作流程:

  1. 用数据线把 NodeMCU 连到电脑,确认 COM 口号。
  2. 如果烧的是全新 Flash,最好先执行擦除(Erase Flash),避免残留旧分区数据导致启动异常。
  3. 在 Flash Download Tools 里选择 bin 文件和对应地址,注意勾选正确的 SPI SPEED 和 SPI MODE,一般默认 40MHz / QIO。
  4. 按住板上的 Flash 按键,再按一下 RST,松手后立即点击烧录。
  5. 烧录完成后,按一下 RST,重新进入正常运行模式。

这里的核心原理是:ESP8266 的 Flash 里其实分成 boot 段、应用段、文件系统和配置区。地址错位后,芯片能上电但就是跑不起来,串口打印全是乱码或重启循环。所以看到任何固件包,第一件事不是点下载,而是读一遍它自带的说明文件,确认地址表和 SPI 模式,这能省下大量排查时间。

2.3 为什么不推荐一上来就用 Mixly

热词里有 Mixly,我明白,图形化编程确实能把门槛压得很低,很多 ESP8266 教学场景也会用它。但我的观点是:如果你打算长期用 ESP8266 做东西,最好从 Arduino 代码起步,至少把引脚、函数、数据格式这三件事搞明白,再回到 Mixly 也不会吃亏。

Mixly 把底层封装得很干净,拖几个积木就能点灯、串口打印。但调试问题时,你面对的仍然是串口日志、JSON 数据、Wi-Fi 连接异常。如果一个错误是 Mini 代码里某个引脚编号不对,图形化界面反而不容易定位,因为你看到的是积木,底层生成的代码才是真正运行的东西。

当然,如果你是纯零基础、完全没接触过程序,用 Mixly 先建立“程序能控制硬件”的感觉也不是不行。我的建议是:最多用 Mixly 做一两个小实验,然后就转到 Arduino IDE。后面你遇到的每一个项目,几乎都能找到现成的 Arduino 库,这条路最宽。

3. 第一个实验:点灯、连 WiFi、解决丢包

3.1 点灯实验里最容易犯的两个错误

第一个实验不要搞太复杂,确认“烧录链路通、串口通、引脚能输出”就可以了。灯用板载 LED 就够,不需要外接。

void setup() { pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); } void loop() { digitalWrite(LED_BUILTIN, LOW); delay(500); digitalWrite(LED_BUILTIN, HIGH); delay(500); }

注意 NodeMCU 的板载 LED 一般接在 GPIO2 也就是 D4 上,且低电平点亮。所以代码里先写 LOW 能看到灯亮,这是正常的,别慌。

新手最容易犯的两个错误:

  1. 外接 LED 直接接 5V。ESP8266 的 GPIO 不是 5V 容忍引脚,直接接 5V 会烧引脚。外接 LED 一定串一个 220Ω 到 1kΩ 的限流电阻,供电用 3.3V。
  2. 选错了开发板型号导致编译出来的引脚映射和板子不一致。比如 NodeMCU 上丝印写的是 D4,但在 Arduino 代码里写数字引脚号 4,对应的是 GPIO4,不是 D4。NodeMCU 的 D4 实际是 GPIO2,即数字引脚 2。

搞定点灯后,用串口监视器能看到115200波特率下的输出,说明串口通信也正常。这一步是整个学习过程的“体检”,最好一次跑通,后面才有信心继续。

3.2 连接 WiFi 并打印 IP 地址

点灯跑通后,下一个目标就是让板子连上家里的路由器。

#include <ESP8266WiFi.h> const char* ssid = "你的WiFi名"; const char* password = "你的WiFi密码"; void setup() { Serial.begin(115200); 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() { }

这段代码看起来很简单,但里面有很关键的一点:WiFi.begin是非阻塞的,它发起连接后马上返回,真正的连接过程在后台进行。如果循环里不加等待,打印出来的可能一直是没有获取到 IP 的状态。我这里用了while轮询WiFi.status(),等你看到连接成功,才说明板子已经拿到 DHCP 分配的 IP。

SSID 里有中文的话,不同编译环境可能出问题,建议先用手机开热点测试,热点名用纯英文,等主线跑通再换家里路由器。这样可以排除很多编码和加密协议兼容性的干扰项。

3.3 丢包最简单三个步骤

热词里的“esp8266丢包最简单三个步骤”其实很有价值。Wi-Fi 模块在项目里被人说得最多的就是“不稳定”“丢包”,但大多数情况下不是芯片垃圾,而是使用环境和方法有问题。

我实测下来,最快定位丢包问题的三步是:

  1. 先看供电。用手机充电头或电脑 USB 口供电时,如果线材太长太细、适配器电流不到 1A,Wi-Fi 一收发就会掉电重启。换成 5V 1A 以上的独立适配器,或在板载 3V3 引脚附近并联一个大电容,很多丢包立刻消失。
  2. 再看距离。ESP8266 是 2.4GHz Wi-Fi,穿墙能力一般。如果板子放在金属外壳里,或者离路由器隔了两三堵墙,丢包率高是正常的。近距离测试,比如 2 米范围,如果丢包率显著下降,说明是环境问题。
  3. 最后看路由器的无线设置。路由器开了双频合一功能时,2.4GHz 和 5GHz 共用 SSID,ESP8266 只会用 2.4GHz,但有可能在漫游切换中频繁重连。最好在路由器后台单独开一个 2.4GHz 的 SSID,信道固定成 1、6、11 当中的一个,带宽设置为 20MHz。

这三点排查完,绝大多数“连不上”“疯狂丢包”的问题都能解决。只要不是芯片本身坏了,掉线频率会降到可接受范围。真正的工业级稳定性还需要做断线重连、看门狗和消息确认,那是后话。

4. 上云实操:用 MQTT 把 ESP8266 接上 OneNet

4.1 MQTT 连接里最容易被忽略的三个参数

把 ESP8266 接到云平台,最常用的协议就是 MQTT。它基于发布订阅模型,客户端往主题里发消息,订阅了该主题的客户端就会收到消息。因为消息头很小、对嵌入式设备友好,几乎成了物联网设备的标配。

OneNet 是中国移动的物联网平台,也是很多人做课程设计、毕业设计时的首选。它支持 MQTT 接入,但和公共测试 broker 不一样,OneNet 对设备身份认证和主题格式有自己的一套要求。连接时最容易被忽略的是下面三个参数:

  • 服务端地址和端口。OneNet 的 MQTT 接入地址以平台文档为准,公共地址常见的是mqtt.heclouds.com,端口常用1883。早期版本也有用183.230.40.396002的。写代码时别死记 IP,先去 OneNet 控制台或文档页看当前最新的接入信息。
  • 认证参数。OneNet 的 MQTT 登录名和密码,通常不是简单的设备名加密码,而是产品 ID、设备名称、设备密钥这三者组合。不同版本后台展示的名称都不一样,有的叫“设备ID”,有的叫“产品ID”,有的叫“apiKey”。我的建议是直接把控制台显示的每一段字符串复制到代码常量里,逐个检查是否多复制了空格或换行。
  • 主题格式。上报数据和接收命令,用的是两套不同主题。新版 OneNet 推荐的是$sys/{产品ID}/{设备名称}/thing/property/post这类主题,老版本则是$dp这种。主题写错了,连接成功但发不了数据,或者收不到下行命令。

理解这三组参数后,再去看官方示例代码就不会懵了。很多人在网上找了一份旧教程,对着老 IP 和老主题写代码,结果连不上或发布失败,并不一定是代码问题,而是平台版本已经变了。

4.2 用 PubSubClient 完成上报和下发

我建议用PubSubClient这个库,它很小,内部依赖也不复杂。先在 Arduino IDE 的库管理器里搜索PubSubClient并安装,然后编写下面的基础模板。

#include <ESP8266WiFi.h> #include <PubSubClient.h> WiFiClient espClient; PubSubClient client(espClient); const char* mqtt_server = "mqtt.heclouds.com"; const char* mqtt_user = "产品ID或设备名"; const char* mqtt_pass = "设备密钥"; void callback(char* topic, byte* payload, unsigned int length) { Serial.print("收到命令 ["); Serial.print(topic); Serial.print("] "); for (unsigned int i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println(); } void reconnect() { while (!client.connected()) { Serial.print("正在连接OneNet..."); if (client.connect("设备名称", mqtt_user, mqtt_pass)) { Serial.println("连接成功"); client.subscribe("$sys/产品ID/设备名称/thing/property/post"); } else { Serial.print("连接失败,错误码:"); Serial.print(client.state()); delay(5000); } } } void setup() { Serial.begin(115200); WiFi.begin("你的WiFi名", "你的WiFi密码"); while (WiFi.status() != WL_CONNECTED) { delay(500); } client.setServer(mqtt_server, 1883); client.setCallback(callback); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); }

发布数据时,直接调用publish。OneNet 新版属性上报一般用 JSON 格式:

String payload = "{\"temperature\":{\"value\":26.5}}"; client.publish("$sys/产品ID/设备名称/thing/property/post", payload.c_str());

这里最容易踩的坑是client.loop()没有在循环里被持续调用。MQTT 客户端要在高频循环里读取、处理网络事件,如果你写一个delay(10000)睡得很久,就会出现“连上后收不到任何消息”的现象。哪怕要延时,也应该用非阻塞方式,比如记录上次发送时间,让loop()一直能跑。

4.3 ESP8266 连接 OneNet 失败速查表

我在折腾这个环节时,前后遇到过的连接失败问题基本可以整理成一张速查表:

现象最常见原因处理办法
连接后立刻断开,错误码 -2认证参数不对,客户端 ID 或用户名密码不匹配重新核对产品ID、设备名称、设备密钥,注意大小写
一直连接超时服务器地址写错、端口不对、当前网络无法访问该平台在电脑上用 MQTT 客户端工具先测一遍服务器连通性
连接成功,但云端看不到数据上报发布主题写错,或数据格式不符合平台要求对照文档核对主题和 JSON,格式里字段名要和物模型一致
订阅命令后收不到下发消息设备没有保持在线,或主题是旧版格式确认loop()一直在跑,订阅主题和平台一致
WiFi 频繁断开,MQTT 跟着掉供电不稳、路由器信道拥挤先按 3.3 节解决供电和无线环境
使用 8883 端口时握手失败需要 TLS 但固件没启用 WiFiClientSecure开发阶段先改用 1883 明文通信,生产再考虑加密

排查顺序也要固定下来:供电 → 串口日志 → WiFi 连接 → MQTT 连接 → 数据格式。千万别一上来就怀疑 MQTT 代码,很多时候是 WiFi 根本没连上或者网络到平台的链路不通。

5. 把学到的东西用到真实项目里

5.1 智能浇花系统的硬件选型思路

搜索热词里出现“基于esp8266的智能浇花系统设计毕业论文”,说明很多人想把 ESP8266 用在学习和毕设上。这个方向确实挺适合入门,因为硬件简单、逻辑清晰,还能串起完整的数据链路。

基本组成是一块 NodeMCU、土壤湿度传感器、继电器或小水泵、电源,以及可选的水位传感器。土壤湿度传感器有两种常见类型:

  • 电阻式:便宜,但靠两片裸露金属探针测土壤电阻,长时间通电容易电解腐蚀。
  • 电容式:贵一点,但寿命长很多,对精确度要求不高的浇花系统更推荐电容式。

如果你的板子是 NodeMCU,ADC 只有一个引脚,输入电压范围也只有 0 到 1V 左右。土壤湿度传感器模块一般输出 0 到 3.3V 或 0 到 5V,需要先看模块输出电压范围,再用电阻分压或运放调整到 ADC 允许的范围。模拟量并不是越高越好,传感器放在干燥环境中的值要记录成“阈值基准”,再通过测试确定“该浇水了”的触发点。

控制水泵时,不要直接拿 GPIO 去驱动电机。用继电器模块或者电机驱动板,让大电流走独立回路,单片机只控制开关信号。最好加一个光耦或者用继电器模块自带的隔离电路,避免电机启动瞬间的反电动势打坏板子。整体逻辑就是定时读土壤湿度,如果低于阈值就开泵 5 秒,同时把湿度和水泵状态通过 MQTT 上报到 OneNet,云端可以再做阈值告警或远程手动控制。

5.2 小车方案里的丢包和供电问题

“esp8266小车”也是热门玩法,方向是做一个用手机或网页遥控的小车。硬件上,ESP8266 负责接收控制指令、转发给电机驱动,比如 L298N 或 DRV8833。

小车项目里有两个比较棘手的问题。第一个是供电。电机启动瞬间电流很大,如果电池直接给 ESP8266 和电机共用一个电源,电压会被瞬间拉低,ESP8266 一重启,Wi-Fi 重连要好几秒,就出现“控制器一到就掉线”的假死状态。我的做法是电池组先给电机驱动模块供电,再通过驱动模块上的 5V/3.3V 输出引脚给 NodeMCU 供电,或者干脆用单独的小功率电池给 NodeMCU 供电,把两个电源的地线连在一起作参考地。

第二个问题是丢包。小车移动过程中,Wi-Fi 信号会不断变化,而且 ESP8266 天线方位一变,信号强度波动很大。最简单的改善办法是先确保供电稳定,然后把控制指令设计得短小精悍,比如一个字节代表前进、后退、停止,而不是传输一大串 JSON。这样可以降低出错概率和解析负担。如果要用 MQTT 给小车下发指令,开发阶段可以用 QoS 0,因为实时控制场景偶尔丢一帧可以接受,最重要的是保证loop()高频执行。

其他扩展方向还有很多,比如智能家居开关、空气质量监测站、设备状态远程上报等等。核心能力都离不开“采集传感器数据 → 联网 → 上云 → 接收控制指令”这条链路。把 ESP8266 跑通之后,后续再换 ESP32、用蓝牙或 LoRa 也只是接口换了个玩法。


我最初折腾 ESP8266 的时候,最大的体会是:相比写代码,更难的是搞定供电、引脚和协议匹配这些“看起来很小”的事。遇到问题别急着怀疑板子坏了,先从串口日志和基础环境入手排查,十次里有九次是这种低级问题。如果你手头刚好有一块板子,就按顺序跑一遍点灯、连 WiFi、上报数据这三个实验,卡在哪个环节再回头翻对应章节。另说一个省钱建议:买模组或开发板时顺手买三片同型号的,一是运费摊薄,二是烧坏了不心疼,实测下来 ESP8266 这体质真不容易烧,反而是接线错误更容易让它“假死”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 8:27:12

服务器PCIe卡更换全流程:从识别规划到验证的最佳实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 8:27:10

纯HTML+CSS+JS自适应固定悬浮导航条网站源码模板

简介&#xff1a;这是一款面向网页设计初学者的自适应固定悬浮导航条源码模板&#xff0c;基于HTML5与CSS、JavaScript实现&#xff0c;解决长页面导航始终可见、滚动后快速访问主要链接的常见需求。包内共72个文件&#xff0c;以js脚本、css样式表、html页面和jpg图片为主&…

作者头像 李华
网站建设 2026/9/8 8:26:29

软件测试面试高频问题全解析:从技术基础到项目经验实战指南

软件测试面试必问的几个问题 每年都有大批人涌进软件测试这个行业&#xff0c;也有不少人在面试门口折戟沉沙。我做了这么多年测试&#xff0c;也当过面试官&#xff0c;见过了太多候选人——有的技术底子不错&#xff0c;却在几个基础问题上翻了车&#xff1b;有的履历平平&a…

作者头像 李华
网站建设 2026/9/8 8:26:09

Godot项目工程化:命名规范、类型检查与性能优化实践

这次我们来看一个不是工具、也不是插件的开发效率主题&#xff1a;Godot 项目里的脚本命名、类型检查与性能优化。很多人在 Godot 里能很快跑出 Demo&#xff0c;但项目一旦膨胀到几十个场景、上百个脚本&#xff0c;改一个变量名要在全局搜索上半天&#xff0c;帧率掉下去也不…

作者头像 李华
网站建设 2026/9/8 8:25:26

Qwen3.8-27B本地部署实战:MoE架构、显存评估与排错指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 8:24:35

FT230XS USB转UART驱动安装与常见问题排查全攻略

简介&#xff1a;面向需要调试或量产FT230XS芯片设备的开发者和硬件工程师&#xff0c;这套驱动包用于解决FT230XS芯片在Windows系统下的USB转串口通信问题&#xff0c;提供设备枚举、数据传输、中断处理等底层支持&#xff0c;并支持波特率、数据位、停止位等参数配置。压缩包…

作者头像 李华