Openwrt21.02实现文件列表展示并下载文件

在部署好的Openwrt系统上实现文件列表展示并下载文件

按照Luci开发的标准模式:MVC方式编写

1. 新增controller,注册页面路由、列表函数、下载函数

lua 复制代码
-- luci\lua\luci\controller\myapp\custom_file_downloader.lua

module("luci.controller.myapp.custom_file_downloader", package.seeall)

function index()
    -- 在LuCI主菜单的"系统"下注册一个入口
    entry({"admin", "system", "file_download"}, firstchild(), _("文件下载"), 60).index = true

    -- 第一个子节点:显示文件列表页面
    entry({"admin", "system", "file_download", "list"}, template("custom_file_downloader/file_list"), _("文件列表"),
        1)

    -- 第二个子节点:处理文件下载的实际请求(这是一个调用函数,而非页面)
    entry({"admin", "system", "file_download", "download"}, call("download_file"))
end

-- 替换旧的 get_file_list 函数
function get_file_list(directory)
    local fs = require "nixio.fs"
    local files = {}

    -- 使用 nixio.fs.dir 迭代目录
    for filename in fs.dir(directory) do
        if filename ~= "." and filename ~= ".." then
            local filepath = directory .. "/" .. filename
            local stat = fs.stat(filepath)
            if stat then
                table.insert(files, {
                    name = filename,
                    size = stat.size,
                    -- nixio.fs.stat 返回的 mtime 已经是时间戳
                    modtime = os.date("%Y-%m-%d %H:%M:%S", stat.mtime),
                    path = filepath
                })
            end
        end
    end
    table.sort(files, function(a, b)
        return a.name < b.name
    end)
    return files
end

-- 处理文件下载的Action函数
function download_file()
    local fs = require "nixio.fs"
    local http = require "luci.http"
    local filepath = luci.http.formvalue("file") -- 从GET参数中获取文件路径


    -- **安全检查:确保文件路径在允许的范围内,防止目录遍历攻击**
    local allowed_dir = "/tmp" -- 只允许下载/tmp目录下的文件
    if not filepath or not filepath:find("^" .. allowed_dir) then
        http.status(403, "Forbidden")
        http.write("Access denied.")
        return
    end

    -- 检查文件是否存在且是普通文件
    if not fs.stat(filepath) or fs.stat(filepath).type ~= "reg" then
        http.status(404, "Not Found")
        http.write("File not found.")
        return
    end

    -- 获取文件名(去除路径)
    local filename = filepath:match("([^/]+)$")

    -- 设置HTTP响应头以触发下载
    http.header('Content-Disposition', 'attachment; filename="' .. filename .. '"')
    http.header('Content-Type', 'application/json') -- 这里设置为application/octet-stream也是可以的
    http.header('Content-Length', tostring(fs.stat(filepath).size))

    -- 以二进制块模式读取并发送文件内容
    local chunk_size = 4096 -- 或调整为 8192, 16384 以获得更好性能
    local fd = nixio.open(filepath, "r")
    if fd then
        local chunk = fd:read(chunk_size)
        while chunk do
            http.write(chunk)
            chunk = fd:read(chunk_size)
            if not chunk or #chunk == 0 then
                break
            end
        end
        -- 无论成功与否,都确保关闭文件描述符
        fd:close()

    end

这里的关键步骤是:http.header('Content-Type', 'application/json'),设置为application/octet-stream,也是可以的。

这里的关键步骤还有是:while循环中chunk 判断,bituck 读取文件时,每次读取的字节数可能会小于 chunk_size,这时需要判断 chunk 是否为空或者长度为0,如果为空或者长度为0,则说明文件已读完。

文件读取,除了用二进制块模式读取文件,也可以用其他方式,如 nixio.fs.readfile、readall。

以下是一个示例,使用 readall 读取文件全部内容并发送内容:

lua 复制代码
-- 简洁可靠的读取方式
    local fd = nixio.open(filepath, "r")

    if fd then
        -- 使用 pcall 捕获读取过程中可能发生的任何异常
        local ok, data_or_error = pcall(fd.readall, fd)
        nixio.syslog("info", "ok:" ..tostring(ok))
        nixio.syslog("info", "data:" ..data_or_error)

        -- 读取完成后立即关闭文件,释放资源
        fd:close()

        -- 根据读取结果处理  
        if ok and data_or_error then
            -- 成功读取到数据
            http.write(data_or_error)
            nixio.syslog("info", "data:" ..data_or_error)
        else
            -- 读取失败:ok为false时,data_or_error是错误信息;data_or_error为nil时是EOF。
            -- 可以选择记录日志或返回错误,但至少不会"卡住"
            -- 例如:http.status(500, "Read Error")
            nixio.syslog("info", "data:" .. data_or_error)
            http.write("读取文件时发生错误")
        end
    end
    -- 注意:这个函数不返回任何值,因为我们已经直接操作了HTTP响应流

