Просмотр исходного кода

update: 8000文件管理系统更新为使用exremotefile扩展库方式创建

wang-wenzhong1 6 месяцев назад
Родитель
Сommit
86c0f98424

+ 0 - 43
module/Air8000/project/wifi_ap_read_file/ap_init.lua

@@ -1,43 +0,0 @@
---[[
-@module  ap_init
-@summary ap_init AP启动功能模块
-@version 1.0
-@date    2025.09.02
-@author  拓毅恒
-@usage
-用法实例:
-
-启动 AP 服务
-- 运行 create_ap 任务,来执行开启 AP 的操作,并返回设置的SSID和PASSWD。
-
-本文件在其余文件中用到了其中的变量,可直接在所需文件中 require "ap_init" 来加载运行。
-]]
-
-dnsproxy = require("dnsproxy")
-dhcpsrv = require("dhcpsrv")
--- 配置参数
-local AP_SSID       = "Air8000_FileHub"
-local AP_PASSWORD   = "12345678"
-
--- 创建AP热点
-local function create_ap()
-    log.info("WIFI", "创建AP热点: " .. AP_SSID)
-    wlan.init()
-    sys.wait(100)
-    wlan.createAP(AP_SSID, AP_PASSWORD)
-    netdrv.ipv4(socket.LWIP_AP, "192.168.4.1", "255.255.255.0", "192.168.4.1")
-    while netdrv.ready(socket.LWIP_AP) ~= true do
-        sys.wait(100)
-    end
-    dnsproxy.setup(socket.LWIP_AP, socket.LWIP_GP)
-    dhcpsrv.create({adapter=socket.LWIP_AP})
-    sys.publish("AP_CREATE_OK")
-end
-
--- 启动AP配置任务
-sys.taskInit(create_ap)
-
-return {
-    AP_SSID = AP_SSID,
-    AP_PASSWORD = AP_PASSWORD,
-}

+ 0 - 848
module/Air8000/project/wifi_ap_read_file/http_server.lua

