feat: add hook system, QR code login, and layered cache
- Implement extensible hook system for content moderation with builtin and AI-powered moderation hooks - Add QR code login feature with SSE for real-time status updates and scan/confirm/cancel workflow - Introduce layered cache with local LRU + Redis backend for improved read performance - Refactor post and comment services to use hook-based moderation instead of direct AI service calls - Add local cache configuration options (size, buckets, ttl)
This commit is contained in:
167
internal/cache/lru_striped.go
vendored
Normal file
167
internal/cache/lru_striped.go
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
)
|
||||
|
||||
type LRUStriped struct {
|
||||
buckets []*LRU
|
||||
name string
|
||||
}
|
||||
|
||||
type LRUStripedOptions struct {
|
||||
Size int
|
||||
DefaultExpiry time.Duration
|
||||
Name string
|
||||
Buckets int
|
||||
}
|
||||
|
||||
func NewLRUStriped(opts *LRUStripedOptions) *LRUStriped {
|
||||
if opts.Buckets <= 0 {
|
||||
opts.Buckets = runtime.NumCPU()
|
||||
}
|
||||
if opts.Buckets < 4 {
|
||||
opts.Buckets = 4
|
||||
}
|
||||
if opts.Size < opts.Buckets {
|
||||
opts.Size = opts.Buckets * 100
|
||||
}
|
||||
|
||||
opts.Size += int(math.Ceil(float64(opts.Size) * 10.0 / 100.0))
|
||||
bucketSize := (opts.Size / opts.Buckets) + (opts.Size % opts.Buckets)
|
||||
|
||||
buckets := make([]*LRU, opts.Buckets)
|
||||
for i := 0; i < opts.Buckets; i++ {
|
||||
buckets[i] = NewLRU(&LRUOptions{
|
||||
Size: bucketSize,
|
||||
DefaultExpiry: opts.DefaultExpiry,
|
||||
Name: opts.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return &LRUStriped{
|
||||
buckets: buckets,
|
||||
name: opts.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LRUStriped) hashKey(key string) uint64 {
|
||||
return xxhash.Sum64String(key)
|
||||
}
|
||||
|
||||
func (l *LRUStriped) keyBucket(key string) *LRU {
|
||||
return l.buckets[l.hashKey(key)%uint64(len(l.buckets))]
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Set(key string, value any, ttl time.Duration) {
|
||||
l.keyBucket(key).Set(key, value, ttl)
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Get(key string) (any, bool) {
|
||||
return l.keyBucket(key).Get(key)
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Delete(key string) {
|
||||
l.keyBucket(key).Delete(key)
|
||||
}
|
||||
|
||||
func (l *LRUStriped) DeleteByPrefix(prefix string) {
|
||||
for _, bucket := range l.buckets {
|
||||
bucket.DeleteByPrefix(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Clear() {
|
||||
for _, bucket := range l.buckets {
|
||||
bucket.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Exists(key string) bool {
|
||||
return l.keyBucket(key).Exists(key)
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Increment(key string) int64 {
|
||||
return l.keyBucket(key).Increment(key)
|
||||
}
|
||||
|
||||
func (l *LRUStriped) IncrementBy(key string, value int64) int64 {
|
||||
return l.keyBucket(key).IncrementBy(key, value)
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Len() int {
|
||||
var total int
|
||||
for _, bucket := range l.buckets {
|
||||
total += bucket.Len()
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l *LRUStriped) DeleteMulti(keys []string) error {
|
||||
var err error
|
||||
for _, key := range keys {
|
||||
l.Delete(key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *LRUStriped) GetMulti(keys []string, values []any) []error {
|
||||
errs := make([]error, len(values))
|
||||
for i, key := range keys {
|
||||
if val, ok := l.Get(key); ok {
|
||||
values[i] = val
|
||||
} else {
|
||||
errs[i] = ErrKeyNotFound
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (l *LRUStriped) SetMulti(items map[string]any, ttl time.Duration) error {
|
||||
for key, value := range items {
|
||||
l.Set(key, value, ttl)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LRUStriped) Keys() []string {
|
||||
keys := make([]string, 0)
|
||||
for _, bucket := range l.buckets {
|
||||
bucket.lock.RLock()
|
||||
for key, ent := range bucket.items {
|
||||
e := ent.Value.(*lruEntry)
|
||||
if e.generation == bucket.currentGeneration && (e.expires.IsZero() || !time.Now().After(e.expires)) {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
bucket.lock.RUnlock()
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
type LRUStripedInterface interface {
|
||||
Set(key string, value any, ttl time.Duration)
|
||||
Get(key string) (any, bool)
|
||||
Delete(key string)
|
||||
DeleteByPrefix(prefix string)
|
||||
Clear()
|
||||
Exists(key string) bool
|
||||
Increment(key string) int64
|
||||
IncrementBy(key string, value int64) int64
|
||||
Len() int
|
||||
Name() string
|
||||
DeleteMulti(keys []string) error
|
||||
GetMulti(keys []string, values []any) []error
|
||||
SetMulti(items map[string]any, ttl time.Duration) error
|
||||
Keys() []string
|
||||
}
|
||||
|
||||
var _ LRUStripedInterface = (*LRUStriped)(nil)
|
||||
Reference in New Issue
Block a user