feat: add kuaidi100 logistics sync
This commit is contained in:
187
server-api/app/support/Kuaidi100Client.php
Normal file
187
server-api/app/support/Kuaidi100Client.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user