188 lines
6.0 KiB
PHP
188 lines
6.0 KiB
PHP
<?php
|
||
|
||
namespace app\support;
|
||
|
||
class Kuaidi100Client
|
||
{
|
||
private const QUERY_URL = 'https://poll.kuaidi100.com/poll/query.do';
|
||
private const SUBSCRIBE_URL = 'https://poll.kuaidi100.com/poll';
|
||
private const AUTONUMBER_URL = 'http://www.kuaidi100.com/autonumber/auto';
|
||
private const COMPANY_CATALOG_URL = 'http://api.kuaidi100.com/manager/openapi/download/kdbm.do';
|
||
|
||
public function __construct(private ?Kuaidi100ConfigService $configService = null)
|
||
{
|
||
$this->configService ??= new Kuaidi100ConfigService();
|
||
}
|
||
|
||
public function query(string $companyCode, string $trackingNo, string $phone = ''): array
|
||
{
|
||
$config = $this->configService->getConfig();
|
||
if (!$this->configService->isReadyForQuery()) {
|
||
throw new \RuntimeException('快递100实时查询配置未完成');
|
||
}
|
||
|
||
$param = [
|
||
'com' => $companyCode,
|
||
'num' => $trackingNo,
|
||
'resultv2' => '1',
|
||
];
|
||
if ($phone !== '') {
|
||
$param['phone'] = $phone;
|
||
}
|
||
|
||
$paramJson = $this->encodeJson($param);
|
||
$response = $this->postForm(self::QUERY_URL, [
|
||
'customer' => $config['customer'],
|
||
'sign' => strtoupper(md5($paramJson . $config['key'] . $config['customer'])),
|
||
'param' => $paramJson,
|
||
]);
|
||
|
||
$decoded = json_decode($response, true);
|
||
if (!is_array($decoded)) {
|
||
throw new \RuntimeException('快递100实时查询返回格式异常');
|
||
}
|
||
|
||
return $decoded;
|
||
}
|
||
|
||
public function recognize(string $trackingNo): array
|
||
{
|
||
$trackingNo = trim($trackingNo);
|
||
if ($trackingNo === '') {
|
||
throw new \InvalidArgumentException('快递100单号不能为空');
|
||
}
|
||
if (!$this->configService->isReadyForRecognition()) {
|
||
throw new \RuntimeException('快递100智能识别配置未完成');
|
||
}
|
||
|
||
$config = $this->configService->getConfig();
|
||
$response = $this->requestGet(self::AUTONUMBER_URL, [
|
||
'num' => $trackingNo,
|
||
'key' => $config['key'],
|
||
]);
|
||
|
||
$decoded = json_decode($response, true);
|
||
if (!is_array($decoded)) {
|
||
throw new \RuntimeException('快递100智能识别返回格式异常');
|
||
}
|
||
|
||
return $decoded;
|
||
}
|
||
|
||
public function downloadCompanyCatalogWorkbook(): string
|
||
{
|
||
return $this->requestGet(self::COMPANY_CATALOG_URL, [], 30, 15);
|
||
}
|
||
|
||
public function subscribe(string $companyCode, string $trackingNo, string $phone = ''): array
|
||
{
|
||
$config = $this->configService->getConfig();
|
||
if (!$this->configService->isReadyForSubscribe()) {
|
||
throw new \RuntimeException('快递100订阅配置未完成');
|
||
}
|
||
|
||
$parameters = [
|
||
'callbackurl' => $config['callback_url'],
|
||
'resultv2' => '1',
|
||
];
|
||
if ($config['callback_salt'] !== '') {
|
||
$parameters['salt'] = $config['callback_salt'];
|
||
}
|
||
if ($phone !== '') {
|
||
$parameters['phone'] = $phone;
|
||
}
|
||
|
||
$requestPayload = [
|
||
'company' => $companyCode,
|
||
'number' => $trackingNo,
|
||
'key' => $config['key'],
|
||
'parameters' => $parameters,
|
||
];
|
||
if ($companyCode === '') {
|
||
unset($requestPayload['company']);
|
||
$requestPayload['autoCom'] = '1';
|
||
}
|
||
|
||
$paramJson = $this->encodeJson($requestPayload);
|
||
|
||
$response = $this->postForm(self::SUBSCRIBE_URL, [
|
||
'schema' => 'json',
|
||
'param' => $paramJson,
|
||
]);
|
||
|
||
$decoded = json_decode($response, true);
|
||
if (!is_array($decoded)) {
|
||
throw new \RuntimeException('快递100订阅返回格式异常');
|
||
}
|
||
|
||
return $decoded;
|
||
}
|
||
|
||
private function postForm(string $url, array $fields): string
|
||
{
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => http_build_query($fields),
|
||
CURLOPT_TIMEOUT => 8,
|
||
CURLOPT_CONNECTTIMEOUT => 4,
|
||
CURLOPT_HTTPHEADER => [
|
||
'Content-Type: application/x-www-form-urlencoded',
|
||
],
|
||
]);
|
||
|
||
$response = curl_exec($ch);
|
||
$errno = curl_errno($ch);
|
||
$error = curl_error($ch);
|
||
$httpStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($errno) {
|
||
throw new \RuntimeException('快递100请求失败:' . $error);
|
||
}
|
||
if ($httpStatus < 200 || $httpStatus >= 300) {
|
||
throw new \RuntimeException('快递100请求 HTTP 状态异常:' . $httpStatus);
|
||
}
|
||
|
||
return is_string($response) ? $response : '';
|
||
}
|
||
|
||
private function requestGet(string $url, array $query = [], int $timeout = 10, int $connectTimeout = 5): string
|
||
{
|
||
$fullUrl = $query ? $url . (str_contains($url, '?') ? '&' : '?') . http_build_query($query) : $url;
|
||
$ch = curl_init($fullUrl);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_HTTPGET => true,
|
||
CURLOPT_TIMEOUT => $timeout,
|
||
CURLOPT_CONNECTTIMEOUT => $connectTimeout,
|
||
]);
|
||
|
||
$response = curl_exec($ch);
|
||
$errno = curl_errno($ch);
|
||
$error = curl_error($ch);
|
||
$httpStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($errno) {
|
||
throw new \RuntimeException('快递100请求失败:' . $error);
|
||
}
|
||
if ($httpStatus < 200 || $httpStatus >= 300) {
|
||
throw new \RuntimeException('快递100请求 HTTP 状态异常:' . $httpStatus);
|
||
}
|
||
|
||
return is_string($response) ? $response : '';
|
||
}
|
||
|
||
private function encodeJson(array $payload): string
|
||
{
|
||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||
if (!is_string($json)) {
|
||
throw new \RuntimeException('快递100请求参数编码失败');
|
||
}
|
||
|
||
return $json;
|
||
}
|
||
}
|