🎉 Initial commit

This commit is contained in:
2025-03-19 21:10:19 +08:00
commit 86c8755f79
70 changed files with 6915 additions and 0 deletions

313
cache/cache.go vendored Normal file
View File

@@ -0,0 +1,313 @@
package cache
import (
"container/list"
"sync"
"time"
)
// CacheOptions 缓存配置选项
type CacheOptions struct {
Enabled bool // 是否启用缓存
Size int // 缓存大小0表示无限制
Expiration time.Duration // 缓存项过期时间0表示永不过期
EvictionType string // 缓存淘汰策略,支持"lru"(最近最少使用)和"fifo"(先进先出)
Compression CompressOptions // 压缩选项
Monitoring MonitorOptions // 监控选项
}
// DefaultCacheOptions 默认缓存配置
var DefaultCacheOptions = CacheOptions{
Enabled: true,
Size: 100, // 默认缓存100个SVG
Expiration: time.Hour, // 默认缓存项过期时间为1小时
EvictionType: "lru", // 默认使用LRU淘汰策略
Compression: DefaultCompressOptions, // 默认压缩选项
Monitoring: DefaultMonitorOptions, // 默认监控选项
}
// CacheKey 缓存键结构
type CacheKey struct {
Id string
SansEnv bool
Theme int
Part int
}
// CacheItem 缓存项结构
type CacheItem struct {
SVG string // SVG内容
Compressed []byte // 压缩后的SVG数据
IsCompressed bool // 是否已压缩
CreatedAt time.Time // 创建时间
LastUsed time.Time // 最后使用时间
}
// PNCache SVG缓存结构
type PNCache struct {
Options CacheOptions
Items map[CacheKey]*list.Element // 存储缓存项的映射
EvictionList *list.List // 用于实现LRU/FIFO的双向链表
Mutex sync.RWMutex
Hits int // 缓存命中次数
Misses int // 缓存未命中次数
Monitor *Monitor // 缓存监控器
}
// NewCache 创建一个新的缓存实例
func NewCache(options CacheOptions) *PNCache {
cache := &PNCache{
Options: options,
Items: make(map[CacheKey]*list.Element),
EvictionList: list.New(),
Hits: 0,
Misses: 0,
}
// 如果启用了监控,创建并启动监控器
if options.Monitoring.Enabled {
cache.Monitor = NewMonitor(cache, options.Monitoring)
cache.Monitor.Start()
}
return cache
}
// NewDefaultCache 使用默认配置创建一个新的缓存实例
func NewDefaultCache() *PNCache {
return NewCache(DefaultCacheOptions)
}
// Get 从缓存中获取SVG
func (c *PNCache) Get(key CacheKey) (string, bool) {
if !c.Options.Enabled {
c.Misses++
return "", false
}
c.Mutex.Lock() // 使用写锁以便更新LRU信息
defer c.Mutex.Unlock()
element, found := c.Items[key]
if !found {
c.Misses++
return "", false
}
// 获取缓存项
cacheItem := element.Value.(*CacheItem)
// 检查是否过期
if c.Options.Expiration > 0 {
if time.Since(cacheItem.CreatedAt) > c.Options.Expiration {
// 删除过期项
c.EvictionList.Remove(element)
delete(c.Items, key)
c.Misses++
return "", false
}
}
// 更新LRU信息
if c.Options.EvictionType == "lru" {
cacheItem.LastUsed = time.Now()
c.EvictionList.MoveToFront(element)
}
c.Hits++
// 如果数据已压缩,需要解压缩
if cacheItem.IsCompressed {
svg, err := DecompressSVG(cacheItem.Compressed, true)
if err != nil {
// 解压失败,返回未压缩的原始数据
return cacheItem.SVG, true
}
return svg, true
}
return cacheItem.SVG, true
}
// Set 将SVG存入缓存
func (c *PNCache) Set(key CacheKey, svg string) {
if !c.Options.Enabled {
return
}
c.Mutex.Lock()
defer c.Mutex.Unlock()
// 尝试压缩SVG数据
var compressed []byte
var isCompressed bool
// 如果启用了压缩尝试压缩SVG
if c.Options.Compression.Enabled {
// 首先优化SVG
optimizedSVG := OptimizeSVG(svg)
// 然后压缩
compressed, isCompressed = CompressSVG(optimizedSVG, c.Options.Compression)
// 如果压缩成功使用优化后的SVG
if isCompressed {
svg = optimizedSVG
}
}
// 检查是否已存在
if element, exists := c.Items[key]; exists {
// 更新现有项
cacheItem := element.Value.(*CacheItem)
cacheItem.SVG = svg
cacheItem.Compressed = compressed
cacheItem.IsCompressed = isCompressed
cacheItem.LastUsed = time.Now()
cacheItem.CreatedAt = time.Now()
// 如果使用LRU策略将项移到链表前端
if c.Options.EvictionType == "lru" {
c.EvictionList.MoveToFront(element)
}
return
}
// 如果达到大小限制,需要淘汰一个项
if c.Options.Size > 0 && len(c.Items) >= c.Options.Size {
c.evictItem()
}
// 创建新的缓存项
now := time.Now()
cacheItem := &CacheItem{
SVG: svg,
Compressed: compressed,
IsCompressed: isCompressed,
CreatedAt: now,
LastUsed: now,
}
// 添加到链表和映射
element := c.EvictionList.PushFront(cacheItem)
c.Items[key] = element
}
// evictItem 根据淘汰策略移除一个缓存项
func (c *PNCache) evictItem() {
if c.EvictionList.Len() == 0 {
return
}
// 获取要淘汰的元素
var element *list.Element
switch c.Options.EvictionType {
case "lru":
// LRU策略移除链表尾部元素最近最少使用
element = c.EvictionList.Back()
default:
// 默认使用FIFO策略移除链表尾部元素最先添加
element = c.EvictionList.Back()
}
if element != nil {
// 从链表中移除
c.EvictionList.Remove(element)
// 从映射中找到并删除对应的键
for k, v := range c.Items {
if v == element {
delete(c.Items, k)
break
}
}
}
}
// Clear 清空缓存
func (c *PNCache) Clear() {
c.Mutex.Lock()
defer c.Mutex.Unlock()
c.Items = make(map[CacheKey]*list.Element)
c.EvictionList = list.New()
c.Hits = 0
c.Misses = 0
}
// Size 返回当前缓存项数量
func (c *PNCache) Size() int {
c.Mutex.RLock()
defer c.Mutex.RUnlock()
return len(c.Items)
}
// Stats 返回缓存统计信息
func (c *PNCache) Stats() (Hits, Misses int, hitRate float64) {
c.Mutex.RLock()
defer c.Mutex.RUnlock()
Hits = c.Hits
Misses = c.Misses
total := Hits + Misses
if total > 0 {
hitRate = float64(Hits) / float64(total)
}
return
}
// RemoveExpired 移除所有过期的缓存项
func (c *PNCache) RemoveExpired() int {
if c.Options.Expiration <= 0 {
return 0
}
c.Mutex.Lock()
defer c.Mutex.Unlock()
count := 0
now := time.Now()
// 遍历所有缓存项,检查是否过期
for key, element := range c.Items {
cacheItem := element.Value.(*CacheItem)
if now.Sub(cacheItem.CreatedAt) > c.Options.Expiration {
// 从链表中移除
c.EvictionList.Remove(element)
// 从映射中删除
delete(c.Items, key)
count++
}
}
return count
}
// GetOptions 获取当前缓存选项
func (c *PNCache) GetOptions() CacheOptions {
c.Mutex.RLock()
defer c.Mutex.RUnlock()
return c.Options
}
// UpdateOptions 更新缓存选项
func (c *PNCache) UpdateOptions(options CacheOptions) {
c.Mutex.Lock()
defer c.Mutex.Unlock()
// 更新选项
c.Options = options
// 如果新的缓存大小小于当前项数,需要淘汰一些项
if c.Options.Size > 0 && c.Options.Size < len(c.Items) {
// 计算需要淘汰的项数
toEvict := len(c.Items) - c.Options.Size
// 淘汰多余的项
for i := 0; i < toEvict; i++ {
c.evictItem()
}
}
}

142
cache/compress.go vendored Normal file
View File

@@ -0,0 +1,142 @@
package cache
import (
"bytes"
"compress/gzip"
"io"
"strings"
)
// CompressOptions 压缩选项
type CompressOptions struct {
Enabled bool // 是否启用压缩
Level int // 压缩级别 (1-9)1为最快压缩9为最佳压缩
MinSizeBytes int // 最小压缩大小,小于此大小的数据不进行压缩
Ratio float64 // 压缩比阈值,压缩后大小/原始大小,小于此值才保存压缩结果
}
// DefaultCompressOptions 默认压缩选项
var DefaultCompressOptions = CompressOptions{
Enabled: true,
Level: 6, // 默认压缩级别为6平衡压缩率和性能
MinSizeBytes: 100, // 默认最小压缩大小为100字节
Ratio: 0.9, // 默认压缩比阈值为0.9即至少要压缩到原始大小的90%以下才保存压缩结果
}
// CompressSVG 压缩SVG数据
// 返回压缩后的数据和是否进行了压缩
func CompressSVG(svg string, options CompressOptions) ([]byte, bool) {
if !options.Enabled || len(svg) < options.MinSizeBytes {
return []byte(svg), false
}
// 创建一个bytes.Buffer来存储压缩数据
var buf bytes.Buffer
// 创建一个gzip.Writer设置压缩级别
writer, err := gzip.NewWriterLevel(&buf, options.Level)
if err != nil {
return []byte(svg), false
}
// 写入SVG数据
_, err = writer.Write([]byte(svg))
if err != nil {
return []byte(svg), false
}
// 关闭writer确保所有数据都被写入
err = writer.Close()
if err != nil {
return []byte(svg), false
}
// 获取压缩后的数据
compressed := buf.Bytes()
// 计算压缩比
ratio := float64(len(compressed)) / float64(len(svg))
// 如果压缩比不理想,返回原始数据
if ratio >= options.Ratio {
return []byte(svg), false
}
return compressed, true
}
// DecompressSVG 解压缩SVG数据
func DecompressSVG(data []byte, isCompressed bool) (string, error) {
// 如果数据未压缩,直接返回字符串
if !isCompressed {
return string(data), nil
}
// 检查数据是否为gzip格式
if !isGzipped(data) {
return string(data), nil
}
// 创建一个gzip.Reader
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return string(data), err
}
defer reader.Close()
// 读取解压缩后的数据
decompressed, err := io.ReadAll(reader)
if err != nil {
return string(data), err
}
return string(decompressed), nil
}
// isGzipped 检查数据是否为gzip格式
func isGzipped(data []byte) bool {
// gzip文件的魔数是0x1f 0x8b
return len(data) > 2 && data[0] == 0x1f && data[1] == 0x8b
}
// OptimizeSVG 优化SVG字符串移除不必要的空白和注释
func OptimizeSVG(svg string) string {
// 移除XML注释
svg = removeXMLComments(svg)
// 移除多余的空白
svg = removeExtraWhitespace(svg)
return svg
}
// removeXMLComments 移除XML注释
func removeXMLComments(svg string) string {
for {
start := strings.Index(svg, "<!--")
if start == -1 {
break
}
end := strings.Index(svg[start:], "-->") + start
if end > start {
svg = svg[:start] + svg[end+3:]
} else {
break
}
}
return svg
}
// removeExtraWhitespace 移除多余的空白
func removeExtraWhitespace(svg string) string {
// 替换多个空白字符为单个空格
svg = strings.Join(strings.Fields(svg), " ")
// 优化常见的SVG标签周围的空白
svg = strings.ReplaceAll(svg, "> <", "><")
svg = strings.ReplaceAll(svg, " />", "/>")
svg = strings.ReplaceAll(svg, " =", "=")
svg = strings.ReplaceAll(svg, "= ", "=")
return svg
}

