From 1e0fa15c42ca9130fb75c2ec55527ae32968176e Mon Sep 17 00:00:00 2001 From: dragon Date: Wed, 23 Jul 2025 11:24:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=AD=98=E5=82=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=88=B0=20redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 10 +--- go.mod | 7 ++- go.sum | 22 +++++-- pkg/server.go | 2 +- pkg/utils/config.go | 118 +++++++++---------------------------- pkg/utils/config_memory.go | 105 +++++++++++++++++++++++++++++++++ pkg/utils/config_redis.go | 44 ++++++++++++++ 7 files changed, 202 insertions(+), 106 deletions(-) create mode 100644 pkg/utils/config_memory.go create mode 100644 pkg/utils/config_redis.go diff --git a/config.go b/config.go index 9b0c613..70d91c5 100644 --- a/config.go +++ b/config.go @@ -4,7 +4,6 @@ import ( _ "embed" "net/http" "os" - "path/filepath" "text/template" "time" @@ -86,16 +85,11 @@ func (c *Config) NewPageServerOptions() (*pkg.ServerOptions, error) { DefaultErrorHandler: c.ErrorHandler, Cache: utils.NewCacheMemory(int(cacheMaxSize), int(cacheMaxSize)), } - if c.Cache.Storage != "" { - if err := os.MkdirAll(filepath.Dir(c.Cache.Storage), 0o755); err != nil && !os.IsExist(err) { - return nil, err - } - } - memory, err := utils.NewConfigMemory(c.Cache.Storage) + cfg, err := utils.NewAutoConfig(c.Cache.Storage) if err != nil { return nil, errors.Wrapf(err, "failed to init config memory") } - rel.KVConfig = memory + rel.KVConfig = cfg return &rel, nil } diff --git a/go.mod b/go.mod index cad2f18..54a6df6 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/uuid v1.6.0 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/pkg/errors v0.9.1 + github.com/redis/go-redis/v9 v9.11.0 github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 @@ -17,12 +18,14 @@ require ( require ( github.com/42wim/httpsig v1.2.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/sys v0.33.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/sys v0.34.0 // indirect ) diff --git a/go.sum b/go.sum index b6fdb70..ef4dadc 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,19 @@ github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= @@ -25,6 +33,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs= +github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -44,18 +54,18 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/server.go b/pkg/server.go index 3fd8c49..725cc49 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -50,7 +50,7 @@ type ServerOptions struct { } func DefaultOptions(domain string) ServerOptions { - configMemory, _ := utils.NewConfigMemory("") + configMemory, _ := utils.NewAutoConfig("") return ServerOptions{ Domain: domain, DefaultBranch: "gh-pages", diff --git a/pkg/utils/config.go b/pkg/utils/config.go index 433e7b8..2aa7b67 100644 --- a/pkg/utils/config.go +++ b/pkg/utils/config.go @@ -1,13 +1,13 @@ package utils import ( - "encoding/json" + "context" + "fmt" "io" - "os" - "sync" + "net/url" + "strconv" + "strings" "time" - - "go.uber.org/zap" ) const TtlKeep = -1 @@ -19,92 +19,32 @@ type KVConfig interface { io.Closer } -// ConfigMemory 一个简单的内存配置归档,仅用于测试 -type ConfigMemory struct { - data sync.Map - store string -} - -func NewConfigMemory(store string) (KVConfig, error) { - ret := &ConfigMemory{ - store: store, - data: sync.Map{}, +func NewAutoConfig(src string) (KVConfig, error) { + if src == "" || + strings.HasPrefix(src, "./") || + strings.HasPrefix(src, "/") || + strings.HasPrefix(src, "\\") || + strings.HasPrefix(src, ".\\") { + return NewConfigMemory(src) } - if store != "" { - item := make(map[string]ConfigContent) - data, err := os.ReadFile(store) - if err != nil && !os.IsNotExist(err) { + parse, err := url.Parse(src) + if err != nil { + return nil, err + } + switch parse.Scheme { + case "local": + return NewConfigMemory(parse.Path) + case "redis": + query := parse.Query() + addr := query.Get("addr") + pass := query.Get("pass") + db := query.Get("db") + dbi, err := strconv.Atoi(db) + if err != nil { return nil, err } - if err == nil { - err = json.Unmarshal(data, &item) - if err != nil { - return nil, err - } - } - for key, content := range item { - if content.Ttl == nil || time.Now().Before(*content.Ttl) { - ret.data.Store(key, content) - } - } - clear(item) + return NewConfigRedis(context.Background(), addr, pass, dbi) + default: + return nil, fmt.Errorf("unsupported scheme: %s", parse.Scheme) } - return ret, nil -} - -type ConfigContent struct { - Data string `json:"data"` - Ttl *time.Time `json:"ttl,omitempty"` -} - -func (m *ConfigMemory) Put(key string, value string, ttl time.Duration) error { - d := time.Now().Add(ttl) - td := &d - if ttl == -1 { - td = nil - } - m.data.Store(key, ConfigContent{ - Data: value, - Ttl: td, - }) - return nil -} - -func (m *ConfigMemory) Get(key string) (string, error) { - if value, ok := m.data.Load(key); ok { - content := value.(ConfigContent) - if content.Ttl != nil && time.Now().After(*content.Ttl) { - return "", os.ErrNotExist - } - return content.Data, nil - } - return "", os.ErrNotExist -} - -func (m *ConfigMemory) Delete(key string) error { - m.data.Delete(key) - return nil -} - -func (m *ConfigMemory) Close() error { - defer m.data.Clear() - if m.store != "" { - item := make(map[string]ConfigContent) - now := time.Now() - m.data.Range( - func(key, value interface{}) bool { - content := value.(ConfigContent) - if content.Ttl == nil || now.Before(*content.Ttl) { - item[key.(string)] = content - } - return true - }) - zap.L().Debug("回写内容到本地存储", zap.String("store", m.store), zap.Int("length", len(item))) - saved, err := json.Marshal(item) - if err != nil { - return err - } - return os.WriteFile(m.store, saved, 0o600) - } - return nil } diff --git a/pkg/utils/config_memory.go b/pkg/utils/config_memory.go new file mode 100644 index 0000000..74a309c --- /dev/null +++ b/pkg/utils/config_memory.go @@ -0,0 +1,105 @@ +package utils + +import ( + "encoding/json" + "os" + "path/filepath" + "sync" + "time" + + "go.uber.org/zap" +) + +// ConfigMemory 一个简单的内存配置归档,仅用于测试 +type ConfigMemory struct { + data sync.Map + store string +} + +func NewConfigMemory(store string) (KVConfig, error) { + ret := &ConfigMemory{ + store: store, + data: sync.Map{}, + } + if store != "" { + zap.L().Info("parse config from store", zap.String("store", store)) + if err := os.MkdirAll(filepath.Dir(store), 0o755); err != nil && !os.IsExist(err) { + return nil, err + } + item := make(map[string]ConfigContent) + data, err := os.ReadFile(store) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + if err == nil { + err = json.Unmarshal(data, &item) + if err != nil { + return nil, err + } + } + for key, content := range item { + if content.Ttl == nil || time.Now().Before(*content.Ttl) { + ret.data.Store(key, content) + } + } + clear(item) + } + return ret, nil +} + +type ConfigContent struct { + Data string `json:"data"` + Ttl *time.Time `json:"ttl,omitempty"` +} + +func (m *ConfigMemory) Put(key string, value string, ttl time.Duration) error { + d := time.Now().Add(ttl) + td := &d + if ttl == -1 { + td = nil + } + m.data.Store(key, ConfigContent{ + Data: value, + Ttl: td, + }) + return nil +} + +func (m *ConfigMemory) Get(key string) (string, error) { + if value, ok := m.data.Load(key); ok { + content := value.(ConfigContent) + if content.Ttl != nil && time.Now().After(*content.Ttl) { + return "", os.ErrNotExist + } + return content.Data, nil + } + return "", os.ErrNotExist +} + +func (m *ConfigMemory) Delete(key string) error { + m.data.Delete(key) + return nil +} + +func (m *ConfigMemory) Close() error { + defer m.data.Clear() + if m.store != "" { + item := make(map[string]ConfigContent) + now := time.Now() + m.data.Range( + func(key, value interface{}) bool { + content := value.(ConfigContent) + if content.Ttl == nil || now.Before(*content.Ttl) { + item[key.(string)] = content + } + return true + }) + zap.L().Debug("回写内容到本地存储", zap.String("store", m.store), zap.Int("length", len(item))) + saved, err := json.Marshal(item) + if err != nil { + return err + } + return os.WriteFile(m.store, saved, 0o600) + } + return nil +} diff --git a/pkg/utils/config_redis.go b/pkg/utils/config_redis.go new file mode 100644 index 0000000..78baee1 --- /dev/null +++ b/pkg/utils/config_redis.go @@ -0,0 +1,44 @@ +package utils + +import ( + "context" + "fmt" + "time" + + "github.com/redis/go-redis/v9" +) + +type ConfigRedis struct { + ctx context.Context + client *redis.Client +} + +func NewConfigRedis(ctx context.Context, addr string, password string, db int) (*ConfigRedis, error) { + if addr == "" { + return nil, fmt.Errorf("addr is empty") + } + return &ConfigRedis{ + ctx: ctx, + client: redis.NewClient(&redis.Options{ + Addr: addr, + Password: password, + DB: db, + }), + }, nil +} + +func (r *ConfigRedis) Put(key string, value string, ttl time.Duration) error { + return r.client.Set(r.ctx, key, value, ttl).Err() +} + +func (r *ConfigRedis) Get(key string) (string, error) { + return r.client.Get(r.ctx, key).Result() +} + +func (r *ConfigRedis) Delete(key string) error { + return r.client.Del(r.ctx, key).Err() +} + +func (r *ConfigRedis) Close() error { + return r.client.Close() +}