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配置详见

相关推荐
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack5 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
花酒锄作田5 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082855 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php