diff --git a/pkg/coach-client/client.go b/pkg/coach-client/client.go new file mode 100644 index 0000000..3f0fa5d --- /dev/null +++ b/pkg/coach-client/client.go @@ -0,0 +1,60 @@ +package coach_client + +import ( + "context" + "gitea.timerzz.com/kedaya_haitao/common/pkg/proxy" + restry_pool "gitea.timerzz.com/kedaya_haitao/common/pkg/restry-pool" + "github.com/go-resty/resty/v2" + "github.com/pkg/errors" + proxy2 "github.com/timerzz/proxypool/pkg/proxy" + "log/slog" + "time" +) + +type Client struct { + pool *proxy.ProxyPool + defaultTimeOut time.Duration +} + +func NewClient(pool *proxy.ProxyPool) *Client { + return &Client{ + pool: pool, + defaultTimeOut: 10 * time.Minute, + } +} + +func tryRequest(ctx context.Context, urlPath string, respData any, proxyGetter func() proxy2.Proxy) error { + for p := proxyGetter(); p != nil; p = proxyGetter() { + select { + case <-ctx.Done(): + return nil + default: + + } + _, err := callByProxy(ctx, p, urlPath, respData) + if err != nil { + slog.Debug(err.Error()) + continue + } + return nil + } + return errors.New("未获取到可用的代理") +} + +func callByProxy(ctx context.Context, p proxy2.Proxy, urlPath string, result any) (*resty.Response, error) { + addr, err := proxy.UrlToMetadata(urlPath) + if err != nil { + return nil, err + } + subCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + conn, err := proxy.ConnFromProxy(subCtx, p, addr) + if err != nil { + return nil, errors.Wrap(err, "创建conn失败") + } + defer conn.Close() + + var cli = restry_pool.GetRestyClient(conn) + defer restry_pool.PutRestyClient(cli) + return cli.R().SetResult(result).Get(urlPath) +} diff --git a/pkg/coach-client/client_test.go b/pkg/coach-client/client_test.go new file mode 100644 index 0000000..a8eff65 --- /dev/null +++ b/pkg/coach-client/client_test.go @@ -0,0 +1,18 @@ +package coach_client + +import ( + "context" + "gitea.timerzz.com/kedaya_haitao/common/pkg/proxy" + "testing" +) + +func TestClient(t *testing.T) { + subs := []string{"https://us.timerzz.com:26106/vmess/sub", "https://us.timerzz.com:26106/trojan/sub", "https://us.timerzz.com:26106/ssr/sub"} + pool := proxy.NewProxyPool(subs) + client := NewClient(pool) + resp, err := client.ViewAllBags(context.Background(), 1) + if err != nil { + t.Fatal(err) + } + t.Log(resp) +} diff --git a/pkg/coach-client/details.go b/pkg/coach-client/details.go new file mode 100644 index 0000000..e058909 --- /dev/null +++ b/pkg/coach-client/details.go @@ -0,0 +1,40 @@ +package coach_client + +import ( + "context" + "fmt" + "net/url" +) + +type ProductData struct { + Id string `json:"id"` + Name string `json:"name"` + Brand string `json:"brand"` + Inventory Inventory `json:"inventory"` + Url string `json:"url"` + MasterId string `json:"masterId"` + Prices struct { + CurrentPrice float64 `json:"currentPrice"` + } `json:"prices"` + Remark string `json:"-"` +} + +type ProductDataResponse struct { + ProductData []*ProductData `json:"productsData"` +} + +func (c *Client) RequestProductDetail(ctx context.Context, pid string) (data ProductData, err error) { + sctx, cancel := context.WithTimeout(ctx, c.defaultTimeOut) + defer cancel() + var resp ProductDataResponse + urlPath := fmt.Sprintf("https://www.coachoutlet.com/api/get-products?ids=%s&includeInventory=false", url.QueryEscape(pid)) + err = tryRequest(sctx, urlPath, &resp, c.pool.RandomIterator()) + if len(resp.ProductData) == 0 && err != nil { + err = fmt.Errorf("获取详情信息为空") + return + } + if len(resp.ProductData) > 0 { + data = *resp.ProductData[0] + } + return +} diff --git a/pkg/coach-client/inventory.go b/pkg/coach-client/inventory.go new file mode 100644 index 0000000..6a70793 --- /dev/null +++ b/pkg/coach-client/inventory.go @@ -0,0 +1,37 @@ +package coach_client + +import ( + "context" + "fmt" + "net/url" + "time" +) + +type Inventory struct { + Id string `json:"id"` + Ats int `json:"ats"` + PreOrderable bool `json:"preorderable"` + BackOrderable bool `json:"backorderable"` + Orderable bool `json:"orderable"` + AllocationResetDate time.Time `json:"allocationResetDate,omitempty"` + Perpetual bool `json:"perpetual"` + StockLevel int `json:"stockLevel"` +} + +type InventoryResponse struct { + Inventory struct { + Status string `json:"status"` + InventoryListID string `json:"inventoryListID"` + InventoryInfo Inventory `json:"inventoryInfo"` + } `json:"inventory"` +} + +func (c *Client) RequestInventory(ctx context.Context, pid string) (inv Inventory, err error) { + sctx, cancel := context.WithTimeout(ctx, c.defaultTimeOut) + defer cancel() + var resp InventoryResponse + urlPath := fmt.Sprintf("https://www.coachoutlet.com/api/inventory?vgId=%s&includeVariantData=false", url.QueryEscape(pid)) + err = tryRequest(sctx, urlPath, &resp, c.pool.RandomIterator()) + inv = resp.Inventory.InventoryInfo + return +} diff --git a/pkg/coach-client/view-all-bags.go b/pkg/coach-client/view-all-bags.go new file mode 100644 index 0000000..1d0c833 --- /dev/null +++ b/pkg/coach-client/view-all-bags.go @@ -0,0 +1,68 @@ +package coach_client + +import ( + "context" + "fmt" +) + +type PageDataResponse struct { + PageData struct { + Total int `json:"total"` + Page int `json:"page"` + TotalPages int `json:"totalPages"` + PageSize int `json:"pageSize"` + Products []Product `json:"products"` + } `json:"pageData"` +} +type Product struct { + Name string `json:"name"` + Colors []Color `json:"colors"` + VariantsOnSale []Variant `json:"variantsOnSale"` +} + +type Color struct { + Id string `json:"id"` //"id": "IMDQC", + Text string `json:"text"` //颜色的描述 + Orderable bool `json:"orderable"` + Media Media `json:"media"` + Url string `json:"url"` //"/products/eliza-flap-crossbody-bag-in-signature-canvas/CP009-IMDQC.html", + VgId string `json:"vgId"` // "vgId": "CP009-IMDQC", + MasterId string `json:"masterId"` //"masterId": "CP009" +} + +type Media struct { + Thumbnail Thumbnail `json:"thumbnail"` +} + +type Thumbnail struct { + Src string `json:"src"` +} + +type Variant struct { + Id string `json:"id"` + OnSale bool `json:"onSale"` + Price Price `json:"price"` +} + +type Price struct { + Sales Sales `json:"sales"` + MarkdownDiscPercent int `json:"markdownDiscPercent"` +} + +type Sales struct { + Value float64 `json:"value"` + Currency string `json:"currency"` + Formatted string `json:"formatted"` + DecimalPrice string `json:"decimalPrice"` +} + +func (c *Client) ViewAllBags(ctx context.Context, page int) (resp PageDataResponse, err error) { + if page < 1 { + page = 1 + } + sctx, cancel := context.WithTimeout(ctx, c.defaultTimeOut) + defer cancel() + urlPath := fmt.Sprintf("https://www.coachoutlet.com/api/get-shop/bags/view-all?page=%d", page) + err = tryRequest(sctx, urlPath, &resp, c.pool.RandomIterator()) + return +}