为ESP32简易文件服务器添加文件列表、删除及下载功能求助
ESP32文件服务器扩展需求:添加文件列表、删除及下载功能
现有基于ESP32 + SPIFFS + AsyncWebServer的代码仅支持文件上传,需要扩展文件列表展示、文件删除、文件下载功能,尝试过GitHub找方案但未找到可行方法。
现有ESP32代码
#include <Arduino.h> #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include <SPI.h> #include <SPIFFS.h> void setup() { Serial.begin(115200); if(!SPIFFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); } start_access_point(); start_web_server(); } void loop() { } const char* ap_ssid = "ESP32"; const char* ap_pwd = "12345678"; void start_access_point(){ WiFi.softAP(ap_ssid, ap_pwd); IPAddress myIP = WiFi.softAPIP(); Serial.println("Access point started"); Serial.print("AP IP address: "); Serial.println(myIP); } AsyncWebServer server(80); const char* PARAM_SSID = "ssid"; const char* PARAM_PWD = "pwd"; void start_web_server(){ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.html", "text/html"); }); server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ if(request->hasArg("file")){ String fileName = request->arg("file"); File f = SPIFFS.open("/data/fileName", "w"); if(!f){ request->send(500, "text/plain", "Could not open file for writing"); return; } std::string fileData = std::string(request->arg("file").c_str()); f.write((uint8_t*)fileData.c_str(), fileData.length()); f.close(); request->send(200, "text/plain", "File uploaded successfully"); } else{ request->send(500, "text/plain", "No file data found in request"); } }); server.begin(); }
对应HTML代码
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <style>.app-container{ border-radius: 5px; background-color: #f2f2f2; padding: 20px; } h3{ text-align: center; color: #5a5c69 !important; font-family: Arial, Helvetica, sans-serif; } /* Style inputs, select elements and textareas */ input[type=text], select, textarea{ width: 100%; padding: 12px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; resize: vertical; } input[type=password], select, textarea{ width: 100%; padding: 12px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; resize: vertical; } /* Style the label to display next to the inputs */ label { padding: 12px 12px 12px 0; display: inline-block; } /* Style the submit button */ input[type=submit] { background-color: #4CAF50; color: white; padding: 12px 20px; border: none; border-radius: 4px; cursor: pointer; float: right; } /* Floating column for labels: 25% width */ .col-25 { float: left; width: 25%; margin-top: 6px; } /* Floating column for inputs: 75% width */ .col-75 { float: left; width: 75%; margin-top: 6px; } /* Clear floats after the columns */ .row:after { content: ""; display: table; clear: both; } /* Responsive layout - when the screen is less than 600px wide, make the two columns stack on top of each other instead of next to each other */ @media screen and (max-width: 600px) { .col-25, .col-75, input[type=submit] { width: 100%; margin-top: 0; } } </style> <title id="apptitle">Setup Wifi</title> <head> <title>File Upload</title> </head> <body> <h1>File Upload</h1> <form method="post" action="/upload" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" value="Upload" /> </form> <a href="/download/fileName">Download file</a> </body> </html>
解决方案
一、修改ESP32代码(添加完整功能)
#include <Arduino.h> #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include <SPI.h> #include <SPIFFS.h> void setup() { Serial.begin(115200); // 挂载SPIFFS,失败时自动格式化 if(!SPIFFS.begin(true)){ Serial.println("An Error has occurred while mounting SPIFFS"); } start_access_point(); start_web_server(); } void loop() {} const char* ap_ssid = "ESP32"; const char* ap_pwd = "12345678"; void start_access_point(){ WiFi.softAP(ap_ssid, ap_pwd); IPAddress myIP = WiFi.softAPIP(); Serial.println("Access point started"); Serial.print("AP IP address: "); Serial.println(myIP); } AsyncWebServer server(80); // 生成文件列表HTML片段 String getFileListHTML() { String html = "<ul>"; File root = SPIFFS.open("/"); File file = root.openNextFile(); while (file) { if (!file.isDirectory()) { String fileName = file.name(); // 跳过首页文件,避免误删 if (fileName != "/index.html") { html += "<li>"; html += fileName.substring(1); // 去掉开头的斜杠 html += " <a href='/download?file=" + fileName + "' class='download-link'>下载</a>"; html += " <a href='/delete?file=" + fileName + "' class='delete-link' onclick='return confirm(\"确定删除该文件?\")'>删除</a>"; html += "</li>"; } } file = root.openNextFile(); } html += "</ul>"; return html; } void start_web_server(){ // 首页:动态注入文件列表 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ File indexFile = SPIFFS.open("/index.html", "r"); if (indexFile) { String htmlContent = indexFile.readString(); indexFile.close(); // 替换占位符为实际文件列表 htmlContent.replace("{{FILE_LIST}}", getFileListHTML()); request->send(200, "text/html", htmlContent); } else { request->send(500, "text/plain", "Failed to load index page"); } }); // 文件上传:修复multipart/form-data处理逻辑 server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ request->redirect("/"); // 上传完成后跳回首页 }, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { if (!index) { // 开始上传,创建文件 request->_tempFile = SPIFFS.open("/" + filename, "w"); if (!request->_tempFile) { Serial.println("Failed to open file for writing"); request->send(500, "text/plain", "Upload failed: cannot open file"); return; } Serial.printf("Upload started: %s\n", filename.c_str()); } if (len) { // 写入文件数据 request->_tempFile.write(data, len); } if (final) { // 上传完成,关闭文件 request->_tempFile.close(); Serial.printf("Upload finished: %s\n", filename.c_str()); } } ); // 文件下载 server.on("/download", HTTP_GET, [](AsyncWebServerRequest *request){ if (request->hasParam("file")) { String filePath = request->getParam("file")->value(); if (SPIFFS.exists(filePath)) { // 以附件形式返回文件,触发浏览器下载 request->send(SPIFFS, filePath, "application/octet-stream", true); } else { request->send(404, "text/plain", "File not found"); } } else { request->send(400, "text/plain", "Missing file parameter"); } }); // 文件删除 server.on("/delete", HTTP_GET, [](AsyncWebServerRequest *request){ if (request->hasParam("file")) { String filePath = request->getParam("file")->value(); // 禁止删除首页文件 if (filePath == "/index.html") { request->send(403, "text/plain", "Cannot delete index.html"); return; } if (SPIFFS.exists(filePath)) { if (SPIFFS.remove(filePath)) { request->send(200, "text/plain", "File deleted"); } else { request->send(500, "text/plain", "Failed to delete file"); } } else { request->send(404, "text/plain", "File not found"); } } else { request->send(400, "text/plain", "Missing file parameter"); } // 删除后跳回首页刷新列表 request->redirect("/"); }); server.begin(); }
二、修改HTML代码(优化界面与交互)
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <style> .app-container{ border-radius: 5px; background-color: #f2f2f2; padding: 20px; max-width: 800px; margin: 20px auto; font-family: Arial, Helvetica, sans-serif; } h1, h3{ text-align: center; color: #5a5c69; } .upload-form { margin: 20px 0; text-align: center; } input[type=file], input[type=submit] { padding: 10px; margin: 5px; border-radius: 4px; border: 1px solid #ccc; } input[type=submit] { background-color: #4CAF50; color: white; border: none; cursor: pointer; transition: background-color 0.2s; } input[type=submit]:hover { background-color: #45a049; } .file-list { margin-top: 30px; padding: 15px; background-color: white; border-radius: 4px; } .file-list ul { list-style-type: none; padding: 0; } .file-list li { padding: 10px; border-bottom: 1px solid #eee; display: flex; align-items: center; justify-content: space-between; } .file-list a { text-decoration: none; padding: 6px 12px; border-radius: 4px; font-size: 14px; transition: opacity 0.2s; } .file-list a:hover { opacity: 0.8; } .download-link { background-color: #2196F3; color: white; margin-right: 8px; } .delete-link { background-color: #f44336; color: white; } </style> <title>ESP32文件服务器</title> </head> <body> <div class="app-container"> <h1>ESP32文件服务器</h1> <form class="upload-form" method="post" action="/upload" enctype="multipart/form-data"> <input type="file" name="file" required /> <input type="submit" value="上传文件" /> </form> <div class="file-list"> <h3>已上传文件</h3> {{FILE_LIST}} </div> </div> </body> </html>
关键修改说明
- 修复上传逻辑:原代码错误使用
request->arg("file")获取文件内容,改为AsyncWebServer标准的multipart上传回调处理,支持大文件分片上传 - 文件列表功能:新增
getFileListHTML()遍历SPIFFS目录,生成带下载/删除链接的HTML片段,动态注入首页 - 文件下载功能:通过
/download接口接收文件名参数,从SPIFFS读取文件并以附件形式返回 - 文件删除功能:通过
/delete接口接收文件名参数,删除指定文件后自动跳回首页刷新列表 - 界面优化:调整HTML布局与样式,提升移动端适配性和交互体验
内容的提问来源于stack exchange,提问作者ParrotXray
相关产品推荐
相关产品推荐

