- 基于OpenResty的RESTful API服务 - 支持Cookie/Token等数据类型的存储和管理 - 登录认证保护 - Web管理界面 - 数据过期管理
386 lines
14 KiB
Nginx Configuration File
386 lines
14 KiB
Nginx Configuration File
worker_processes 1;
|
||
error_log stderr warn;
|
||
pid /tmp/nginx.pid;
|
||
|
||
events {
|
||
worker_connections 1024;
|
||
}
|
||
|
||
http {
|
||
include /usr/local/openresty/nginx/conf/mime.types;
|
||
default_type application/json;
|
||
|
||
access_log /dev/stdout;
|
||
|
||
sendfile on;
|
||
keepalive_timeout 65;
|
||
client_max_body_size 10M;
|
||
|
||
# Lua 共享字典用于缓存
|
||
lua_shared_dict data_cache 10m;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name _;
|
||
|
||
# CORS 配置
|
||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||
add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, OPTIONS' always;
|
||
add_header 'Access-Control-Allow-Headers' 'Content-Type, X-Password' always;
|
||
|
||
# OPTIONS 请求
|
||
if ($request_method = 'OPTIONS') {
|
||
return 204;
|
||
}
|
||
|
||
# 健康检查
|
||
location = /health {
|
||
access_log off;
|
||
return 200 '{"status":"ok"}';
|
||
add_header Content-Type application/json;
|
||
}
|
||
|
||
# 登录页面
|
||
location = /login {
|
||
root /usr/local/openresty/nginx/html;
|
||
try_files /login.html =404;
|
||
}
|
||
|
||
# 登录API
|
||
location = /api/login {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson"
|
||
ngx.req.read_body()
|
||
local body = ngx.req.get_body_data()
|
||
local ok, data = pcall(cjson.decode, body)
|
||
|
||
if ok and data.password == "admin123123123" then
|
||
ngx.header["Set-Cookie"] = "auth=verified; Path=/; Max-Age=604800"
|
||
ngx.status = 200
|
||
ngx.say(cjson.encode({success = true}))
|
||
else
|
||
ngx.status = 401
|
||
ngx.say(cjson.encode({error = "Invalid password"}))
|
||
end
|
||
}
|
||
}
|
||
|
||
# 静态文件(需要认证)
|
||
location / {
|
||
access_by_lua_block {
|
||
local cookie = ngx.var.http_cookie
|
||
if not cookie or not string.match(cookie, "auth=verified") then
|
||
return ngx.redirect("/login")
|
||
end
|
||
}
|
||
root /usr/local/openresty/nginx/html;
|
||
index index.html;
|
||
}
|
||
|
||
# POST /api/data - 上传/更新数据
|
||
location = /api/data {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson"
|
||
local method = ngx.req.get_method()
|
||
|
||
-- GET 请求:列出所有 tag
|
||
if method == "GET" then
|
||
-- 读取索引文件
|
||
local index_file = io.open("/data/index.json", "r")
|
||
if not index_file then
|
||
ngx.status = 200
|
||
ngx.say(cjson.encode({tags = {}}))
|
||
return
|
||
end
|
||
|
||
local index_content = index_file:read("*all")
|
||
index_file:close()
|
||
|
||
local ok, index = pcall(cjson.decode, index_content)
|
||
if not ok then
|
||
ngx.status = 500
|
||
ngx.say(cjson.encode({error = "Invalid index file"}))
|
||
return
|
||
end
|
||
|
||
-- 计算状态
|
||
local now = os.time()
|
||
for tag, item in pairs(index.tags) do
|
||
if item.expires_at then
|
||
local expires_time = os.time({
|
||
year = tonumber(item.expires_at:sub(1, 4)),
|
||
month = tonumber(item.expires_at:sub(6, 7)),
|
||
day = tonumber(item.expires_at:sub(9, 10)),
|
||
hour = tonumber(item.expires_at:sub(12, 13)),
|
||
min = tonumber(item.expires_at:sub(15, 16)),
|
||
sec = tonumber(item.expires_at:sub(18, 19))
|
||
})
|
||
|
||
if expires_time < now then
|
||
item.status = "expired"
|
||
elseif (expires_time - now) < 172800 then -- 2 days
|
||
item.status = "expiring_soon"
|
||
else
|
||
item.status = "valid"
|
||
end
|
||
else
|
||
item.status = "no_expiry"
|
||
end
|
||
end
|
||
|
||
ngx.status = 200
|
||
ngx.say(cjson.encode(index))
|
||
return
|
||
end
|
||
|
||
-- POST 请求:上传数据
|
||
if method == "POST" then
|
||
-- 读取请求体
|
||
ngx.req.read_body()
|
||
local body = ngx.req.get_body_data()
|
||
|
||
if not body then
|
||
ngx.status = 400
|
||
ngx.say(cjson.encode({error = "Empty body"}))
|
||
return
|
||
end
|
||
|
||
-- 解析 JSON
|
||
local ok, data = pcall(cjson.decode, body)
|
||
if not ok then
|
||
ngx.status = 400
|
||
ngx.say(cjson.encode({error = "Invalid JSON"}))
|
||
return
|
||
end
|
||
|
||
-- 验证密码
|
||
if data.password ~= "admin123123123" then
|
||
ngx.status = 401
|
||
ngx.say(cjson.encode({error = "Invalid password"}))
|
||
return
|
||
end
|
||
|
||
-- 验证必填字段
|
||
if not data.tag or not data.type or not data.data then
|
||
ngx.status = 400
|
||
ngx.say(cjson.encode({error = "Missing required fields: tag, type, data"}))
|
||
return
|
||
end
|
||
|
||
-- 生成文件名(tag 中的特殊字符替换为 _)
|
||
local filename = data.tag:gsub("[^%w%-]", "_") .. ".json"
|
||
local filepath = "/data/" .. filename
|
||
|
||
-- 添加时间戳
|
||
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
|
||
if not data.metadata then
|
||
data.metadata = {}
|
||
end
|
||
if not data.metadata.created_at then
|
||
data.metadata.created_at = timestamp
|
||
end
|
||
data.metadata.updated_at = timestamp
|
||
|
||
-- 计算过期时间
|
||
if data.metadata.expires_in then
|
||
local expires_at = os.time() + data.metadata.expires_in
|
||
data.metadata.expires_at = os.date("%Y-%m-%d %H:%M:%S", expires_at)
|
||
end
|
||
|
||
-- 移除密码字段
|
||
data.password = nil
|
||
|
||
-- 保存数据文件
|
||
local file = io.open(filepath, "w")
|
||
if not file then
|
||
ngx.status = 500
|
||
ngx.say(cjson.encode({error = "Failed to save file"}))
|
||
return
|
||
end
|
||
file:write(cjson.encode(data))
|
||
file:close()
|
||
|
||
-- 更新索引
|
||
local index_file = io.open("/data/index.json", "r")
|
||
local index = {tags = {}}
|
||
if index_file then
|
||
local index_content = index_file:read("*all")
|
||
index_file:close()
|
||
local ok, parsed = pcall(cjson.decode, index_content)
|
||
if ok then
|
||
index = parsed
|
||
end
|
||
end
|
||
|
||
-- 更新或添加 tag(tags是对象,直接赋值)
|
||
if not index.tags then
|
||
index.tags = {}
|
||
end
|
||
|
||
index.tags[data.tag] = {
|
||
tag = data.tag,
|
||
type = data.type,
|
||
file = filename,
|
||
created_at = index.tags[data.tag] and index.tags[data.tag].created_at or data.metadata.created_at,
|
||
updated_at = timestamp,
|
||
expires_at = data.metadata.expires_at
|
||
}
|
||
|
||
-- 保存索引
|
||
index_file = io.open("/data/index.json", "w")
|
||
if index_file then
|
||
index_file:write(cjson.encode(index))
|
||
index_file:close()
|
||
end
|
||
|
||
ngx.status = 200
|
||
ngx.say(cjson.encode({
|
||
success = true,
|
||
message = "Data saved",
|
||
tag = data.tag,
|
||
timestamp = timestamp
|
||
}))
|
||
return
|
||
end
|
||
|
||
-- 其他方法不支持
|
||
ngx.status = 405
|
||
ngx.say(cjson.encode({error = "Method not allowed"}))
|
||
}
|
||
}
|
||
|
||
# GET /api/data/copy/:tag - 一键复制格式
|
||
location ~ ^/api/data/copy/([^/]+)$ {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson"
|
||
local tag = ngx.var[1]
|
||
|
||
-- 生成文件名
|
||
local filename = tag:gsub("[^%w%-]", "_") .. ".json"
|
||
local filepath = "/data/" .. filename
|
||
|
||
-- 读取数据
|
||
local file = io.open(filepath, "r")
|
||
if not file then
|
||
ngx.status = 404
|
||
ngx.say(cjson.encode({error = "Tag not found"}))
|
||
return
|
||
end
|
||
|
||
local content = file:read("*all")
|
||
file:close()
|
||
|
||
local ok, data = pcall(cjson.decode, content)
|
||
if not ok then
|
||
ngx.status = 500
|
||
ngx.say(cjson.encode({error = "Invalid data file"}))
|
||
return
|
||
end
|
||
|
||
-- 生成复制文本
|
||
local copy_config = {
|
||
serverUrl = "http://47.122.126.244:5001",
|
||
password = "admin123123123",
|
||
tag = tag,
|
||
type = data.type
|
||
}
|
||
|
||
local copy_text = cjson.encode(copy_config)
|
||
|
||
ngx.status = 200
|
||
ngx.say(cjson.encode({
|
||
copy_text = copy_text,
|
||
formatted = "```json\\n" .. copy_text .. "\\n```"
|
||
}))
|
||
}
|
||
}
|
||
|
||
# GET /api/data/:tag - 获取数据
|
||
# DELETE /api/data/:tag - 删除数据
|
||
location ~ ^/api/data/([^/]+)$ {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson"
|
||
local tag = ngx.var[1]
|
||
local method = ngx.req.get_method()
|
||
|
||
-- 生成文件名
|
||
local filename = tag:gsub("[^%w%-]", "_") .. ".json"
|
||
local filepath = "/data/" .. filename
|
||
|
||
-- DELETE 请求
|
||
if method == "DELETE" then
|
||
-- 验证密码
|
||
local password = ngx.var.http_x_password
|
||
if password ~= "admin123123123" then
|
||
ngx.status = 401
|
||
ngx.say(cjson.encode({error = "Invalid password"}))
|
||
return
|
||
end
|
||
|
||
-- 删除文件
|
||
os.remove(filepath)
|
||
|
||
-- 更新索引
|
||
local index_file = io.open("/data/index.json", "r")
|
||
if index_file then
|
||
local index_content = index_file:read("*all")
|
||
index_file:close()
|
||
|
||
local ok, index = pcall(cjson.decode, index_content)
|
||
if ok then
|
||
-- tags 是对象,直接删除key
|
||
if index.tags and index.tags[tag] then
|
||
index.tags[tag] = nil
|
||
end
|
||
|
||
index_file = io.open("/data/index.json", "w")
|
||
if index_file then
|
||
index_file:write(cjson.encode(index))
|
||
index_file:close()
|
||
end
|
||
end
|
||
end
|
||
|
||
ngx.status = 200
|
||
ngx.say(cjson.encode({
|
||
success = true,
|
||
message = "Data deleted",
|
||
tag = tag
|
||
}))
|
||
return
|
||
end
|
||
|
||
-- GET 请求
|
||
if method == "GET" then
|
||
-- 验证密码
|
||
local password = ngx.var.http_x_password
|
||
if password ~= "admin123123123" then
|
||
ngx.status = 401
|
||
ngx.say(cjson.encode({error = "Invalid password"}))
|
||
return
|
||
end
|
||
|
||
-- 读取数据
|
||
local file = io.open(filepath, "r")
|
||
if not file then
|
||
ngx.status = 404
|
||
ngx.say(cjson.encode({error = "Tag not found"}))
|
||
return
|
||
end
|
||
|
||
local content = file:read("*all")
|
||
file:close()
|
||
|
||
ngx.status = 200
|
||
ngx.say(content)
|
||
return
|
||
end
|
||
|
||
-- 其他方法不支持
|
||
ngx.status = 405
|
||
ngx.say(cjson.encode({error = "Method not allowed"}))
|
||
}
|
||
}
|
||
}
|
||
}
|