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函数,这个函数会返回一个文件列表,这个列表中包含文件名、大小、修改时间等信息。

相关推荐
_200_2 天前
Lua 运算符
开发语言·junit·lua
_200_2 天前
Lua 基本数据类型
开发语言·junit·lua
print_Hyon2 天前
【CTF-WEB】在线Lua执行器漏洞
lua·ctf
Wang's Blog4 天前
Lua: 事件处理深度解析之从协程到跨平台架构实践
junit·架构·lua
码上宝藏5 天前
从解耦到拓展:Clapper 0.10.0 插件化架构设计与 Lua 脚本集成
linux·开发语言·lua·视频播放器·clapper
蜀中孤鹰5 天前
从秒级到毫秒级:一次Redis限流脚本的深度优化实战
redis·spring cloud·lua
Wang's Blog5 天前
Lua: Web应用开发之OpenResty与Lapis框架深度指南
lua·openresty
木风小助理5 天前
Shell编程中awk命令详解:从基础到高阶应用
lua
每天回答3个问题6 天前
Lua数组
ue4·lua·虚幻引擎