- config: 反向代理、缓存、负载均衡、安全、SSL 等配置模板 - lua: API 网关、认证、动态路由、限流、WebSocket 等脚本示例 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
139 lines
3.5 KiB
Lua
139 lines
3.5 KiB
Lua
-- cache_handler.lua
|
||
--
|
||
-- 缓存处理逻辑:读取、写入、失效
|
||
-- 配合 nginx.conf 中的 set 指令使用:
|
||
-- set $cache_key "prefix:$request_uri"
|
||
-- set $cache_ttl 60
|
||
--
|
||
-- 在 location 块中使用:
|
||
-- content_by_lua_file /path/to/cache_handler.lua;
|
||
|
||
local cjson = require "cjson.safe"
|
||
|
||
local _M = {}
|
||
|
||
-- 默认 TTL(秒)
|
||
local DEFAULT_TTL = 60
|
||
|
||
--- 获取缓存数据
|
||
-- @param dict 共享字典对象
|
||
-- @param key 缓存键
|
||
-- @return data 解码后的数据, ttl 剩余秒数
|
||
function _M.get(dict, key)
|
||
local value = dict:get(key)
|
||
if not value then
|
||
return nil, 0
|
||
end
|
||
local ttl = dict:ttl(key) or 0
|
||
return cjson.decode(value), ttl
|
||
end
|
||
|
||
--- 设置缓存数据
|
||
-- @param dict 共享字典对象
|
||
-- @param key 缓存键
|
||
-- @param data 要缓存的数据(table)
|
||
-- @param ttl 过期时间(秒),默认 60
|
||
-- @return ok, err, forcible
|
||
function _M.set(dict, key, data, ttl)
|
||
ttl = ttl or DEFAULT_TTL
|
||
local json, err = cjson.encode(data)
|
||
if not json then
|
||
return nil, "encode failed: " .. tostring(err)
|
||
end
|
||
return dict:set(key, json, ttl)
|
||
end
|
||
|
||
--- 手动失效单个 key
|
||
-- @param dict 共享字典对象
|
||
-- @param key 缓存键
|
||
-- @return true
|
||
function _M.invalidate(dict, key)
|
||
dict:delete(key)
|
||
return true
|
||
end
|
||
|
||
--- 批量失效(按前缀匹配)
|
||
-- @param dict 共享字典对象
|
||
-- @param prefix key 前缀
|
||
-- @return count 清除数量
|
||
function _M.invalidate_prefix(dict, prefix)
|
||
local keys = dict:get_keys(0)
|
||
local count = 0
|
||
for _, key in ipairs(keys) do
|
||
if string.sub(key, 1, #prefix) == prefix then
|
||
dict:delete(key)
|
||
count = count + 1
|
||
end
|
||
end
|
||
return count
|
||
end
|
||
|
||
--- 失效全部缓存
|
||
-- @param dict 共享字典对象
|
||
function _M.invalidate_all(dict)
|
||
dict:flush_all()
|
||
dict:flush_expired(0)
|
||
end
|
||
|
||
--- 获取缓存统计
|
||
-- @param dict 共享字典对象
|
||
-- @return stats table
|
||
function _M.stats(dict)
|
||
local keys = dict:get_keys(0)
|
||
return {
|
||
capacity = dict:capacity(),
|
||
free_space = dict:free_space(),
|
||
key_count = #keys,
|
||
}
|
||
end
|
||
|
||
--- 主入口:作为 content_by_lua_file 使用时调用
|
||
-- 从 nginx 变量读取 key/ttl,执行 cache-then-origin 流程
|
||
-- 外部数据通过 _M.fetch_data 回调提供
|
||
function _M.run(fetch_data)
|
||
local cache = ngx.shared.response_cache
|
||
|
||
local key = ngx.var.cache_key
|
||
if not key then
|
||
ngx.log(ngx.ERR, "cache_key variable not set")
|
||
ngx.status = 500
|
||
ngx.say(cjson.encode({error = "cache key not configured"}))
|
||
return
|
||
end
|
||
|
||
local ttl = tonumber(ngx.var.cache_ttl) or DEFAULT_TTL
|
||
|
||
-- 尝试缓存命中
|
||
local cached, remaining = _M.get(cache, key)
|
||
if cached then
|
||
ngx.header["X-Cache"] = "HIT"
|
||
ngx.header["X-Cache-TTL"] = tostring(remaining)
|
||
ngx.header["Content-Type"] = "application/json"
|
||
ngx.say(cjson.encode(cached))
|
||
return
|
||
end
|
||
|
||
-- 缓存未命中,调用外部数据源
|
||
local data, err = fetch_data()
|
||
if not data then
|
||
ngx.status = 502
|
||
ngx.say(cjson.encode({error = err or "upstream error"}))
|
||
return
|
||
end
|
||
|
||
-- 写入缓存
|
||
local ok, set_err, forcible = _M.set(cache, key, data, ttl)
|
||
if not ok then
|
||
ngx.log(ngx.ERR, "cache set failed: ", tostring(set_err))
|
||
end
|
||
if forcible then
|
||
ngx.log(ngx.WARN, "cache LRU eviction occurred")
|
||
end
|
||
|
||
ngx.header["X-Cache"] = "MISS"
|
||
ngx.header["Content-Type"] = "application/json"
|
||
ngx.say(cjson.encode(data))
|
||
end
|
||
|
||
return _M
|