From 08bb2af2c35271be130c93431549760c458f494f Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Mon, 10 Nov 2025 16:48:19 +0800 Subject: [PATCH] add tools request libs, migrate for hyperf framework --- .../app/Platform/Tools/GuzzleRetryConfig.php | 60 +++++ backend/app/Platform/Tools/HttpClient.php | 205 ++++++++++++++++++ .../Platform/Tools/Request/CompanyRequest.php | 75 +++++++ .../app/Platform/Tools/Request/KpiRequest.php | 32 +++ .../Platform/Tools/Request/OrderRequest.php | 61 ++++++ .../Platform/Tools/Request/ProductRequest.php | 52 +++++ .../Platform/Tools/Request/RefundRequest.php | 52 +++++ .../Platform/Tools/Request/StoreRequest.php | 78 +++++++ .../Request/WarehouseInventoryRequest.php | 86 ++++++++ backend/app/Platform/Tools/RequestTrait.php | 31 +++ .../Platform/Tools/ToolsRequestMatcher.php | 17 ++ 11 files changed, 749 insertions(+) create mode 100644 backend/app/Platform/Tools/GuzzleRetryConfig.php create mode 100644 backend/app/Platform/Tools/HttpClient.php create mode 100644 backend/app/Platform/Tools/Request/CompanyRequest.php create mode 100644 backend/app/Platform/Tools/Request/KpiRequest.php create mode 100644 backend/app/Platform/Tools/Request/OrderRequest.php create mode 100644 backend/app/Platform/Tools/Request/ProductRequest.php create mode 100644 backend/app/Platform/Tools/Request/RefundRequest.php create mode 100644 backend/app/Platform/Tools/Request/StoreRequest.php create mode 100644 backend/app/Platform/Tools/Request/WarehouseInventoryRequest.php create mode 100644 backend/app/Platform/Tools/RequestTrait.php create mode 100644 backend/app/Platform/Tools/ToolsRequestMatcher.php diff --git a/backend/app/Platform/Tools/GuzzleRetryConfig.php b/backend/app/Platform/Tools/GuzzleRetryConfig.php new file mode 100644 index 0000000..ce7e54c --- /dev/null +++ b/backend/app/Platform/Tools/GuzzleRetryConfig.php @@ -0,0 +1,60 @@ +getStatusCode(), [429, 500]); + }; + } + + + public static function retryDelay() : callable + { + + return function (int $retries, ResponseInterface $response): int + { + $delay = 0; + + if (!$response->hasHeader('Retry-After')) { + $delay = RetryMiddleware::exponentialDelay($retries); + } else { + $retryAfter = $response->getHeaderLine('Retry-After'); + $delay = $retryAfter; + + if (!is_numeric($retryAfter)) { + $delay = (new \DateTime($retryAfter))->getTimestamp() - time(); + } + $delay = (int)$delay * 1000; + } + + $code = $response->getStatusCode(); + $seconds = $delay / 1000; + $message = "请求失败, 远程服务器响应码为 $code, 此为第 $retries 次尝试,{$seconds} 秒之后重试"; + + dump($message); + Log::warning($message); + + return $delay; + }; + + } + + +} diff --git a/backend/app/Platform/Tools/HttpClient.php b/backend/app/Platform/Tools/HttpClient.php new file mode 100644 index 0000000..1920f43 --- /dev/null +++ b/backend/app/Platform/Tools/HttpClient.php @@ -0,0 +1,205 @@ +push(Middleware::retry(self::retryPolicy(), self::retryDelay())); + + $config = [ + 'headers' => [ + 'Accept' => 'application/json; charset=utf-8', + 'Content-Type' => 'application/json; charset=utf-8', + 'Hash' => self::getHash() + ], + 'base_uri' => self::getHost(), + 'connect_timeout' => 10, + 'timeout' => 60, + 'handler' => $stack, + 'debug' => false + ]; + + return new Client($config); + } + + /** + * 重试策略 + * + * @return callable + */ + public static function retryPolicy(): callable + { + return function (int $retries, RequestInterface $request, ResponseInterface $response = null, \Throwable $exception = null): bool { + // 记录异常信息 + if ($exception) { + dump($exception->getMessage()); + Log::error("重试异常: " . $exception->getMessage()); + } + + // 检查是否为连接异常或请求异常(包括超时) + $should_retry = false; + + if ($exception) { + // 处理连接异常(包括连接超时) + if ($exception instanceof ConnectException) { + $should_retry = true; + } + // 处理请求异常(包括请求超时) + elseif ($exception instanceof RequestException) { + // 如果是超时导致的请求异常 + if (strpos($exception->getMessage(), 'timed out') !== false) { + $should_retry = true; + } + // 如果有响应,检查状态码 + elseif ($exception->hasResponse()) { + $status_code = $exception->getResponse()->getStatusCode(); + $should_retry = in_array($status_code, [429, 500, 502, 503, 504]); + } + } + } + + // 首先检查重试次数 + if ($retries >= self::MAX_RETRIES) { + return false; + } + + // 检查状态码 (需要先确保 $response 不为 null) + $status_code_error = $response && in_array($response->getStatusCode(), [429, 500, 502, 503, 504]); + + // 如果是应该重试的异常、状态码错误或响应为 null,则重试 + return $should_retry || $status_code_error || is_null($response); + }; + } + + /** + * 重试延迟策略 + * + * @return callable + */ + public static function retryDelay(): callable + { + return function (int $retries, ResponseInterface $response = null): int { + $delay = 0; + + if (is_null($response) || !$response->hasHeader('Retry-After')) { + $delay = RetryMiddleware::exponentialDelay($retries) * 5; + } else { + $retryAfter = $response->getHeaderLine('Retry-After'); + $delay = $retryAfter; + + if (!is_numeric($retryAfter)) { + $delay = (new \DateTime($retryAfter))->getTimestamp() - time(); + } + $delay = (int)$delay * 1000; + } + + $seconds = $delay / 1000; + $message = "请求失败, 此为第 $retries 次尝试,{$seconds} 秒之后重试"; + + // 如果有响应,添加状态码信息 + if ($response) { + $code = $response->getStatusCode(); + dump($response->getBody()->getContents()); + $message = "请求失败, 远程服务器响应码为 $code, 此为第 $retries 次尝试,{$seconds} 秒之后重试"; + } + + dump($message); + Log::error($message); + + return $delay; + }; + } + + static function get(string $path, array $query = []): array + { + try{ + $response = static::create()->request('GET', $path, ['query' => $query]); + // dump($response); + return static::parse($response); + }catch(\Throwable $exception) { + dump($exception->getMessage()); + dump($exception->getTraceAsString()); + exit(1); + } + } + + static function post(string $path, array $query = [], array $data = []): array + { + try{ + $response = static::create()->request('POST', $path, ['query' => $query, 'body' => json_encode($data)]); + // dump($response); + // dump(static::parse($response)); + return static::parse($response); + }catch(\Throwable $exception){ + dump($exception->getMessage()); + dump($exception->getTraceAsString()); + exit(1); + } + } + + static function put(string $path, array $query = [], array $data = []) + { + try{ + $response = static::create()->request('PUT', $path, ['query' => $query, 'body' => json_encode($data)]); + // dump($response); + return static::parse($response); + }catch(\Throwable $exception){ + dump($exception->getMessage()); + dump($exception->getTraceAsString()); + exit(1); + } + } + + static function delete(string $path) + { + try{ + $response = static::create()->request('DELETE', $path); + // dump($response); + return static::parse($response); + }catch(\Throwable $exception){ + dump($exception->getMessage()); + dump($exception->getTraceAsString()); + exit(1); + } + } + + protected static function parse(ResponseInterface $response): array + { + + $http_code = $response->getStatusCode(); + $body = $response->getBody(); + // when deal with stream object, need to move to pointer to the ram head + $body->rewind(); + $content = $body->getContents(); + $payload = \json_decode($content); + if ($http_code == 200) { + return $payload; + } + } + +} diff --git a/backend/app/Platform/Tools/Request/CompanyRequest.php b/backend/app/Platform/Tools/Request/CompanyRequest.php new file mode 100644 index 0000000..abdca48 --- /dev/null +++ b/backend/app/Platform/Tools/Request/CompanyRequest.php @@ -0,0 +1,75 @@ +filter(function($el) use ($name){ + return strtolower($el['label']) == $name ? $el : null; + }); + + if($stores->isEmpty()){ + throw new \InvalidArgumentException("tools 中未找到 name 为 $name 的公司信息,请确认 tools 系统中已创建该公司的记录!"); + } + + return $stores->first(); + + + } + public static function all(int $page = 1, int $size = 500, $query = []): LazyCollection + { + $path = 'api/ext/companies'; + $collection = new LazyCollection(); + static::fetch($collection, $path, $page, $size, $query); + if($collection->isEmpty()){ + throw new \InvalidArgumentException("获取的远程 tools 公司列表为空,请检查 token 配置"); + } + + return $collection; + } + + + + protected static function fetch(LazyCollection &$collection, $path, $page, $size, $query) + { + $init_query = [ + 'page' => $page, + 'size' => $size + ]; + + $query = !empty($query) ? array_merge($init_query, $query) : $init_query; + + $response = static::get($path, $query); + + if($response['data']){ + $collection = $collection->merge($response['data']); + } + + if($response['page'] < $response['total_page']){ + self::fetch($collection, $path, $page + 1, $size, $query); + } + } + + public static function update(int $id, array $data): array + { + $path = "/api/ext/companies/$id"; + return static::put($path, [], $data); + } + + + + +} diff --git a/backend/app/Platform/Tools/Request/KpiRequest.php b/backend/app/Platform/Tools/Request/KpiRequest.php new file mode 100644 index 0000000..c6cca71 --- /dev/null +++ b/backend/app/Platform/Tools/Request/KpiRequest.php @@ -0,0 +1,32 @@ + $store_id, 'from' => $date, 'to' => $date]); + return isset($response['data']) && !empty($response['data']) ? $response['data']['0'] : null; + } + + public static function add(array $data): array + { + $path = "/api/ext/kpi"; + return static::post($path, [], $data); + } + + public static function update(array $data): array + { + $path = "/api/ext/kpi"; + return static::put($path, [], $data); + } +} diff --git a/backend/app/Platform/Tools/Request/OrderRequest.php b/backend/app/Platform/Tools/Request/OrderRequest.php new file mode 100644 index 0000000..598d9bd --- /dev/null +++ b/backend/app/Platform/Tools/Request/OrderRequest.php @@ -0,0 +1,61 @@ + $t_store_id, + 'platformOrderId' => $platform_order_id, + ]; + $response = static::get($path, $query); + + if(isset($response['data']) && is_array($response['data']) && count($response['data']) == 1) { + return $response['data']['0']; + } + + return false; + } + + public static function all(array $query):array + { + $path = "/api/ext/orders"; + return static::get($path, $query); + } + + + public static function add(array $data): array + { + $path = "/api/ext/orders"; + return static::post($path, [], $data); + } + + public static function update(array $data): array + { + $path = "/api/ext/orders"; + return static::put($path, [], $data); + } + + public static function queryByStorePlatformOrderIds(int $t_store_id, array $ids) : array + { + $path = "/api/ext/orders/batch-query-by-platform-order-ids"; + return static::post($path, [], ['store' => $t_store_id, 'platformOrderIds' => $ids]); + } +} diff --git a/backend/app/Platform/Tools/Request/ProductRequest.php b/backend/app/Platform/Tools/Request/ProductRequest.php new file mode 100644 index 0000000..0834e51 --- /dev/null +++ b/backend/app/Platform/Tools/Request/ProductRequest.php @@ -0,0 +1,52 @@ + $tools_store_id, 'platformSkuId' => $platformSkuId]); + if(0 === $response['total'] || '0' === $response['total'] ){ + return null; + } + if(1 < $response['total']){ + throw new \Exception('参数有误,产品匹配到多个结果!'); + } + + return $response['data']['0']; + } + + public static function add(array $data): array + { + $path = "/api/ext/products"; + return static::post($path, [], $data); + } + + public static function update(array $data): array + { + $path = "/api/ext/products/batch"; + return static::put($path, [], $data); + } + + public static function list(array $query) : array + { + $path = "/api/ext/products"; + return static::get($path, $query); + } + +} diff --git a/backend/app/Platform/Tools/Request/RefundRequest.php b/backend/app/Platform/Tools/Request/RefundRequest.php new file mode 100644 index 0000000..f7cafbc --- /dev/null +++ b/backend/app/Platform/Tools/Request/RefundRequest.php @@ -0,0 +1,52 @@ + $t_store_id, + 'order' => $t_order_id, + ]; + $response = static::get($path, $query); + + if(isset($response['data']) && is_array($response['data']) && count($response['data']) == 1) { + return $response['data']['0']; + } + + return false; + } + + public static function add(array $data): array + { + $path = "/api/ext/refunds"; + return static::post($path, [], $data); + } + + public static function update(array $data): array + { + $path = "/api/ext/refunds"; + return static::put($path, [], $data); + } + + public static function queryByStorePlatformOrderIds(int $t_store_id, array $ids) : array + { + $path = "/api/ext/refunds/batch-query-by-ref-order-ids"; + return static::post($path, [], ['store' => $t_store_id, 'orderIds' => $ids]); + } +} diff --git a/backend/app/Platform/Tools/Request/StoreRequest.php b/backend/app/Platform/Tools/Request/StoreRequest.php new file mode 100644 index 0000000..cf6fb8a --- /dev/null +++ b/backend/app/Platform/Tools/Request/StoreRequest.php @@ -0,0 +1,78 @@ +filter(function($el) use ($name, $platform){ + return $el['platform']['name'] == $platform && strtolower($el['label']) == $name ? $el : null; + }); + + if($stores->isEmpty()){ + throw new \InvalidArgumentException("tools 中未找到平台为 $platform, name 为 $name 的店铺信息,请确认 tools 系统中已创建该平台和店铺的记录!"); + } + + return $stores->first(); + + + } + public static function all(int $page = 1, int $size = 500, $query = []): LazyCollection + { + $path = 'api/ext/stores'; + $collection = new LazyCollection(); + static::fetch($collection, $path, $page, $size, $query); + if($collection->isEmpty()){ + throw new \InvalidArgumentException("获取的远程 tools 商店列表为空,请检查 token 配置"); + } + return $collection; + } + + public static function orderNone(array $query) { + $path = 'api/ext/stores/order-none'; + return static::get($path, $query); + + } + + protected static function fetch(LazyCollection &$collection, $path, $page, $size, $query) + { + $init_query = [ + 'page' => $page, + 'size' => $size + ]; + + $query = !empty($query) ? array_merge($init_query, $query) : $init_query; + + $response = static::get($path, $query); + + if($response['data']){ + $collection = $collection->merge($response['data']); + } + + if($response['page'] < $response['total_page']){ + self::fetch($collection, $path, $page + 1, $size, $query); + } + } + + public static function update(int $id, array $data): array + { + $path = "/api/ext/stores/$id"; + return static::put($path, [], $data); + } + + + + +} diff --git a/backend/app/Platform/Tools/Request/WarehouseInventoryRequest.php b/backend/app/Platform/Tools/Request/WarehouseInventoryRequest.php new file mode 100644 index 0000000..8cfcc10 --- /dev/null +++ b/backend/app/Platform/Tools/Request/WarehouseInventoryRequest.php @@ -0,0 +1,86 @@ + $sku, + 'barcode' => $barcode, + 'company_id' => $companyId, + 'warehouse_id' => $warehouseId, + 'warehouse_sub_id' => $warehouseSubId, + 'inventory_type' => $inventoryType, + ]; + + $response = static::get($path, $query); + + if (isset($response['data']) && is_array($response['data']) && count($response['data']) == 1) { + return $response['data'][0]; + } + + return null; + } + + public static function add(array $data): array + { + $path = "/api/ext/warehouse-inventory"; + return static::post($path, [], $data); + } + + public static function update(int $id, array $data): array + { + $path = "/api/ext/warehouse-inventory/$id"; + return static::put($path, [], $data); + } + + public static function list(array $query) : array + { + $path = "/api/ext/warehouse-inventory"; + return static::get($path, $query); + } + + public static function remove(int $id): array + { + $path = "/api/ext/warehouse-inventory/$id"; + return static::delete($path); + } + + + public static function updateHistory(array $data): array + { + $path = "/api/ext/warehouse-inventory-history"; + return static::post($path, [], $data); + } + +} diff --git a/backend/app/Platform/Tools/RequestTrait.php b/backend/app/Platform/Tools/RequestTrait.php new file mode 100644 index 0000000..bf4e276 --- /dev/null +++ b/backend/app/Platform/Tools/RequestTrait.php @@ -0,0 +1,31 @@ +getHost(); + return $limit === $request->getHost(); + } +}