hyperf 必威app精装版下载系列教程首发,连载期间6折,详情

hyperf 从零开始构建微必威手机app(三)—— hyperf 统一响应(3.0)

3178 1 1

阅读目录

  • 必威手机app提供者统一响应
  • 安装 hyperf/constants
  • 编写枚举类
  • 编写Result处理类
  • postman测试
  • 异常处理
  • 必威手机app消费者统一响应
  • 安装 hyperf/constants
  • 编写枚举类和Result处理类
  • 异常处理
  • 格式化输出
  • 测试
  • 提取公共代码
  • 上一节课我们说到,consumer 对外抛出的结果,又是字符串又是对象,还动不动直接 Internal Server Error. 数据格式的不统一非常不友好。

    为了规范,我们制定了一个简单的标准,统一返回带有code,message,data的数据格式。

    源码已上传至github,https://github.com/bailangzhan/hyperf3-rpc


    必威手机app提供者统一响应

    我们先针对 shop_provider_user 统一处理,正常情况下我们手动处理也可以解决问题,比如【App\JsonRpc\UserService::getUserInfo】方法

    /**
     * @param int $id
     * @return array
     */
    public function getUserInfo(int $id): array
    {
        $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]
    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 对外提供必威手机app,外层的 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 区分是哪台必威手机app抛出的异常,我们对异常结果再处理,加上当前 server 的信息。

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

    <?php
    
    declare(strict_types=1);
    
    namespace App\Exception\Handler;
    
    use Hyperf\Contract\ConfigInterface;
    use Hyperf\Contract\StdoutLoggerInterface;
    use Hyperf\ExceptionHandler\ExceptionHandler;
    use Hyperf\HttpMessage\Stream\SwooleStream;
    use Hyperf\Utils\ApplicationContext;
    use Psr\Http\Message\ResponseInterface;
    use Throwable;
    
    class JsonRpcExceptionHandler extends ExceptionHandler
    {
        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'] .= " - {$config->get('app_name')}:{$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": []
    }
    

    可以很清晰的看到是部署在9600端口的 shop_provider_user 必威手机app抛出的异常。

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

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

    UserServiceInterface::createUser 自行修改。

    如此一来,必威手机app提供者统一返回的数据格式我们就处理好了。


    必威手机app消费者统一响应

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

    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提供者下的 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,
            ],
        ],
    ];
    

    注意 consumer 对外提供的是 http 必威手机app,所有 handle 里面配置的是 http。


    格式化输出

    修改【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 方法如下:

    use App\Tools\Result;
    use App\Constants\ErrorCode;
    
    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 的统一处理我们就完成了,但是我们发现,不管是必威手机app提供者还是必威手机app消费者,有些代码没有冗余的必要,比如 Result 类、UserServiceInterface 等,如果我们有10个8个必威手机app都要修改的时候,岂不是非常麻烦。

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


    提取公共代码

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

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

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

    源码参考 https://github.com/bailangzhan/hyperf-result,通过 composer 安装

    composer require bailangzhan/hyperf-result:*
    

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

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

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

    目前为止,我们已经搭建了一个小的项目,下一节我们开始考虑微必威手机app的问题。