package express_tool import ( "context" "encoding/json" "fmt" "github.com/pkg/errors" "io" "log" "net/http" "net/url" "strings" "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, prefix, number string, opt ...time.Duration) (res *ExpressRes, err error) { if a.cache != nil { res, err = a.cache.Get(ctx, a.numberKey(prefix, 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(prefix, number), string(infoJson), opt[0]) if err != nil { return nil, errors.Wrapf(err, "缓存物流信息失败, number:%s", number) } } return } func (a *AliCloudExpressClient) DeleteLogisticsInfoCache(ctx context.Context, prefix, number string) (err error) { if a.cache == nil { return errors.New("缓存不能为空") } err = a.cache.Del(ctx, a.numberKey(prefix, number)) return err } // ipKey 生成Redis key func (a *AliCloudExpressClient) numberKey(prefix, number string) string { return fmt.Sprintf("%s:number:%s", prefix, number) } // 调用实名认证API func (s *AliCloudExpressClient) CallRealNameAuthAPI(ctx context.Context, name, idCard string) (*RealNameAuthData, error) { // 验证输入 if strings.TrimSpace(name) == "" || strings.TrimSpace(idCard) == "" { return nil, errors.New("姓名和身份证号不能为空") } // 设置参数 params := url.Values{} params.Add("realName", name) params.Add("cardNo", idCard) // 构建完整URL fullURL, err := url.JoinPath(s.Host) if err != nil { return nil, fmt.Errorf("构建实名认证URL失败: %w, 姓名:%s, 身份证号:%s", err, name, idCard) } // 创建HTTP客户端 client := &http.Client{ Timeout: 2 * time.Second, } // 创建POST请求 req, err := http.NewRequestWithContext(ctx, http.MethodPost, fullURL, strings.NewReader(params.Encode())) if err != nil { return nil, fmt.Errorf("创建实名认证请求失败: %w, 姓名:%s, 身份证号:%s", err, name, idCard) } // 设置请求头 req.Header.Set("Authorization", "APPCODE "+s.AppCode) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") // 发送请求 resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("发送实名认证请求失败: %w, 姓名:%s, 身份证号:%s", err, name, idCard) } defer func() { if closeErr := resp.Body.Close(); closeErr != nil { log.Printf("关闭实名认证响应体失败: %v, 姓名:%s, 身份证号:%s", closeErr, name, idCard) } }() // 读取响应体 body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取实名认证响应失败: %w, 姓名:%s, 身份证号:%s", err, name, idCard) } // 解析JSON响应 var result RealNameAuthData if err = json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("解析实名认证响应失败: %w, 响应内容:%s, 姓名:%s, 身份证号:%s", err, string(body), name, idCard) } // 检查API返回的错误码 if result.ErrorCode != 0 { return &result, fmt.Errorf("实名认证接口返回错误, 错误码:%d, 原因:%s, 姓名:%s, 身份证号:%s", result.ErrorCode, result.Reason, name, idCard) } // 检查认证结果 if !result.Result.Isok { return &result, fmt.Errorf("实名认证不通过, 原因:%s, 姓名:%s, 身份证号:%s", result.Reason, name, idCard) } return &result, nil } 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"` } type RealNameAuthData struct { ErrorCode int `json:"error_code"` Reason string `json:"reason"` Result struct { Realname string `json:"realname"` Idcard string `json:"idcard"` Isok bool `json:"isok"` IdCardInfor struct { Province string `json:"province"` City string `json:"city"` District string `json:"district"` Area string `json:"area"` Sex string `json:"sex"` Birthday string `json:"birthday"` } `json:"IdCardInfor"` } `json:"result"` Sn string `json:"sn"` }