diff --git a/go.mod b/go.mod index 81fbde1..bbc15ea 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,11 @@ go 1.18.0 require github.com/pkg/errors v0.9.1 require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/redis/go-redis/v9 v9.10.0 // indirect github.com/stretchr/testify v1.10.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ba9f6d0..5916905 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,15 @@ +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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/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.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs= +github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/ip_tool/hua_chen_client.go b/ip_tool/hua_chen_client.go index 621bdb8..ef342e7 100644 --- a/ip_tool/hua_chen_client.go +++ b/ip_tool/hua_chen_client.go @@ -1,6 +1,7 @@ package ip_tool import ( + "context" "encoding/json" "fmt" "io" @@ -15,12 +16,14 @@ import ( type HuaChenIpClient struct { AppCode string Host string + cache ICacheAdapter } -func NewHuaChenIpClient(appCode string) *HuaChenIpClient { +func NewHuaChenIpClient(appCode string, cache ICacheAdapter) *HuaChenIpClient { return &HuaChenIpClient{ AppCode: appCode, Host: "https://c2ba.api.huachen.cn", + cache: cache, } } @@ -87,6 +90,31 @@ func (h *HuaChenIpClient) GetIpInfo(ip string) (res *ApiResult, err error) { return &apiResult, nil } +func (h *HuaChenIpClient) GetIpInfoFormCache(ctx context.Context, ip string) (res *ApiResult, err error) { + getCache, err := h.cache.Get(ctx, h.ipKey(ip)) + if err != nil { + return nil, errors.Wrapf(err, "获取缓存失败,ip:%s", ip) + } + if getCache != nil { + return getCache, nil + } + info, err := h.GetIpInfo(ip) + if err != nil { + return nil, errors.Wrapf(err, "获取ip:%s信息失败,", ip) + } + err = h.cache.Set(ctx, h.ipKey(ip), *info, 24*time.Hour) + if err != nil { + return nil, errors.Wrapf(err, "缓存ip:%s信息失败,", ip) + } + res = info + return +} + +// ipKey 生成Redis key +func (h *HuaChenIpClient) ipKey(ip string) string { + return fmt.Sprintf("ip:%s", ip) +} + type ApiResult struct { Ret int `json:"ret"` Msg string `json:"msg"` diff --git a/ip_tool/hua_chen_client_test.go b/ip_tool/hua_chen_client_test.go index 99a3460..03a1356 100644 --- a/ip_tool/hua_chen_client_test.go +++ b/ip_tool/hua_chen_client_test.go @@ -1,8 +1,14 @@ package ip_tool import ( + "context" + "encoding/json" + "fmt" + "github.com/pkg/errors" + "github.com/redis/go-redis/v9" "log" "testing" + "time" ) func TestHuaChenIpClient_GetIpInfo(t *testing.T) { @@ -42,3 +48,78 @@ func TestHuaChenIpClient_GetIpInfo(t *testing.T) { }) } } + +func TestHuaChenIpClient_GetIpInfo1(t *testing.T) { + rdb := redis.NewClient(&redis.Options{ + Addr: "", + Password: "", + DB: 1, + }) + + // 创建IP缓存实例 + ipCache := NewRedisCache(rdb) + h := &HuaChenIpClient{ + AppCode: "", + Host: "https://c2ba.api.huachen.cn", + cache: ipCache, + } + gotRes, err := h.GetIpInfoFormCache(context.Background(), "8.138.116.112") + log.Println(gotRes, err) +} + +type RedisCache struct { + client *redis.Client +} + +func NewRedisCache(client *redis.Client) *RedisCache { + return &RedisCache{client: client} +} + +func (r *RedisCache) Set(ctx context.Context, ip string, info ApiResult, ttl time.Duration) error { + if ip == "" { + return errors.New("ip不能为空") + } + + data, err := json.Marshal(info) + if err != nil { + return fmt.Errorf("无法封送IP信息: %w", err) + } + + return r.client.Set(ctx, ip, data, ttl).Err() +} + +// Get 从Redis获取IP信息 +func (r *RedisCache) Get(ctx context.Context, ip string) (*ApiResult, error) { + if ip == "" { + return nil, errors.New("ip不能为空") + } + + data, err := r.client.Get(ctx, ip).Bytes() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, nil // 键不存在,返回nil而不是错误 + } + return nil, fmt.Errorf("无法获取IP信息: %w", err) + } + + var info ApiResult + if err = json.Unmarshal(data, &info); err != nil { + return nil, fmt.Errorf("无法解析IP信息: %w", err) + } + + return &info, nil +} + +// Exists 检查IP是否存在缓存中 +func (r *RedisCache) Exists(ctx context.Context, ip string) (bool, error) { + if ip == "" { + return false, errors.New("ip不能为空") + } + + exists, err := r.client.Exists(ctx, ip).Result() + if err != nil { + return false, fmt.Errorf("无法检查IP是否存在: %w", err) + } + + return exists > 0, nil +} diff --git a/ip_tool/ip_cache.go b/ip_tool/ip_cache.go new file mode 100644 index 0000000..52d8c03 --- /dev/null +++ b/ip_tool/ip_cache.go @@ -0,0 +1,13 @@ +package ip_tool + +import ( + "context" + "time" +) + +// Cache 定义缓存接口,遵循接口隔离原则 +type ICacheAdapter interface { + Set(ctx context.Context, ip string, info ApiResult, ttl time.Duration) error + Get(ctx context.Context, ip string) (*ApiResult, error) + Exists(ctx context.Context, ip string) (bool, error) +}