清理代码

This commit is contained in:
dragon
2025-09-25 11:57:23 +08:00
parent 9a425a057e
commit 92c0f73020
13 changed files with 92 additions and 74 deletions

48
pkg/middleware/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,48 @@
package cache
import (
"io"
"sync"
"time"
"github.com/pkg/errors"
)
type CacheContent struct {
io.ReadSeekCloser
Length int
LastModified time.Time
}
func (c *CacheContent) ReadToString() (string, error) {
all, err := io.ReadAll(c)
if err != nil {
return "", err
}
return string(all), nil
}
type Cache interface {
Put(key string, reader io.Reader) error
// Get return CacheContent or nil when put nil io.reader
Get(key string) (*CacheContent, error)
Delete(pattern string) error
io.Closer
}
var ErrCacheOutOfMemory = errors.New("内容无法被缓存,超过最大限定值")
// TODO: 优化锁结构
// 复杂场景请使用其他缓存服务
type CacheMemory struct {
l sync.RWMutex
data map[string]*[]byte
lastModify map[string]time.Time
sizeGlobal int
sizeItem int
current int
cache []byte
ordered []string
}

139
pkg/middleware/cache/cache_memory.go vendored Normal file
View File

@@ -0,0 +1,139 @@
package cache
import (
"bytes"
"io"
"os"
"strings"
"sync"
"time"
"gopkg.d7z.net/gitea-pages/pkg/utils"
)
func NewCacheMemory(maxUsage, maxGlobalUsage int) *CacheMemory {
return &CacheMemory{
data: make(map[string]*[]byte),
lastModify: make(map[string]time.Time),
l: sync.RWMutex{},
sizeGlobal: maxGlobalUsage,
sizeItem: maxUsage,
cache: make([]byte, maxUsage+1),
ordered: make([]string, 0),
}
}
func (c *CacheMemory) Put(key string, reader io.Reader) error {
c.l.Lock()
defer c.l.Unlock()
size := 0
// 可以指定空的 reader 作为 404 缓存
if reader != nil {
var err error
size, err = io.ReadAtLeast(reader, c.cache, 1)
if err != nil {
return err
}
}
if size == len(c.cache) {
return ErrCacheOutOfMemory
}
currentItemSize := 0
if data, ok := c.data[key]; ok {
currentItemSize = len(*data)
}
available := c.sizeGlobal + currentItemSize - (c.current + size)
if available < 0 {
// 清理旧的内容
count := 0
for i, k := range c.ordered {
available += len(*c.data[k])
if available > 0 {
break
}
count = i + 1
}
if available < 0 {
// 清理全部内容也无法留出空间
return ErrCacheOutOfMemory
}
for _, s := range c.ordered[:count] {
delete(c.data, s)
delete(c.lastModify, s)
}
c.ordered = c.ordered[count:]
}
if reader != nil {
dest := make([]byte, size)
copy(dest, c.cache[:size])
c.data[key] = &dest
c.lastModify[key] = time.Now()
c.current -= currentItemSize
c.current += len(dest)
} else {
c.data[key] = nil
c.lastModify[key] = time.Now()
c.current -= currentItemSize
}
nextOrdered := make([]string, 0, len(c.ordered))
for _, s := range c.ordered {
if s != key {
nextOrdered = append(nextOrdered, s)
}
}
c.ordered = append(nextOrdered, key)
return nil
}
func (c *CacheMemory) Get(key string) (*CacheContent, error) {
c.l.RLock()
defer c.l.RUnlock()
if i, ok := c.data[key]; ok {
if i == nil {
return nil, nil
}
return &CacheContent{
ReadSeekCloser: utils.NopCloser{
bytes.NewReader(*i),
},
Length: len(*i),
LastModified: c.lastModify[key],
}, nil
}
return nil, os.ErrNotExist
}
func (c *CacheMemory) Delete(pattern string) error {
c.l.Lock()
defer c.l.Unlock()
nextOrder := make([]string, 0, len(c.ordered))
for _, key := range c.ordered {
if strings.HasPrefix(key, pattern) {
c.current -= len(*c.data[key])
delete(c.data, key)
delete(c.lastModify, key)
} else {
nextOrder = append(nextOrder, key)
}
}
clear(c.ordered)
c.ordered = nextOrder
return nil
}
func (c *CacheMemory) Close() error {
c.l.Lock()
defer c.l.Unlock()
clear(c.ordered)
clear(c.data)
clear(c.lastModify)
c.current = 0
return nil
}

75
pkg/middleware/cache/cache_test.go vendored Normal file
View File

@@ -0,0 +1,75 @@
package cache
import (
"fmt"
"io"
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestCacheGetPutDelete(t *testing.T) {
memory := NewCacheMemory(1024, 10240)
require.NoError(t, memory.Put("hello", strings.NewReader("world")))
value, err := memory.Get("hello")
require.NoError(t, err)
all, err := io.ReadAll(value)
require.NoError(t, err)
require.Equal(t, "world", string(all))
require.Equal(t, 5, memory.current)
require.NoError(t, memory.Put("hello", strings.NewReader("kotlin")))
value, err = memory.Get("hello")
require.NoError(t, err)
all, err = io.ReadAll(value)
require.NoError(t, err)
require.Equal(t, "kotlin", string(all))
require.Equal(t, 6, memory.current)
require.Equal(t, 1, len(memory.data))
require.NoError(t, memory.Put("data", strings.NewReader("kotlin")))
require.Equal(t, 12, memory.current)
require.Equal(t, 2, len(memory.data))
require.Equal(t, 2, len(memory.ordered))
require.NoError(t, memory.Delete("hello"))
value, err = memory.Get("hello")
require.Error(t, err)
require.Equal(t, 1, len(memory.data))
require.Equal(t, 1, len(memory.ordered))
require.NoError(t, memory.Put("hello", nil))
value, err = memory.Get("hello")
require.NoError(t, err)
require.Nil(t, value)
}
func TestCacheLimit(t *testing.T) {
memory := NewCacheMemory(5, 5*5)
require.NoError(t, memory.Put("hello", strings.NewReader("world")))
require.Equal(t, 5, memory.current)
require.ErrorIs(t, memory.Put("hello", strings.NewReader("world1")), ErrCacheOutOfMemory)
require.Equal(t, 5, memory.current)
for i := 0; i < 4; i++ {
require.NoError(t, memory.Put(fmt.Sprintf("hello-%d", i), strings.NewReader("govet")))
}
value, err := memory.Get("hello")
require.NoError(t, err)
all, err := io.ReadAll(value)
require.NoError(t, err)
require.Equal(t, "world", string(all))
require.NoError(t, memory.Put("test", strings.NewReader("govet")))
value, err = memory.Get("hello")
require.ErrorIs(t, err, os.ErrNotExist)
require.Equal(t, 5, len(memory.data))
require.Equal(t, 5, len(memory.ordered))
require.Equal(t, 5, len(memory.lastModify))
}