mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
55db1c01ec
Result: {"status":"keep","total_issues":8,"eslint_errors":0,"eslint_problems":0,"eslint_warnings":0,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_total":0,"golint_test_usetesting":0,"golint_total":8,"golint_usestdlibvars":0,"golint_vetx_total":0,"golint_wastedassign":0,"measure_s":112,"tsc_errors":0,"vitest_failed":0,"vitest_total":126}
146 lines
4.0 KiB
Go
146 lines
4.0 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package cache provides in-process upload access-control caches.
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/upload/shared"
|
|
uploadstorage "github.com/Rain-kl/Wavelet/internal/apps/upload/storage"
|
|
"github.com/Rain-kl/Wavelet/internal/infra/objectstore"
|
|
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
|
"github.com/Rain-kl/Wavelet/pkg/util"
|
|
)
|
|
|
|
const fileAccessInvalidationChannel = "upload:file_access_invalidation"
|
|
|
|
var (
|
|
accessCacheOnce sync.Once
|
|
|
|
fileAccessWhitelistMu sync.RWMutex
|
|
fileAccessWhitelistTypes map[string]struct{}
|
|
fileAccessWhitelistValid bool
|
|
fileAccessWhitelistCheckedAt time.Time
|
|
)
|
|
|
|
// ResetAccessCaches clears in-process upload access caches.
|
|
func ResetAccessCaches() {
|
|
uploadstorage.ResetMigrationAccessCache()
|
|
|
|
fileAccessWhitelistMu.Lock()
|
|
fileAccessWhitelistValid = false
|
|
fileAccessWhitelistTypes = nil
|
|
fileAccessWhitelistMu.Unlock()
|
|
}
|
|
|
|
// PublishAccessCacheInvalidation broadcasts upload access cache eviction to all nodes.
|
|
func PublishAccessCacheInvalidation(ctx context.Context) {
|
|
if db.Redis != nil {
|
|
_ = db.Redis.Publish(ctx, fileAccessInvalidationChannel, "reset").Err()
|
|
}
|
|
}
|
|
|
|
func ensureAccessCacheListener() {
|
|
accessCacheOnce.Do(startAccessCacheInvalidationListener)
|
|
}
|
|
|
|
func startAccessCacheInvalidationListener() {
|
|
redis := db.Redis // 调用方 goroutine 上捕获,避免 goroutine 内读可变全局(测试会替换 db.Redis)
|
|
if redis == nil {
|
|
return
|
|
}
|
|
|
|
util.Go(func() {
|
|
pubsub := redis.Subscribe(
|
|
context.Background(),
|
|
objectstore.ConfigInvalidationChannel,
|
|
fileAccessInvalidationChannel,
|
|
)
|
|
defer func() {
|
|
_ = pubsub.Close()
|
|
}()
|
|
|
|
for range pubsub.Channel() {
|
|
ResetAccessCaches()
|
|
}
|
|
})
|
|
}
|
|
|
|
// IsFilePublic reports whether uploadType is in the public access whitelist.
|
|
func IsFilePublic(ctx context.Context, uploadType string) bool {
|
|
whitelist := loadFileAccessWhitelist(ctx)
|
|
_, ok := whitelist[strings.ToLower(uploadType)]
|
|
return ok
|
|
}
|
|
|
|
func loadFileAccessWhitelist(ctx context.Context) map[string]struct{} {
|
|
ensureAccessCacheListener()
|
|
|
|
fileAccessWhitelistMu.RLock()
|
|
if fileAccessWhitelistValid && time.Since(fileAccessWhitelistCheckedAt) < time.Duration(shared.AccessCacheTTL)*time.Second {
|
|
types := fileAccessWhitelistTypes
|
|
fileAccessWhitelistMu.RUnlock()
|
|
return types
|
|
}
|
|
fileAccessWhitelistMu.RUnlock()
|
|
|
|
fileAccessWhitelistMu.Lock()
|
|
defer fileAccessWhitelistMu.Unlock()
|
|
|
|
if fileAccessWhitelistValid && time.Since(fileAccessWhitelistCheckedAt) < time.Duration(shared.AccessCacheTTL)*time.Second {
|
|
return fileAccessWhitelistTypes
|
|
}
|
|
|
|
fileAccessWhitelistTypes = fetchFileAccessWhitelist(ctx)
|
|
fileAccessWhitelistValid = true
|
|
fileAccessWhitelistCheckedAt = time.Now()
|
|
return fileAccessWhitelistTypes
|
|
}
|
|
|
|
func fetchFileAccessWhitelist(ctx context.Context) map[string]struct{} {
|
|
whitelist := parseFileAccessWhitelist(ctx)
|
|
types := make(map[string]struct{}, len(whitelist))
|
|
for _, item := range whitelist {
|
|
types[strings.ToLower(item)] = struct{}{}
|
|
}
|
|
return types
|
|
}
|
|
|
|
func parseFileAccessWhitelist(ctx context.Context) []string {
|
|
sc, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyFileAccessWhitelist)
|
|
if err != nil || sc.Value == "" {
|
|
return []string{shared.DefaultPublicUploadType}
|
|
}
|
|
|
|
var whitelist []string
|
|
if err := json.Unmarshal([]byte(sc.Value), &whitelist); err == nil && len(whitelist) > 0 {
|
|
return whitelist
|
|
}
|
|
|
|
whitelist = parseCommaSeparatedWhitelist(sc.Value)
|
|
if len(whitelist) == 0 {
|
|
return []string{shared.DefaultPublicUploadType}
|
|
}
|
|
return whitelist
|
|
}
|
|
|
|
func parseCommaSeparatedWhitelist(value string) []string {
|
|
parts := strings.Split(value, ",")
|
|
whitelist := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
part = strings.TrimSpace(part)
|
|
if part != "" {
|
|
whitelist = append(whitelist, part)
|
|
}
|
|
}
|
|
return whitelist
|
|
}
|