lolly/internal/lua/api_shared_dict.go
xfy ad177e9640 docs(lua): 为 Lua API 模块添加标准化 godoc 注释
为所有 Lua API 文件添加完整的包级和函数级文档注释:
- api_balancer: 负载均衡 API(set_current_peer, set_more_tries 等)
- api_ctx: 请求上下文存储 API(ngx.ctx)
- api_location: 子请求捕获 API(ngx.location.capture)
- api_log: 日志输出 API(ngx.log)
- api_req: 请求对象 API
- api_resp: 响应对象 API
- api_shared_dict: 共享字典 API
- api_socket_tcp: TCP socket API
- api_timer: 定时器 API
- api_var: 变量 API
- engine: Lua 引擎核心
- context: 请求上下文管理
- coroutine: 协程调度器
- middleware: 中间件集成
- filter_writer: 响应过滤器
- cache: Lua 脚本缓存
- shared_dict: 共享字典实现
- socket_manager: socket 连接管理

注释格式遵循 Go 官方风格,包含功能说明、参数说明和注意事项。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 10:59:17 +08:00

477 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package lua 提供 ngx.shared.DICT API 实现。
//
// 该文件实现共享内存字典的 Lua API兼容 OpenResty/ngx_lua 语义。
// 包括:
// - SharedDictManager管理多个命名的 SharedDict 实例
// - Lua 元表注册:支持 dict:get/set/add/replace/incr/delete 等方法
// - dict:flush_all/flush_expired/get_keys/size/free_space 管理方法
//
// 安全说明:
// - 共享字典仅支持字符串键值对
// - incr 操作使用手动整数解析(不使用 strconv 依赖)
// - Scheduler 模式下共享字典仍然可用
//
// 作者xfy
package lua
import (
"sync"
"time"
glua "github.com/yuin/gopher-lua"
)
// SharedDictManager 共享字典管理器。
//
// 管理多个命名的 SharedDict 实例,支持并发安全的创建、查询和清理操作。
type SharedDictManager struct {
// dicts 字典名称到实例的映射
dicts map[string]*SharedDict
// mu 读写锁
mu sync.RWMutex
}
// NewSharedDictManager 创建共享字典管理器实例。
//
// 返回值:
// - *SharedDictManager: 初始化的管理器实例
func NewSharedDictManager() *SharedDictManager {
return &SharedDictManager{
dicts: make(map[string]*SharedDict),
}
}
// CreateDict 创建或获取指定名称的共享字典。
//
// 如果字典已存在则直接返回,否则创建新字典。
//
// 参数:
// - name: 字典名称
// - maxItems: 最大条目数LRU 淘汰阈值)
//
// 返回值:
// - *SharedDict: 共享字典实例
func (m *SharedDictManager) CreateDict(name string, maxItems int) *SharedDict {
m.mu.Lock()
defer m.mu.Unlock()
if dict, ok := m.dicts[name]; ok {
return dict
}
dict := NewSharedDict(name, maxItems)
m.dicts[name] = dict
return dict
}
// GetDict 获取指定名称的共享字典。
//
// 参数:
// - name: 字典名称
//
// 返回值:
// - *SharedDict: 字典实例,不存在时返回 nil
func (m *SharedDictManager) GetDict(name string) *SharedDict {
m.mu.RLock()
defer m.mu.RUnlock()
return m.dicts[name]
}
// Close 清理所有字典引用。
//
// 将 dicts 映射置为 nil释放所有字典引用。
func (m *SharedDictManager) Close() {
m.mu.Lock()
defer m.mu.Unlock()
m.dicts = nil
}
// DictConfig 共享字典配置。
type DictConfig struct {
// Name 字典名称
Name string
// MaxItems 最大条目数
MaxItems int
}
// RegisterSharedDictAPI 注册 ngx.shared.DICT API 到 Lua 状态机。
//
// 在 ngx 表下创建 shared 子表和字典元表,
// 注册 get/set/add/replace/incr/delete/flush_all/flush_expired/get_keys/size/free_space 方法。
//
// 参数:
// - L: Lua 状态
// - manager: 共享字典管理器
// - ngx: ngx 全局表
func RegisterSharedDictAPI(L *glua.LState, manager *SharedDictManager, ngx *glua.LTable) {
// 创建 ngx.shared 表
shared := L.NewTable()
// ngx.shared.DICT - 返回字典 userdata
L.SetField(shared, "DICT", L.NewFunction(func(L *glua.LState) int {
name := L.CheckString(1)
dict := manager.GetDict(name)
if dict == nil {
L.Push(glua.LNil)
L.Push(glua.LString("shared dict not found: " + name))
return 2
}
// 返回字典 userdata
ud := L.NewUserData()
ud.Value = dict
L.SetMetatable(ud, L.GetTypeMetatable("ngx.shared.dict"))
L.Push(ud)
return 1
}))
L.SetField(ngx, "shared", shared)
// 创建字典类型元表
mt := L.NewTypeMetatable("ngx.shared.dict")
L.SetField(mt, "__index", L.NewFunction(dictIndex))
L.SetField(mt, "__newindex", L.NewFunction(dictNewIndex))
L.SetField(mt, "__tostring", L.NewFunction(dictToString))
// 注册字典方法
methods := L.NewTable()
L.SetField(methods, "get", L.NewFunction(dictGet))
L.SetField(methods, "set", L.NewFunction(dictSet))
L.SetField(methods, "add", L.NewFunction(dictAdd))
L.SetField(methods, "replace", L.NewFunction(dictReplace))
L.SetField(methods, "incr", L.NewFunction(dictIncr))
L.SetField(methods, "delete", L.NewFunction(dictDelete))
L.SetField(methods, "flush_all", L.NewFunction(dictFlushAll))
L.SetField(methods, "flush_expired", L.NewFunction(dictFlushExpired))
L.SetField(methods, "get_keys", L.NewFunction(dictGetKeys))
L.SetField(methods, "size", L.NewFunction(dictSize))
L.SetField(methods, "free_space", L.NewFunction(dictFreeSpace))
L.SetField(mt, "methods", methods)
}
// checkSharedDict 从 Lua 调用参数中验证并获取 SharedDict 实例。
//
// 参数:
// - L: Lua 状态
//
// 返回值:
// - *SharedDict: 字典实例,类型不正确时引发 Lua 错误
func checkSharedDict(L *glua.LState) *SharedDict {
ud := L.CheckUserData(1)
dict, ok := ud.Value.(*SharedDict)
if !ok {
L.RaiseError("invalid shared dict")
}
return dict
}
// dictIndex 字典 __index 元方法。
//
// 先检查是否为方法名(如 get、set 等),若是则返回方法;
// 否则作为 key 获取字典中的值。
func dictIndex(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
// 检查是否是方法
ud, ok := L.Get(1).(*glua.LUserData)
if !ok {
L.RaiseError("expected userdata")
return 0
}
methods := L.GetField(ud.Metatable, "methods")
if method := L.GetField(methods, key); method != glua.LNil {
L.Push(method)
return 1
}
// 否则作为 key 获取值
value, expired, err := dict.Get(key)
if err != nil {
L.RaiseError("%s", err.Error())
return 0
}
if expired {
L.Push(glua.LNil)
L.Push(glua.LString("expired"))
return 2
}
if value == "" {
L.Push(glua.LNil)
return 1
}
L.Push(glua.LString(value))
return 1
}
// dictNewIndex 字典 __newindex 元方法。
//
// 处理 dict[key] = value 形式的赋值,设置永不过期的键值对。
func dictNewIndex(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
value := L.CheckString(3)
ok, err := dict.Set(key, value, 0)
if err != nil {
L.RaiseError("%s", err.Error())
return 0
}
if !ok {
L.Push(glua.LFalse)
L.Push(glua.LString("no memory"))
return 2
}
L.Push(glua.LTrue)
return 1
}
// dictToString 字典 __tostring 元方法,返回 "ngx.shared.dict:{name}" 格式字符串。
func dictToString(L *glua.LState) int {
dict := checkSharedDict(L)
L.Push(glua.LString("ngx.shared.dict:" + dict.name))
return 1
}
// dictGet 实现 dict:get(key) 方法。
//
// 获取指定键的值,支持过期检测。
//
// 返回值(推入 Lua 栈):
// - value: 键对应的值,不存在或过期时返回 nil
// - flags: 标志位(当前始终返回 0
// - err: 错误信息(不存在时不返回)
func dictGet(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
value, expired, err := dict.Get(key)
if err != nil {
L.Push(glua.LNil)
L.Push(glua.LString(err.Error()))
return 2
}
if expired {
L.Push(glua.LNil)
L.Push(glua.LString("expired"))
return 2
}
if value == "" {
L.Push(glua.LNil)
return 1
}
L.Push(glua.LString(value))
L.Push(glua.LNumber(0)) // flags暂不支持
return 2
}
// dictSet 实现 dict:set(key, value, exptime?, flags?) 方法。
//
// 设置键值对,支持可选过期时间(秒)。
//
// 返回值(推入 Lua 栈):
// - ok: true 表示设置成功
// - err: 失败时的错误信息
func dictSet(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
value := L.CheckString(3)
ttl := time.Duration(0)
if L.GetTop() >= 4 {
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
}
// flags 参数暂不使用
ok, err := dict.Set(key, value, ttl)
if err != nil {
L.Push(glua.LFalse)
L.Push(glua.LString(err.Error()))
return 2
}
if !ok {
L.Push(glua.LFalse)
L.Push(glua.LString("no memory"))
return 2
}
L.Push(glua.LTrue)
return 1
}
// dictAdd 实现 dict:add(key, value, exptime?, flags?) 方法。
//
// 仅在键不存在时添加,存在时返回错误。
func dictAdd(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
value := L.CheckString(3)
ttl := time.Duration(0)
if L.GetTop() >= 4 {
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
}
ok, err := dict.Add(key, value, ttl)
if err != nil {
L.Push(glua.LFalse)
L.Push(glua.LString(err.Error()))
return 2
}
if !ok {
L.Push(glua.LFalse)
L.Push(glua.LString("exists"))
return 2
}
L.Push(glua.LTrue)
return 1
}
// dictReplace 实现 dict:replace(key, value, exptime?, flags?) 方法。
//
// 仅在键存在且未过期时替换值,不存在时返回 "not found" 错误。
func dictReplace(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
value := L.CheckString(3)
ttl := time.Duration(0)
if L.GetTop() >= 4 {
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
}
// 检查是否存在Get 返回: value, expired, error
// expired=true 表示存在但已过期expired=false 表示不存在或存在且未过期
// 需要区分不存在和存在的情况
val, expired, _ := dict.Get(key)
// 如果 val 为空且 expired 为 false表示 key 不存在
// 如果 expired 为 true表示 key 存在但已过期(也算不存在)
if val == "" && !expired {
// key 不存在
L.Push(glua.LFalse)
L.Push(glua.LString("not found"))
return 2
}
if expired {
// key 存在但已过期,也算不存在
L.Push(glua.LFalse)
L.Push(glua.LString("not found"))
return 2
}
setOK, err := dict.Set(key, value, ttl)
if err != nil {
L.Push(glua.LFalse)
L.Push(glua.LString(err.Error()))
return 2
}
if !setOK {
L.Push(glua.LFalse)
L.Push(glua.LString("no memory"))
return 2
}
L.Push(glua.LTrue)
return 1
}
// dictIncr 实现 dict:incr(key, value) 方法。
//
// 将指定键的值作为整数自增,返回新值。
// 如果键不存在则创建初始值为 0 再自增。
// 如果值不是纯数字,返回 nil 和错误。
func dictIncr(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
increment := int(L.CheckNumber(3))
newValue, err := dict.Incr(key, increment)
if err != nil {
L.Push(glua.LNil)
L.Push(glua.LString(err.Error()))
return 2
}
if newValue == 0 {
// Incr 返回 0 表示值不是纯数字字符串(非错误)
// 在 Lua 中返回 nil 表示操作失败
L.Push(glua.LNil)
L.Push(glua.LString("not a number"))
return 2
}
L.Push(glua.LNumber(newValue))
return 1
}
// dictDelete 实现 dict:delete(key) 方法。
//
// 删除指定键。
func dictDelete(L *glua.LState) int {
dict := checkSharedDict(L)
key := L.CheckString(2)
_ = dict.Delete(key) // Delete 返回错误,但在 Lua API 中忽略
L.Push(glua.LTrue)
return 1
}
// dictFlushAll 实现 dict:flush_all() 方法。
//
// 清空字典中的所有条目。
func dictFlushAll(L *glua.LState) int {
dict := checkSharedDict(L)
_ = dict.FlushAll() // FlushAll 返回错误,但在 Lua API 中忽略
return 0
}
// dictFlushExpired 实现 dict:flush_expired(max_count?) 方法。
//
// 清除所有过期条目,返回被清除的数量。
func dictFlushExpired(L *glua.LState) int {
dict := checkSharedDict(L)
count := dict.FlushExpired()
L.Push(glua.LNumber(count))
return 1
}
// dictGetKeys 实现 dict:get_keys(max_count?) 方法。
//
// 获取字典中的所有键。当前实现返回空表(待完善)。
func dictGetKeys(L *glua.LState) int {
checkSharedDict(L) // 验证参数但不使用返回值
// 暂不实现完整版,返回空表
keys := L.NewTable()
L.Push(keys)
return 1
}
// dictSize 获取条目数
// dict:size() -> count
func dictSize(L *glua.LState) int {
dict := checkSharedDict(L)
L.Push(glua.LNumber(dict.Size()))
return 1
}
// dictFreeSpace 获取剩余容量
// dict:free_space() -> slots
func dictFreeSpace(L *glua.LState) int {
dict := checkSharedDict(L)
L.Push(glua.LNumber(dict.FreeSlots()))
return 1
}