Openresty + Lua 搭建文件服务器

目的

搭建静态文件服务器,用户访问浏览器地址下载相应的文件

如访问:http://127.0.0.1/doc/test.docx 即是下载test.docx文件

步骤

安装openresty

配置nginx.conf

#user  nobody;
worker_processes  1;

events {
    worker_connections 4096 ;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    lua_package_path "lualib/?.lua;";
    lua_package_cpath "lualib/?.so;";
 
    resolver 114.114.114.114;
 
    server {
        listen       80;
        server_name  localhost;

        # 文件存储路径
        root C:/data/sharefile;
        set $store_dir C:/data/sharefile/; # 文件存储路径
        location / {
            autoindex on; # 索引
            autoindex_exact_size on; # 显示文件大小
            autoindex_localtime on; # 显示文件时间
        }

        location /download {
            alias C:/data/sharefile;
            autoindex on;
            index  index.html index.htm;
        }
        
        location /upload {
            client_max_body_size    4000m;  
            default_type  application/json;
            lua_code_cache on;  
            content_by_lua_file lua_scripts/upload.lua;
        }
        
        location /delete {
            default_type  application/json;
            content_by_lua_file lua_scripts/deletefile.lua;
        }
        
        location /move{
            default_type  application/json;
            content_by_lua_file lua_scripts/move.lua;
        }
        
        location /isexit{
            default_type  application/json;
            content_by_lua_file lua_scripts/isexit.lua;
        }
    }
}

重新启动 openresty

访问 http://127.0.0.1,效果如下

https://jsd.cdn.zzko.cn/gh/valtenhyl/filebed@master/filebed/openresty_index.png

配置

上传文件

-- upload.lua

--==========================================
-- 文件上传
--==========================================

local upload = require "resty.upload"
-- 文件保存的根路径
-- local saveRootPath = "/data/sharefile/"
local saveRootPath = ngx.var.store_dir
local baseurl = "download/"
local urllist = "{"
local dir = "0"
local newfilename
local acdd = "y" --[[自动创建目录]]
local arf = "y" --[[自动重命名文件]]

dir = ngx.req.get_uri_args()["dir"]
if dir == nil then
    dir = "0"
end
local date_ym = os.date("%Y%m");
local date_d = os.date("%d");

acdd = ngx.req.get_uri_args()["acdd"]
if acdd == "n" then
    saveRootPath = saveRootPath .. dir .. "/"
    baseurl = baseurl .. dir .. "/"
else
    saveRootPath = saveRootPath .. date_ym .. "/" .. date_d .. "/" .. dir .. "/"
    baseurl = baseurl .. date_ym .. "/" .. date_d .. "/" .. dir .. "/"
end

arf = ngx.req.get_uri_args()["arf"]

local chunk_size = 40960
local form = upload:new(chunk_size)
if not form then
    ngx.say("{\"error\":\"error\", \"msg\":\"no data post\"}")
    return
end

-- 保存的文件对象
local file

-- 
function getFileName(res)
    local filename = ngx.re.match(res, '(.+)filename="(.+)"(.*)')
    if filename then
        return filename[2]
    end
end

--
function getExtension(str)
    return str:match(".+%.(%w+)$")
end
--
function get16Md5(str)
    return string.sub(str, 9, 24)
end

function getNewFileName(file_name)
    if arf == "n" then
        newfilename = file_name
        return newfilename
    else
        local extension = getExtension(file_name)
        local filedate = os.date("%y%m%d%H%M%S");
        local md5 = get16Md5(ngx.md5(file_name));
        if extension then
            newfilename = filedate .. md5 .. "." .. extension
            if not extension then
                newfilename = filedate .. md5
            end
        end
        return newfilename
    end
end

function dir_exists(path)
    local file = io.open(path, "rb")
    if file then
        --ngx.log(ngx.ERR, "path dir_exists************")
        file:close()
    end
    if not file then
        ngx.log(ngx.ERR, "path not dir_exists************")
        -- linux系统
        -- os.execute('mkdir -p ' .. path)

        -- windows系统
        local path1 = string.gsub(path, '/', '\\')
        os.execute('md '.. path1)
    end
end


