diff --git a/express_tool/ali_cloud_client.go b/express_tool/ali_cloud_client.go new file mode 100644 index 0000000..fe1438f --- /dev/null +++ b/express_tool/ali_cloud_client.go @@ -0,0 +1,153 @@ +package express_tool + +import ( + "context" + "encoding/json" + "fmt" + "github.com/pkg/errors" + "io" + "log" + "net/http" + "net/url" + "time" +) + +type AliCloudExpressClient struct { + AppCode string + Host string + cache ICacheAdapter +} + +func NewAliCloudExpressClient(host, appCode string) *AliCloudExpressClient { + return &AliCloudExpressClient{ + AppCode: appCode, + Host: host, + } +} + +func (a *AliCloudExpressClient) GetLogisticsInfo(mobile, number string) (res *ExpressRes, err error) { + if mobile == "" || number == "" { + return nil, errors.New("请输入手机号和物流单号") + } + // 设置参数 + params := url.Values{} + params.Add("mobile", mobile) + params.Add("number", number) + // 拼接URL + var fullURL string + fullURL, err = url.JoinPath(a.Host) + if err != nil { + return nil, errors.Wrapf(err, "查询物流信息失败, 拼接路径错误! 手机号:%s, 物流单号:%s", mobile, number) + } + // 拼接参数 + fullURL = fmt.Sprintf("%s?%s", fullURL, params.Encode()) + // 创建HTTP客户端 + client := &http.Client{} + // 创建请求 + var req *http.Request + req, err = http.NewRequest(http.MethodGet, fullURL, nil) + if err != nil { + return nil, errors.Wrapf(err, "查询物流信息失败, 创建请求失败! 手机号:%s, 物流单号:%s", mobile, number) + } + + // 设置请求头 + req.Header.Add("Authorization", "APPCODE "+a.AppCode) + + // 发送请求 + client.Timeout = 2 * time.Second + var resp *http.Response + resp, err = client.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "查询物流信息失败, 发送请求失败! 手机号:%s, 物流单号:%s", mobile, number) + } + defer func(Body io.ReadCloser) { + err = Body.Close() + if err != nil { + log.Printf("查询物流信息失败, 关闭响应体失败! 手机号:%s, 物流单号:%s , %+v\n", mobile, number, err) + } + }(resp.Body) + + // 读取响应体 + var body []byte + body, err = io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrapf(err, "查询物流信息失败, 读取响应体失败! 手机号:%s, 物流单号:%s", mobile, number) + } + //log.Printf("查询物流信息成功! %s\n", string(body)) + + // 解析JSON响应 + var expressRes ExpressRes + err = json.Unmarshal(body, &expressRes) + if err != nil { + return nil, errors.Wrapf(err, "查询物流信息失败, 解析JSON响应失败, %s! 手机号:%s, 物流单号:%s", string(body), mobile, number) + } + + if expressRes.Code != 0 { + return &expressRes, errors.Wrapf(err, "查询物流信息失败! expressRes:%+v 手机号:%s, 物流单号:%s", expressRes, mobile, number) + } + + if expressRes.Data == nil { + return &expressRes, errors.Wrapf(err, "查询物流信息失败,没有查询到物流信息! expressRes:%+v 手机号:%s, 物流单号:%s", expressRes, mobile, number) + } + + return &expressRes, nil +} + +func (a *AliCloudExpressClient) Set(cache ICacheAdapter) { + a.cache = cache +} + +func (a *AliCloudExpressClient) GetLogisticsInfoFormCache(ctx context.Context, mobile, number string, opt ...time.Duration) (res *ExpressRes, err error) { + if a.cache != nil { + res, err = a.cache.Get(ctx, a.numberKey(number)) + if err != nil { + return nil, errors.Wrapf(err, "获取缓存失败, number:%s", number) + } + if res != nil { + return res, nil + } + } + res, err = a.GetLogisticsInfo(mobile, number) + if err != nil { + return nil, errors.Wrapf(err, "获取物流信息失败, number:%s", number) + } + + var infoJson []byte + infoJson, err = json.Marshal(res) + if err != nil { + return nil, errors.Wrapf(err, "无法将物流信息转换为JSON, number:%s", number) + } + + if len(opt) > 0 { + err = a.cache.Set(ctx, a.numberKey(number), string(infoJson), opt[0]) + if err != nil { + return nil, errors.Wrapf(err, "缓存物流信息失败, number:%s", number) + } + } + return +} + +// ipKey 生成Redis key +func (a *AliCloudExpressClient) numberKey(number string) string { + return fmt.Sprintf("app:number:%s", number) +} + +type ExpressRes struct { + Code int `json:"code"` + Desc string `json:"desc"` + Data *Data `json:"data"` +} + +type Data struct { + State int `json:"state" dc:"物流状态【1在途中,2派件中,3已签收,4派送失败,5揽收,6退回,7转单,8疑难,9退签,10待清关,11清关中,12已清关,13清关异常】"` + Name string `json:"name" dc:"物流名"` + Com string `json:"com"` + Number string `json:"number" dc:"单号"` + Logo string `json:"logo" dc:"图标地址"` + List []*List `json:"list"` +} + +type List struct { + Time string `json:"time"` + Status string `json:"status"` +} diff --git a/express_tool/ali_cloud_client_test.go b/express_tool/ali_cloud_client_test.go new file mode 100644 index 0000000..688b7c2 --- /dev/null +++ b/express_tool/ali_cloud_client_test.go @@ -0,0 +1,108 @@ +package express_tool + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/redis/go-redis/v9" + "log" + "testing" + "time" +) + +func TestAliCloudExpressClient_GetLogisticsInfo(t *testing.T) { + type fields struct { + AppCode string + Host string + } + type args struct { + mobile string + number string + } + tests := []struct { + name string + fields fields + args args + wantRes *ExpressRes + wantErr bool + }{ + { + name: "test1", + fields: fields{ + AppCode: "", + Host: "", + }, + args: args{ + mobile: "", + number: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &AliCloudExpressClient{ + AppCode: tt.fields.AppCode, + Host: tt.fields.Host, + } + gotRes, err := a.GetLogisticsInfo(tt.args.mobile, tt.args.number) + log.Println(gotRes, err) + }) + } +} + +func TestAliCloudExpressClient_GetLogisticsInfoFormCache(t *testing.T) { + rdb := redis.NewClient(&redis.Options{ + Addr: "", + Password: "", + DB: 1, + }) + + // 创建缓存实例 + cache := NewRedisCache(rdb) + a := &AliCloudExpressClient{ + AppCode: "", + Host: "", + cache: cache, + } + gotRes, err := a.GetLogisticsInfoFormCache(context.Background(), "", "", time.Minute) + 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, number string, res string, ttl time.Duration) error { + if number == "" { + return errors.New("number不能为空") + } + + return r.client.Set(ctx, number, res, ttl).Err() +} + +// Get 从Redis获取IP信息 +func (r *RedisCache) Get(ctx context.Context, number string) (*ExpressRes, error) { + if number == "" { + return nil, errors.New("number不能为空") + } + + data, err := r.client.Get(ctx, number).Bytes() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, nil // 键不存在,返回nil而不是错误 + } + return nil, fmt.Errorf("无法获取物流信息: %w", err) + } + + var info ExpressRes + if err = json.Unmarshal(data, &info); err != nil { + return nil, fmt.Errorf("无法解析物流信息: %w", err) + } + + return &info, nil +} diff --git a/express_tool/express_cache.go b/express_tool/express_cache.go new file mode 100644 index 0000000..61e8e1c --- /dev/null +++ b/express_tool/express_cache.go @@ -0,0 +1,11 @@ +package express_tool + +import ( + "context" + "time" +) + +type ICacheAdapter interface { + Set(ctx context.Context, number string, res string, ttl time.Duration) error + Get(ctx context.Context, number string) (*ExpressRes, error) +}