初始提交: 通用数据同步服务

- 基于OpenResty的RESTful API服务
- 支持Cookie/Token等数据类型的存储和管理
- 登录认证保护
- Web管理界面
- 数据过期管理
This commit is contained in:
2026-08-14 09:53:32 +08:00
commit edf4dfcd52
22 changed files with 4389 additions and 0 deletions
+385
View File
@@ -0,0 +1,385 @@
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
-- 更新或添加 tagtags是对象,直接赋值)
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"}))
}
}
}
}