diff --git a/app/admin/apis/shopee_product.go b/app/admin/apis/shopee_product.go index e76ea9a..8951355 100644 --- a/app/admin/apis/shopee_product.go +++ b/app/admin/apis/shopee_product.go @@ -35,6 +35,9 @@ func (e ShopeeProduct) newHuohanhanClient() (*huohanhan.Client, error) { } cfg := config.HuohanhanConfig{BaseURL: ext.ExtConfig.Huohanhan.BaseURL, Account: ext.ExtConfig.Huohanhan.Account, Password: ext.ExtConfig.Huohanhan.Password, OCRURL: ext.ExtConfig.Huohanhan.OCRURL} logger := logx.New(1000) + if redisState, _ := huohanhan.NewRedisStateStore(ext.ExtConfig.Redis.Addr, ext.ExtConfig.Redis.Password, ext.ExtConfig.Redis.DB, time.Duration(ext.ExtConfig.Redis.AuthTTLSeconds)*time.Second); redisState != nil { + state = huohanhan.NewCachedStateStore(redisState, state) + } auth := huohanhan.NewAuthManager(cfg, state, logger, huohanhan.AuthOptions{}) return huohanhan.NewClient(cfg, auth, logger, nil) } diff --git a/config.example.yaml b/config.example.yaml index 79dc8f0..024cc18 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -47,11 +47,16 @@ settings: # 代码生成是使用前端代码存放位置,需要指定到src文件夹,相对路径 frontpath: ../go-admin-ui/src extend: # 扩展项使用说明 - huohanhan: + huohanhan: base_url: https://www.huohanhan.com account: "" password: "" ocr_url: https://ocr.ilapage.cn/ocr + redis: + addr: 127.0.0.1:6379 + password: "" + db: 0 + auth_ttl_seconds: 86400 demo: name: data cache: diff --git a/config/extend.go b/config/extend.go index 06cb849..1163b2c 100644 --- a/config/extend.go +++ b/config/extend.go @@ -5,6 +5,13 @@ var ExtConfig Extend type Extend struct { AMap AMap `yaml:"demo" json:"demo"` Huohanhan HuohanhanConfig `yaml:"huohanhan" json:"huohanhan"` + Redis RedisConfig `yaml:"redis" json:"redis"` +} +type RedisConfig struct { + Addr string `yaml:"addr" json:"addr"` + Password string `yaml:"password" json:"password"` + DB int `yaml:"db" json:"db"` + AuthTTLSeconds int `yaml:"auth_ttl_seconds" json:"auth_ttl_seconds"` } type AMap struct { Key string `yaml:"key" json:"key"` diff --git a/go.mod b/go.mod index deec3e1..5525fc2 100644 --- a/go.mod +++ b/go.mod @@ -112,6 +112,7 @@ require ( github.com/prometheus/procfs v0.21.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.61.0 // indirect + github.com/redis/go-redis/v9 v9.22.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/shamsher31/goimgext v1.0.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -124,6 +125,7 @@ require ( github.com/ugorji/go/codec v1.3.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect diff --git a/go.sum b/go.sum index 7620542..0117143 100644 --- a/go.sum +++ b/go.sum @@ -476,6 +476,8 @@ github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9q github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA= github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= +github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -583,6 +585,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= diff --git a/internal/huohanhan/redis_state.go b/internal/huohanhan/redis_state.go new file mode 100644 index 0000000..ad4ed18 --- /dev/null +++ b/internal/huohanhan/redis_state.go @@ -0,0 +1,66 @@ +package huohanhan + +import ( + "context" + "github.com/redis/go-redis/v9" + "time" +) + +type RedisStateStore struct { + client *redis.Client + prefix string + ttl time.Duration +} +type CachedStateStore struct { + cache *RedisStateStore + durable StateStore +} + +func NewCachedStateStore(cache *RedisStateStore, durable StateStore) StateStore { + return &CachedStateStore{cache: cache, durable: durable} +} + +func NewRedisStateStore(addr, password string, db int, ttl time.Duration) (*RedisStateStore, error) { + if addr == "" { + return nil, nil + } + if ttl <= 0 { + ttl = 24 * time.Hour + } + return &RedisStateStore{client: redis.NewClient(&redis.Options{Addr: addr, Password: password, DB: db}), prefix: "erpgo:huohanhan:", ttl: ttl}, nil +} +func (s *RedisStateStore) SetKV(key, value, _ string) error { + return s.client.Set(context.Background(), s.prefix+key, value, s.ttl).Err() +} +func (s *RedisStateStore) GetKV(key string) (string, bool, error) { + v, err := s.client.Get(context.Background(), s.prefix+key).Result() + if err == redis.Nil { + return "", false, nil + } + return v, err == nil, err +} +func (s *RedisStateStore) Delete(key string) error { + return s.client.Del(context.Background(), s.prefix+key).Err() +} +func (s *RedisStateStore) Close() error { return s.client.Close() } +func (s *CachedStateStore) SetKV(key, value, now string) error { + if err := s.durable.SetKV(key, value, now); err != nil { + return err + } + if value == "" { + _ = s.cache.Delete(key) + } else { + _ = s.cache.SetKV(key, value, now) + } + return nil +} +func (s *CachedStateStore) GetKV(key string) (string, bool, error) { + if v, ok, err := s.cache.GetKV(key); err == nil && ok && v != "" { + return v, true, nil + } + v, ok, err := s.durable.GetKV(key) + if err == nil && ok && v != "" { + _ = s.cache.SetKV(key, v, "") + } + return v, ok && v != "", err +} diff --git a/tickets/LOCAL-010-redis-huohanhan-auth-cache.md b/tickets/LOCAL-010-redis-huohanhan-auth-cache.md new file mode 100644 index 0000000..7747b46 --- /dev/null +++ b/tickets/LOCAL-010-redis-huohanhan-auth-cache.md @@ -0,0 +1,25 @@ +# LOCAL-010:Redis 缓存货憨憨认证状态 + +- 状态:进行中 +- 来源:用户需求(2026-09-14) +- 前置:LOCAL-009 + +## 目标 + +使用 Redis 缓存货憨憨 token 和 Cookie,减少每次商品请求的在线校验;MySQL 保留持久化状态和恢复来源。 + +## 规则 + +- Redis 认证状态默认 TTL 24 小时; +- Redis 没有状态时从 MySQL 恢复,仍无状态才登录; +- 货憨憨请求返回认证失效时删除 Redis 状态,重新登录并重试一次; +- Redis 不可用时自动回退 MySQL; +- 不记录 token、Cookie 或 Redis 内容到日志。 + +## 验收 + +- Redis 命中时不再额外调用货憨憨在线校验; +- Redis 过期或清空后可以从 MySQL 恢复; +- 认证失效会清除缓存并重新登录; +- Redis 不可用不影响现有 MySQL 流程; +- 相关测试通过。