带你轻松使用Hyperf玩转Grpc(三)编写服务

三、编写服务

相信大家打开这篇文章都是对grpc有一定的了解了,并且使用hyperf但官方文档中并无针对grpc的详细注册、发现等教程,官方文档中较多的是围绕 jsonrpc。对比jsonrpc grpc依赖protobuf有更小的传输压缩体积、多语言兼容等优势。

定义服务

下面就开始 编写 proto 并生成代码

在 proto 文件夹下 分别创建 product_srv.proto 、shopping_cart_srv.proto、order_srv.proto

关于 proto 文件 的内容格式以及如何定义这边不再啰嗦,大家请自行 google 学习一下。

我直接将定义好的 服务 展示,大家复制即可。

product.proto

syntax = "proto3";

package mall;

option php_generic_services = true;
option php_metadata_namespace = "App\\Grpc\\GPBMetadata";
option php_namespace = "App\\Grpc\\Mall\\ProductSrv";

service ProductSrv {
  rpc create (ProductCreateRequest) returns (ProductId) {}
  rpc getList (ProductListRequest) returns (ProductListReply){}
  rpc getStock (ProductId) returns (ProductStockReply){}
  rpc incStock (ProductStockRequest) returns (ProductStockReply){}
  rpc decStock (ProductStockRequest) returns (ProductStockReply){}
}

message ProductCreateRequest {
  string title = 1;
  string icon = 2;
  int32 stock = 3;
}

message Product {
  int32 id = 1;
  string title = 2;
  string icon = 3;
  int32 stock = 4;
  string created_at = 5;
  string updated_at = 6;
}

message ProductId {
  int32 id = 1;
}

message ProductListRequest {
  int32 page = 1;
  int32 pageSize = 2;
}

message ProductListReply {
  int32 total = 1;
  repeated Product list = 2;
}

message ProductStockReply {
  int32 stock = 1;
}

message ProductStockRequest {
  int32 id = 1;
  int32 stock = 2;
}

其他服务大家可以 自行实现,这边就先使用 product 服务做演示。

生成代码

进入service/mall-service 根目录,执行

protoc --php_out=./ --grpc_out=./ --plugin=protoc-gen-grpc=/usr/local/lib/grpc_php_plugin -I=/app/proto product_srv.proto

然后到我们的项目下查看,这边以phpstorm打开项目为例

这样我们就把定义好的服务成功生成代码了

编写服务

这一步就从一个刚接触 hyperf 框架的 新手角度 描述步骤。

先把需要用到的插件引入

composer require crayoon/hyperf-grpc

0、先完成配置

在宿主机中使用mysql管理工具 连接 mysql 并创建 mall 数据库,这一步就不啰嗦了。选择 utf8mb4(utf8mb4_general_ci)。

host:127.0.0.1:33306 ( 若修改了环境变量自行更换 )
username:root
password:123456

创建product表

