6、Hyperf 3 微服务系列 - 异常处理,返回相关格式的数据
hi,我是温新,一名 PHPer
Hyperf 3 微服务代码已上传至 Github:https://github.com/ziruchu/hyperf3-microservice-code
本篇文章将结合枚举类对返回消息进行统一封装。在封装之前先来看看项目目录。
# 现在有 3 个应用
note # 此篇文章暂未使用
note_consumer_user # 消费者
note_provider_user # 服务者
服务者统一返回数据
注意:当前位于 note_provider_user
第一步:安装枚举组件并编写枚举类
安装组件
composer require hyperf/constants
生成枚举类
php bin/hyperf.php gen:constant ResponseCode
编写枚举类
<?php
// app/Constants/ResponseCode.php
declare(strict_types=1);
namespace App\Constants;
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
#[Constants]
class ResponseCode extends AbstractConstants
{
/**
* @Message("Server Error!")
*/
const SERVER_ERROR = 500;
/**
* @Message("success")
*/
public const SUCCESS = 200;
/**
* @Message("error")
*/
public const ERROR = 0;
}
第二步:创建公共统一数据返回类
<?php
// app/Tools/ResponseTool.php
namespace App\Tools;
use App\Constants\ResponseCode;
class ResponseTool
{
public static function success(array $data = [])
{
return self::commonResuls(ResponseCode::SUCCESS, ResponseCode::getMessage(ResponseCode::SUCCESS), $data);
}
public static function error(int $code = ResponseCode::ERROR, string $message = '', array $data = [])
{
if (empty($message)) {
return self::commonResuls($code, ResponseCode::getMessage($code), $data);
} else {
return self::commonResuls($code, $message, $data);
}
}
// 返回统一的数据
public static function commonResuls(int $code, string $message, array $data)
{
return [
'code' => $code,
'message' => $message,
'data' => $data
];
}
}
第三步:修改 App\JsonRpc\Service\UserService.php 中的 getUserInfo 方法
<?php
// app/JsonRpc/Service/UserService.php
use App\Tools\ResponseTool;
public function getUserInfo(int $id)
{
$user = User::query()->find($id);
if (empty($user)) {
throw new \RuntimeException('没有该用户');
}
return ResponseTool::success($user->toArray());
}
第四步:postman 测试
获取存在的用户信息
POST请求 http://192.168.31.90:9600
请求参数
{
"jsonrpc": "2.0",
"method": "/user/getUserInfo",
"params": {
"id": 1
},
"id": "",
"context": []
}
返回结果
{
"jsonrpc": "2.0",
"id": "",
"result": {
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "李四",
"gender": 1,
"created_at": "2023-03-21 04:37:43",
"updated_at": "2023-03-21 04:37:43"
}
},
"context": []
}
获取不存在的用户信息
POST请求 http://192.168.31.90:9600
请求参数
{
"jsonrpc": "2.0",
"method": "/user/getUserInfo",
"params": {
"id": 11
},
"id": "",
"context": []
}
返回结果
{
"jsonrpc": "2.0",
"id": "",
"error": {
"code": -32000,
"message": "没有该用户",
"data": {
"class": "RuntimeException",
"code": 0,
"message": "没有该用户"
}
},
"context": []
}
当获取不存在的用户信息时,抛出异常。
异常处理
为了知道是哪台服务抛出了异常,现在需要对异常进行处理。
1、处理服务者远程调用异常
<?php
// app/Exception/Handler/JsonRpcExceptionHandler.php
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')]
private string $appName;
public function handle(Throwable $throwable, ResponseInterface $response)
{
/*
$$responseContents 结构如下:
Array
(
[jsonrpc] => 2.0
[id] =>
[error] => Array
(
[code] => -32000
[message] => 没有该用户
[data] => Array
(
[class] => RuntimeException
[code] => 0
[message] => 没有该用户
)
)
[context] => Array()
)
*/
$responseContents = json_decode($response->getBody()->getContents(), true);
$errorMessage = $responseContents['error']['message'];
if (! empty($responseContents['error'])) {
$port = 0;
$host = '';
$config = ApplicationContext::getContainer()->get(ConfigInterface::class);
$servers = $config->get('server.servers');
foreach ($servers as $server) {
if ($server['name'] == 'jsonrpc-http') {
$port = $server['port'];
$host = $server['host'];
break;
}
}
$responseContents['error']['message'] = $this->appName . '-' . $host .':'. $port . '-' . $errorMessage;
}
$data = json_encode($responseContents, JSON_UNESCAPED_UNICODE);
return $response->withStatus(200)->withBody(new SwooleStream($data));
}
public function isValid(Throwable $throwable): bool
{
return true;
}
}
2、注册异常
<?php
// config/autoload/exception.php
declare(strict_types=1);
return [
'handler' => [
'jsonrpc-http' => [
\App\Exception\Handler\JsonRpcExceptionHandler::class,
]
],
];
3、使用 postman 进行异常测试
# 获取一个不存在的用户信息
POST请求 http://192.168.31.90:9600
请求参数
{
"jsonrpc": "2.0",
"method": "/user/getUserInfo",
"params": {
"id": 11
},
"id": "",
"context": []
}
# 查询结果
{
"jsonrpc": "2.0",
"id": "",
"error": {
"code": -32000,
"message": "node_provider_user-0.0.0.0:9600-没有该用户",
"data": {
"class": "RuntimeException",
"code": 0,
"message": "没有该用户"
}
},
"context": []
}
获取用户异常信息已经得到处理,同样的,也可以对创建用户进行处理。
4、创建用户信息异常处理
<?php
// app/JsonRpc/Service/UserService.php
public function createUser(string $name, string $gender)
{
if (empty($name)) {
throw new \RuntimeException('用户名不能为空');
}
$user = User::query()->create([
'name' => $name,
'gender' => $gender,
]);
return $user ? ResponseTool::success() : ResponseTool::error('创建用户失败');
}
消费者异常与数据处理
注意:现在已经切换到到了 note_consumer_user
上面已经对服务提供者异常信息进行了处理,现在我们来请求一下消费者看看是什么情况。
$ curl http://192.168.31.90:9501/users/show?jd=2
Internal Server Error.
当消费者获取一个不存在的用户信息时,直接报错,这并不友好,下面就对这种情况进行处理。处理的方式和服务提供者一样。
第一步:安装枚举组件并编写枚举类
安装组件
composer require hyperf/constants
第二步:代码复制
把服务者中的 Constants\ResponseCode.php
、Tools\ResponseTool.php
复制到消费者,同时对应好目录。
第三步:修改控制器方法
1、修改获取用户信息方法
<?php
// app/Controller/UserController.php
use App\Constants\ResponseCode;
// 获取用户信息
#[GetMapping('/users/show')]
public function getUserInfo()
{
$id = (int) $this->request->input('id');
$user = $this->userService->getUserInfo($id);
if ($user['code'] != ResponseCode::SUCCESS) {
throw new \RuntimeException($user['message']);
}
return ResponseTool::success($user['data']);
}
2、postman 测试
GET请求 http://192.168.31.90:9501/users/show?id=1
结果
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "李四",
"gender": 1,
"created_at": "2023-03-21 04:37:43",
"updated_at": "2023-03-21 04:37:43"
}
}
3、改造创建用户方法
<?php
// 添加用户
#[PostMapping('/users/store')]
public function store()
{
$name = (string)$this->request->input('name', '');
$gender = (int)$this->request->input('gender', 0);
$user = $this->userService->createUser($name, $gender);
if ($user['code'] != ResponseCode::SUCCESS) {
throw new \RuntimeException($user['message']);
}
return ResponseTool::success($user['data']);
}
4、测试添加用户
说明:postman 中,请求参数使用 json
请求方法: http://192.168.31.90:9501/users/store
参数:
{
"name":"好帅",
"gender":2
}
返回结果
{
"code": 200,
"message": "success",
"data": []
}
异常处理
1、创建异常处理类
<?php
// app/Exception/Handler/ApiExceptionHandler.php
namespace App\Exception\Handler;
use App\Exception\ApiException;
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 __construct(protected StdoutLoggerInterface $logger)
{
}
public function handle(Throwable $throwable, ResponseInterface $response)
{
// 处理 RuntimeException 异常
if ($throwable instanceof \RuntimeException) {
return $this->exceptionHandle($throwable, $response);
}
$this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
$this->logger->error($throwable->getTraceAsString());
return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
}
public function isValid(Throwable $throwable): bool
{
return true;
}
// 返回异常信息
public function exceptionHandle(Throwable $throwable, ResponseInterface $response)
{
$data = json_encode([
'code' => $throwable->getCode(),
'message' => $throwable->getMessage(),
], JSON_UNESCAPED_UNICODE);
return $response->withAddedHeader('Content-Type', ' application/json; charset=UTF-8')
->withStatus(500)
->withBody(new SwooleStream($data));
}
}
2、注册异常
<?php
// config/autoload/exception.php
declare(strict_types=1);
return [
'handler' => [
'http' => [
// 在默认的异常类中埋进行远程调用异常处理
App\Exception\Handler\AppExceptionHandler::class,
],
],
];
3、测试异常 - 获取不存在的用户信息
$ curl http://192.168.31.90:9501/users/show?jd=211
{"code":-32000,"message":"node_provider_user-0.0.0.0:9600-没有该用户"}
本文对服务者的调用是通过手动方式的,也可以注意到,使用注解服务时 publishTo
参数是没有填写的。目的是为了演示手动调用的过程。
到这里,就已经使用了微服务,没错,就是已经使用了。
后面我们将使用服务注册与自动发现。
我是温新,本篇文章结束。
请登录后再评论