--[[
local uritoken = ngx.req.get_uri_args()["token"] 
local cookietoken = ngx.var.cookie_token --$cookie_token
local token = uritoken or cookietoken

if token == nil then
    ngx.say("{\"error\":\"error\", \"msg\":\"token is empty\",url:\"\"}")
    return
end

]]
while true do
    --ngx.log(ngx.ERR, "1111************")
    local typ, res, err = form:read()
    --ngx.log(ngx.ERR, "2222-typ************"..typ)
    if not typ then
        ngx.say("{\"error\":\"error\", \"msg\":\"" .. tostring(err) .. "\"}")
        return
    end

    if typ == "header" then
        --[[
                ngx.log(ngx.ERR, "3333-typ=header************"..type(res))
                ngx.log(ngx.ERR, "RRRR-typ=header-res[1]************"..#(res))

                for key, value in pairs(res) do
                    ngx.log(ngx.ERR, "KKK-typ=header-res[1] key************"..key)
                    ngx.log(ngx.ERR, "VVV-typ=header-res[1] value************"..value)
                end
        ]]
        --ngx.log(ngx.ERR, "4444-typ=header-res[1]************"..res[1])

        if res[1] ~= "Content-Type" then
            --ngx.log(ngx.ERR, "5555-res[2]************" .. res[2])
            local file_name = getFileName(res[2])
            if file_name then
                getNewFileName(file_name)
                dir_exists(saveRootPath)
                local file_name_1 = saveRootPath .. newfilename

                local fileexists = io.open(file_name_1, "rb")
                if fileexists then
                    fileexists:close()
                    ngx.say("{\"error\":\"error\", \"msg\":\"[" .. newfilename .. "] file already exists\"}")
                    return
                end

                -- linux系统
                -- file = io.open(file_name_1, "w+")
                -- windows系统需要加b
                file = io.open(file_name_1, "w+b")
                urllist = urllist .. "\"" .. file_name .. "\":\"" .. baseurl .. newfilename .. "\","
                if not file then
                    ngx.say("{\"error\":\"error\", \"msg\":\"failed to open file " .. file_name_1 .. "\"}")
                    return
                end

            end
        end
    elseif typ == "body" then
        if file then
            --	ngx.log(ngx.ERR, "9999-1111-write************"..res)
            file:write(res)
        end
    elseif typ == "part_end" then
        if file then
            file:close()
        end
        file = nil
    elseif typ == "eof" then
        break
    else
        -- do nothing
    end

end

local ul = string.sub(urllist, 1, -2)
local ull = ul .. "}"
--ngx.log(ngx.ERR, "ull************"..ull)

ngx.header["Content-Type"] = "application/json; charset=utf-8"
--ngx.header["Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept,filedir"
--ngx.header["filedir"] = ""
ngx.say("{\"error\":\"success\", \"msg\":\"success\",\"urllist\":" .. ull .. "}")

http://127.0.0.1/upload?dir=test&acdd=n&arf=n

参数 acddarf 可选

下载文件

download.lua

-- download.lua

--==========================================
-- 下载上传
--==========================================

local http = require "resty.http"

local upload = require "resty.upload"
local oss = require "resty.oss"
ngx.say("1231=================")
-- 获取上传的文件
function readFile()
    local chunk_size = 4096
    local form, err = upload:new(chunk_size)
    form:set_timeout(20000)
    local file = {}
    if not err then
        while true do
            local typ, res, err2 = form:read()
            if not typ then
                err = err2
                print("failed to read: ", err2)
                break
            end
            if typ == 'header' and res[1] == 'Content-Disposition' then
                local filename = string.match(res[2], 'filename="(.*)"')
                file.name = filename
            end
            if typ == 'header' and res[1] == 'Content-Type' then
                file['type'] = res[2]
            end
            if typ == 'body' and file then
                file[typ] = (file[typ] or '') .. res
            end
            if typ == "eof" then
                break
            end
        end
    end
    return file, err
end

local file, err = readFile()

local oss_config = {
    accessKey  = "xxxxxxxxxxxxxxxxxxxx",
    secretKey  = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    bucket      = "xxxx",
    endpoint   = "oss-cn-beijing.aliyuncs.com"
}

local client = oss.new(oss_config)
local url = client:put_object(file.body, file.type, file.name)
ngx.say(url)

ngx.say("asdfasdfasdf2134123")

http://127.0.0.1/download/seal.jpg

移动文件

-- move.lua

--==========================================
-- 移动上传
--==========================================

local storepath = "/data/sharefile/"
local request_method = ngx.var.request_method
local sourcedir = ngx.req.get_uri_args()["sourceDir"]
local filename = ngx.req.get_uri_args()["fileName"]
local targetdir = ngx.req.get_uri_args()["targetDir"]
local romoveCmd = "mv".." "..storepath..sourcedir..filename.." "..storepath..targetdir..filename
local mv_file = os.execute(romoveCmd)
ngx.header["Content-Type"] = "application/json; charset=utf-8"
if(mv_file ~= 0) then 
	ngx.say("{\"error\":\"error\", \"msg\":\"move fail\"}")
else
	ngx.say("{\"error\":\"success\", \"msg\":\""..targetdir..filename.."\"}")
end

删除文件

-- delete.lua

--==========================================
-- 删除上传
--==========================================

local storepath = "/data/sharefile/"
local request_method = ngx.var.request_method
local filename = ngx.req.get_uri_args()["f"]
local rm_file = os.remove(storepath..filename)
ngx.header["Content-Type"]    = "application/json; charset=utf-8"
if(rm_file == nil) then 
	ngx.say("{\"error\":\"error\", \"msg\":\"file not exist or other reasons\"}")
else
	ngx.say("{\"error\":\"success\", \"msg\":\"success\"}")
end