@@ -1,848 +0,0 @@
---[[
-@module  http_server
-@summary http_server 启动功能模块
-@version 1.0
-@date    2025.09.02
-@author  拓毅恒
-@usage
-用法实例:
-
-启动 http_server 服务
-- 运行 http_server_start_task 任务,来执行开启 httpsrv 的操作。
-- 浏览器输入 http://192.168.4.1 访问文件管理系统
-
-文件下载方式:
-- 通过文件管理界面点击下载按钮
-- 直接通过URL访问文件:http://192.168.4.1/filename.ext
-- 访问SD卡文件:http://192.168.4.1/sd/filename.ext
-- 注:SD卡目录下文件夹名称不能为中文,否则会识别失败,无法下载文件夹中文件
-
-本文件没有对外接口,直接在 main.lua 中 require "http_server" 即可加载运行。
-]]
--- 配置参数
-local SERVER_PORT = 80
-
--- 导入必要的模块
-local ap_init = require "ap_init"
-
--- 登录配置
-local login_config = {
-    username = "admin",
-    password = "123456"
-}
-
--- 会话管理
-local authenticated_sessions = {}
-
--- 获取文件信息
-local function get_file_info(path)
-    log.info("FILE_INFO", "获取文件信息: " .. path)
-
-    -- 获取文件名
-    local filename = path:match("([^/]+)$") or ""
-
-    -- 获取大小
-    local direct_size = io.fileSize(path)
-    if direct_size and direct_size > 0 then
-        log.info("FILE_INFO", "获取文件大小成功: " .. direct_size .. " 字节")
-        return {
-            name = filename,
-            size = direct_size,
-            isDirectory = false,
-            path = path
-        }
-    end
-
-    -- 检查文件是否存在,避免对文件进行错误的目录判断
-    if not io.exists(path) then
-        log.info("FILE_INFO", "文件不存在: " .. path)
-        return {
-            name = filename,
-            size = 0,
-            isDirectory = false,
-            path = path
-        }
-    end
-
-    -- 尝试判断是否为目录
-    local ret, data = io.lsdir(path, 1, 0)
-    if ret and data and type(data) == "table" and #data > 0 then
-        log.info("FILE_INFO", "路径是一个目录: " .. path)
-        return {
-            name = filename,
-            size = 0,
-            isDirectory = true,
-            path = path
-        }
-    end
-
-    -- 检查文件是否存在
-    if not io.exists(path) then
-        log.info("FILE_INFO", "文件不存在: " .. path)
-        return {
-            name = filename,
-            size = 0,
-            isDirectory = false,
-            path = path
-        }
-    end
-
-    -- 尝试打开文件获取大小
-    local file = io.open(path, "rb")
-    if file then
-        -- 尝试获取文件大小
-        local file_size = io.fileSize(path)
-
-        -- 如果返回0或nil,尝试通过读取文件内容获取大小
-        if not file_size or file_size == 0 then
-            log.info("FILE_INFO", "获取文件大小,尝试读取文件内容")
-            local content = file:read("*a")
-            file_size = #content
-            log.info("FILE_INFO", "使用文件内容长度获取大小: " .. file_size .. " 字节")
-        else
-            log.info("FILE_INFO", "获取文件大小成功: " .. file_size .. " 字节")
-        end
-
-        file:close()
-        log.info("FILE_INFO", "成功获取文件信息: " .. filename .. ", 大小: " .. file_size .. " 字节")
-        return {
-            name = filename,
-            size = file_size,
-            isDirectory = false,
-            path = path
-        }
-    end
-end
-
--- 定义系统文件的规则(系统文件不显示)
-local function is_system_file(filename)
-    -- 系统文件扩展名列表
-    local system_extensions = {".luac", ".html", ".md"}
-    -- 特殊系统文件名
-    local special_system_files = {".airm2m_all_crc#.bin"}
-
-    -- 检查文件名是否匹配特殊系统文件名
-    for _, sys_file in ipairs(special_system_files) do
-        if filename == sys_file then
-            return true
-        end
-    end
-
-    -- 检查文件扩展名是否为系统文件扩展名
-    for _, ext in ipairs(system_extensions) do
-        if filename:sub(-#ext) == ext then
-            return true
-        end
-    end
-
-    return false
-end
-
--- 扫描目录
-local function scan_with_lsdir(path, files)
-    log.info("LIST_DIR", "开始扫描目录")
-    -- 确保路径格式正确,处理多层目录和编码问题
-    local scan_path = path
-    log.info("LIST_DIR", "原始路径: " .. scan_path)
-
-    -- 规范化路径,处理URL编码残留问题
-    scan_path = scan_path:gsub("%%(%x%x)", function(hex)
-        return string.char(tonumber(hex, 16))
-    end)
-    log.info("LIST_DIR", "解码后路径: " .. scan_path)
-
-    -- 移除多余的斜杠
-    scan_path = scan_path:gsub("//+", "/")
-    log.info("LIST_DIR", "去重斜杠后路径: " .. scan_path)
-
-    -- 规范化路径,移除可能的尾部斜杠
-    scan_path = scan_path:gsub("/*$", "")
-    log.info("LIST_DIR", "移除尾部斜杠后路径: " .. scan_path)
-
-    -- 确保路径以/开头
-    if not scan_path:match("^/") then
-        scan_path = "/" .. scan_path
-    end
-    log.info("LIST_DIR", "确保以/开头后路径: " .. scan_path)
-
-    -- 确保路径以/结尾
-    scan_path = scan_path .. (scan_path == "" and "" or "/")
-
-    log.info("LIST_DIR", "开始扫描路径: " .. scan_path)
-
-    -- 扫描目录,最多列出50个文件,从第0个开始
-    local ret, data = io.lsdir(scan_path, 50, 0)
-
-    if ret then
-        log.info("LIST_DIR", "成功获取目录内容,文件数量: " .. #data)
-        log.info("LIST_DIR", "目录内容: " .. json.encode(data))
-
-        -- 遍历目录内容
-        for i = 1, #data do
-            local entry = data[i]
-            local is_dir = (entry.type ~= 0)
-            local entry_type = is_dir and "目录" or "文件"
-            log.info("LIST_DIR", "找到条目: " .. entry.name .. ", 类型: " .. entry_type)
-
-            local full_path = scan_path .. entry.name
-
-            -- 处理目录和文件的不同逻辑
-            if is_dir then
-                -- 对于目录,直接构造信息
-                local dir_info = {
-                    name = entry.name,
-                    size = 0,
-                    isDirectory = true,
-                    path = full_path
-                }
-                -- 过滤sd卡系统文件夹目录
-                if entry.name ~= "System Volume Information" then
-                    table.insert(files, dir_info)
-                    log.info("LIST_DIR", "添加目录: " .. entry.name .. ", 路径: " .. full_path)
-                end
-            else
-                -- 检查是否为用户文件
-                local is_user_file = not is_system_file(entry.name)
-
-                -- 只有用户文件才会被添加到列表中
-                if is_user_file then
-                    -- 对于文件,调用get_file_info获取详细信息
-                    local file_info = get_file_info(full_path)
-                    if file_info and file_info.size ~= nil then
-                        file_info.isDirectory = false
-                        table.insert(files, file_info)
-                        log.info("LIST_DIR", "添加文件: " .. entry.name .. ", 大小: " .. file_info.size ..
-                            " 字节, 路径: " .. file_info.path)
-                    else
-                        -- 如果get_file_info失败,使用默认值
-                        local default_info = {
-                            name = entry.name,
-                            size = entry.size or 0,
-                            isDirectory = false,
-                            path = full_path
-                        }
-                        table.insert(files, default_info)
-                        log.info("LIST_DIR", "添加文件(默认信息): " .. entry.name .. ", 大小: " ..
-                            (entry.size or 0) .. " 字节")
-                    end
-                end
-            end
-        end
-        return true
-    else
-        log.info("LIST_DIR", "扫描失败: " .. (data or "未知错误"))
-    end
-    return false
-end
-
--- 列出目录
-local function list_directory(path)
-    -- 初始化文件列表
-    local files = {}
-
-    log.info("LIST_DIR", "开始处理目录请求: " .. path)
-
-    -- 扫描方法表
-    local scan_success = scan_with_lsdir(path, files)
-
-    -- 记录扫描结果
-    if scan_success then
-        log.info("LIST_DIR", "扫描方法成功")
-    else
-        log.info("LIST_DIR", "扫描方法失败")
-    end
-
-    log.info("LIST_DIR", "目录扫描完成,总共找到文件数量: " .. #files)
-    return files
-end
-
--- 会话验证
-local function validate_session(headers)
-    -- 获取Cookie中的session_id
-    local cookies = headers['Cookie'] or ''
-    local session_id = nil
-    if cookies then
-        session_id = cookies:match('session_id=([^;]+)')
-    end
-
-    -- 检查会话ID是否已认证
-    if session_id and authenticated_sessions[session_id] then
-        return true
-    else
-        return false
-    end
-end
-
--- 生成会话ID
-local function generate_session_id()
-    local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
-    local id = ""
-    for i = 1, 32 do
-        local rand = math.random(1, #chars)
-        id = id .. chars:sub(rand, rand)
-    end
-    return id
-end
-
--- 检查字符串是否以指定前缀开头
-function string_starts_with(str, prefix)
-    return string.sub(str, 1, string.len(prefix)) == prefix
-end
-
--- HTTP请求处理
-local function handle_http_request(fd, method, uri, headers, body)
-    log.info("HTTP", method, uri)
-
-    -- 登录
-    if uri == "/login" and method == "POST" then
-        local data = json.decode(body or "{}")
-        log.info("LOGIN", "收到登录请求,用户名: " .. (data and data.username or "空"))
-        if data and data.username == login_config.username and data.password == login_config.password then
-            local session_id = generate_session_id()
-            authenticated_sessions[session_id] = os.time()
-
-            -- 计算已认证会话数量
-            local session_count = 0
-            for _ in pairs(authenticated_sessions) do
-                session_count = session_count + 1
-            end
-
-            log.info("LOGIN", "登录成功!用户名: " .. data.username)
-            log.info("LOGIN", "生成SessionID: " .. session_id)
-            log.info("LOGIN", "当前已认证会话数量: " .. session_count)
-
-            -- 设置Cookie
-            return 200, {
-                ["Content-Type"] = "application/json",
-                ["Set-Cookie"] = "session_id=" .. session_id .. "; Path=/; Max-Age=3600"
-            }, json.encode({
-                success = true,
-                session_id = session_id
-            })
-        else
-            return 200, {
-                ["Content-Type"] = "application/json"
-            }, json.encode({
-                success = false,
-                message = "用户名或密码错误"
-            })
-        end
-    end
-
-    -- 登出
-    if uri == "/logout" and method == "POST" then
-        local cookie = headers["Cookie"] or ""
-        for session_id in cookie:gmatch("session_id=([^;]+)") do
-            authenticated_sessions[session_id] = nil
-        end
-        return 200, {
-            ["Set-Cookie"] = "session_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT"
-        }, ""
-    end
-
-    -- 检查认证
-    if uri == "/check-auth" then
-        return 200, {
-            ["Content-Type"] = "application/json"
-        }, json.encode({
-            authenticated = validate_session(headers)
-        })
-    end
-
-    -- 扫描文件接口
-    if string_starts_with(uri, "/scan-files") then
-        log.info("SCAN", "收到文件扫描请求")
-
-        -- 检查传统认证方式
-        local is_authenticated = validate_session(headers)
-
-        -- 如果传统认证失败,尝试从URL参数中获取用户名和密码
-        if not is_authenticated then
-            local url_username = uri:match("username=([^&]+)")
-            local url_password = uri:match("password=([^&]+)")
-            if url_username and url_password then
-                url_username = url_username:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                url_password = url_password:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                if url_username == login_config.username and url_password == login_config.password then
-                    log.info("AUTH", "扫描请求通过URL参数认证成功")
-                    is_authenticated = true
-                else
-                    log.info("AUTH", "扫描请求URL参数认证失败: 用户名或密码错误")
-                end
-            else
-                log.info("AUTH", "扫描请求URL中没有找到用户名和密码参数")
-            end
-        end
-
-        -- 如果认证仍然失败,返回未授权访问
-        if not is_authenticated then
-            log.info("HTTP", "未授权访问文件扫描功能")
-            return 401, {
-                ["Content-Type"] = "application/json"
-            }, json.encode({
-                success = false,
-                message = "未授权访问"
-            })
-        end
-
-        -- 执行文件扫描
-        log.info("SCAN", "开始扫描内部文件系统和TF卡...")
-
-        -- 定义要扫描的挂载点,包括SD卡挂载点
-        local mount_points = {"/", "/luadb/", "/sd/"}
-        local found_files = {}
-
-        -- 对每个挂载点执行扫描
-        for _, mount_point in ipairs(mount_points) do
-            log.info("SCAN", "开始扫描挂载点: " .. mount_point)
-
-            -- 如果路径不以/结尾,添加/确保路径格式正确
-            local scan_path = mount_point
-            if not scan_path:match("/$") then
-                scan_path = scan_path .. (scan_path == "" and "" or "/")
-            end
-
-            -- 扫描目录
-            log.info("SCAN", "开始扫描路径: " .. scan_path)
-            -- 尝试列出目录内容,最多列出50个文件
-            local ret, data = io.lsdir(scan_path, 50, 0)
-
-            if ret then
-                log.info("SCAN", "成功获取目录内容,文件数量: " .. #data)
-                log.info("SCAN", "目录内容: " .. json.encode(data))
-
-                -- 遍历目录内容
-                for i = 1, #data do
-                    local entry = data[i]
-                    local full_path = scan_path .. entry.name
-
-                    -- 如果是文件(type == 0),添加到文件列表
-                    if entry.type == 0 then
-                        local info = get_file_info(full_path)
-                        if info then
-                            table.insert(found_files, {
-                                name = entry.name,
-                                size = info.size,
-                                path = full_path
-                            })
-                            log.info("SCAN", "找到文件: " .. entry.name .. ", 大小: " .. info.size ..
-                                " 字节, 路径: " .. full_path)
-                        else
-                            -- 如果get_file_info失败,使用io.lsdir返回的大小
-                            table.insert(found_files, {
-                                name = entry.name,
-                                size = entry.size or 0,
-                                path = full_path
-                            })
-                            log.info("SCAN", "找到文件: " .. entry.name .. ", 大小: " .. (entry.size or 0) ..
-                                " 字节, 路径: " .. full_path)
-                        end
-                    else
-                        -- 如果是目录,记录但不添加到文件列表
-                        log.info("SCAN", "找到目录: " .. entry.name .. ", 路径: " .. full_path)
-                    end
-                end
-            else
-                log.info("SCAN", "扫描失败: " .. (data or "未知错误"))
-            end
-
-            local list_files = list_directory(mount_point)
-            if list_files then
-                for _, file in ipairs(list_files) do
-                    -- 只记录非目录文件
-                    if not file.isDirectory then
-                        -- 确保文件路径正确
-                        local file_path = file.path or (mount_point .. (mount_point == "/" and "" or "/") .. file.name)
-
-                        -- 检查文件是否已添加
-                        local is_exists = false
-                        for _, f in ipairs(found_files) do
-                            if f.name == file.name and f.path == file_path then
-                                is_exists = true
-                                break
-                            end
-                        end
-                        if not is_exists then
-                            table.insert(found_files, {
-                                name = file.name,
-                                size = file.size,
-                                path = file_path
-                            })
-                            log.info("SCAN",
-                                "从list_directory添加文件: " .. file.name .. ", 大小: " .. file.size ..
-                                    " 字节, 路径: " .. file_path)
-                        end
-                    end
-                end
-            end
-
-            log.info("SCAN", "挂载点扫描完成: " .. mount_point .. ", 找到文件: " .. #found_files .. " 个")
-        end
-
-        -- 扫描完成后,打印详细的文件列表
-        log.info("SCAN", "文件扫描完成,总共找到文件数量: " .. #found_files)
-        for i, file in ipairs(found_files) do
-            log.info("SCAN", "文件[" .. i .. "]: " .. file.name .. ", 大小: " .. file.size .. " 字节, 路径: " ..
-                file.path)
-        end
-
-        -- 返回扫描结果
-        return 200, {
-            ["Content-Type"] = "application/json"
-        }, json.encode({
-            success = true,
-            foundFiles = #found_files,
-            files = found_files,
-            message = "文件扫描完成"
-        })
-    end
-
-    -- 文件列表
-    if string_starts_with(uri, "/list") then
-
-        -- 检查传统认证方式
-        local is_authenticated = validate_session(headers)
-
-        -- 如果传统认证失败,尝试从URL参数中获取用户名和密码
-        if not is_authenticated then
-            local url_username = uri:match("username=([^&]+)")
-            local url_password = uri:match("password=([^&]+)")
-            if url_username and url_password then
-                url_username = url_username:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                url_password = url_password:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                if url_username == login_config.username and url_password == login_config.password then
-                    log.info("AUTH", "通过URL参数认证成功")
-                    is_authenticated = true
-                else
-                    log.info("AUTH", "URL参数认证失败: 用户名或密码错误")
-                end
-            else
-                log.info("AUTH", "URL中没有找到用户名和密码参数")
-            end
-        end
-
-        -- 如果认证仍然失败,返回未授权访问
-        if not is_authenticated then
-            log.info("HTTP", "未授权访问文件列表")
-            return 401, {
-                ["Content-Type"] = "text/plain"
-            }, "未授权访问"
-        end
-        local path = uri:match("path=([^&]+)") or "/luadb"
-        log.info("HTTP", "请求的文件列表路径: " .. path)
-        path = path:gsub("%%(%x%x)", function(hex)
-            return string.char(tonumber(hex, 16))
-        end)
-        log.info("HTTP", "解码后的文件列表路径: " .. path)
-
-        -- 调用list_directory函数扫描目录
-        log.info("HTTP", "开始扫描目录")
-        local files = list_directory(path)
-
-        -- 记录传给页面的文件数据
-        log.info("HTTP", "准备返回文件列表,数量: " .. #files)
-        for i, file in ipairs(files) do
-            log.info("HTTP", "文件[" .. i .. "]: " .. file.name .. ", 大小: " .. file.size)
-        end
-
-        return 200, {
-            ["Content-Type"] = "application/json"
-        }, json.encode({
-            success = true,
-            files = files
-        })
-    end
-
-    -- 文件下载
-    if string_starts_with(uri, "/download") then
-        log.info("DOWNLOAD", "收到下载请求: " .. uri)
-
-        -- 检查传统认证方式
-        local is_authenticated = validate_session(headers)
-
-        -- 如果传统认证失败,尝试从URL参数中获取用户名和密码
-        if not is_authenticated then
-            local url_username = uri:match("username=([^&]+)")
-            local url_password = uri:match("password=([^&]+)")
-            if url_username and url_password then
-                url_username = url_username:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                url_password = url_password:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                if url_username == login_config.username and url_password == login_config.password then
-                    log.info("AUTH", "下载请求通过URL参数认证成功")
-                    is_authenticated = true
-                else
-                    log.info("AUTH", "下载请求URL参数认证失败: 用户名或密码错误")
-                end
-            else
-                log.info("AUTH", "下载请求URL中没有找到用户名和密码参数")
-            end
-        end
-
-        -- 如果认证仍然失败,返回未授权访问
-        if not is_authenticated then
-            log.info("HTTP", "未授权访问文件下载")
-            return 401, {
-                ["Content-Type"] = "text/plain"
-            }, "未授权访问"
-        end
-
-        -- 获取请求的文件路径
-        local path = uri:match("path=([^&]+)") or ""
-        path = path:gsub("%%(%x%x)", function(hex)
-            return string.char(tonumber(hex, 16))
-        end)
-
-        -- 检查文件是否存在
-        if not io.exists(path) then
-            log.info("DOWNLOAD", "文件不存在: " .. path)
-            return 404, {
-                ["Content-Type"] = "text/plain"
-            }, "文件不存在"
-        end
-
-        -- 尝试打开文件以确认可访问性并获取文件信息
-        local file = io.open(path, "rb")
-        if not file then
-            log.info("DOWNLOAD", "文件无法打开: " .. path)
-            return 404, {
-                ["Content-Type"] = "text/plain"
-            }, "文件无法打开"
-        end
-
-        -- 获取文件名
-        local filename = path:match("([^/]+)$")
-
-        -- 获取文件大小
-        local file_size = io.fileSize(path)
-
-        -- 关闭文件
-        file:close()
-
-        log.info("DOWNLOAD", "确认文件信息: " .. filename .. ", 大小: " .. file_size .. " 字节")
-
-        -- 使用httpsrv下载,直接重定向URL
-        -- 如需要下载文件系统中123.mp3,直接重定向到URL:http://192.168.4.1/123.mp3
-        -- 如果路径以/sd/开头,则保留完整的sd路径
-        local redirect_url = "/" .. filename
-        if string_starts_with(path, "/sd/") then
-            -- 保留完整的sd路径,以便直接访问sd卡文件及其子目录
-            redirect_url = path
-        end
-
-        log.info("DOWNLOAD", "开始下载文件:" .. redirect_url)
-
-        -- 返回HTTP 302重定向响应
-        return 302, {
-            ["Location"] = redirect_url,
-            ["Content-Type"] = "text/html"
-        }, [[
-            <html>
-            <head><title>重定向下载</title></head>
-            <body>
-                <p>正在重定向到文件下载...</p>
-            </body>
-            </html>
-        ]]
-    end
-
-    -- 文件删除
-    if string_starts_with(uri, "/delete") and method == "POST" then
-        -- 检查传统认证方式
-        local is_authenticated = validate_session(headers)
-
-        -- 如果传统认证失败,尝试从URL参数中获取用户名和密码
-        if not is_authenticated then
-            local url_username = uri:match("username=([^&]+)")
-            local url_password = uri:match("password=([^&]+)")
-            if url_username and url_password then
-                url_username = url_username:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                url_password = url_password:gsub("%%(%x%x)", function(hex)
-                    return string.char(tonumber(hex, 16))
-                end)
-                if url_username == login_config.username and url_password == login_config.password then
-                    log.info("AUTH", "通过URL参数认证成功")
-                    is_authenticated = true
-                else
-                    log.info("AUTH", "URL参数认证失败: 用户名或密码错误")
-                end
-            else
-                log.info("AUTH", "URL中没有找到用户名和密码参数")
-            end
-        end
-
-        -- 如果认证仍然失败,返回未授权访问
-        if not is_authenticated then
-            log.info("HTTP", "未授权访问文件删除")
-            return 401, {
-                ["Content-Type"] = "application/json"
-            }, json.encode({
-                success = false,
-                message = "未授权访问"
-            })
-        end
-        local path = uri:match("path=([^&]+)") or ""
-        path = path:gsub("%%(%x%x)", function(hex)
-            return string.char(tonumber(hex, 16))
-        end)
-        if not io.exists(path) then
-            return 200, {
-                ["Content-Type"] = "application/json"
-            }, json.encode({
-                success = false,
-                message = "文件不存在"
-            })
-        end
-        -- 尝试删除文件
-        local ok, err = os.remove(path)
-        if ok then
-            return 200, {
-                ["Content-Type"] = "application/json"
-            }, json.encode({
-                success = true,
-                message = "文件删除成功"
-            })
-        else
-            return 200, {
-                ["Content-Type"] = "application/json"
-            }, json.encode({
-                success = false,
-                message = "删除失败: " .. (err or "未知错误")
-            })
-        end
-    end
-
-    -- 首页
-    if uri == "/" then
-        local html_file = io.open("/index.html", "r")
-        if html_file then
-            local content = html_file:read("*a")
-            html_file:close()
-            return 200, {
-                ["Content-Type"] = "text/html"
-            }, content
-        end
-    end
-
-    -- 直接文件路径访问
-    -- 检查是否是API路径,如果不是,则尝试作为文件路径访问
-    local is_api_path = string_starts_with(uri, "/login") or string_starts_with(uri, "/logout") or
-                            string_starts_with(uri, "/check-auth") or string_starts_with(uri, "/scan-files") or
-                            string_starts_with(uri, "/list") or string_starts_with(uri, "/download") or
-                            string_starts_with(uri, "/delete") or uri == "/"
-
-    if not is_api_path then
-        log.info("DIRECT_ACCESS", "尝试直接访问文件: " .. uri)
-
-        -- 确定实际文件路径
-        local file_path = uri
-
-        -- 如果路径不是以/sd/开头,则默认在/luadb/目录下查找
-        if not string_starts_with(file_path, "/sd/") then
-            -- 移除开头的斜杠
-            if file_path:sub(1, 1) == "/" then
-                file_path = file_path:sub(2)
-            end
-            -- 添加/luadb/前缀
-            file_path = "/luadb/" .. file_path
-        end
-
-        log.info("DIRECT_ACCESS", "解析后的实际文件路径: " .. file_path)
-
-        -- 检查文件是否存在
-        if not io.exists(file_path) then
-            log.info("DIRECT_ACCESS", "文件不存在: " .. file_path)
-            return 404, {
-                ["Content-Type"] = "text/plain"
-            }, "文件不存在"
-        end
-
-        -- 尝试打开文件
-        local file = io.open(file_path, "rb")
-        if not file then
-            log.info("DIRECT_ACCESS", "文件无法打开: " .. file_path)
-            return 404, {
-                ["Content-Type"] = "text/plain"
-            }, "文件无法打开"
-        end
-
-        -- 获取文件名
-        local filename = file_path:match("([^/]+)$")
-
-        -- 读取文件内容
-        local content = file:read("*a")
-
-        -- 关闭文件
-        file:close()
-
-        log.info("DIRECT_ACCESS", "文件读取完成: " .. filename .. ", 大小: " .. #content .. " 字节")
-
-        -- 设置HTTP头部
-        local response_headers = {
-            ["Content-Type"] = "application/octet-stream",
-            ["Content-Disposition"] = "attachment; filename=\"" .. filename .. "\""
-        }
-
-        return 200, response_headers, content
-    end
-
-    return 404, {
-        ["Content-Type"] = "text/plain"
-    }, "页面未找到"
-end
-
--- HTTP服务器启动任务
-local function http_server_start_task()
-    -- 等待AP初始化完成
-    sys.waitUntil("AP_CREATE_OK")
-
-    -- 确认SD卡是否挂载成功
-    local retry_count = 0
-    local max_retries = 3
-
-    while retry_count < max_retries do
-        local free_space, err = fatfs.getfree("/sd")
-        if free_space then
-            log.info("HTTP", "SD卡挂载成功,可用空间: " .. json.encode(free_space))
-            break
-        else
-            retry_count = retry_count + 1
-            log.warn("HTTP", "SD卡挂载检查失败 (" .. retry_count .. "): " .. (err or "未知错误"))
-            if retry_count < max_retries then
-                sys.wait(1000)
-            else
-                log.error("HTTP", "SD卡挂载失败,将继续启动但可能无法访问SD卡内容")
-            end
-        end
-    end
-
-    -- 启动HTTP服务器
-    httpsrv.start(SERVER_PORT, handle_http_request, socket.LWIP_AP)
-
-    log.info("HTTP", "文件服务器已启动")
-    log.info("HTTP", "请连接WiFi: " .. ap_init.AP_SSID .. ",密码: " .. ap_init.AP_PASSWORD)
-    log.info("HTTP", "然后访问: http://192.168.4.1:" .. SERVER_PORT)
-end
-
--- 启动HTTP服务器任务
-sys.taskInit(http_server_start_task)
-
-return {
-    handle_http_request = handle_http_request,
-    login_config = login_config,
-    authenticated_sessions = authenticated_sessions,
-    SERVER_PORT = SERVER_PORT
-}

+ 0 - 472
module/Air8000/project/wifi_ap_read_file/index.html

@@ -1,472 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Air8000 文件管理系统</title>
-    <style>
-        body {
-            font-family: 'Microsoft YaHei', Arial, sans-serif;
-            background-color: #f5f5f5;
-            margin: 0;
-            padding: 20px;
-            color: #333;
-        }
-        .container {
-            max-width: 1200px;
-            margin: auto;
-            background: white;
-            padding: 20px;
-            border-radius: 8px;
-            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
-        }
-        .header {
-            background: #2c3e50;
-            color: white;
-            padding: 15px;
-            border-radius: 5px;
-            margin-bottom: 20px;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-        }
-        .login-form {
-            max-width: 400px;
-            margin: 100px auto;
-            padding: 40px;
-            background: white;
-            border-radius: 8px;
-            box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
-        }
-        .form-group {
-            margin-bottom: 15px;
-        }
-        label {
-            display: block;
-            margin-bottom: 5px;
-            font-weight: bold;
-        }
-        input[type="text"], input[type="password"] {
-            width: 100%;
-            padding: 10px;
-            border: 1px solid #ddd;
-            border-radius: 4px;
-            box-sizing: border-box;
-        }
-        button {
-            background-color: #3498db;
-            color: white;
-            border: none;
-            padding: 10px 20px;
-            border-radius: 4px;
-            cursor: pointer;
-            font-size: 16px;
-        }
-        button:hover {
-            background-color: #2980b9;
-        }
-        .file-list {
-            width: 100%;
-            border-collapse: collapse;
-            margin-top: 20px;
-        }
-        .file-list th, .file-list td {
-            padding: 12px;
-            text-align: left;
-            border-bottom: 1px solid #ddd;
-        }
-        .file-list th {
-            background-color: #f8f9fa;
-            font-weight: bold;
-        }
-        .file-list tr:hover {
-            background-color: #f5f5f5;
-        }
-        .download-btn {
-            background-color: #27ae60;
-            color: white;
-            padding: 5px 10px;
-            text-decoration: none;
-            border-radius: 3px;
-            font-size: 12px;
-        }
-        .download-btn:hover {
-            background-color: #219a52;
-        }
-        .delete-btn {
-            background-color: #e74c3c;
-            color: white;
-            border: none;
-            padding: 5px 10px;
-            border-radius: 3px;
-            font-size: 12px;
-            cursor: pointer;
-        }
-        .delete-btn:hover {
-            background-color: #c0392b;
-        }
-        .breadcrumb {
-            padding: 10px 0;
-            margin-bottom: 20px;
-        }
-        .breadcrumb a {
-            color: #3498db;
-            text-decoration: none;
-            cursor: pointer;
-        }
-        .breadcrumb a:hover {
-            text-decoration: underline;
-        }
-        .hidden {
-            display: none;
-        }
-        .error {
-            color: #e74c3c;
-            margin-top: 10px;
-        }
-    </style>
-</head>
-<body>
-    <div class="container">
-        <!-- 登录页面 -->
-        <div id="loginPage" class="login-form">
-            <h2>Air8000 文件管理系统登录</h2>
-            <div class="form-group">
-                <label for="username">用户名:</label>
-                <input type="text" id="username">
-            </div>
-            <div class="form-group">
-                <label for="password">密码:</label>
-                <input type="password" id="password">
-            </div>
-            <button onclick="login()">登录</button>
-            <div id="loginError" class="error hidden"></div>
-        </div>
-
-        <!-- 文件管理页面 -->
-        <div id="filePage" class="hidden">
-            <div class="header">
-                <h1>Air8000 文件管理系统</h1>
-                <div>
-                    <button onclick="scanFiles()" style="margin-right: 10px;">扫描文件</button>
-                    <button onclick="logout()">退出登录</button>
-                </div>
-            </div>
-
-            <div class="breadcrumb" id="breadcrumb">
-                <a onclick="navigateTo('/')">根目录</a>
-                <span> | </span>
-                <a onclick="navigateTo('/sd')">TF/SD目录</a>
-            </div>
-
-            <table class="file-list">
-                <thead>
-                    <tr>
-                        <th>名称</th>
-                        <th>大小</th>
-                        <th>操作</th>
-                    </tr>
-                </thead>
-                <tbody id="fileListBody">
-                </tbody>
-            </table>
-        </div>
-    </div>
-
-    <script>
-        let currentPath = '/';
-        let isLoggedIn = false;
-
-        function login() {
-            const username = document.getElementById('username').value;
-            const password = document.getElementById('password').value;
-
-            fetch('/login', {
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/json',
-                },
-                credentials: 'include', // 确保发送和接收cookies
-                body: JSON.stringify({username: username, password: password})
-            })
-            .then(response => {
-                // 打印所有响应头,查看Cookie设置
-                console.log('登录响应头:', response.headers);
-                return response.json();
-            })
-            .then(data => {
-                console.log('登录响应数据:', data);
-                if (data.success) {
-                    isLoggedIn = true;
-                    // 存储session_id到localStorage作为备用认证方式
-                    if (data.session_id) {
-                        localStorage.setItem('session_id', data.session_id);
-                        console.log('已存储SessionID到localStorage:', data.session_id);
-                    }
-                    document.getElementById('loginPage').classList.add('hidden');
-                    document.getElementById('filePage').classList.remove('hidden');
-                    loadFiles('/luadb');
-                } else {
-                    document.getElementById('loginError').textContent = data.message || '登录失败';
-                    document.getElementById('loginError').classList.remove('hidden');
-                }
-            })
-            .catch(error => {
-                console.error('登录请求错误:', error);
-                document.getElementById('loginError').textContent = '登录请求失败';
-                document.getElementById('loginError').classList.remove('hidden');
-            });
-        }
-
-        function logout() {
-            fetch('/logout', {
-                method: 'POST',
-                credentials: 'include' // 确保发送cookies
-            })
-            .then(() => {
-                isLoggedIn = false;
-                // 清除localStorage中的session_id
-                localStorage.removeItem('session_id');
-                document.getElementById('filePage').classList.add('hidden');
-                document.getElementById('loginPage').classList.remove('hidden');
-                document.getElementById('username').value = '';
-                document.getElementById('password').value = '';
-                document.getElementById('loginError').classList.add('hidden');
-            });
-        }
-
-        // 扫描文件函数
-        function scanFiles() {
-            if (!isLoggedIn) return;
-
-            // 获取用户名和密码用于URL参数认证
-            const username = document.getElementById('username')?.value || 'admin';
-            const password = document.getElementById('password')?.value || '123456';
-
-            // 构建带认证参数的扫描请求URL
-            const url = '/scan-files?username=' + encodeURIComponent(username) +
-                       '&password=' + encodeURIComponent(password);
-
-            console.log('发送文件扫描请求,URL:', url);
-
-            // 显示扫描提示
-            alert('开始扫描文件,请查看系统日志了解扫描进度...');
-
-            fetch(url, {
-                method: 'GET',
-                credentials: 'include'
-            })
-            .then(response => {
-                if (!response.ok) {
-                    throw new Error('扫描请求错误: ' + response.status);
-                }
-                return response.json();
-            })
-            .then(data => {
-                console.log('扫描响应:', data);
-                if (data && data.success) {
-                    alert('文件扫描完成!已扫描到 ' + data.foundFiles + ' 个文件,显示扫描到的用户文件。');
-                    // 重新加载文件列表
-                    loadFiles(currentPath);
-                } else {
-                    alert('文件扫描失败: ' + (data.message || '未知错误'));
-                }
-            })
-            .catch(error => {
-                console.error('扫描文件请求错误:', error);
-                alert('扫描文件请求失败');
-            });
-        }
-
-        function loadFiles(path) {
-            if (!isLoggedIn) return;
-
-            // 准备请求头
-            const headers = {
-                'Content-Type': 'application/json'
-            };
-
-            // 由于传统认证方式不可靠,我们使用URL参数认证
-            // 获取用户名和密码用于URL参数认证
-            const username = document.getElementById('username')?.value || 'admin';
-            const password = document.getElementById('password')?.value || '123456';
-
-            // 构建带认证参数的URL
-            const url = '/list?path=' + encodeURIComponent(path) +
-                        '&username=' + encodeURIComponent(username) +
-                        '&password=' + encodeURIComponent(password);
-
-            console.log('使用URL参数认证,请求URL:', url);
-
-            fetch(url, {
-                credentials: 'include', // 确保发送cookies
-                headers: headers
-            })
-            .then(response => {
-                if (!response.ok) {
-                    throw new Error('网络响应错误: ' + response.status);
-                }
-                return response.json();
-            })
-            .then(data => {
-                console.log('文件列表数据:', data);
-
-                // 只使用服务器返回的数据
-                if (data && data.success && Array.isArray(data.files)) {
-                    displayFiles(data.files, path);
-                } else {
-                    // 如果数据无效,显示空列表
-                    displayFiles([], path);
-                }
-
-                updateBreadcrumb(path);
-            })
-            .catch(error => {
-                console.error('加载文件列表错误:', error);
-                // 发生错误时显示空列表
-                displayFiles([], path);
-                updateBreadcrumb(path);
-            });
-        }
-
-        function displayFiles(files, path) {
-            const tbody = document.getElementById('fileListBody');
-            tbody.innerHTML = '';
-
-            // 确保files是数组
-            if (!Array.isArray(files)) {
-                files = [];
-            }
-
-            console.log('显示文件数量:', files.length);
-
-            files.forEach(file => {
-                // 确保文件对象有必要的属性
-                const safeFile = {
-                    name: file.name || "未知文件名",
-                    size: file.size || 0,
-                    isDirectory: file.isDirectory || false,
-                    path: file.path || (path + '/' + (file.name || "未知文件名"))
-                };
-
-                const row = document.createElement('tr');
-                let nameCell, actionCell;
-
-                if (safeFile.isDirectory) {
-                    nameCell = `<td><a href="#" onclick="navigateTo('${encodeURIComponent(path + '/' + safeFile.name)}')">${safeFile.name}/</a></td>`;
-                    actionCell = '<td></td>';
-                } else {
-                    nameCell = `<td>${safeFile.name}</td>`;
-                    // 为下载链接添加URL参数认证
-                    const username = document.getElementById('username')?.value || 'admin';
-                    const password = document.getElementById('password')?.value || '123456';
-                    const downloadUrl = '/download?path=' + encodeURIComponent(safeFile.path) +
-                                        '&username=' + encodeURIComponent(username) +
-                                        '&password=' + encodeURIComponent(password);
-
-                    // 添加下载和删除按钮
-                    actionCell = `<td>
-                        <a href="${downloadUrl}" class="download-btn" style="margin-right: 5px;">下载</a>
-                        <button class="delete-btn" onclick="deleteFile('${encodeURIComponent(safeFile.path)}')">删除</button>
-                    </td>`;
-                }
-
-                row.innerHTML = `
-                    ${nameCell}
-                    <td>${formatSize(safeFile.size)}</td>
-                    ${actionCell}
-                `;
-                tbody.appendChild(row);
-            });
-        }
-
-        // 删除文件函数
-        function deleteFile(filePath) {
-            if (confirm('确定要删除这个文件吗?')) {
-                // 获取用户名和密码用于URL参数认证
-                const username = document.getElementById('username')?.value || 'admin';
-                const password = document.getElementById('password')?.value || '123456';
-
-                // 构建带认证参数的删除请求URL
-                const url = '/delete?path=' + filePath +
-                            '&username=' + encodeURIComponent(username) +
-                            '&password=' + encodeURIComponent(password);
-
-                console.log('使用URL参数认证进行删除操作,请求URL:', url);
-
-                fetch(url, {
-                    method: 'POST',
-                    credentials: 'include'
-                })
-                .then(response => response.json())
-                .then(data => {
-                    if (data.success) {
-                        // 删除成功后重新加载文件列表
-                        loadFiles(currentPath);
-                    } else {
-                        alert('删除失败: ' + (data.message || '未知错误'));
-                    }
-                })
-                .catch(error => {
-                    alert('删除请求失败');
-                });
-            }
-        }
-
-        function updateBreadcrumb(path) {
-            const breadcrumb = document.getElementById('breadcrumb');
-
-            // 先设置根目录和TF/SD目录链接
-            breadcrumb.innerHTML = '<a onclick="navigateTo(\'\')">根目录</a><span> | </span><a onclick="navigateTo(\'/sd\')">TF/SD目录</a>';
-
-            // 然后添加当前路径的层次结构(如果不是根目录)
-            if (path !== '/' && path !== '/sd') {
-                const parts = path.split('/').filter(p => p);
-                let current = '';
-
-                // 仅在非根目录和非SD目录时添加分隔符
-                breadcrumb.innerHTML += ' > ';
-
-                parts.forEach((part, index) => {
-                    current += '/' + part;
-                    if (index > 0) {
-                        breadcrumb.innerHTML += ' > ';
-                    }
-                    breadcrumb.innerHTML += '<a onclick="navigateTo(\'' + current + '\')">' + part + '</a>';
-                });
-            }
-        }
-
-        function navigateTo(path) {
-            currentPath = path;
-            loadFiles(path);
-        }
-
-        function formatSize(bytes) {
-            if (bytes === 0) return '0 B';
-            const k = 1024;
-            const sizes = ['B', 'KB', 'MB', 'GB'];
-            const i = Math.floor(Math.log(bytes) / Math.log(k));
-            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
-        }
-
-        // 格式化日期函数已移除
-
-        // 启动后检查认证状态
-        window.onload = function() {
-            fetch('/check-auth', {
-                credentials: 'include' // 确保发送cookies
-            })
-            .then(response => response.json())
-            .then(data => {
-                if (data.authenticated) {
-                    isLoggedIn = true;
-                    document.getElementById('loginPage').classList.add('hidden');
-                    document.getElementById('filePage').classList.remove('hidden');
-                    loadFiles('/luadb');
-                }
-            });
-        };
-    </script>
-</body>
-</html>

+ 4 - 3
module/Air8000/project/wifi_ap_read_file/main.lua

@@ -1,8 +1,8 @@
 --[[
 @module  main
 @summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
-@version 1.0
-@date    2025.09.01
+@version 1.1
+@date    2025.09.22
 @author  拓毅恒
 @usage
 演示功能概述
@@ -13,7 +13,8 @@ HTTPSVR 文件管理系统是一种基于Air8000模组的轻量级文件服务
 设备启动后,自动创建AP热点,并初始化SD卡挂载。同时启动HTTP服务器,提供文件列表浏览、文件下载等功能。
 用户只需连接到设备的WiFi热点,通过浏览器访问指定IP地址,即可查看和管理设备中的文件。
 1.3 核心功能特性
-- **AP热点创建**:设备自动创建名为`Air8000_FileHub`的WiFi热点,供用户连接
+- **任务控制**:通过默认配置和boot按键控制是否启停文件管理任务
+- **热点创建**:设备自动创建名为`LuatOS_FileHub`的WiFi热点,供用户连接
 - **SD卡管理**:自动挂载SD卡,支持浏览和下载SD卡中的文件
 - **文件浏览**:通过浏览器查看设备内部存储和SD卡中的文件列表
 - **文件下载**:支持直接通过URL下载文件,支持大文件下载

+ 31 - 24
module/Air8000/project/wifi_ap_read_file/readme.md

@@ -11,7 +11,7 @@ HTTPSVR 文件管理系统是一种基于Air8000模组的轻量级文件服务
 ### 1.3 核心功能特性
 
 - **任务控制**:通过默认配置和boot按键控制是否启停文件管理任务
-- **热点创建**:设备自动创建名为`Air8000_FileHub`的WiFi热点,供用户连接
+- **热点创建**:设备自动创建名为`LuatOS_FileHub`的WiFi热点,供用户连接
 - **SD卡管理**:自动挂载SD卡,支持浏览和下载SD卡中的文件
 - **文件浏览**:通过浏览器查看设备内部存储和SD卡中的文件列表
 - **文件下载**:支持直接通过URL下载文件,支持大文件下载
@@ -38,8 +38,8 @@ HTTPSVR 文件管理系统是一种基于Air8000模组的轻量级文件服务
 
 #### 4、文件访问路径映射
 
-- 访问普通文件(如 `http://192.168.4.1/456.txt`)映射到内部存储 `/luadb/456.txt`
-- 访问SD卡文件(如 `http://192.168.4.1/sd/readme.md`)映射到SD卡路径 `/sd/readme.md`
+- 访问普通文件(如 `http://192.168.4.1/explorer.html/456.txt`)映射到内部存储 `/luadb/456.txt`
+- 访问SD卡文件(如 `http://192.168.4.1/explorer.html/sd/readme.md`)映射到SD卡路径 `/sd/readme.md`
 
 #### 5、安全认证机制
 
@@ -50,22 +50,23 @@ HTTPSVR 文件管理系统是一种基于Air8000模组的轻量级文件服务
 
 #### 6、运行效果
 
-- **WiFi创建成功**:手机/电脑可搜索到`Air8000_FileHub`热点
-- **服务器启动成功**:连接热点后访问`http://192.168.4.1`可进入文件管理界面
+- **WiFi创建成功**:手机/电脑可搜索到`LuatOS_FileHub`热点
+- **服务器启动成功**:连接热点后访问`http://192.168.4.1/explorer.html`可进入文件管理界面
 - **文件浏览**:可查看设备内部存储和SD卡中的文件列表
 - **文件下载**:点击文件或通过直接URL可下载文件到本地
 
 #### 7、快速上手
 
-1. 烧录固件后上电,设备自动创建WiFi热点并启动文件服务器
-2. 使用手机或电脑连接到`Air8000_FileHub`热点(密码:12345678)
-3. 打开浏览器,输入`http://192.168.4.1`访问文件管理系统
-4. 使用用户名`admin`和密码`123456`登录
-5. 浏览文件列表,点击文件可直接下载
+1. 烧录固件后上电,设备初始化按键功能,等待按键初始化完毕日志中会输出"系统已就绪,等待boot按键触发"的打印
+2. 按下boot按键,设备自动创建WiFi热点并启动文件服务器
+3. 使用手机或电脑连接到`LuatOS_FileHub`热点(密码:12345678)
+4. 打开浏览器,输入`http://192.168.4.1/explorer.html`访问文件管理系统
+5. 使用用户名`admin`和密码`123456`登录
+6. 浏览文件列表,点击文件可直接下载
 
 ## 演示硬件环境
 
-1、Air8000核心板/开发板一块
+1、Air8000开发板一块
 
 2、配套天线一套
 
@@ -87,10 +88,16 @@ HTTPSVR 文件管理系统是一种基于Air8000模组的轻量级文件服务
 
 3、烧录完成后,给设备上电,观察串口日志确认系统正常启动
 
+```lua
+[000000000.497] I/user.main 系统已就绪,等待boot按键触发
+```
+
+4、按下boot按键,启动文件管理系统。
+
 ```lua
 系统启动,创建AP:
 [000000000.434] I/user.main WIFI_AP_READ_FILE 001.000.000
-[000000000.441] I/user.WIFI 创建AP热点: Air8000_FileHub
+[000000000.441] I/user.WIFI 创建AP热点: LuatOS_FileHub
 [000000000.496] I/user.执行AP创建操作 true 正常吗?
 
 AP创建成功,开始挂载SD卡:
@@ -107,28 +114,28 @@ AP创建成功,开始挂载SD卡:
 SD卡挂载完毕,开始启动系统:
 [000000002.632] I/httpsrv http listen at 192.168.4.1:80
 [000000002.632] I/user.HTTP 文件服务器已启动
-[000000002.632] I/user.HTTP 请连接WiFi: Air8000_FileHub,密码: 12345678
+[000000002.632] I/user.HTTP 请连接WiFi: LuatOS_FileHub,密码: 12345678
 [000000002.633] I/user.HTTP 然后访问: http://192.168.4.1:80
 
 ```
 
-4、在手机或电脑的WiFi设置中,搜索并连接名为`Air8000_FileHub`的热点,密码为`12345678`
+5、在手机或电脑的WiFi设置中,搜索并连接名为`LuatOS_FileHub`的热点,密码为`12345678`
 
-5、连接成功后,打开浏览器,在地址栏输入`http://192.168.4.1`,进入文件管理系统登录页面
+6、连接成功后,打开浏览器,在地址栏输入`http://192.168.4.1/explorer.html`,进入文件管理系统登录页面
 
-6、输入默认用户名`admin`和密码`123456`,点击登录
+7、输入默认用户名`admin`和密码`123456`,点击登录
 
-7、登录成功后,即可查看设备内部存储和SD卡中的文件列表
+8、登录成功后,即可查看设备内部存储和SD卡中的文件列表
 
-8、点击文件列表中文件名后的 **`下载`**,或在地址栏直接输入文件URL(如`http://192.168.4.1/456.txt`),即可下载文件到本地
+9、点击文件列表中文件名后的 **`下载`**,或在地址栏直接输入文件URL(如`http://192.168.4.1/explorer.html/456.txt`),即可下载文件到本地
 
-9、如需要访问SD卡中的文件,点击网页左上角的 `TF/SD目录` ,即可切换到SD卡目录中,如需下载文件,点击文件名后的 **`下载`**,或在地址栏直接输入文件URL格式为`http://192.168.4.1/sd/文件名`(如`http://192.168.4.1/sd/readme.md`),即可下载文件到本地
+10、如需要访问SD卡中的文件,点击网页左上角的 `TF/SD目录` ,即可切换到SD卡目录中,如需下载文件,点击文件名后的 **`下载`**,或在地址栏直接输入文件URL格式为`http://192.168.4.1/explorer.html/sd/文件名`(如`http://192.168.4.1/explorer.html/sd/readme.md`),即可下载文件到本地
 
 ## 系统参数说明
 
 ### AP参数
 
-- SSID:Air8000_FileHub
+- SSID:LuatOS_FileHub
 - 密码:12345678
 - 信道:6
 - 最大连接数:4
@@ -144,7 +151,7 @@ SD卡挂载完毕,开始启动系统:
 
 - 端口:80
 - 支持范围:文件浏览、文件下载
-- 访问地址:http://192.168.4.1
+- 访问地址:<http://192.168.4.1/explorer.html>
 
 ## 注意事项
 
@@ -152,10 +159,10 @@ SD卡挂载完毕,开始启动系统:
 
 2、使用SD卡时,请确保卡片格式为FAT32格式,实测最大容量支持1T
 
-3、SD卡目录下文件夹名称不能为中文,否则会识别失败,无法下载文件夹中文件
+3、确认LuatOS/script/libs扩展库目录下**explorer.html**文件烧录到设备中,否则**无法启动**SERVER服务器;
 
-4、下载大文件时,建议使用稳定的网络连接,避免下载中断
+4、SD卡目录下文件夹名称不能为中文,否则会识别失败,无法下载文件夹中文件
 
-5、如需修改WiFi名称、密码或认证信息,请修改对应配置文件中的相关参数
+5、如需修改WiFi名称、密码或认证信息,请修改`exremotefile.open()`配置文件中的相关参数
 
 6、如遇到无法连接WiFi热点或访问文件系统的情况,请检查设备电源和固件是否正常

+ 0 - 63
module/Air8000/project/wifi_ap_read_file/spi_sdcard_init.lua

@@ -1,63 +0,0 @@
---[[
-@module  spi_sdcard_init
-@summary spi_sdcard_init 功能模块
-@version 1.0
-@date    2025.09.02
-@author  拓毅恒
-@usage
-用法实例:
-- 在main.lua中添加:require "spi_sdcard_init"
-- 系统会自动启动SD卡初始化任务
-- 初始化完成后,可以通过文件系统API访问SD卡文件
-
-注意事项:
-- 使用时请确保正确连接SD卡硬件
-- SD卡需为FAT32格式
-- 开发板上TF和以太网是同一个SPI,使用TF时必须要将以太网拉高
-
-本文件没有对外接口,直接在 main.lua 中 require "spi_sdcard_init" 即可加载运行。
-]]
-
-local ETH3V3_EN =140--以太网供电
-local SPI_TF_CS = 20--SD卡片选
-local SPI_ETH_CS = 12--以太网片选
-
--- 注:开发板上TF和以太网是同一个SPI,使用Air8000开发板时必须要将以太网拉高
--- 如果使用其他硬件,需要根据硬件原理图来决定是否需要此操作
--- 配置以太网供电引脚,设置为输出模式,并启用上拉电阻
-gpio.setup(ETH3V3_EN, 1,gpio.PULLUP)
--- 配置以太网片选引脚,设置为输出模式,并启用上拉电阻
-gpio.setup(SPI_ETH_CS, 1,gpio.PULLUP)
-
--- SD卡初始化函数
-function sdcard_init()
-    log.info("SDCARD", "开始初始化SD卡")
-
-    -- 配置SPI,设置SPI1,波特率为400000,用于SD卡初始化
-    local result = spi.setup(1, nil, 0, 0, 8, 400 * 1000)
-    -- 记录SD卡初始化时SPI打开的结果
-    log.info("sdcard_init", "open spi", result)
-
-    -- 配置SD卡片选引脚,设置为输出模式,并启用上拉电阻
-    gpio.setup(SPI_TF_CS, 1, gpio.PULLUP)
-
-    -- 挂载SD卡到文件系统,指定挂载点为"/sd"
-    local mount_result = fatfs.mount(fatfs.SPI, "/sd", 1, SPI_TF_CS, 24 * 1000 * 1000)
-    log.info("SDCARD", "挂载SD卡结果:", mount_result)
-
-    -- 获取SD卡的可用空间信息
-    local data, err = fatfs.getfree("/sd")
-    -- 如果成功获取到可用空间信息
-    if data then
-        -- 记录SD卡的可用空间信息
-        log.info("fatfs", "可用空间", json.encode(data))
-    else
-        -- 记录获取可用空间信息时的错误信息
-        log.info("fatfs", "err", err)
-    end
-
-    log.info("SDCARD", "SD卡初始化完成")
-end
-
--- 启动SD卡配置任务
-sys.taskInit(sdcard_init)

+ 8 - 11
module/Air8000/project/wifi_ap_read_file/task_control.lua

@@ -3,6 +3,8 @@
 -- 1. 设置为true: 开机自动创建ap热点,初始化sd卡,创建http 文件服务器
 -- 2. 设置为false: 默认不创建ap热点,不初始化sd卡,不创建http 文件服务器;通过短按boot按键控制开关
 
+-- 导入exremotefile库
+local exremotefile = require "exremotefile"
 local AUTO_START = false -- 默认使用boot按键控制方式
 
 -- 系统状态变量
@@ -13,11 +15,10 @@ local is_running = false -- 标记系统是否正在运行
 local function start_services()
     if not is_running then
         log.info("main", "启动系统服务")
-
-        -- 加载并初始化各个功能模块
-        require "ap_init"
-        require "spi_sdcard_init"
-        http_server = require "http_server"
+        
+        -- 自定义参数启动(使用8000开发板)
+        -- 启动后连接默认AP热点,访问日志中的地址"http://192.168.4.1:80/explorer.html"来访问文件管理服务器。
+        exremotefile.open(nil, {is_8000_development_board = true})
 
         is_running = true
         log.info("main", "系统服务启动完成")
@@ -29,12 +30,8 @@ local function stop_services()
     if is_running then
         log.info("main", "停止系统服务")
 
-        -- 停止HTTP服务器
-        httpsrv.stop(http_server.SERVER_PORT,nil,socket.LWIP_AP)
-        -- 取消挂载SD
-        fatfs.unmount("/sd")
-        -- 断开AP
-        wlan.stopAP()
+        -- 关闭远程文件管理系统
+        exremotefile.close()
 
         is_running = false
         log.info("main", "系统服务已停止")