You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Lua通过multipart/form-data向Paperless-ngx API上传文件?

解决Paperless-ngx API Lua上传400错误(document: No file was submitted)

问题根源

你遇到的400错误是因为Lua代码构建的multipart/form-data请求格式不符合Paperless-ngx API要求,核心问题在于文件字段的Content-Disposition格式不正确,或是请求体未正确包含文件内容、边界符设置错误。

先参考你能正常运行的curl命令格式:

curl -X POST -H "Authorization: Token YOUR_API_TOKEN" \
  -F "document=@/home/user/invoice.pdf;type=application/pdf" \
  -F "title=2024年5月发票" \
  http://paperless.example.com/api/documents/post_document/

curl会自动处理multipart边界符、Content-Type头及文件字段的正确格式,而Lua需要手动构建这些内容(或依赖HTTP库的正确API)。


解决方案(分两种常用Lua HTTP库)

1. 使用lua-resty-http(OpenResty/Nginx环境)

这是最推荐的方式,库本身支持multipart请求构建,无需手动拼接请求体:

local http = require "resty.http"
local httpc = http.new()

-- 设置超时(解决之前的超时问题)
httpc:set_timeout(10000) -- 10秒超时

local file_path = "/path/to/your/document.pdf"
local file_handle = io.open(file_path, "rb")
if not file_handle then
    ngx.log(ngx.ERR, "无法打开文件: ", file_path)
    return
end
local file_content = file_handle:read("*a")
file_handle:close()

local res, err = httpc:request_uri("http://paperless.example.com/api/documents/post_document/", {
    method = "POST",
    headers = {
        ["Authorization"] = "Token YOUR_API_TOKEN",
        -- 无需手动设置Content-Type,库会自动处理
    },
    body = {
        -- 核心:文件字段需指定filename和content_type
        document = {
            filename = "invoice.pdf",
            content_type = "application/pdf",
            content = file_content
        },
        title = "2024年5月发票",
        -- 可选字段示例:correspondent = "1"(对应联系人ID)
    },
    multipart = true -- 启用multipart模式
})

if not res then
    ngx.log(ngx.ERR, "请求失败: ", err)
    return
end

ngx.say("状态码: ", res.status)
ngx.say("响应内容: ", res.body)
httpc:close()

2. 使用luasocket + luasocket.http(通用Lua环境)

普通Lua环境下需手动构建multipart请求体:

local http = require("socket.http")
local ltn12 = require("ltn12")

local boundary = "----LuaPaperlessBoundary" .. os.time()
local file_path = "/path/to/your/document.pdf"
local file_handle = io.open(file_path, "rb")
if not file_handle then
    print("无法打开文件: ", file_path)
    return
end
local file_content = file_handle:read("*a")
file_handle:close()

-- 构建multipart请求体各部分
local parts = {
    -- title字段
    "--" .. boundary .. "\r\n",
    'Content-Disposition: form-data; name="title"\r\n\r\n',
    "2024年5月发票\r\n",
    -- document文件字段(必须包含filename和Content-Type)
    "--" .. boundary .. "\r\n",
    'Content-Disposition: form-data; name="document"; filename="invoice.pdf"\r\n',
    "Content-Type: application/pdf\r\n\r\n",
    file_content .. "\r\n",
    -- 结束边界
    "--" .. boundary .. "--\r\n"
}

local request_body = table.concat(parts)

local response_body = {}
local res, code, headers, status = http.request{
    url = "http://paperless.example.com/api/documents/post_document/",
    method = "POST",
    headers = {
        ["Authorization"] = "Token YOUR_API_TOKEN",
        ["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
        ["Content-Length"] = tostring(#request_body)
    },
    source = ltn12.source.string(request_body),
    sink = ltn12.sink.table(response_body)
}

print("状态码: ", code)
print("响应内容: ", table.concat(response_body))

关键注意事项

  • 文件字段必须指定filename:Paperless-ngx API依赖filename参数识别文件字段,缺少会返回document: No file was submitted错误。
  • 正确设置Content-Type:文件字段要对应正确的MIME类型(如pdf用application/pdf,图片用image/jpeg)。
  • 超时问题解决:显式设置超时时间(如10秒),避免因网络或Paperless处理缓慢导致超时。
  • API Token权限:确保你的Token拥有document.add权限,否则会返回403错误(虽当前是400,但也需排查)。

内容的提问来源于stack exchange,提问作者nodecentral

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 21:02:10