253 lines
7.5 KiB
Go
253 lines
7.5 KiB
Go
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"`
|
||
}
|