ESP32-S3 SoftAP配网全链路实战:从零搭建到工业级优化
在智能家居设备日益复杂的今天,确保用户能“开箱即连”已成为产品成败的关键一环。想象一下:你刚买了一台智能灯泡,说明书上写着“首次使用需配网”,但家里Wi-Fi密码记不清、路由器藏在柜子里……这时候如果设备自己能变成一个热点,让你用手机直接连上去填个表单就搞定——是不是瞬间体验感拉满?💡
这正是 ESP32-S3 的 SoftAP(软件接入点)模式所擅长的场景。作为乐鑫科技推出的高性能双模SoC芯片,它不仅集成了Wi-Fi和蓝牙5.0,还具备强大的处理能力与丰富的外设接口,成为物联网终端中的“全能选手”。而SoftAP功能,则是其在无预知网络环境下实现自主配网的核心技术路径。
本文将带你深入这场“嵌入式配网革命”的心脏地带,不讲空话套话,只聚焦真实开发中踩过的坑、调过的参数、跑通的流程。我们将从最基础的环境搭建开始,一路打通事件驱动、HTTP服务、安全控制、状态切换直至用户体验优化的完整闭环,最终呈现一套可直接用于量产项目的工业级方案。
准备好了吗?我们这就出发!🚀
开发环境搭建:别让第一步绊住你的节奏
很多开发者第一次接触ESP-IDF时,总会被那一长串命令搞得头晕目眩:“先克隆仓库?再执行install.sh?然后export?还要装Python依赖?”其实整个过程就像搭积木,只要步骤清晰,几分钟就能搞定。
选对版本,少走弯路
首先明确一点:
稳定压倒一切
。虽然GitHub上有最新的master分支,但对于生产项目,强烈建议锁定某个发布版,比如
v5.1
或
v4.4
。这些版本经过充分测试,文档齐全,社区支持也更完善。
git clone -b v5.1 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh
上面这三行命令适用于Linux/macOS用户。Windows同学也不用慌,官方提供了图形化安装包
esp-idf-setup.exe
,点几下鼠标自动完成所有配置,连环境变量都不用手动设置 👍。
📌 小贴士:如果你用的是 VS Code,配合 Espressif IDF 插件简直是神仙组合!不仅能一键引导安装流程,还能可视化管理工程、烧录固件、查看串口日志,效率翻倍!
安装完成后记得激活环境:
. ./export.sh
验证是否成功?简单,随便哪个目录敲一句:
idf.py --version
看到输出类似
ESP-IDF v5.1.2
就说明OK了。如果提示“command not found”,八成是
. ./export.sh
没生效,可以把它加到
.bashrc
或
.zshrc
里永久生效。
| 操作系统 | 推荐方式 | 是否需要手动配置 |
|---|---|---|
| Windows | GUI安装程序(esp-idf-setup.exe) | 否 ✅ |
| Linux |
终端执行
./install.sh
| 否 ✅ |
| macOS | 同上 | 否 ✅ |
创建第一个工程:别复制错了!
接下来我们创建一个名为
softap_demo
的项目。最省事的方法是从官方示例中拷贝模板:
cp -r $IDF_PATH/examples/wifi/softap softap_demo
cd softap_demo
进目录后你会看到这样的结构:
softap_demo/
├── main/
│ ├── main.c
│ └── CMakeLists.txt
├── CMakeLists.txt
├── sdkconfig.defaults
└── partitions.csv
几个关键文件解释一下:
-
main/main.c:主程序入口; -
sdkconfig.defaults:默认配置项,比如SSID、密码等; -
partitions.csv:Flash分区布局,决定代码、数据、NVS各占多少空间; -
两个
CMakeLists.txt:构建规则,顶层定义组件,子层指定源码。
然后设定目标芯片为 ESP32-S3:
idf.py set-target esp32s3
这条命令会触发下载对应架构的工具链(xtensa-esp32s3-elf-gcc),并更新头文件路径。之后就可以打开配置界面:
idf.py menuconfig
在这里你可以修改Wi-Fi名称、日志等级、任务堆栈大小等等。初次使用保持默认即可。
最后一步,编译 + 烧录 + 启动串口监视器一条龙:
idf.py build flash monitor
如果一切顺利,你应该能在串口看到类似下面的日志:
I (321) wifi: mode : softAP (xx:xx:xx:xx:xx:xx)
I (322) wifi: Total power save buffer number: 16
I (322) wifi: Init max length of beacon: 752/752
I (328) wifi: Start wifi timer successfully
I (528) SoftAP_Main: WiFi initialized successfully!
🎉 恭喜!你的ESP32-S3已经成功开启SoftAP模式,正在广播信号!
SoftAP核心机制解析:不只是开个热点那么简单
你以为SoftAP就是让ESP32-S3变成一个Wi-Fi热点?Too young too simple 😏 实际上,背后有一整套精密协作的模块在运行:TCP/IP协议栈、DHCP服务器、事件循环、NVS存储……任何一个环节出问题,都会导致配网失败。
让我们从代码层面拆解这个过程。
初始化顺序不能乱!
在
app_main()
函数中,初始化顺序至关重要。我见过太多人因为调换了几行代码而导致“Wi-Fi启动失败”或“内存溢出”。
正确的初始化序列如下:
void app_main(void)
{
// 1. 初始化NVS —— 用来保存Wi-Fi凭证
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NEW_VERSION_DETECTED) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 2. 初始化TCP/IP协议栈
ESP_ERROR_CHECK(esp_netif_init());
// 3. 创建事件循环 —— 这是异步通信的大脑
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 4. 创建AP模式的网络接口,并自动启用DHCP服务器
esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
assert(ap_netif != NULL);
// 5. 初始化Wi-Fi驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
}
逐条说明:
- NVS初始化 :非易失性存储(NVS)用于持久化保存Wi-Fi账号密码。必须最先初始化,否则后续写入会失败。
- esp_netif_init() :这是ESP-IDF中网络抽象层的基础,没有它,你就没法创建“虚拟网卡”。
- 事件循环 :相当于一个消息总线,所有Wi-Fi状态变化(如客户端连接、断开)都通过它通知应用层。
- esp_netif_create_default_wifi_ap() :创建一个预设的AP网络接口,内部已绑定DHCP服务,客户端连上来就能自动获得IP。
- Wi-Fi驱动初始化 :最后才轮到Wi-Fi本身登场,传入默认配置即可。
⚠️ 常见错误:有人把
esp_wifi_init()放在最前面,结果报错ESP_ERR_WIFI_NOT_INIT。记住一句话: 资源要先准备好,设备才能启动 。
配置SoftAP参数:细节决定成败
接下来设置热点的具体参数:
wifi_config_t ap_config = {
.ap = {
.ssid = "ESP32_SoftAP",
.ssid_len = strlen("ESP32_SoftAP"),
.channel = 6,
.authmode = WIFI_AUTH_WPA2_PSK,
.password = "12345678",
.max_connection = 4,
.pmf_cfg = {.required = false},
},
};
字段详解👇:
| 字段 | 说明 |
|---|---|
.ssid
| 热点名称,最长31字符,别用中文!某些手机识别不了 |
.ssid_len
| 可设为0由系统自动计算,但显式指定更稳妥 |
.channel
| 建议选6或11信道,避开家用路由器常用1/6/11之外的干扰源 |
.authmode
| 认证模式,WPA2-Personal最常用,安全性够用 |
.password
| 至少8位!少于8位会直接拒绝启动 |
.max_connection
| 最大允许连接数,默认4,最大6,别贪多 |
🔥 特别提醒:
WIFI_AUTH_OPEN
虽然方便(无需密码),但在公共场合极易被蹭连甚至攻击,除非特殊需求否则不要用!
设置完后按顺序应用:
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); // 先设模式
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config)); // 再写配置
ESP_ERROR_CHECK(esp_wifi_start()); // 最后启动
顺序不能颠倒!否则会报
ESP_ERR_WIFI_MODE
错误。
自定义DHCP地址池:给客户端更多选择
默认情况下,ESP32-S3分配的IP范围是
192.168.4.100 ~ 192.168.4.104
,总共只有5个地址。如果你希望支持更多设备临时连接调试,或者想换个子网避免冲突,完全可以自定义。
#include "esp_netif_dhcp.h"
// 获取AP网络接口
esp_netif_t *ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
// 先停掉DHCP服务
esp_netif_dhcps_stop(ap_netif);
// 设置新的IP信息
esp_netif_ip_info_t ip_info;
memset(&ip_info, 0, sizeof(ip_info));
IP4_ADDR(&ip_info.ip, 192, 168, 10, 1);
IP4_ADDR(&ip_info.gw, 192, 168, 10, 1);
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
esp_netif_set_ip_info(ap_netif, &ip_info);
// 配置新的地址池(起始: 192.168.10.100,共10个)
esp_netif_ip_range_t range = {
.base_addr = IP4_INIT_ADDR_T(IP4_ADDR(192, 168, 10, 100)),
.count = 10
};
esp_netif_dhcps_set_addr_range(ap_netif, &range);
// 重新启动DHCP
esp_netif_dhcps_start(ap_netif);
这样,任何连接上来的设备都会拿到
192.168.10.x
的地址,干净清爽,还不容易跟家里的主路由撞车。
| 配置项 | 默认值 | 建议值 |
|---|---|---|
| AP IP地址 | 192.168.4.1 | 192.168.10.1 |
| DHCP起始 | .100 | .100 |
| 地址数量 | 5 | 8~10 |
日志系统:你唯一的“眼睛”
在没有屏幕、没有键盘的嵌入式世界里,日志是你了解设备状态的唯一途径。ESP-IDF 提供了非常成熟的日志框架,合理使用能让排错效率提升十倍不止。
使用ESP_LOG系列宏输出信息
static const char *TAG = "SOFTAP_MAIN";
ESP_LOGI(TAG, "Starting WiFi SoftAP mode...");
ESP_LOGI(TAG, "SSID: %s, Channel: %d", ap_config.ap.ssid, ap_config.ap.channel);
if (strlen((char *)ap_config.ap.password) < 8) {
ESP_LOGE(TAG, "Password too short! Minimum 8 characters required.");
}
输出效果:
I (321) SOFTAP_MAIN: Starting WiFi SoftAP mode...
I (321) SOFTAP_MAIN: SSID: ESP32_SoftAP, Channel: 6
五种等级任你选择:
| 宏 | 含义 | 使用场景 |
|---|---|---|
ESP_LOGE
| 错误 | 必须干预的问题 |
ESP_LOGW
| 警告 | 潜在风险 |
ESP_LOGI
| 信息 | 正常运行日志(默认) |
ESP_LOGD
| 调试 | 开发阶段详细跟踪 |
ESP_LOGV
| 详细 | 极细粒度追踪,慎用 |
可以通过
menuconfig
动态调节日志级别:
idf.py menuconfig
路径:
Component config → Log output → Default log verbosity
建议开发阶段设为
Debug
,量产前降为
Info
,减少串口流量和CPU负担。
监听关键事件:让程序“活”起来
仅仅打印静态日志还不够,我们要知道“谁连上了”、“谁断开了”、“AP有没有真正启动”。
注册事件监听器:
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) {
ESP_LOGI(TAG, "✅ SoftAP started successfully!");
} else if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* conn = (wifi_event_ap_staconnected_t*)event_data;
ESP_LOGI(TAG, "📱 Station connected, MAC: " MACSTR ", AID=%d",
MAC2STR(conn->mac), conn->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* disc = (wifi_event_ap_stadisconnected_t*)event_data;
ESP_LOGI(TAG, "📴 Station disconnected, MAC: " MACSTR ", reason=%d",
MAC2STR(disc->mac), disc->reason);
}
}
// 注册监听器
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
典型输出:
I (456) SOFTAP_MAIN: ✅ SoftAP started successfully!
I (1234) SOFTAP_MAIN: 📱 Station connected, MAC: a0:b1:c2:d3:e4:f5, AID=1
I (5678) SOFTAP_MAIN: 📴 Station disconnected, MAC: a0:b1:c2:d3:e4:f5, reason=8
其中
reason=8
表示客户端主动断开(比如浏览器关闭)。其他常见原因还有超时、认证失败等,可用于诊断连接稳定性。
常见错误码速查表
遇到问题别慌,先看日志。以下是高频错误码对照表:
| 错误码(十六进制) | 宏定义 | 原因 | 解决方法 |
|---|---|---|---|
0x103
|
ESP_ERR_WIFI_NOT_INIT
| Wi-Fi未初始化 |
检查
esp_wifi_init()
是否调用
|
0x104
|
ESP_ERR_WIFI_IF
| 接口不存在 |
确保
esp_netif_create_default_wifi_ap()
成功
|
0x106
|
ESP_ERR_WIFI_MODE
| 模式不匹配 |
检查
esp_wifi_set_mode()
参数
|
0x107
|
ESP_ERR_WIFI_PASSWORD
| 密码太短 | WPA2至少8位 |
0x3003
|
ESP_FAIL
| 一般性失败 | 查看上下文日志定位具体步骤 |
例如出现:
E (123) wifi: wifi_new:2080 : call esp_wifi_init first!
说明你在没调
esp_wifi_init()
的情况下就想设配置,典型的调用顺序错误。
构建Web配网界面:让用户轻松填个表单
现在ESP32-S3已经是一个热点了,但怎么让用户输入目标Wi-Fi的账号密码呢?答案是: 内置一个轻量级HTTP服务器 。
ESP-IDF 提供了
esp_http_server
组件,专为资源受限设备设计,完全可以在ESP32-S3上流畅运行。
启动HTTP服务器
首先在
CMakeLists.txt
中引入组件:
REQUIRES httpd
然后初始化服务器:
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.uri_match_fn = httpd_uri_match_wildcard; // 支持通配符匹配
esp_err_t err = httpd_start(&server, &config);
if (err != ESP_OK) {
ESP_LOGE("HTTPD", "❌ Error starting server: %s", esp_err_to_name(err));
return;
}
默认监听80端口,最大连接数7个,足够应付大多数场景。
| 配置项 | 默认值 | 建议 |
|---|---|---|
| port | 80 | 若占用可改8080 |
| max_open_sockets | 7 | 根据并发调整 |
| recv_wait_timeout | 5秒 | 控制请求超时 |
设计HTML页面:极简主义至上
由于Flash空间有限,页面必须尽可能小。推荐做法是将HTML压缩后嵌入固件。
<!DOCTYPE html>
<html>
<head>
<title>Configure WiFi</title>
<meta name="viewport" content="width=device-width">
</head>
<body>
<h2>🔌 Connect to Your Network</h2>
<form action="/connect" method="POST">
<label>SSID:</label><br>
<input type="text" name="ssid" required><br><br>
<label>Password:</label><br>
<input type="password" name="pass" minlength="8"><br><br>
<button type="submit">🚀 Connect</button>
</form>
</body>
</html>
保存为
index.html
,然后用gzip压缩:
gzip -k index.html
生成
index.html.gz
,再用工具转成C数组(可用
file-to-c
工具或在线转换器),最后声明外部符号:
extern const uint8_t index_html_gz_start[] asm("_binary_index_html_gz_start");
extern const uint8_t index_html_gz_end[] asm("_binary_index_html_gz_end");
编写处理器返回页面:
static esp_err_t serve_home(httpd_req_t *req) {
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
httpd_resp_send(req, (const char*)index_html_gz_start,
index_html_gz_end - index_html_gz_start);
return ESP_OK;
}
注册URI路由:
httpd_uri_t home_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = serve_home,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &home_uri);
现在手机连上
ESP32_SoftAP
后访问
http://192.168.4.1
,就能看到配网页面啦!
处理POST请求:提取用户输入
当用户点击“Connect”按钮,浏览器会发送POST请求到
/connect
。我们需要解析表单数据。
static esp_err_t handle_connect_post(httpd_req_t *req) {
char ssid[32] = {0}, pass[64] = {0};
size_t len = req->content_len;
char *buf = malloc(len + 1);
if (!buf) {
httpd_resp_send_500(req);
return ESP_FAIL;
}
int read_len = httpd_req_recv(req, buf, len);
if (read_len <= 0) {
free(buf);
httpd_resp_send_400(req);
return ESP_FAIL;
}
buf[read_len] = '\0';
parse_query_string(buf, ssid, pass); // 自定义解析函数
free(buf);
save_credentials(ssid, pass); // 存入NVS
start_sta_connection(); // 触发STA连接
httpd_resp_set_status(req, "303 See Other");
httpd_resp_set_hdr(req, "Location", "/success");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
关键点:
-
httpd_req_recv()接收原始POST体; -
parse_query_string()是你自己写的解析函数,处理ssid=MyWiFi&pass=12345678这种格式; - 成功后返回303重定向,跳转到成功页。
| HTTP状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 成功 | 返回网页内容 |
| 303 See Other | 重定向 | 提交后跳转 |
| 400 Bad Request | 客户端错误 | 数据缺失 |
| 500 Internal Server Error | 服务器异常 | 内存不足 |
安全与资源管理:别让设备被“撑死”
SoftAP模式虽好,但也容易被滥用。比如有人写个脚本疯狂连接又断开,很快就会耗尽内存。因此必须做好防护。
限制最大连接数
wifi_config_t ap_config = {
.ap = {
.max_connection = 2, // 最多2台设备
},
};
结合事件回调统计:
static uint8_t client_count = 0;
static void on_sta_connected(...) {
client_count++;
if (client_count > MAX_CLIENTS) {
esp_wifi_deauth_sta(conn->aid); // 主动踢出
}
}
MAC地址过滤(可选)
可通过白名单机制只允许特定设备访问:
static wifi_mac_filter_t mac_filter = {
.num = 1,
.mac_list = {{0x10, 0x23, 0x45, 0x67, 0x89, 0xab}}
};
esp_wifi_set_mac_filter(WIFI_IF_AP, &mac_filter);
⚠️ 注意:需在
menuconfig
中启用
CONFIG_WIFI_MAC_FILTER=y
。
动态调节发射功率
电池供电设备尤其要注意功耗。可以根据阶段动态调低TX功率:
// 初始发现期:高功率
esp_wifi_set_max_tx_power(78); // ~19.5dBm
// 用户连接后:降为中等
esp_wifi_set_max_tx_power(40); // ~10dBm
// 配网完成:关闭或最低
esp_wifi_set_max_tx_power(20);
节能效果显著,实测电流可从180mA降至80mA左右。
切换至Station模式:迈向真正的联网
用户提交Wi-Fi信息后,设备就要关闭SoftAP,切换到Station模式去连接真正的路由器了。这个过程看似简单,实则暗藏陷阱。
清理资源:防止“僵尸连接”
很多人直接调
esp_wifi_stop()
就以为万事大吉,其实不然。必须依次清理:
void cleanup_softap_resources(httpd_handle_t server) {
if (server) esp_http_server_stop(server); // 停HTTP
esp_wifi_disconnect(); // 断开所有客户端
esp_wifi_stop(); // 停Wi-Fi
esp_netif_destroy(ap_netif); // 销毁netif
}
否则可能引发内存泄漏或下次启动失败。
加载凭证并连接
从NVS读取SSID和密码:
nvs_open("storage", NVS_READONLY, &handle);
nvs_get_str(handle, "wifi_ssid", ssid, &len);
nvs_get_str(handle, "wifi_pswd", pass, &len);
nvs_close(handle);
wifi_config_t cfg = {.sta = {.ssid = "", .password = ""}};
strncpy((char*)cfg.sta.ssid, ssid, 32);
strncpy((char*)cfg.sta.password, pass, 64);
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(&cfg);
esp_wifi_start();
esp_wifi_connect();
监听连接结果
注册事件监听器判断成败:
if (event_id == IP_EVENT_STA_GOT_IP) {
ESP_LOGI("NET", "🎉 Got IP: " IPSTR, IP2STR(&got_ip->ip_info.ip));
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI("NET", "💔 Connection failed, retrying...");
esp_wifi_connect();
}
加上超时机制:
EventBits_t bits = xEventGroupWaitBits(..., pdMS_TO_TICKS(30000));
if (bits & WIFI_CONNECTED_BIT) {
update_led_status(true); // 绿灯亮
} else {
enter_fallback_softap_mode(); // 回退重试
}
工业级优化:让你的产品脱颖而出
做到上面这些,你的设备已经能用了。但要卖得好?还得卷体验!
🔐 HTTPS加密传输
防止Wi-Fi密码被嗅探,可用mbedTLS实现HTTPS:
httpd_ssl_config_t config = HTTPD_SSL_CONFIG_DEFAULT();
config.cacert_pem = server_cert;
config.prvtkey_pem = server_key;
httpd_ssl_start(&server, &config);
代价是增加约20KB RAM占用,首次握手慢1秒左右,适合高安全需求产品。
🧩 二维码配网
生成包含Wi-Fi信息的二维码贴在设备上,用户扫码即可一键连接:
import qrcode
qr_data = f"WIFI:T:WPA;S:ESP32_SoftAP;P:12345678;;"
img = qrcode.make(qr_data)
img.save("softap_qr.png")
小米、涂鸦等生态广泛采用此方案,极大降低用户操作门槛。
⏳ 自动重试 + 低功耗恢复
若连续失败3次,进入轻度休眠每30秒唤醒一次尝试重启SoftAP:
void retry_softap_mechanism() {
static uint8_t fail_count = 0;
if (++fail_count >= 3) {
esp_sleep_enable_timer_wakeup(30 * 1000000);
esp_light_sleep_start();
start_softap_mode();
fail_count = 0;
}
}
既省电又能自动恢复,特别适合无人值守设备。
🔄 OTA远程升级
联网成功后立即检查云端是否有新固件:
esp_http_client_config_t ota_cfg = {
.url = "https://firmware.example.com/device.bin"
};
esp_https_ota(&ota_cfg);
实现“配网即更新”,出厂固件有bug也能远程修复。
性能测试与指标分析
上线前务必做一轮完整测试:
| 指标 | 合格标准 |
|---|---|
| 启动时间 | ≤ 3秒 |
| 页面加载 | ≤ 1.5秒 |
| POST处理 | ≤ 800ms |
| 最大连接数 | ≥ 4台 |
| DHCP成功率 | ≥ 98% |
| 信号覆盖 | ≥ 20米 |
| 内存峰值 | ≤ 280KB |
| 功耗(TX) | ≤ 180mA @3.3V |
多客户端压力测试代码:
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
client_count++;
ESP_LOGW(TAG, "当前连接总数: %d", client_count);
if (client_count > 4) {
esp_wifi_deauth_sta(conn->aid);
}
}
这种高度集成的设计思路,正引领着智能音频设备向更可靠、更高效的方向演进。🌟
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
1057

被折叠的 条评论
为什么被折叠?



