主要修复: - errcheck: defer Close 使用 //nolint:errcheck,类型断言改为 ok 检查 - govet fieldalignment: 调整结构体字段顺序优化内存布局 - revive unused-parameter: 将未使用参数改为 _ - exhaustive: 添加缺失的 switch case 或 default - goconst: 提取重复字符串为常量 (accessAllow, accessDeny 等) - staticcheck SA9003: 修复空分支逻辑 - gofmt: 运行 gofmt -w 格式化 - nolintlint: 修复 nolint 注释格式 其他改进: - 更新 .golangci.yml 配置,启用更严格的检查 - 移除未使用的代码和导入 - 简化测试辅助函数调用 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
339 lines
6.6 KiB
Go
339 lines
6.6 KiB
Go
// Package lua 提供 Lua 脚本嵌入能力
|
|
package lua
|
|
|
|
import (
|
|
"container/list"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// SharedDict 共享内存字典
|
|
// 支持并发安全的 key-value 存储,带 LRU 汰出策略
|
|
type SharedDict struct {
|
|
data map[string]*sharedDictEntry
|
|
lruList *list.List
|
|
name string
|
|
maxItems int
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// sharedDictEntry 字典条目
|
|
type sharedDictEntry struct {
|
|
expiredAt time.Time
|
|
element *list.Element
|
|
key string
|
|
value string
|
|
}
|
|
|
|
// NewSharedDict 创建共享字典
|
|
func NewSharedDict(name string, maxItems int) *SharedDict {
|
|
return &SharedDict{
|
|
name: name,
|
|
maxItems: maxItems,
|
|
data: make(map[string]*sharedDictEntry),
|
|
lruList: list.New(),
|
|
}
|
|
}
|
|
|
|
// Get 获取值
|
|
// 返回 value, expired, err
|
|
func (d *SharedDict) Get(key string) (string, bool, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
entry, ok := d.data[key]
|
|
if !ok {
|
|
return "", false, nil // 不存在
|
|
}
|
|
|
|
// 检查过期
|
|
if !entry.expiredAt.IsZero() && time.Now().After(entry.expiredAt) {
|
|
// 已过期,删除并返回
|
|
d.deleteEntry(entry)
|
|
return "", true, nil // 存在但已过期
|
|
}
|
|
|
|
// 更新 LRU - 移到前端
|
|
d.lruList.MoveToFront(entry.element)
|
|
|
|
return entry.value, false, nil
|
|
}
|
|
|
|
// Set 设置值
|
|
// 返回 ok, err (ok=false 表示容量满且无法淘汰)
|
|
func (d *SharedDict) Set(key, value string, ttl time.Duration) (bool, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
// 检查是否已存在
|
|
if entry, ok := d.data[key]; ok {
|
|
// 更新现有条目
|
|
entry.value = value
|
|
if ttl > 0 {
|
|
entry.expiredAt = time.Now().Add(ttl)
|
|
} else {
|
|
entry.expiredAt = time.Time{} // 清除过期时间
|
|
}
|
|
d.lruList.MoveToFront(entry.element)
|
|
return true, nil
|
|
}
|
|
|
|
// 新条目,检查容量
|
|
if len(d.data) >= d.maxItems {
|
|
// 尝试淘汰过期条目
|
|
d.evictExpired()
|
|
if len(d.data) >= d.maxItems {
|
|
// 淘汰 LRU 最久未使用的条目
|
|
if !d.evictLRU() {
|
|
return false, nil // 无法淘汰(字典为空?)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 创建新条目
|
|
expiredAt := time.Time{}
|
|
if ttl > 0 {
|
|
expiredAt = time.Now().Add(ttl)
|
|
}
|
|
|
|
element := d.lruList.PushFront(key)
|
|
entry := &sharedDictEntry{
|
|
key: key,
|
|
value: value,
|
|
expiredAt: expiredAt,
|
|
element: element,
|
|
}
|
|
d.data[key] = entry
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Add 添加值(仅在不存在时设置)
|
|
// 返回 ok, err (ok=false 表示已存在或容量满)
|
|
func (d *SharedDict) Add(key, value string, ttl time.Duration) (bool, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
// 检查是否已存在(包括过期的也算存在)
|
|
if _, ok := d.data[key]; ok {
|
|
return false, nil // 已存在
|
|
}
|
|
|
|
// 检查容量并淘汰
|
|
if len(d.data) >= d.maxItems {
|
|
d.evictExpired()
|
|
if len(d.data) >= d.maxItems {
|
|
if !d.evictLRU() {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// 创建新条目
|
|
expiredAt := time.Time{}
|
|
if ttl > 0 {
|
|
expiredAt = time.Now().Add(ttl)
|
|
}
|
|
|
|
element := d.lruList.PushFront(key)
|
|
entry := &sharedDictEntry{
|
|
key: key,
|
|
value: value,
|
|
expiredAt: expiredAt,
|
|
element: element,
|
|
}
|
|
d.data[key] = entry
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Incr 自增数值
|
|
// 返回 new_value, err
|
|
func (d *SharedDict) Incr(key string, increment int) (int, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
entry, ok := d.data[key]
|
|
if !ok {
|
|
// 不存在,创建初始值
|
|
if len(d.data) >= d.maxItems {
|
|
d.evictExpired()
|
|
if len(d.data) >= d.maxItems {
|
|
if !d.evictLRU() {
|
|
return 0, nil // 无法创建
|
|
}
|
|
}
|
|
}
|
|
|
|
element := d.lruList.PushFront(key)
|
|
entry = &sharedDictEntry{
|
|
key: key,
|
|
value: "0",
|
|
element: element,
|
|
}
|
|
d.data[key] = entry
|
|
}
|
|
|
|
// 解析数值
|
|
var current int
|
|
for _, c := range entry.value {
|
|
if c < '0' || c > '9' {
|
|
return 0, nil // 不是数值
|
|
}
|
|
current = current*10 + int(c-'0')
|
|
}
|
|
|
|
newValue := current + increment
|
|
entry.value = intToStr(newValue)
|
|
d.lruList.MoveToFront(entry.element)
|
|
|
|
return newValue, nil
|
|
}
|
|
|
|
// Delete 删除条目
|
|
func (d *SharedDict) Delete(key string) error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if entry, ok := d.data[key]; ok {
|
|
d.deleteEntry(entry)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FlushAll 清空所有条目
|
|
func (d *SharedDict) FlushAll() error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
d.data = make(map[string]*sharedDictEntry)
|
|
d.lruList = list.New()
|
|
return nil
|
|
}
|
|
|
|
// FlushExpired 清除所有过期条目
|
|
// 返回清除的条目数
|
|
func (d *SharedDict) FlushExpired() int {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
return d.evictExpired()
|
|
}
|
|
|
|
// Size 返回当前条目数
|
|
func (d *SharedDict) Size() int {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
return len(d.data)
|
|
}
|
|
|
|
// FreeSlots 返回剩余容量
|
|
func (d *SharedDict) FreeSlots() int {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
return d.maxItems - len(d.data)
|
|
}
|
|
|
|
// deleteEntry 删除条目(内部方法,已持有锁)
|
|
func (d *SharedDict) deleteEntry(entry *sharedDictEntry) {
|
|
d.lruList.Remove(entry.element)
|
|
delete(d.data, entry.key)
|
|
}
|
|
|
|
// evictExpired 淘汰过期条目(内部方法,已持有锁)
|
|
func (d *SharedDict) evictExpired() int {
|
|
now := time.Now()
|
|
count := 0
|
|
|
|
// 从 LRU 链表尾部(最久未使用)开始检查
|
|
for elem := d.lruList.Back(); elem != nil; {
|
|
// 类型断言检查
|
|
key, ok := elem.Value.(string)
|
|
if !ok {
|
|
// 类型不正确,移除元素
|
|
next := elem.Prev()
|
|
d.lruList.Remove(elem)
|
|
elem = next
|
|
continue
|
|
}
|
|
entry, ok := d.data[key]
|
|
if !ok {
|
|
// 数据不一致,跳过
|
|
next := elem.Prev()
|
|
d.lruList.Remove(elem)
|
|
elem = next
|
|
continue
|
|
}
|
|
|
|
if !entry.expiredAt.IsZero() && now.After(entry.expiredAt) {
|
|
// 已过期,删除
|
|
d.deleteEntry(entry)
|
|
count++
|
|
elem = d.lruList.Back() // 重新从尾部开始
|
|
} else {
|
|
break // 未过期,停止(链表顺序保证前面都是未过期的)
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// evictLRU 淘汰 LRU 最久未使用的条目(内部方法,已持有锁)
|
|
func (d *SharedDict) evictLRU() bool {
|
|
if d.lruList.Len() == 0 {
|
|
return false
|
|
}
|
|
|
|
elem := d.lruList.Back()
|
|
if elem == nil {
|
|
return false
|
|
}
|
|
|
|
// 类型断言检查
|
|
key, ok := elem.Value.(string)
|
|
if !ok {
|
|
// 类型不正确,移除链表元素
|
|
d.lruList.Remove(elem)
|
|
return d.evictLRU()
|
|
}
|
|
entry, ok := d.data[key]
|
|
if ok {
|
|
d.deleteEntry(entry)
|
|
return true
|
|
}
|
|
|
|
// 数据不一致,移除链表元素并重试
|
|
d.lruList.Remove(elem)
|
|
return d.evictLRU()
|
|
}
|
|
|
|
// intToStr 整数转字符串(简单实现,避免 strconv 依赖)
|
|
func intToStr(n int) string {
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
|
|
var negative bool
|
|
if n < 0 {
|
|
negative = true
|
|
n = -n
|
|
}
|
|
|
|
var buf []byte
|
|
for n > 0 {
|
|
buf = append(buf, byte('0'+n%10))
|
|
n /= 10
|
|
}
|
|
|
|
if negative {
|
|
buf = append(buf, '-')
|
|
}
|
|
|
|
// 反转
|
|
for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
|
|
buf[i], buf[j] = buf[j], buf[i]
|
|
}
|
|
|
|
return string(buf)
|
|
}
|