另一个示例是基于nixio.fs.readfile 函数:

lua 复制代码
    local data = nixio.fs.readfile(filepath)
    if data then
        http.write(data)
        nixio.syslog("info", "data:" .. data)
    else
        nixio.syslog("err", "读取文件失败: " .. filepath)
        http.status(500, "Read Error")
        http.write("读取文件时发生错误")
    end

2. 新增view,实现文件列表页面,调用controller中的函数

lua 复制代码
--luci\lua\luci\view\custom_file_downloader\file_list.htm

<%+header%>
<h2><a href="<%=url('admin/system/file_download')%>"><%:文件下载%></a> >> <%:文件列表%></h2>

<div class="cbi-map">
    <fieldset class="cbi-section">
        <table class="cbi-section-table" style="width: 100%">
            <tr class="cbi-section-table-titles">
                <th class="cbi-section-table-cell">文件名</th>
                <th class="cbi-section-table-cell">大小(字节)</th>
                <th class="cbi-section-table-cell">修改时间</th>
                <th class="cbi-section-table-cell">操作</th>
            </tr>
            <%
            local dir = "/tmp" -- 这里可以修改为你想要展示的目录
            require "luci.controller.myapp.custom_file_downloader" -- 这里是关键的地方,否则报错
            local files = luci.controller.myapp.custom_file_downloader.get_file_list(dir)
            for i, file in ipairs(files) do
            %>
            <tr class="cbi-section-table-row cbi-rowstyle-<%=i%2+1%>">
                <td><%=pcdata(file.name)%></td>
                <td><%=file.size%></td>
                <td><%=file.modtime%></td>
                <td>
                    <!-- 下载链接:调用download动作,并传递文件路径作为参数 -->
                    <a href="<%=url('admin/system/file_download/download')%>?file=<%=luci.http.urlencode(file.path)%>">
                        <button class="cbi-button cbi-button-download">下载</button>
                    </a>
                </td>
            </tr>
            <% end %>
        </table>
    </fieldset>
</div>
<%+footer%>

这里关键步骤是:require "luci.controller.myapp.custom_file_downloader",否则刷新页面会报错:

shell 复制代码
Sun Dec 28 21:23:57 2025 daemon.err uhttpd[4528]: /usr/lib/lua/luci/template.lua:97: Failed to execute template 'custom_file_downloader/file_list'.
Sun Dec 28 21:23:57 2025 daemon.err uhttpd[4528]: A runtime error occurred: [string "/usr/lib/lua/luci/view/custom_file_download..."]:3: attempt to index field 'controller' (a nil value)

这里引入了自定义的模块,这个模块在/usr/lib/lua/luci/controller/myapp/custom_file_downloader.lua中定义,这个模块中定义了get_file_list函数,这个函数会返回一个文件列表,这个列表中包含文件名、大小、修改时间等信息。

相关推荐
上海合宙LuatOS16 小时前
Air8000多网通信- RNDIS/ECM
物联网·lua·嵌入式开发·多网通信
Linux运维技术栈19 小时前
一次暴力枚举攻击的防御实践:从 IP 封禁到 WAF,再到 Nginx+Lua 业务层防御
tcp/ip·nginx·安全·lua·云服务器
诙_2 天前
由C++速通Lua
开发语言·lua
yeshan5 天前
【Draft】基于 cluacov 的 Lua 代码分支覆盖率统计:从行级近似到指令级精确
单元测试·lua
zz0723205 天前
Redis + Lua 实现高性能分布式限流
redis·lua·aop·限流算法·分布式限流
衣舞晨风5 天前
运行时行为盲区:API7 AI 网关CPU打满故障的AI辅助事后复盘
lua·openresty·apisix·coroutine·cpu-saturation·socket-buffer
笨鸟先飞的橘猫8 天前
基于Skynet的分布式游戏场景题:大型MMO的跨服战场系统设计
分布式·学习·游戏·面试·lua
Huanzhi_Lin10 天前
skynet笔记
笔记·lua·skynet·actor·actor模型
笨鸟先飞的橘猫10 天前
MMO游戏中的“跨服团队副本”匹配与状态同步系统
分布式·学习·游戏·lua·skynet
Kiyra11 天前
限流不是加个计数器就行:用 Lua 脚本实现多维度原子限流
开发语言·人工智能·网络协议·职场和发展·架构·lua·ai-native