龙行博客

走路看风景,经历看人生,岁月留痕迹,人生留轨迹,17的历史,18的豪情,时间的匆忙,人生的风景,放开心胸往前走,成功再远行,放开理想往前走,梦想再行动。
现在位置:首页 > 编程语言 > PHP > Hyper服务消费者统一响应

Hyper服务消费者统一响应

龙行    PHP    2022-10-23    224    0评论    

Hyper服务消费者统一响应

服务提供者统一响应

我们先针对provider统一处理,正常情况下我们手动处理也可以解决问题,比如

【App\JsonRpc\UserService::getUserInfo】

public function getUserInfo(int $id)
{
   $user = User::query()->find($id);
   if (empty($user)) {
       throw new \RuntimeException("user not found");
   }
   return [
       'code' => 200,
       'message' => 'success',
       'data' => $user->toArray(),
   ];
}

但每次都这样写非常麻烦,下面我们基于 hyperf/constants 进行简单的封装。

安装 hyperf/constants
composer require hyperf/constants
生成枚举类
php bin/hyperf.php gen:constant ErrorCode

修改后的 App\Constants\ErrorCode.php 如下

<?php
declare(strict_types=1);
namespace App\Constants;
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
/**
* @Constants
*/
#[Constants]
class ErrorCode extends AbstractConstants
{
   /**
    * @Message("Server Error!")
    */
   const SERVER_ERROR = 500;
   /**
    * @Message("success")
    */
   public const SUCCESS = 200;
   /**
    * @Message("error")
    */
   public const ERROR = 0;
}
定义 Result 处理类
新建【App\Tools\Result.php】

<?php
namespace App\Tools;
use App\Constants\ErrorCode;
class Result
{
   public static function success($data = [])
   {
       return static::result(ErrorCode::SUCCESS, ErrorCode::getMessage(ErrorCode::SUCCESS), $data);
   }
   public static function error($message = '', $code = ErrorCode::ERROR, $data = [])
   {
       if (empty($message)) {
           return static::result($code, ErrorCode::getMessage($code), $data);
       } else {
           return static::result($code, $message, $data);
       }
   }
   protected static function result($code, $message, $data)
   {
       return [
           'code' => $code,
           'message' => $message,
           'data' => $data,
       ];
   }
}
测试

现在我们重新修改 App\JsonRpc\UserService::getUserInfo 方法如下

use App\Tools\Result;

public function getUserInfo(int $id)
{
   $user = User::query()->find($id);
   if (empty($user)) {
       throw new \RuntimeException("user not found");
   }
   return Result::success($user->toArray());
}

重新请求 user/getUserInfo 测试下

POST请求 http://127.0.0.1:9600
请求参数
{
   "jsonrpc": "2.0",
   "method": "/user/getUserInfo",
   "params": {
       "id": 1
   },
   "id": "61025bc35e07d",
   "context": []
}
结果
{
   "jsonrpc": "2.0",
   "id": "61025bc35e07d",
   "result": {
       "code": 200,
       "message": "success",
       "data": {
           "id": 1,
           "name": "zhangsan",
           "gender": 3,
           "created_at": "1630187123",
           "updated_at": "1630187123"
       }
   },
   "context": []
}

因为provider对外提供服务,外层的jsonrpc格式是固定的,consumer拿到的数据取决于 result 字段,所以满足了我们制定的标准。

请求一个不存在的记录测试下,比如id=100

POST请求 http://127.0.0.1:9600
请求参数
{
   "jsonrpc": "2.0",
   "method": "/user/getUserInfo",
   "params": {
       "id": 100
   },
   "id": "61025bc35e07d",
   "context": []
}
结果
{
   "jsonrpc": "2.0",
   "id": "61025bc35e07d",
   "error": {
       "code": -32000,
       "message": "user not found",
       "data": {
           "class": "RuntimeException",
           "code": 0,
           "message": "user not found"
       }
   },
   "context": []
}

可以看到我们抛出的 RuntimeException 被 hyperf 主动接管,这也是我们想要的。

异常处理

provider后面我们会做集群处理,为了方便 consumer 区分是哪台服务抛出的异常,我们对异常结果再处理,加上当前server的信息。

新建【App\Exception\Handler\JsonRpcExceptionHandler.php】

<?php
declare(strict_types=1);
namespace App\Exception\Handler;
use Hyperf\Config\Annotation\Value;
use Hyperf\Contract\ConfigInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\Utils\ApplicationContext;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class JsonRpcExceptionHandler extends ExceptionHandler
{
   /**
    * @Value("app_name")
    * @var $appName
    */
   private $appName;
   public function handle(Throwable $throwable, ResponseInterface $response)
   {
       $responseContents = $response->getBody()->getContents();
       $responseContents = json_decode($responseContents, true);
       if (!empty($responseContents['error'])) {
           $port = null;
           $config = ApplicationContext::getContainer()->get(ConfigInterface::class);
           $servers = $config->get('server.servers');
           foreach ($servers as $k => $server) {
               if ($server['name'] == 'jsonrpc-http') {
                   $port = $server['port'];
                   break;
               }
           }
           $responseContents['error']['message'] .= " - {$this->appName}:{$port}";
       }
       $data = json_encode($responseContents, JSON_UNESCAPED_UNICODE);
       return $response->withStatus(200)->withBody(new SwooleStream($data));
   }
   public function isValid(Throwable $throwable): bool
   {
       return true;
   }
}

修改config/autoload/exceptions.php文件,定义异常处理类

<?php
declare(strict_types=1);
return [
   'handler' => [
       'jsonrpc-http' => [
           App\Exception\Handler\JsonRpcExceptionHandler::class,
       ],
   ],
];

重新请求一个不存在的记录

POST请求 http://127.0.0.1:9600
请求参数
{
   "jsonrpc": "2.0",
   "method": "/user/getUserInfo",
   "params": {
       "id": 100
   },
   "id": "61025bc35e07d",
   "context": []
}
结果
{
   "jsonrpc": "2.0",
   "id": "61025bc35e07d",
   "error": {
       "code": -32000,
       "message": "user not found - shop_provider_user:9600",
       "data": {
           "class": "RuntimeException",
           "code": 0,
           "message": "user not found"
       }
   },
   "context": []
}

同样,UserService::createUser方法也可以快速处理。

public function createUser(string $name, int $gender)
{
   if (empty($name)) {
       throw new \RuntimeException("name不能为空");
   }
   $result = User::query()->create([
       'name' => $name,
       'gender' => $gender,
   ]);
   return $result ? Result::success() : Result::error("fail");
}

如此一来,服务提供者统一返回的数据格式我们就处理好了。

服务消费者统一响应

在我们不做任何处理的时候,请求一个不存在的用户信息

GET请求 http://127.0.0.1:9501/user/getUserInfo?id=100
结果
Internal Server Error.

可见针对异常还没有处理。

安装 hyperf/constants
cd shop_consumer_user
composer require hyperf/constants
编写枚举类和Result处理类

复制服务提供者下的 App\Constants\ErrorCode.php 和 App\Tools\Result.php 到shop_consumer_user/app目录下。

异常处理

config/autoload/exceptions.php文件内定义的异常处理类

<?php
declare(strict_types=1);
return [
   'handler' => [
       'http' => [
           Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
           App\Exception\Handler\AppExceptionHandler::class,
       ],
   ],
];
格式化输出
【App\Exception\Handler\AppExceptionHandler.php文件】

<?php
declare(strict_types=1);
namespace App\Exception\Handler;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class AppExceptionHandler extends ExceptionHandler
{
   public function handle(Throwable $throwable, ResponseInterface $response)
   {
       // 格式化输出
       $data = json_encode([
           'code' => $throwable->getCode(),
           'message' => $throwable->getMessage(),
       ], JSON_UNESCAPED_UNICODE);
       // 阻止异常冒泡
       $this->stopPropagation();
       return $response
           ->withAddedHeader('Content-Type', ' application/json; charset=UTF-8')
           ->withStatus(500)
           ->withBody(new SwooleStream($data));
       //return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
   }
   public function isValid(Throwable $throwable): bool
   {
       return true;
   }
}
测试

对 UserController::getUserInfo 方法进行改写如下

public function getUserInfo()
{
   $id = (int) $this->request->input('id');
   $result = $this->userServiceClient->getUserInfo($id);
   if ($result['code'] != ErrorCode::SUCCESS) {
       throw new \RuntimeException($result['message']);
   }
   return Result::success($result['data']);
}

postman分别对正常请求和异常请求测试下

GET请求 http://127.0.0.1:9501/user/getUserInfo?id=1
结果
{
   "code": 200,
   "message": "success",
   "data": {
       "id": 1,
       "name": "zhangsan",
       "gender": 3,
       "created_at": "1630187123",
       "updated_at": "1630187123"
   }
}
GET请求 http://127.0.0.1:9501/user/getUserInfo?id=100
{
   "code": -32000,
   "message": "user not found - shop_provider_user:9600"
}

AppExceptionHandler类可以根据自己的需要进行自定义。

同样的,createUser 方法我们也处理如下

public function createUser()
{
   $name = (string) $this->request->input('name', '');
   $gender = (int) $this->request->input('gender', 0);
   $result = $this->userServiceClient->createUser($name, $gender);
   if ($result['code'] != ErrorCode::SUCCESS) {
       throw new \RuntimeException($result['message']);
   }
   return Result::success($result['data']);
}

针对 consumer 的统一处理我们就完成了,但是我们发现,不管是服务提供者还是服务消费者,有些代码没有冗余的必要,比如Result工具类、UserServiceInterface等,如果我们有10个8个服务且要修改它的时候,改起来非常麻烦。

那怎么样把这些公共代码提取出来呢?大家不妨思考一下再继续阅读。

提取公共代码

我们把Result类和ErrorCode类提取出来形成一个基于composer的公共组件,修改代码的时候,只需要针对源组件包修改发布,需要的模块通过composer安装即可。

由于大部分代码都是复用的,这里就不贴代码了。

下面是一个基于hyperf ConfigProvider 机制实现的组件,暂时只支持hyperf框架下使用,并没有去兼容通用性。

composer require bailangzhan/hyperf-result

我们在消费者的UserController::getUserInfo接口下尝试使用:

public function getUserInfo()
{
   $id = (int) $this->request->input('id');
   $result = $this->userServiceClient->getUserInfo($id);
   if ($result['code'] != ErrorCode::SUCCESS) {
       throw new \RuntimeException($result['message']);
   }
   return \Bailangzhan\Result\Result::success($result['data']);
}

postman请求测试发现接口正常,其他接口以及 provider 大家可以参考修改。

目前为止,我们已经搭建了一个小的项目,下一节我们开始考虑微服务的问题



评论一下 分享本文 赞助站长

赞助站长X

扫码赞助站长
联系站长
龙行博客
  • 版权申明:此文如未标注转载均为本站原创,自由转载请表明出处《龙行博客》。
  • 本文网址:https://www.liaotaoo.cn/416.html
  • 上篇文章:Hyperf-Task使用
  • 下篇文章:Hyperf-consul容器ping不通的情况
  • swoole hyperf
快捷导航
联系博主
在线壁纸
给我留言
四四五五
音乐欣赏
返回顶部