package pixelnebula import ( "crypto/sha256" "encoding/hex" "fmt" "hash" "log" "os" "path/filepath" "regexp" "runtime" "strconv" "strings" "sync" "time" "github.com/landaiqing/go-pixelnebula/animation" "github.com/landaiqing/go-pixelnebula/cache" "github.com/landaiqing/go-pixelnebula/converter" "github.com/landaiqing/go-pixelnebula/errors" "github.com/landaiqing/go-pixelnebula/style" "github.com/landaiqing/go-pixelnebula/theme" ) const ( hashLength = 12 ) var ( // 优化正则表达式,使用更高效的模式 numberRegex = regexp.MustCompile(`[0-9]`) // 使用非贪婪模式并优化颜色匹配模式 colorRegex = regexp.MustCompile(`#([^;]*);`) // 使用字节池减少内存分配 hashBufPool = sync.Pool{ New: func() interface{} { buf := make([]byte, 64) return &buf }, } // 使用分片锁减少锁竞争 keyCacheShards = 16 // 分片数量 keyCacheLocks = make([]sync.RWMutex, keyCacheShards) keyCacheShardData = make([]map[string][2]int, keyCacheShards) // 使用对象池减少内存分配 builderPool = sync.Pool{ New: func() interface{} { return &strings.Builder{} }, } mapPool = sync.Pool{ New: func() interface{} { return make(map[string]string, 6) }, } keyMapPool = sync.Pool{ New: func() interface{} { return make(map[string][2]int, 6) }, } ) // init 初始化分片缓存 func init() { // 初始化分片缓存 for i := 0; i < keyCacheShards; i++ { keyCacheShardData[i] = make(map[string][2]int) } } // 计算字符串哈希获取分片索引 func getShardIndex(key string) int { var hashKey uint32 for i := 0; i < len(key); i++ { hashKey = hashKey*31 + uint32(key[i]) } return int(hashKey % uint32(keyCacheShards)) } type PNOptions struct { ThemeIndex int // 主题索引 StyleIndex int // 风格索引 ParallelRender bool // 是否启用并行渲染 ConcurrencyPool int // 并发池大小,默认为CPU核心数 } type PixelNebula struct { SvgEnd string ThemeManager *theme.Manager StyleManager *style.Manager AnimManager *animation.Manager Cache *cache.PNCache Hasher hash.Hash Options *PNOptions Width int Height int ImgData []byte } // NewPixelNebula 创建一个PixelNebula实例 func NewPixelNebula() *PixelNebula { return &PixelNebula{ SvgEnd: "", ThemeManager: theme.NewThemeManager(), StyleManager: style.NewShapeManager(), AnimManager: animation.NewAnimationManager(), Hasher: sha256.New(), Options: &PNOptions{ThemeIndex: -1, StyleIndex: -1, ParallelRender: false, ConcurrencyPool: runtime.NumCPU()}, // 初始化为 -1 表示未设置 Width: 231, Height: 231, } } // getSvgStart 根据当前宽高生成SVG开始标签 func (pn *PixelNebula) getSvgStart() string { return fmt.Sprintf("", pn.Width, pn.Height) } // WithTheme 设置固定主题 func (pn *PixelNebula) WithTheme(themeIndex int) *PixelNebula { // 如果已设置 style,则验证主题索引是否有效 if styleIndex := pn.Options.StyleIndex; styleIndex >= 0 { // 获取该风格下的主题数量 themeCount := pn.ThemeManager.ThemeCount(styleIndex) if themeIndex < 0 || themeIndex >= themeCount { log.Printf("pixelnebula: theme index range is:[0, %d), but got %d", themeCount, themeIndex) panic(errors.ErrInvalidTheme) } } pn.Options.ThemeIndex = themeIndex return pn } // WithStyle 设置固定风格 func (pn *PixelNebula) WithStyle(style style.StyleType) *PixelNebula { styleIndex, err := pn.StyleManager.GetStyleIndex(style) if err != nil { panic(err) } pn.Options.StyleIndex = styleIndex return pn } // WithSize 设置尺寸 func (pn *PixelNebula) WithSize(width, height int) *PixelNebula { pn.Width = width pn.Height = height return pn } // WithCustomizeTheme 设置自定义主题 func (pn *PixelNebula) WithCustomizeTheme(theme []theme.Theme) *PixelNebula { pn.ThemeManager.CustomizeTheme(theme) return pn } // WithCustomizeStyle 设置自定义风格 func (pn *PixelNebula) WithCustomizeStyle(style []style.StyleSet) *PixelNebula { pn.StyleManager.CustomizeStyle(style) return pn } // hashToNum 将哈希字符串转换为数字 func (pn *PixelNebula) hashToNum(hash []string) int64 { if len(hash) == 0 { return 0 } // 使用位运算直接计算哈希值,减少内存分配和计算复杂度 var result int64 for _, h := range hash { // 优化:直接处理字符,避免ParseInt的开销 for i := 0; i < len(h); i++ { if h[i] >= '0' && h[i] <= '9' { // 使用位运算进行计算: result = result*10 + (h[i] - '0') result = (result << 3) + (result << 1) + int64(h[i]-'0') } } } // 确保结果为正数 if result < 0 { result = -result } return result } // getCacheKey 生成缓存键的哈希表示 func (pn *PixelNebula) getCacheKey(id string, hash []string, index int, opts *PNOptions) string { // 构造一个唯一的键字符串 var key string if opts != nil && opts.StyleIndex >= 0 && opts.ThemeIndex >= 0 { key = fmt.Sprintf("%s_%d_%d_%d", id, index, opts.StyleIndex, opts.ThemeIndex) } else if len(hash) > 0 { key = fmt.Sprintf("%s_%d_%s", id, index, strings.Join(hash, "")) } else { key = fmt.Sprintf("%s_%d", id, index) } return key } // calcKey 计算主题和部分的键值 func (pn *PixelNebula) calcKey(hash []string, opts *PNOptions) [2]int { // 检查是否使用固定值 if opts != nil && opts.StyleIndex >= 0 && opts.ThemeIndex >= 0 { return [2]int{opts.StyleIndex, opts.ThemeIndex} } // 计算缓存键 cacheKey := strings.Join(hash, "") // 计算分片索引 shardIndex := getShardIndex(cacheKey) // 尝试从缓存中获取结果,使用读锁 keyCacheLocks[shardIndex].RLock() if result, ok := keyCacheShardData[shardIndex][cacheKey]; ok { keyCacheLocks[shardIndex].RUnlock() return result } keyCacheLocks[shardIndex].RUnlock() // 计算哈希值 hashNum := pn.hashToNum(hash) // 获取可用的风格数量 styleCount := pn.ThemeManager.StyleCount() if styleCount == 0 { return [2]int{0, 0} } // 使用位运算优化取模操作 styleIndex := int(hashNum % int64(styleCount)) if styleIndex < 0 { styleIndex = -styleIndex } // 获取该风格下的主题数量 themeCount := pn.ThemeManager.ThemeCount(styleIndex) if themeCount == 0 { return [2]int{styleIndex, 0} } // 使用哈希值的不同部分计算主题索引 themeIndex := int(hashNum % int64(themeCount)) if themeIndex < 0 { themeIndex = -themeIndex } // 将结果存入缓存,使用写锁 result := [2]int{styleIndex, themeIndex} keyCacheLocks[shardIndex].Lock() keyCacheShardData[shardIndex][cacheKey] = result keyCacheLocks[shardIndex].Unlock() return result } // WithCache 设置缓存选项 func (pn *PixelNebula) WithCache(options cache.CacheOptions) *PixelNebula { pn.Cache = cache.NewCache(options) // 确保启动监控器 if pn.Cache != nil && options.Monitoring.Enabled && pn.Cache.GetMonitor() == nil { pn.Cache.Monitor = cache.NewMonitor(pn.Cache, options.Monitoring) pn.Cache.Monitor.Start() } return pn } // WithDefaultCache 设置默认缓存选项 func (pn *PixelNebula) WithDefaultCache() *PixelNebula { pn.Cache = cache.NewDefaultCache() // 确保启动监控器 if pn.Cache != nil && pn.Cache.GetOptions().Monitoring.Enabled && pn.Cache.GetMonitor() == nil { pn.Cache.Monitor = cache.NewMonitor(pn.Cache, pn.Cache.GetOptions().Monitoring) pn.Cache.Monitor.Start() } return pn } // WithCompression 设置压缩选项 func (pn *PixelNebula) WithCompression(options cache.CompressOptions) *PixelNebula { if pn.Cache != nil { cacheOptions := pn.Cache.GetOptions() cacheOptions.Compression = options pn.Cache.UpdateOptions(cacheOptions) } return pn } // WithMonitoring 设置监控选项 func (pn *PixelNebula) WithMonitoring(options cache.MonitorOptions) *PixelNebula { if pn.Cache != nil { cacheOptions := pn.Cache.GetOptions() cacheOptions.Monitoring = options pn.Cache.UpdateOptions(cacheOptions) // 如果启用了监控但监控器尚未创建,则创建并启动监控器 if options.Enabled && pn.Cache.Monitor == nil { pn.Cache.Monitor = cache.NewMonitor(pn.Cache, options) pn.Cache.Monitor.Start() } } return pn } // WithAnimation 添加动画效果 func (pn *PixelNebula) WithAnimation(animation animation.Animation) *PixelNebula { pn.AnimManager.AddAnimation(animation) return pn } // WithRotateAnimation 添加旋转动画 func (pn *PixelNebula) WithRotateAnimation(targetID string, fromAngle, toAngle float64, duration float64, repeatCount int) *PixelNebula { anim := animation.NewRotateAnimation(targetID, fromAngle, toAngle, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithGradientAnimation 添加渐变动画 func (pn *PixelNebula) WithGradientAnimation(targetID string, colors []string, duration float64, repeatCount int, animate bool) *PixelNebula { anim := animation.NewGradientAnimation(targetID, colors, duration, repeatCount, animate) pn.AnimManager.AddAnimation(anim) return pn } // WithTransformAnimation 添加变换动画 func (pn *PixelNebula) WithTransformAnimation(targetID string, transformType string, from, to string, duration float64, repeatCount int) *PixelNebula { anim := animation.NewTransformAnimation(targetID, transformType, from, to, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithFadeAnimation 添加淡入淡出动画 func (pn *PixelNebula) WithFadeAnimation(targetID string, from, to string, duration float64, repeatCount int) *PixelNebula { anim := animation.NewFadeAnimation(targetID, from, to, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithPathAnimation 添加路径动画 func (pn *PixelNebula) WithPathAnimation(targetID string, path string, duration float64, repeatCount int) *PixelNebula { anim := animation.NewPathAnimation(targetID, path, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithPathAnimationRotate 添加带旋转的路径动画 func (pn *PixelNebula) WithPathAnimationRotate(targetID string, path string, rotate string, duration float64, repeatCount int) *PixelNebula { anim := animation.NewPathAnimation(targetID, path, duration, repeatCount) anim.WithRotate(rotate) pn.AnimManager.AddAnimation(anim) return pn } // WithColorAnimation 添加颜色变换动画 func (pn *PixelNebula) WithColorAnimation(targetID string, property string, fromColor, toColor string, duration float64, repeatCount int) *PixelNebula { anim := animation.NewColorAnimation(targetID, property, fromColor, toColor, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithBounceAnimation 添加弹跳动画 func (pn *PixelNebula) WithBounceAnimation(targetID string, property string, from, to string, bounceCount int, duration float64, repeatCount int) *PixelNebula { anim := animation.NewBounceAnimation(targetID, property, from, to, bounceCount, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithWaveAnimation 添加波浪动画 func (pn *PixelNebula) WithWaveAnimation(targetID string, amplitude, frequency float64, direction string, duration float64, repeatCount int) *PixelNebula { anim := animation.NewWaveAnimation(targetID, amplitude, frequency, direction, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithBlinkAnimation 添加闪烁动画 func (pn *PixelNebula) WithBlinkAnimation(targetID string, minOpacity, maxOpacity float64, blinkCount int, duration float64, repeatCount int) *PixelNebula { anim := animation.NewBlinkAnimation(targetID, minOpacity, maxOpacity, blinkCount, duration, repeatCount) pn.AnimManager.AddAnimation(anim) return pn } // WithParallelRender 启用并行渲染 func (pn *PixelNebula) WithParallelRender(enabled bool) *PixelNebula { pn.Options.ParallelRender = enabled return pn } // WithConcurrencyPool 设置并发池大小 func (pn *PixelNebula) WithConcurrencyPool(size int) *PixelNebula { if size <= 0 { size = runtime.NumCPU() } pn.Options.ConcurrencyPool = size return pn } // SVGBuilder 用于处理SVG生成后的链式操作 type SVGBuilder struct { pn *PixelNebula svg string id string sansEnv bool themeIndex int styleIndex int width int height int hasError error } // Generate 现在返回 SVGBuilder func (pn *PixelNebula) Generate(id string, sansEnv bool) *SVGBuilder { return &SVGBuilder{ pn: pn, id: id, sansEnv: sansEnv, width: pn.Width, height: pn.Height, themeIndex: pn.Options.ThemeIndex, styleIndex: pn.Options.StyleIndex, } } // SetTheme 设置主题 func (sb *SVGBuilder) SetTheme(theme int) *SVGBuilder { if sb.hasError != nil { return sb } themeCount := sb.pn.ThemeManager.ThemeCount(sb.styleIndex) if theme < 0 || theme >= themeCount { log.Printf("pixelnebula: theme index range is:[0, %d), but got %d", themeCount, theme) sb.hasError = errors.ErrInvalidTheme return sb } sb.themeIndex = theme return sb } // SetStyle 设置风格 // 注意:当使用WithCustomizeStyle设置自定义风格后,此方法将无法正常工作,应使用SetStyleByIndex代替 func (sb *SVGBuilder) SetStyle(style style.StyleType) *SVGBuilder { if sb.hasError != nil { return sb } index, err := sb.pn.StyleManager.GetStyleIndex(style) if err != nil { sb.hasError = err return sb } sb.styleIndex = index return sb } // SetStyleByIndex 设置风格索引 // 此方法可用于设置自定义风格的索引,特别是在使用WithCustomizeStyle后 func (sb *SVGBuilder) SetStyleByIndex(index int) *SVGBuilder { if sb.hasError != nil { return sb } themeCount := sb.pn.ThemeManager.StyleCount() if index < 0 || index >= themeCount { log.Printf("pixelnebula: style index range is:[0, %d), but got %d", themeCount, index) sb.hasError = errors.ErrInvalidStyleName return sb } sb.styleIndex = index return sb } // SetSize 设置尺寸 func (sb *SVGBuilder) SetSize(width, height int) *SVGBuilder { if sb.hasError != nil { return sb } sb.width = width sb.height = height return sb } // SetAnimation 添加动画效果 func (sb *SVGBuilder) SetAnimation(anim animation.Animation) *SVGBuilder { if sb.hasError != nil { return sb } sb.pn.AnimManager.AddAnimation(anim) return sb } // SetRotateAnimation 添加旋转动画 func (sb *SVGBuilder) SetRotateAnimation(targetID string, fromAngle, toAngle float64, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewRotateAnimation(targetID, fromAngle, toAngle, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetGradientAnimation 添加渐变动画 func (sb *SVGBuilder) SetGradientAnimation(targetID string, colors []string, duration float64, repeatCount int, animate bool) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewGradientAnimation(targetID, colors, duration, repeatCount, animate) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetTransformAnimation 添加变换动画 func (sb *SVGBuilder) SetTransformAnimation(targetID string, transformType string, from, to string, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewTransformAnimation(targetID, transformType, from, to, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetFadeAnimation 添加淡入淡出动画 func (sb *SVGBuilder) SetFadeAnimation(targetID string, from, to string, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewFadeAnimation(targetID, from, to, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetPathAnimation 添加路径动画 func (sb *SVGBuilder) SetPathAnimation(targetID string, path string, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewPathAnimation(targetID, path, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetPathAnimationRotate 添加带旋转的路径动画 func (sb *SVGBuilder) SetPathAnimationRotate(targetID string, path string, rotate string, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewPathAnimation(targetID, path, duration, repeatCount) anim.WithRotate(rotate) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetColorAnimation 添加颜色变换动画 func (sb *SVGBuilder) SetColorAnimation(targetID string, property string, fromColor, toColor string, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewColorAnimation(targetID, property, fromColor, toColor, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetBounceAnimation 添加弹跳动画 func (sb *SVGBuilder) SetBounceAnimation(targetID string, property string, from, to string, bounceCount int, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewBounceAnimation(targetID, property, from, to, bounceCount, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetWaveAnimation 添加波浪动画 func (sb *SVGBuilder) SetWaveAnimation(targetID string, amplitude, frequency float64, direction string, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewWaveAnimation(targetID, amplitude, frequency, direction, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetBlinkAnimation 添加闪烁动画 func (sb *SVGBuilder) SetBlinkAnimation(targetID string, minOpacity, maxOpacity float64, blinkCount int, duration float64, repeatCount int) *SVGBuilder { if sb.hasError != nil { return sb } anim := animation.NewBlinkAnimation(targetID, minOpacity, maxOpacity, blinkCount, duration, repeatCount) sb.pn.AnimManager.AddAnimation(anim) return sb } // SetParallelRender 设置是否启用并行渲染 func (sb *SVGBuilder) SetParallelRender(enabled bool) *SVGBuilder { if sb.hasError != nil { return sb } sb.pn.Options.ParallelRender = enabled return sb } // SetConcurrencyPool 设置并发池大小 func (sb *SVGBuilder) SetConcurrencyPool(size int) *SVGBuilder { if sb.hasError != nil { return sb } if size <= 0 { size = runtime.NumCPU() } sb.pn.Options.ConcurrencyPool = size return sb } // Build 生成最终的SVG func (sb *SVGBuilder) Build() *SVGBuilder { if sb.hasError != nil { return sb } opts := &PNOptions{ ThemeIndex: sb.themeIndex, StyleIndex: sb.styleIndex, } svg, err := sb.pn.generateSVG(sb.id, sb.sansEnv, opts) if err != nil { sb.hasError = err return sb } sb.svg = svg sb.pn.ImgData = []byte(svg) sb.pn.Width = sb.width sb.pn.Height = sb.height return sb } // ToSVG 获取SVG字符串 func (sb *SVGBuilder) ToSVG() (string, error) { if sb.svg == "" { sb = sb.Build() } if sb.hasError != nil { return "", sb.hasError } return sb.svg, nil } // ToBase64 获取Base64编码的SVG字符串 注意:这个设置宽高无效 func (sb *SVGBuilder) ToBase64() (string, error) { if sb.svg == "" { sb = sb.Build() } if sb.hasError != nil { return "", sb.hasError } conv := converter.NewSVGConverter([]byte(sb.svg), sb.width, sb.height) return conv.ToBase64() } // ToFile 将SVG代码保存到文件 func (sb *SVGBuilder) ToFile(filePath string) error { if sb.svg == "" { sb = sb.Build() } if sb.hasError != nil { return sb.hasError } return os.WriteFile(filePath, []byte(sb.svg), 0644) } // 将原来的 GenerateSVG 重命名为 generateSVG,作为内部方法 func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (svg string, err error) { if opts == nil { opts = pn.Options } // 验证参数 if id == "" { return "", errors.ErrAvatarIDRequired } // 如果启用了缓存,先尝试从缓存获取 if pn.Cache != nil { cacheKey := cache.CacheKey{ Id: id, SansEnv: sansEnv, } if opts != nil { cacheKey.Theme = opts.ThemeIndex cacheKey.Part = opts.StyleIndex } if cachedSVG, found := pn.Cache.Get(cacheKey); found { return cachedSVG, nil } } // 使用对象池获取缓冲区 hashBuf := hashBufPool.Get().(*[]byte) defer hashBufPool.Put(hashBuf) // 计算avatarId的哈希值 - 优化版本 pn.Hasher.Reset() pn.Hasher.Write([]byte(id)) sum := pn.Hasher.Sum((*hashBuf)[:0]) s := hex.EncodeToString(sum) hashStr := numberRegex.FindAllString(s, -1) if len(hashStr) < hashLength { return "", errors.ErrInsufficientHash } hashStr = hashStr[0:hashLength] // 从对象池获取映射 p := keyMapPool.Get().(map[string][2]int) defer func() { // 清空并归还对象池 for k := range p { delete(p, k) } keyMapPool.Put(p) }() // 计算各部分的键值 p[string(style.TypeEnv)] = pn.calcKey(hashStr[:2], opts) p[string(style.TypeClo)] = pn.calcKey(hashStr[2:4], opts) p[string(style.TypeHead)] = pn.calcKey(hashStr[4:6], opts) p[string(style.TypeMouth)] = pn.calcKey(hashStr[6:8], opts) p[string(style.TypeEyes)] = pn.calcKey(hashStr[8:10], opts) p[string(style.TypeTop)] = pn.calcKey(hashStr[10:], opts) // 获取结果映射 final := mapPool.Get().(map[string]string) defer func() { // 清空并归还对象池 for k := range final { delete(final, k) } mapPool.Put(final) }() // 根据是否启用并行渲染选择处理方式 if opts.ParallelRender { // 并行处理 var wg sync.WaitGroup errChan := make(chan error, len(p)) // 创建互斥锁来保护 final map var finalMux sync.Mutex // 对每个部分启动一个 goroutine for k, v := range p { wg.Add(1) go func(key string, val [2]int) { defer wg.Done() // 使用临时变量处理这个部分 tempResult := "" // 获取主题颜色 themePart, err := pn.ThemeManager.GetTheme(val[0], val[1]) if err != nil { errChan <- err return } colors, ok := themePart[key] if !ok { errChan <- errors.ErrInvalidColor return } // 获取形状SVG shapeType := style.ShapeType(key) svgPart, err := pn.StyleManager.GetShape(val[0], shapeType) if err != nil { errChan <- err return } match := colorRegex.FindAllStringSubmatch(svgPart, -1) // 从对象池获取Builder sb := builderPool.Get().(*strings.Builder) sb.Reset() sb.Grow(len(svgPart) + 50) // 预分配足够的容量 lastIndex := 0 for i, m := range match { if i < len(colors) { // 找到完整匹配的位置 index := strings.Index(svgPart[lastIndex:], m[0]) + lastIndex // 添加匹配前的部分 sb.WriteString(svgPart[lastIndex:index]) // 添加替换后的颜色 // 检查颜色值是否已经包含#前缀 if strings.HasPrefix(colors[i], "#") { sb.WriteString(colors[i]) } else { sb.WriteString("#") sb.WriteString(colors[i]) } sb.WriteString(";") // 更新lastIndex lastIndex = index + len(m[0]) } } // 添加剩余部分 sb.WriteString(svgPart[lastIndex:]) tempResult = sb.String() // 归还Builder到对象池 builderPool.Put(sb) // 使用互斥锁保护对 final map 的写入 finalMux.Lock() final[key] = tempResult finalMux.Unlock() }(k, v) } // 等待所有部分处理完成 wg.Wait() // 检查是否有错误 select { case err := <-errChan: return "", err default: // 没有错误,继续处理 } } else { // 串行处理 for k, v := range p { if err := pn.processSVGPart(k, v, final); err != nil { return "", err } } } // 使用对象池获取主Builder来构建最终SVG builder := builderPool.Get().(*strings.Builder) builder.Reset() // 预估SVG大小,避免多次内存分配 builder.Grow(2048) // 2KB 应该足够容纳大多数SVG // 添加SVG开始标签 builder.WriteString(pn.getSvgStart()) // 获取动画定义 animations := pn.AnimManager.GenerateSVGAnimations() if animations != "" { builder.WriteString(animations) } // 构建和处理旋转动画 - 使用对象池 rotateAnimations := make(map[string]bool) rotateAnimationSVGs := make(map[string]string) // 收集旋转动画 for _, anim := range pn.AnimManager.GetAnimations() { if rotateAnim, ok := anim.(*animation.RotateAnimation); ok { targetID := anim.GetTargetID() rotateAnimations[targetID] = true // 提取animateTransform部分 svgCode := rotateAnim.GenerateSVG() if start := strings.Index(svgCode, ""); end != -1 { rotateAnimationSVGs[targetID] = svgCode[start : start+end+2] } } } } // 处理环境 if !sansEnv { if _, hasRotate := rotateAnimations["env"]; hasRotate { builder.WriteString("\n") builder.WriteString(final["env"]) if animSVG, ok := rotateAnimationSVGs["env"]; ok { builder.WriteString(animSVG) } builder.WriteString("\n") } else { builder.WriteString(final["env"]) } } // 处理其他元素 elements := []string{"head", "clo", "top", "eyes", "mouth"} for _, elem := range elements { if _, hasRotate := rotateAnimations[elem]; hasRotate { builder.WriteString("\n") builder.WriteString(final[elem]) if animSVG, ok := rotateAnimationSVGs[elem]; ok { builder.WriteString(animSVG) } builder.WriteString("\n") } else { builder.WriteString(final[elem]) } } builder.WriteString(pn.SvgEnd) svg = builder.String() // 将生成的SVG存储到实例中 pn.ImgData = []byte(svg) // 归还Builder到对象池 builderPool.Put(builder) // 如果启用了缓存,将结果存入缓存 if pn.Cache != nil { cacheKey := cache.CacheKey{ Id: id, SansEnv: sansEnv, } if opts != nil { cacheKey.Theme = opts.ThemeIndex cacheKey.Part = opts.StyleIndex } pn.Cache.Set(cacheKey, svg) } return svg, nil } // GetCacheStats 获取缓存统计信息 func (pn *PixelNebula) GetCacheStats() (size, hits, misses int, hitRate float64, enabled bool, maxSize int, expiration time.Duration, evictionType string) { if pn.Cache == nil { log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") return 0, 0, 0, 0, false, 0, 0, "" } hits, misses, hitRate = pn.Cache.Stats() options := pn.Cache.GetOptions() return pn.Cache.Size(), hits, misses, hitRate, options.Enabled, options.Size, options.Expiration, options.EvictionType } // CacheItemInfo 缓存项信息结构体 type CacheItemInfo struct { Key cache.CacheKey SVG string Compressed []byte IsCompressed bool CreatedAt time.Time LastUsed time.Time } // GetCacheItems 获取所有缓存项 func (pn *PixelNebula) GetCacheItems() []CacheItemInfo { var result []CacheItemInfo if pn.Cache == nil { log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") return result } // 获取内部缓存项 items := pn.Cache.GetAllItems() if len(items) == 0 { log.Println("pixelnebula: 缓存中没有数据,请先生成一些SVG") } for key, item := range items { cacheItem := CacheItemInfo{ Key: key, SVG: item.SVG, Compressed: item.Compressed, IsCompressed: item.IsCompressed, CreatedAt: item.CreatedAt, LastUsed: item.LastUsed, } result = append(result, cacheItem) } return result } // MonitorSampleInfo 监控样本信息 type MonitorSampleInfo struct { Timestamp time.Time Size int Hits int Misses int HitRate float64 MemoryUsage int64 } // GetMonitorStats 获取监控统计信息 func (pn *PixelNebula) GetMonitorStats() (enabled bool, sampleInterval, adjustInterval time.Duration, targetHitRate float64, lastAdjusted time.Time, samples []MonitorSampleInfo) { if pn.Cache == nil { log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") return false, 0, 0, 0, time.Time{}, nil } options := pn.Cache.GetOptions().Monitoring // 确保监控器已启用并初始化 if !options.Enabled { log.Println("pixelnebula: 监控未启用,请设置Monitoring.Enabled=true") return options.Enabled, options.SampleInterval, options.AdjustInterval, options.TargetHitRate, time.Time{}, nil } if pn.Cache.GetMonitor() == nil { log.Println("pixelnebula: 监控器未初始化,正在初始化...") pn.Cache.Monitor = cache.NewMonitor(pn.Cache, options) pn.Cache.Monitor.Start() } monitor := pn.Cache.GetMonitor() stats := monitor.GetStats() sampleHistory := monitor.GetSampleHistory() if len(sampleHistory) == 0 { log.Println("pixelnebula: 监控样本为空,请等待采样完成") } // 转换样本历史 samplesInfo := make([]MonitorSampleInfo, 0, len(sampleHistory)) for _, sample := range sampleHistory { sampleInfo := MonitorSampleInfo{ Timestamp: sample.LastAdjusted, Size: sample.Size, Hits: sample.Hits, Misses: sample.Misses, HitRate: sample.HitRate, MemoryUsage: sample.MemoryUsage, } samplesInfo = append(samplesInfo, sampleInfo) } return options.Enabled, options.SampleInterval, options.AdjustInterval, options.TargetHitRate, stats.LastAdjusted, samplesInfo } // DeleteCacheItem 删除指定的缓存项 func (pn *PixelNebula) DeleteCacheItem(key string) bool { if pn.Cache == nil { log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") return false } // 解析key字符串,格式为"id_sansEnv_theme_part" parts := strings.Split(key, "_") if len(parts) < 4 { log.Printf("pixelnebula: 无效的key格式: %s,应为'id_sansEnv_theme_part'", key) return false } id := parts[0] sansEnv := parts[1] == "true" themeItem, err := strconv.Atoi(parts[2]) if err != nil { log.Printf("pixelnebula: 解析theme失败: %v", err) return false } part, err := strconv.Atoi(parts[3]) if err != nil { log.Printf("pixelnebula: 解析part失败: %v", err) return false } // 构造CacheKey cacheKey := cache.CacheKey{ Id: id, SansEnv: sansEnv, Theme: themeItem, Part: part, } result := pn.Cache.DeleteItem(cacheKey) if !result { log.Printf("pixelnebula: 未找到缓存项: %s", key) } return result } // ClearCache 清空缓存 func (pn *PixelNebula) ClearCache() { if pn.Cache == nil { log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") return } pn.Cache.Clear() log.Println("pixelnebula: 缓存已清空") } // 将原来的 generateSVG 方法中的部分代码提取为独立函数,方便并行处理 func (pn *PixelNebula) processSVGPart(k string, v [2]int, final map[string]string) error { // 获取主题颜色 themePart, err := pn.ThemeManager.GetTheme(v[0], v[1]) if err != nil { return err } colors, ok := themePart[k] if !ok { return errors.ErrInvalidColor } // 获取形状SVG shapeType := style.ShapeType(k) svgPart, err := pn.StyleManager.GetShape(v[0], shapeType) if err != nil { return err } match := colorRegex.FindAllStringSubmatch(svgPart, -1) // 从对象池获取Builder sb := builderPool.Get().(*strings.Builder) sb.Reset() sb.Grow(len(svgPart) + 50) // 预分配足够的容量 lastIndex := 0 for i, m := range match { if i < len(colors) { // 找到完整匹配的位置 index := strings.Index(svgPart[lastIndex:], m[0]) + lastIndex // 添加匹配前的部分 sb.WriteString(svgPart[lastIndex:index]) // 添加替换后的颜色 // 检查颜色值是否已经包含#前缀 if strings.HasPrefix(colors[i], "#") { sb.WriteString(colors[i]) } else { sb.WriteString("#") sb.WriteString(colors[i]) } sb.WriteString(";") // 更新lastIndex lastIndex = index + len(m[0]) } } // 添加剩余部分 sb.WriteString(svgPart[lastIndex:]) final[k] = sb.String() // 归还Builder到对象池 builderPool.Put(sb) return nil } // GenerateBatch 批量生成SVG图像 // ids:要生成的ID列表 // sansEnv:是否不包含环境 // opts:生成选项 // 返回:SVG映射表(id -> svg)和错误 func (pn *PixelNebula) GenerateBatch(ids []string, sansEnv bool, opts *PNOptions) (map[string]string, error) { if opts == nil { opts = pn.Options } // 验证参数 if len(ids) == 0 { return nil, errors.ErrAvatarIDRequired } // 创建结果映射 result := make(map[string]string, len(ids)) // 如果没有启用并发处理,则串行生成 if !opts.ParallelRender { for _, id := range ids { svg, err := pn.generateSVG(id, sansEnv, opts) if err != nil { return result, err } result[id] = svg } return result, nil } // 并发生成 // 创建一个工作池限制并发数 workerCount := opts.ConcurrencyPool if workerCount <= 0 { workerCount = runtime.NumCPU() } // 创建任务通道 tasks := make(chan string, len(ids)) for _, id := range ids { tasks <- id } close(tasks) // 创建结果通道 type resultPair struct { id string svg string err error } resultChan := make(chan resultPair, len(ids)) // 启动工作池 var wg sync.WaitGroup for i := 0; i < workerCount; i++ { wg.Add(1) go func() { defer wg.Done() // 创建一个新的PixelNebula实例,但只复制必要的字段,避免资源竞争 // 特别是创建独立的哈希实例 workerPN := &PixelNebula{ SvgEnd: pn.SvgEnd, ThemeManager: pn.ThemeManager, // 这些管理器是安全的,因为它们的方法是并发安全的或只读的 StyleManager: pn.StyleManager, AnimManager: pn.AnimManager, Cache: pn.Cache, // 缓存有自己的锁机制 Hasher: sha256.New(), // 创建新的哈希实例,避免并发访问冲突 Options: opts, Width: pn.Width, Height: pn.Height, } for id := range tasks { svg, err := workerPN.generateSVG(id, sansEnv, opts) resultChan <- resultPair{id, svg, err} } }() } // 等待所有工作完成 go func() { wg.Wait() close(resultChan) }() // 收集结果 for r := range resultChan { if r.err != nil { return result, r.err } result[r.id] = r.svg } return result, nil } // SaveBatchToFiles 批量保存SVG到文件 // ids:要生成的ID列表 // sansEnv:是否不包含环境 // opts:生成选项 // filePathPattern:文件路径模式,如 "output/avatar_%s.svg",其中 %s 将被替换为ID // 返回:成功保存的文件数量和错误 func (pn *PixelNebula) SaveBatchToFiles(ids []string, sansEnv bool, opts *PNOptions, filePathPattern string) (int, error) { if opts == nil { opts = pn.Options } // 验证参数 if len(ids) == 0 { return 0, errors.ErrAvatarIDRequired } if filePathPattern == "" { return 0, fmt.Errorf("文件路径模式不能为空") } // 批量生成SVG svgs, err := pn.GenerateBatch(ids, sansEnv, opts) if err != nil { return 0, err } // 保存文件的处理函数 saveFile := func(id, svg, pathPattern string) error { filePath := fmt.Sprintf(pathPattern, id) // 确保目录存在 dir := filepath.Dir(filePath) if err := os.MkdirAll(dir, 0755); err != nil { return err } return os.WriteFile(filePath, []byte(svg), 0644) } // 如果不启用并行处理,串行保存 if !opts.ParallelRender { count := 0 for id, svg := range svgs { if err := saveFile(id, svg, filePathPattern); err != nil { return count, err } count++ } return count, nil } // 并行保存文件 workerCount := opts.ConcurrencyPool if workerCount <= 0 { workerCount = runtime.NumCPU() } // 创建任务通道 type saveTask struct { id string svg string } tasks := make(chan saveTask, len(svgs)) for id, svg := range svgs { tasks <- saveTask{id, svg} } close(tasks) // 创建结果通道和错误通道 type saveResult struct { success bool err error } resultChan := make(chan saveResult, len(svgs)) // 启动工作池 var wg sync.WaitGroup for i := 0; i < workerCount; i++ { wg.Add(1) go func() { defer wg.Done() for task := range tasks { err := saveFile(task.id, task.svg, filePathPattern) resultChan <- saveResult{err == nil, err} } }() } // 等待所有工作完成 go func() { wg.Wait() close(resultChan) }() // 收集结果 successCount := 0 var firstError error for result := range resultChan { if result.success { successCount++ } else if firstError == nil { // 保存第一个遇到的错误 firstError = result.err } } return successCount, firstError } // BatchToBase64 批量转换SVG到Base64 // svgs: SVG字符串映射,key为ID,value为SVG内容 // width, height: SVG尺寸 // 返回: Base64编码的映射表(id -> base64)和错误 func (pn *PixelNebula) BatchToBase64(svgs map[string]string, width, height int) (map[string]string, error) { if len(svgs) == 0 { return nil, fmt.Errorf("没有需要转换的SVG") } // 创建结果映射 result := make(map[string]string, len(svgs)) // 如果没有启用并发处理,则串行转换 if !pn.Options.ParallelRender { for id, svg := range svgs { conv := converter.NewSVGConverter([]byte(svg), width, height) base64Str, err := conv.ToBase64() if err != nil { return result, err } result[id] = base64Str } return result, nil } // 并发转换 // 创建一个工作池限制并发数 workerCount := pn.Options.ConcurrencyPool if workerCount <= 0 { workerCount = runtime.NumCPU() } // 创建任务通道 type convTask struct { id string svg string } tasks := make(chan convTask, len(svgs)) for id, svg := range svgs { tasks <- convTask{id, svg} } close(tasks) // 创建结果通道 type resultPair struct { id string base64 string err error } resultChan := make(chan resultPair, len(svgs)) // 启动工作池 var wg sync.WaitGroup var resultMutex sync.Mutex // 添加互斥锁保护结果映射 for i := 0; i < workerCount; i++ { wg.Add(1) go func() { defer wg.Done() for task := range tasks { // 每个任务创建一个新的转换器实例 conv := converter.NewSVGConverter([]byte(task.svg), width, height) base64Str, err := conv.ToBase64() // 使用通道传递结果,避免直接操作共享map resultChan <- resultPair{task.id, base64Str, err} } }() } // 等待所有工作完成 go func() { wg.Wait() close(resultChan) }() // 收集结果 for r := range resultChan { if r.err != nil { return result, r.err } // 使用互斥锁保护map写入 resultMutex.Lock() result[r.id] = r.base64 resultMutex.Unlock() } return result, nil } // GenerateBatchBase64 添加批量生成Base64方法,直接从ID生成 func (pn *PixelNebula) GenerateBatchBase64(ids []string, sansEnv bool, opts *PNOptions, width, height int) (map[string]string, error) { if opts == nil { opts = pn.Options } // 先批量生成SVG svgs, err := pn.GenerateBatch(ids, sansEnv, opts) if err != nil { return nil, err } // 批量转换为Base64 return pn.BatchToBase64(svgs, width, height) }