PHP客户端调用由Go服务端GRPC接口

一.安装和配置

1.本地windows下安装php的gprc和protobuf扩展:

gRPC扩展:(php7.4.3nts)
https://pecl.php.net/package/gRPC/1.43.0/windows

protobuf扩展:
https://pecl.php.net/package/protobuf/3.24.4/windows

将下载的dll文件放到对应php版本的ext文件下:D:\phpstudy_pro\Extensions\php\php7.4.3nts

再修改对应php版本的php.ini文件添加:

extension="D:/phpstudy_pro/Extensions/php/php7.4.3nts/php_grpc.dll"

extension=D:/phpstudy_pro/Extensions/php/php7.4.3nts/php_protobuf.dll

重启nginx然后在pathinfo()查看或者执行下面的命令看是否有grpc和protobuf扩展

2.composer安装包grpc和protobuf:

composer require google/protobuf:3.24.4

composer require grpc/grpc:^1.42.0 (由于提示1.43.0不存在 则按照1.42.x系列最新版)

3.本地使用grpc_php_plugin插件 可以生成对应proto的php文件

可以通过git clone https://github.com/lifenglsf/grpc_for_windows.git 下载到本地,然后查看文件夹下对应系统的grpc_php_plugin.exe文件

或者在https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.0下面下载对应的版本

(我的是在grpc_for_windows/x64/grpc_php_plugin.exe)

二.指定proto文件生成对应的php文件

1.将go的grpc文件复制到laravel框架:

/protos/food :里面有common.proto和user.proto如下,新增命名空间

2.执行命令将proto文件生成对应php文件(project下有generator和generate_grpc.sh文件)

(1)这里使用了swoole/grpc里面的./generator 可以自动生成所有proto文件对应的php文件,不用单个proto文件去生成

./generator文件内容如下:

php 复制代码
#!/usr/bin/env php
<?php
/*
  +----------------------------------------------------------------------+
  | Swoole-gRPC                                                   |
  +----------------------------------------------------------------------+
  | This source file is subject to version 2.0 of the Apache license,    |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.apache.org/licenses/LICENSE-2.0.html                      |
  | If you did not receive a copy of the Apache2.0 license and are unable|
  | to obtain it through the world-wide-web, please send a note to       |
  | license@swoole.com so we can mail you a copy immediately.            |
  +----------------------------------------------------------------------+
  | Author: Twosee <twose@qq.com>                                        |
  +----------------------------------------------------------------------+
*/

function scan_dir(string $dir, callable $filter = null): array
{
    $files = scandir($dir);
    $files = array_filter($files, function (string $f) {
        return $f[0] !== '.';
    });
    array_walk($files, function (string &$value) use ($dir) {
        $value = rtrim($dir, '/') . '/' . $value;
    });
    return array_values($filter ? array_filter($files, $filter) : $files);
}

function generateClient(string $proto_dir)
{
    #下面的文件名需要修改!!!!!!!!!!!
    $proto_dir = 'D:\project\udc\app\Grpc\Protos\Food';
    echo $proto_dir;
    $source_dirs = scan_dir($proto_dir, function (string $f) use ($proto_dir) {
        return is_dir($f);
    });
    $php_files = [];
    foreach ($source_dirs as $source_dir) {
        $php_files[] = scan_dir($source_dir, function (string $f) {
            return substr($f, -4, 4) === '.php';
        });
    }
    $php_files = array_merge(...$php_files);
    foreach ($php_files as $php_file) {
        $file_content = file_get_contents($php_file);
        $extends_keyword = ' extends \Grpc\BaseStub';
        if (strpos($file_content, $extends_keyword) !== false) {
            // $filename = explode('/', $php_file);
            // $filename = end($filename);

            // use swoole construct
            $file_content = str_replace(
                '__construct($hostname, $opts, $channel',
                '__construct($hostname, $opts',
                $file_content
            );
            // fit swoole arguments
            $file_content = str_replace('$opts = null', '$opts = []', $file_content);
            // use correct return value
            $file_content = preg_replace_callback(
                '/(call options)(\n([ ]+?)\*\/\n[ ]+?public function[\s\S]+?(_\w+Request)[\s\S]+?\[\'([^\']+)\', ?\'\w+\'\],)/',
                function (array $match) {
                    switch ($match[4]) {
                        case '_simpleRequest':
                            return "{$match[1]}\n{$match[3]}* @return {$match[5]}[]|\\Grpc\\StringifyAble[]{$match[2]}";
                        case '_bidiRequest':
                            return "{$match[1]}\n{$match[3]}* @return bool|\\Grpc\\BidiStreamingCall{$match[2]}";
                        case '_serverStreamRequest':
                            return "{$match[1]}\n{$match[3]}* @return bool|\\Grpc\\ServerStreamingCall{$match[2]}";
                        case '_clientStreamRequest':
                            return "{$match[1]}\n{$match[3]}* @return bool|\\Grpc\\ClientStreamingCall{$match[2]}";
                    }
                    return $match[0];
                },
                $file_content
            );

            file_put_contents($php_file, $file_content);
        }
    }
}

