Commit 66e213ae authored by 谢宇轩's avatar 谢宇轩

refactor: 重构错误处理

parent 3dfc9861
...@@ -7,11 +7,12 @@ use GuzzleHttp\Client; ...@@ -7,11 +7,12 @@ use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\HandlerStack; use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use InvalidArgumentException; use InvalidArgumentException;
use Jiwei\EasyHttpSdk\Exception\ApplicationException; use Jiwei\EasyHttpSdk\Exception\ApplicationException;
use Jiwei\EasyHttpSdk\Exception\GuiltyResultException;
use Jiwei\EasyHttpSdk\Exception\SdkException; use Jiwei\EasyHttpSdk\Exception\SdkException;
use Jiwei\EasyHttpSdk\Exception\TimeOutExcetpion; use Jiwei\EasyHttpSdk\Exception\TimeOutExcetpion;
use Jiwei\EasyHttpSdk\Exception\UnknowResultException;
use Jiwei\EasyHttpSdk\Http\SdkRequest; use Jiwei\EasyHttpSdk\Http\SdkRequest;
use Jiwei\EasyHttpSdk\Middleware\JwtMiddleware; use Jiwei\EasyHttpSdk\Middleware\JwtMiddleware;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
...@@ -29,14 +30,12 @@ class Application implements ClientInterface ...@@ -29,14 +30,12 @@ class Application implements ClientInterface
private const TOKEN_CACHE_KEY = "Auth.%s"; private const TOKEN_CACHE_KEY = "Auth.%s";
private const EXPIRES_AT = 30000; /** @var string jwtToken */
private $jwtToken = "";
/** @var Option $option */ /** @var Option $option */
private $option; private $option;
/** @var string jwtToken */
private $jwtToken = "";
/** @var null|LoggerInterface 日志记录器 */ /** @var null|LoggerInterface 日志记录器 */
protected $logger = null; protected $logger = null;
...@@ -194,6 +193,7 @@ class Application implements ClientInterface ...@@ -194,6 +193,7 @@ class Application implements ClientInterface
public function resultDecode(SdkRequest $request, ResponseInterface $response): array public function resultDecode(SdkRequest $request, ResponseInterface $response): array
{ {
// context 是没有处理前的上下文
$context = $this->lastRequestContext; $context = $this->lastRequestContext;
if ($context['request']) { if ($context['request']) {
...@@ -219,31 +219,28 @@ class Application implements ClientInterface ...@@ -219,31 +219,28 @@ class Application implements ClientInterface
$rpcResult = []; $rpcResult = [];
if ($response->getStatusCode() != 304) { try {
$responseInfo = $response->getBody()->getContents(); $rpcResult = $this->option->errorHandlingPolicy()->process($response);
$rpcResult = json_decode($responseInfo, true); } catch (GuiltyResultException $exception) {
if (json_last_error()) { // 没有通过预期判定,转化为 SDK Exception
if ($this->logger) { $this->lastRequestContext['error_message'] = $exception->getMessage();
$this->logger->error("sdk error, bad response content.", $context); $this->lastRequestContext['error_detail'] = $exception->getOptions(); // 获取错误的详情
}
throw new ApplicationException("Content Format error.", $request, []);
}
}
$errorName = $rpcResult['name'] ?? "";
// Paypal的API错误结果是通用的,所以这里简单处理一下
if (!empty($errorName) && $response->getStatusCode() >= 400) {
$errorMessage = sprintf("%s : %s", $errorName, $rpcResult['message'] ?? "");
$this->lastRequestContext['error'] = $errorMessage;
$this->lastRequestContext['error_detail'] = $rpcResult;
if ($this->logger) { if ($this->logger) {
$this->logger->warning("Error", $this->lastRequestContext); $this->logger->warning("Error", $this->lastRequestContext);
} }
throw new SdkException($errorMessage, $request, $response, $rpcResult); throw new SdkException($exception->getMessage(), $request, $response, $rpcResult);
} catch (UnknowResultException $exception) {
// 无法解析,转化为 Application Exception
// 日志中保留 OUT PUT
$this->lastRequestContext['error_message'] = $exception->getMessage();
$this->lastRequestContext['error_detail'] = $exception->getResult(); // 获取错误的详情
if ($this->logger) {
$this->logger->error("sdk error, bad response content.", $this->lastRequestContext);
}
throw new ApplicationException("Content Format error.", $request, []);
} }
$version = $response->getHeader('ETag'); $version = $response->getHeader('ETag');
if (!empty($version)) { if (!empty($version)) {
$rpcResult = array_merge($rpcResult, [ $rpcResult = array_merge($rpcResult, [
'version' => $version[0] 'version' => $version[0]
...@@ -252,8 +249,6 @@ class Application implements ClientInterface ...@@ -252,8 +249,6 @@ class Application implements ClientInterface
if ($this->logger) { if ($this->logger) {
$this->logger->info("Success", $context); $this->logger->info("Success", $context);
} }
return $rpcResult; return $rpcResult;
} }
...@@ -286,7 +281,7 @@ class Application implements ClientInterface ...@@ -286,7 +281,7 @@ class Application implements ClientInterface
if (!$currentToken->isHit()) { if (!$currentToken->isHit()) {
$token = $this->getAccessToken($app_key, $app_secret); $token = $this->getAccessToken($app_key, $app_secret);
$currentToken->set($token)->expiresAfter(self::EXPIRES_AT); $currentToken->set($token)->expiresAfter($this->option->getAuthExpires());
$this->cache->save($currentToken); $this->cache->save($currentToken);
} }
...@@ -303,7 +298,7 @@ class Application implements ClientInterface ...@@ -303,7 +298,7 @@ class Application implements ClientInterface
private function getAccessToken(string $app_key, string $app_secret): string private function getAccessToken(string $app_key, string $app_secret): string
{ {
try { try {
$request = $this->option->getAuthMoudel()($app_key, $app_secret); $request = $this->option->authorization()($app_key, $app_secret);
$response = $this->client->send($request); $response = $this->client->send($request);
} catch (TransferException $exception) { } catch (TransferException $exception) {
throw new RuntimeException('time out!'); throw new RuntimeException('time out!');
......
<?php
namespace Jiwei\EasyHttpSdk\Exception;
use Throwable;
use \UnexpectedValueException;
class GuiltyResultException extends UnexpectedValueException
{
/** @var array<string, mixed> */
private $options;
/**
* @param string $message
* @param array<string, mixed> $options
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($message = "", $options = [], $code = 0, Throwable $previous = null)
{
$this->options = $options;
parent::__construct($message, $code, $previous);
}
/**
* @return array<string, mixed>
*/
public function getOptions(): array
{
return $this->options;
}
}
<?php
namespace Jiwei\EasyHttpSdk\Exception;
use Throwable;
use \UnexpectedValueException;
class UnknowResultException extends UnexpectedValueException
{
/** @var string */
public $result;
/**
* @param string $message
* @param string $result
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($message = "", $result = "", $code = 0, Throwable $previous = null)
{
$this->result = $result;
parent::__construct($message, $code, $previous);
}
/**
* @return string
*/
public function getResult(): string
{
return $this->result;
}
}
<?php
declare(strict_types=1);
namespace Jiwei\EasyHttpSdk\Http;
use ArrayAccess;
use GuzzleHttp\Psr7\Response;
use Jiwei\EasyHttpSdk\Exception\SdkException;
/**
* @implements ArrayAccess<string, mixed>
*/
class Context implements ArrayAccess
{
/** @var SdkRequest */
public $request;
/** @var Response */
public $response;
/** @var int */
public $requestId;
/** @var string */
public $endpoint;
/** @var string */
public $action;
/** @var string */
public $stage;
/** @var string */
public $expend;
/** @var string */
public $httpStatus;
/** @var array<string, mixed> */
public $data;
/** @var SdkException */
public $exception;
/** @var string */
public $errorMessage;
/** @var array<string, mixed>|string */
public $errorDetail;
/**
* @param $offset
* @return bool
*/
public function offsetExists($offset): bool
{
return isset($this->$offset);
}
/**
* @param $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->$offset ?? null;
}
/**
* @param $offset
* @param $value
* @return void
*/
public function offsetSet($offset, $value)
{
if (in_array($offset, get_object_vars($this))) {
$this->$offset = $value;
}
}
/**
* @param $offset
* @return void
*/
public function offsetUnset($offset)
{
$this->$offset = null;
}
}
<?php <?php
declare(strict_types=1);
namespace Jiwei\EasyHttpSdk\Middleware; namespace Jiwei\EasyHttpSdk\Middleware;
...@@ -39,4 +40,4 @@ class EtagMiddleware implements MiddlewareInterface ...@@ -39,4 +40,4 @@ class EtagMiddleware implements MiddlewareInterface
return $handler($request, $options); return $handler($request, $options);
}; };
} }
} }
\ No newline at end of file
<?php <?php
declare(strict_types=1);
namespace Jiwei\EasyHttpSdk\Middleware; namespace Jiwei\EasyHttpSdk\Middleware;
...@@ -29,4 +30,4 @@ class JwtMiddleware implements MiddlewareInterface ...@@ -29,4 +30,4 @@ class JwtMiddleware implements MiddlewareInterface
return $handler($request, $options); return $handler($request, $options);
}; };
} }
} }
\ No newline at end of file
<?php <?php
declare(strict_types=1);
namespace Jiwei\EasyHttpSdk\Middleware; namespace Jiwei\EasyHttpSdk\Middleware;
interface MiddlewareInterface interface MiddlewareInterface
{ {
} }
\ No newline at end of file
<?php <?php
declare(strict_types=1);
namespace Jiwei\EasyHttpSdk\Middleware; namespace Jiwei\EasyHttpSdk\Middleware;
...@@ -36,4 +37,4 @@ class RequestIDMiddleware implements MiddlewareInterface ...@@ -36,4 +37,4 @@ class RequestIDMiddleware implements MiddlewareInterface
return $handler($request, $options); return $handler($request, $options);
}; };
} }
} }
\ No newline at end of file
<?php <?php
declare(strict_types=1);
namespace Jiwei\EasyHttpSdk; namespace Jiwei\EasyHttpSdk;
use GuzzleHttp\Psr7\Request; use Jiwei\EasyHttpSdk\Policy\HandlingPolicyInterface;
use http\Exception\InvalidArgumentException;
abstract class Option abstract class Option
{ {
...@@ -12,39 +12,49 @@ abstract class Option ...@@ -12,39 +12,49 @@ abstract class Option
'development' => '', 'development' => '',
'production' => '' 'production' => ''
]; ];
const AUTH_CACHE_EXPIRES_AT = 30000;
/** @var string App Key 应用标志 */ /** @var string App Key 应用标志 */
private $app_id; private $appId;
/** @var string App Secret 应用密钥 */ /** @var string App Secret 应用密钥 */
private $app_secret; private $appSecret;
/** @var string SDK 的 Stage 环境 */ /** @var string SDK 的 Stage 环境 */
private $stage = "development"; private $stage = "development";
/** @var float 超时时间 */ /** @var float 超时时间 */
private $time_out = 3.0; private $timeOut = 3.0;
/** @var bool 调试模式 */ /** @var bool 调试模式 */
private $debug = false; private $debug = false;
/** /**
* @return \Closure * @return \Closure
*/ */
abstract public function getAuthMoudel(): \Closure; abstract public function authorization(): \Closure;
/**
* 错误处理策略
* @return HandlingPolicyInterface
*/
abstract public function errorHandlingPolicy(): HandlingPolicyInterface;
/** /**
* @param string $app_id * @param string $appId
* @return Option * @return Option
*/ */
public function setAppId(string $app_id): self public function setAppId(string $appId): self
{ {
$this->app_id = $app_id; $this->appId = $appId;
return $this; return $this;
} }
/** /**
* @param string $app_secret * @param string $appSecret
* @return Option * @return Option
*/ */
public function setAppSecret(string $app_secret): self public function setAppSecret(string $appSecret): self
{ {
$this->app_secret = $app_secret; $this->appSecret = $appSecret;
return $this; return $this;
} }
...@@ -59,12 +69,12 @@ abstract class Option ...@@ -59,12 +69,12 @@ abstract class Option
} }
/** /**
* @param float $time_out * @param float $timeOut
* @return Option * @return Option
*/ */
public function setTimeout(float $time_out): self public function setTimeout(float $timeOut): self
{ {
$this->time_out = $time_out; $this->timeOut = $timeOut;
return $this; return $this;
} }
...@@ -83,7 +93,7 @@ abstract class Option ...@@ -83,7 +93,7 @@ abstract class Option
*/ */
public function getAppSecret(): string public function getAppSecret(): string
{ {
return $this->app_secret; return $this->appSecret;
} }
/** /**
...@@ -95,6 +105,15 @@ abstract class Option ...@@ -95,6 +105,15 @@ abstract class Option
return static::ENDPONIT_HOSTS[$this->getStage()] ?? ""; return static::ENDPONIT_HOSTS[$this->getStage()] ?? "";
} }
/**
* @return int
*/
public function getAuthExpires(): int
{
return static::AUTH_CACHE_EXPIRES_AT ?? 30000;
}
/** /**
* @return string * @return string
*/ */
...@@ -108,7 +127,7 @@ abstract class Option ...@@ -108,7 +127,7 @@ abstract class Option
*/ */
public function getTimeOut(): float public function getTimeOut(): float
{ {
return $this->time_out; return $this->timeOut;
} }
/** /**
...@@ -116,7 +135,7 @@ abstract class Option ...@@ -116,7 +135,7 @@ abstract class Option
*/ */
public function getAppId(): string public function getAppId(): string
{ {
return $this->app_id; return $this->appId;
} }
/** /**
......
<?php
namespace Jiwei\EasyHttpSdk\Policy;
use Jiwei\EasyHttpSdk\Exception\GuiltyResultException;
use Jiwei\EasyHttpSdk\Exception\UnknowResultException;
use Psr\Http\Message\ResponseInterface;
class DefaultErrorHandlingPolicy implements HandlingPolicyInterface
{
/**
* @param ResponseInterface $response
* @return array<string, mixed>
*/
public function process(ResponseInterface $response): array
{
if ($response->getStatusCode() == 304) {
return [];
}
$responseInfo = $response->getBody()->getContents();
$rpcResult = json_decode($responseInfo, true);
if (json_last_error()) {
throw new UnknowResultException("Content Format error.", $responseInfo);
}
if (!empty($rpcResult['name']) && $response->getStatusCode() >= 400) {
$errorMessage = sprintf("%s : %s", $rpcResult['name'], $rpcResult['message'] ?? "");
throw new GuiltyResultException($errorMessage, $rpcResult);
}
return $rpcResult;
}
}
<?php
namespace Jiwei\EasyHttpSdk\Policy;
use Psr\Http\Message\ResponseInterface;
interface HandlingPolicyInterface
{
/**
* @param ResponseInterface $response
* @return array<string, mixed>
*/
public function process(ResponseInterface $response): array;
}
<?php <?php
declare(strict_types=1);
namespace Jiwei\EasyHttpSdk; namespace Jiwei\EasyHttpSdk;
use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Request;
use Jiwei\EasyHttpSdk\Policy\DefaultErrorHandlingPolicy;
use Jiwei\EasyHttpSdk\Policy\HandlingPolicyInterface;
class XXXSDKOption extends Option class XXXSDKOption extends Option
{ {
private const AUTH_API_ROUTE = "/api/access/token"; private const AUTH_API_ROUTE = "/api/access/token";
const AUTH_CACHE_EXPIRES_AT = 30000;
const ENDPONIT_HOSTS = [ const ENDPONIT_HOSTS = [
'local' => 'localhost:8080', 'local' => 'localhost:8080',
'development' => 'localhost:8080', 'development' => 'localhost:8080',
'production' => 'localhost:8080' 'production' => 'localhost:8080'
]; ];
public function getAuthMoudel(): \Closure /**
* 鉴权模式
*
* @return \Closure
*/
public function authorization(): \Closure
{ {
return function (string $appId, string $appSecret): Request { return function (string $appId, string $appSecret): Request {
return new Request('post', self::AUTH_API_ROUTE, [ return new Request('post', self::AUTH_API_ROUTE, [
...@@ -24,6 +34,14 @@ class XXXSDKOption extends Option ...@@ -24,6 +34,14 @@ class XXXSDKOption extends Option
]) ])
]); ]);
}; };
}
/**
* 错误处理策略
* @return HandlingPolicyInterface
*/
public function errorHandlingPolicy(): HandlingPolicyInterface
{
return new DefaultErrorHandlingPolicy();
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment