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;
}
}
测试
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 大家可以参考修改。
目前为止,我们已经搭建了一个小的项目,下一节我们开始考虑微服务的问题
- 版权申明:此文如未标注转载均为本站原创,自由转载请表明出处《龙行博客》。
- 本文网址:https://www.liaotaoo.cn/416.html
- 上篇文章:Hyperf-Task使用
- 下篇文章:Hyperf-consul容器ping不通的情况