function generateFromProto(string $proto_path, string $php_out, string $grpc_out, string $plugin, array $proto_list)
{
    $plugin_file = explode('=', $plugin)[1] ?? null;
    if (!$plugin_file) {
        $plugin = "protoc-gen-grpc={$plugin}";
    }
    if (!file_exists($plugin_file)) {
        exit("Can't find the plugin generator file [{$plugin_file}]");
    }

    function realGenerate($proto_path, $php_out, $grpc_out, $plugin, array $proto_list)
    {
        foreach ($proto_list as $key => $proto_file) {
            if (is_dir($proto_file)) {
                $proto_deep_list = scan_dir($proto_file, function (string $f) {
                    return substr($f, -6, 6) === '.proto';
                });
                realGenerate($proto_path, $php_out, $grpc_out, $plugin, $proto_deep_list);
            } else {
                `protoc --proto_path={$proto_path} --php_out={$php_out} --grpc_out={$grpc_out} --plugin={$plugin} {$proto_file}`;
            }
        }
    }

    realGenerate($proto_path, $php_out, $grpc_out, $plugin, $proto_list);
}

function get_command(&$command, &$options, &$params): void
{
    global $argv;
    $arguments = $argv;
    $command = '';//命令
    $options = [];//选项
    $params = []; //参数


    array_shift($arguments);
    if (isset($arguments[0]) && substr($arguments[0], 0, 1) !== '-') {
        $command = array_shift($arguments); //指定第一个参数为命令
    }

    foreach ($arguments as $i => $v) {
        if (empty($v)) {
            continue;
        } elseif (substr($v, 0, 2) === '--') {
            $now = substr($v, 2);
            $now = explode('=', $now);
            $options[trim(array_shift($now))] = trim(implode('=', $now));
        } else {
            $params[] = $v;
        }
    }
}

(function () {
    get_command($command, $options, $params);
    if (empty($command)) {
        $needle_params = [
            'proto_path' => null,
            'php_out' => null,
            'grpc_out' => null,
            'plugin' => __DIR__ . './../../grpc/bins/opt/grpc_php_plugin'
        ];
        $proto_path = $php_out = $grpc_out = $plugin = '';
        foreach ($needle_params as $param_name => $param_default_value) {
            if (empty($options[$param_name])) {
                if ($param_default_value === null) {
                    exit("{$param_name} is missing!");
                } else {
                    $options[$param_name] = $param_default_value;
                }
            } else {
                if ($param_name === 'php_out') {
                    $needle_params['grpc_out'] = $options[$param_name];
                }
            }
            $$param_name = $options[$param_name];
        }
        generateFromProto($proto_path, $php_out, $grpc_out, $plugin, $params);
//        generateClient($php_out);
    }
})();

(2)generate_grpc.sh文件内容如下:

php 复制代码
#!/usr/bin/env bash

# 指定food的proto文件生成php文件

# 生成代码
# 1.protoc-gen-grpc需要指向本地grpc_php_plugin.exe所在位置
# 2.指定要生成的proto文件
./generator \
--proto_path=./protos \
--php_out=./ \
--grpc_out=./ \
--plugin=protoc-gen-grpc=../grpc_for_windows/x64/grpc_php_plugin.exe \
./protos/food
#上述文件名(/protos/food)需要对应修改!!!!!!!!!!!
echo "gRPC 代码已重新生成到 app/Grpc下"
sleep(2)

#双击该文件执行或执行 ./generate_grpc.sh ../grpc_for_windows/x64/grpc_php_plugin.exe
#即可生成对应proto的php文件

(3)生成:

双击generate_grpc.sh文件

或执行 ./generate_grpc.sh ../grpc_for_windows/x64/grpc_php_plugin.exe

即可生成对应proto的php文件

**# 这里是在laravel框架.env同级目录下执行

--php_out php代码输出路径,里面包含request,response,client代码

--grpc_out GPBMetadata输出路径,用于保存.proto的二进制元数据

--plugin 生成代码插件的类型与插件的绝对路径路径**

三.调用go服务端接口

php 复制代码
            $metaData = [
                'Sign' => [$sign],
                'TimeStamp' => [(string)$requestTime],
            ];
            $request = new CommonRequest();
            $request->setData($paramData);
            $hostname =  '127.0.0.1:8081';
            $opts = ['credentials' => ChannelCredentials::createInsecure()];
            $client = new UserClient($hostname,$opts);
            list($response, $status) = $client->UserInfo($request, $metaData)->wait();
            if ($status->code !== \Grpc\STATUS_OK) {
                Log::error("gRPC调用失败: 状态码={$status->code}, 错误详情={$status->details}");
            }
            if($response!=null){
                $responseData = json_decode($response->serializeToJsonString(), true);
            }

四.go配置详见

相关推荐
席万里4 小时前
通过Golang订阅binlog实现轻量级的增量日志解析,并解决缓存不一致的开源库cacheflow
缓存·golang·开源
q***46524 小时前
对基因列表中批量的基因进行GO和KEGG注释
开发语言·数据库·golang
柠石榴4 小时前
GO-1 模型本地部署完整教程
开发语言·后端·golang
大Null4 小时前
Linux安装GO环境
linux·golang
TravisBytes6 小时前
一次 Qt 网络程序诡异崩溃排查:从 Breakpad 堆栈到 lambda 捕获悬空引用
网络·qt·php
周杰伦_Jay19 小时前
【RPC:分布式跨节点透明通信协议】【Raft:简单易实现的分布式共识算法】
分布式·rpc·共识算法
嗝屁小孩纸20 小时前
免费测评RPC分布式博客平台(仅用云服务器支持高性能)
服务器·分布式·rpc
Alex艾力的IT数字空间20 小时前
完整事务性能瓶颈分析案例:支付系统事务雪崩优化
开发语言·数据结构·数据库·分布式·算法·中间件·php
HotCoffee-GPS1 天前
Golang学习笔记:定时crontab
golang