214
cache/monitor.go vendored Normal file
View File

@@ -0,0 +1,214 @@
package cache
import (
"sync"
"time"
)
// MonitorOptions 缓存监控选项
type MonitorOptions struct {
Enabled bool // 是否启用监控
SampleInterval time.Duration // 采样间隔时间
AdjustInterval time.Duration // 调整间隔时间
MinSize int // 最小缓存大小
MaxSize int // 最大缓存大小
TargetHitRate float64 // 目标命中率
SizeGrowthFactor float64 // 缓存大小增长因子
SizeShrinkFactor float64 // 缓存大小收缩因子
ExpirationFactor float64 // 过期时间调整因子
}
// DefaultMonitorOptions 默认监控选项
var DefaultMonitorOptions = MonitorOptions{
Enabled: true,
SampleInterval: time.Minute, // 每分钟采样一次
AdjustInterval: time.Minute * 10, // 每10分钟调整一次
MinSize: 50, // 最小缓存大小
MaxSize: 1000, // 最大缓存大小
TargetHitRate: 0.8, // 目标命中率80%
SizeGrowthFactor: 1.2, // 增长20%
SizeShrinkFactor: 0.8, // 收缩20%
ExpirationFactor: 1.5, // 过期时间调整因子
}
// CacheStats 缓存统计信息
type CacheStats struct {
Size int // 当前缓存大小
Hits int // 命中次数
Misses int // 未命中次数
HitRate float64 // 命中率
MemoryUsage int64 // 内存使用量(字节)
LastAdjusted time.Time // 最后调整时间
SamplesCount int // 样本数量
AvgAccessTime float64 // 平均访问时间(纳秒)
}
// Monitor 缓存监控器
type Monitor struct {
options MonitorOptions
cache *PNCache
stats CacheStats
sampleHistory []CacheStats
mutex sync.RWMutex
stopChan chan struct{}
isRunning bool
}
// NewMonitor 创建一个新的缓存监控器
func NewMonitor(cache *PNCache, options MonitorOptions) *Monitor {
return &Monitor{
options: options,
cache: cache,
sampleHistory: make([]CacheStats, 0, 100), // 预分配100个样本的容量
stopChan: make(chan struct{}),
isRunning: false,
}
}
// Start 启动监控器
func (m *Monitor) Start() {
if !m.options.Enabled || m.isRunning {
return
}
m.mutex.Lock()
m.isRunning = true
m.mutex.Unlock()
go m.monitorRoutine()
}
// Stop 停止监控器
func (m *Monitor) Stop() {
if !m.isRunning {
return
}
m.mutex.Lock()
m.isRunning = false
m.mutex.Unlock()
m.stopChan <- struct{}{}
}
// GetStats 获取当前缓存统计信息
func (m *Monitor) GetStats() CacheStats {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.stats
}
// monitorRoutine 监控例程
func (m *Monitor) monitorRoutine() {
sampleTicker := time.NewTicker(m.options.SampleInterval)
adjustTicker := time.NewTicker(m.options.AdjustInterval)
defer sampleTicker.Stop()
defer adjustTicker.Stop()
for {
select {
case <-m.stopChan:
return
case <-sampleTicker.C:
m.collectSample()
case <-adjustTicker.C:
m.adjustCache()
}
}
}
// collectSample 收集缓存样本
func (m *Monitor) collectSample() {
m.mutex.Lock()
defer m.mutex.Unlock()
// 获取缓存统计信息
hits, misses, hitRate := m.cache.Stats()
size := m.cache.Size()
// 估算内存使用量(简化计算,实际应用中可能需要更精确的方法)
memoryUsage := int64(size * 1024) // 假设每个缓存项平均占用1KB
// 创建新的统计样本
newStat := CacheStats{
Size: size,
Hits: hits,
Misses: misses,
HitRate: hitRate,
MemoryUsage: memoryUsage,
LastAdjusted: time.Now(),
}
// 添加到历史样本
m.sampleHistory = append(m.sampleHistory, newStat)
// 限制历史样本数量保留最近的100个样本
if len(m.sampleHistory) > 100 {
m.sampleHistory = m.sampleHistory[len(m.sampleHistory)-100:]
}
// 更新当前统计信息
m.stats = newStat
m.stats.SamplesCount = len(m.sampleHistory)
}
// adjustCache 根据统计信息调整缓存
func (m *Monitor) adjustCache() {
m.mutex.Lock()
defer m.mutex.Unlock()
// 如果样本数量不足,不进行调整
if len(m.sampleHistory) < 5 {
return
}
// 计算平均命中率
totalHitRate := 0.0
for _, stat := range m.sampleHistory {
totalHitRate += stat.HitRate
}
avgHitRate := totalHitRate / float64(len(m.sampleHistory))
// 获取当前缓存选项
cacheOptions := m.cache.GetOptions()
// 根据命中率调整缓存大小
newSize := cacheOptions.Size
if avgHitRate < m.options.TargetHitRate {
// 命中率低于目标,增加缓存大小
newSize = int(float64(newSize) * m.options.SizeGrowthFactor)
// 确保不超过最大大小
if newSize > m.options.MaxSize {
newSize = m.options.MaxSize
}
} else if avgHitRate > m.options.TargetHitRate+0.1 && m.stats.Size > m.options.MinSize {
// 命中率远高于目标且缓存大小大于最小值,可以适当减小缓存
newSize = int(float64(newSize) * m.options.SizeShrinkFactor)
// 确保不小于最小大小
if newSize < m.options.MinSize {
newSize = m.options.MinSize
}
}
// 根据访问模式调整过期时间
newExpiration := cacheOptions.Expiration
if avgHitRate < m.options.TargetHitRate {
// 命中率低,增加过期时间
newExpiration = time.Duration(float64(newExpiration) * m.options.ExpirationFactor)
} else if avgHitRate > m.options.TargetHitRate+0.1 {
// 命中率高,可以适当减少过期时间
newExpiration = time.Duration(float64(newExpiration) / m.options.ExpirationFactor)
}
// 应用新的缓存选项
if newSize != cacheOptions.Size || newExpiration != cacheOptions.Expiration {
cacheOptions.Size = newSize
cacheOptions.Expiration = newExpiration
m.cache.UpdateOptions(cacheOptions)
// 更新最后调整时间
m.stats.LastAdjusted = time.Now()
}
}