CREATE TABLE `product` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `icon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `stock` int DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

service-mall/.env 中 mysql/redis 配置如下

DB_DRIVER=mysql 
DB_HOST=mysql //对应编排的名称 非容器名
DB_PORT=3306  //内网端口 非对外端口,在宿主机中访问可以使用 127.0.0.1:33306 访问并如何建 product表 这边也不啰嗦
DB_DATABASE=mall
DB_USERNAME=root
DB_PASSWORD=123456
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_general_ci
DB_PREFIX=

REDIS_HOST=redis
REDIS_AUTH=(null)
REDIS_PORT=6379
REDIS_DB=0

1、实现接口

到我们 service-mall/app/Controller 目录下创建ProductSrvController

并实现 App\Grpc\Mall\ProductSrv\ProductSrvInterface

<?php

namespace App\Controller;

use App\Grpc\Mall\ProductSrv\ProductCreateRequest;
use App\Grpc\Mall\ProductSrv\ProductId;
use App\Grpc\Mall\ProductSrv\ProductListRequest;
use App\Grpc\Mall\ProductSrv\ProductSrvInterface;
use App\Grpc\Mall\ProductSrv\ProductStockRequest;

class ProductSrvController implements ProductSrvInterface
{

  public function create(ProductCreateRequest $request)
  {
    // TODO: Implement create() method.
  }

  public function getList(ProductListRequest $request)
  {
    // TODO: Implement getList() method.
  }

  public function getStock(ProductId $request)
  {
    // TODO: Implement getStock() method.
  }

  public function incStock(ProductStockRequest $request)
  {
    // TODO: Implement incStock() method.
  }

  public function decStock(ProductStockRequest $request)
  {
    // TODO: Implement decStock() method.
  }
}

2、创建Product Model类

在 app/Model 下 创建 Product

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

/**
 * @property int $id 
 * @property string $title 
 * @property string $icon 
 * @property int $stock 
 * @property string $created_at 
 * @property string $updated_at
 */
class Product extends Model
{
    /**
     * The table associated with the model.
     */
    protected ?string $table = 'Product';

    protected array $guarded = []; 
}

3、实现ProductSrvController方法

再编辑 service-mall/app/Controller/ProductSrvController

<?php

namespace App\Controller;

use App\Grpc\Mall\ProductSrv\ProductCreateRequest;
use App\Grpc\Mall\ProductSrv\ProductId;
use App\Grpc\Mall\ProductSrv\ProductListReply;
use App\Grpc\Mall\ProductSrv\ProductListRequest;
use App\Grpc\Mall\ProductSrv\ProductSrvInterface;
use App\Grpc\Mall\ProductSrv\ProductStockReply;
use App\Grpc\Mall\ProductSrv\ProductStockRequest;
use App\Model\Product;
use Hyperf\Grpc\StatusCode;
use Hyperf\GrpcServer\Exception\GrpcException;

class ProductSrvController implements ProductSrvInterface
{

  public function create(ProductCreateRequest $request): ProductId
  {
    //@todo 可以将数据访问再抽一层再通过di注入,这边为了简单演示就不搞复杂了
    $new = Product::create([
                           "title" => $request->getTitle(),
                           "icon" => $request->getIcon(),
                           "stock" => 0
                           ]);
    return new ProductId(['id' => $new->id]);
  }

  public function getList(ProductListRequest $request): ProductListReply
  {
    $query = Product::query();
    //@todo 这里可以再自行实现 条件查询
    $total = $query->count();
    $list = [];
    /**
* @var Product $item
*/
    foreach ($query->skip(($request->getPage() - 1) * $request->getPageSize())
             ->take($request->getPageSize())
             ->orderByDesc("id")
             ->get() as $item) {
      $list[] = (new \App\Grpc\Mall\ProductSrv\Product())
        ->setId($item->id)
        ->setIcon($item->icon)
        ->setStock($item->stock)
        ->setTitle($item->title)
        ->setCreatedAt($item->created_at)
        ->setUpdatedAt($item->updated_at);
    }
    return new ProductListReply(compact("total", "list"));
  }

  public function getStock(ProductId $request): ProductStockReply
  {
    $product = $this->getProduct($request->getId());
    return (new ProductStockReply())->setStock($product->stock ?? 0);
  }

  public function incStock(ProductStockRequest $request): ProductStockReply
  {
    $product = $this->getProduct($request->getId());
    $product->stock += $request->getStock();
    $product->save();
    return (new ProductStockReply())->setStock($product->stock);
  }

  public function decStock(ProductStockRequest $request): ProductStockReply
  {
    $product = $this->getProduct($request->getId());
    $product->stock -= $request->getStock();
    $product->save();
    return (new ProductStockReply())->setStock($product->stock);
  }

  private function getProduct(int $id): ?Product {
    $product = Product::where("id","=", $id)->first();
    if(!$product) throw new GrpcException("product not found", StatusCode::ABORTED);
    return $product;
  }
}

至此,我们就把要实现的服务完成了。

本作品采用《CC 协议》,转载必须注明作者和本文链接
from crayxn github.com/crayxn
本帖由 MArtian 于 1年前 加精
讨论数量: 2

其他语言的grpc服务可以注册到这里,然后供 hyperf 使用吗

1年前 评论
Crayxn (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!