Swoft 2.x 微服务进阶

本篇概要:

  • 1. 模块开发;
    • 1.1 准备工作、测试基本接口;
    • 1.2 创建 RPC 网关;

1. 模块开发;

1.1 准备工作、测试基本接口;

启动镜像

# docker 启动进入 Swoft 镜像
docker run -it --name swoft_rpc -p 8308:18307 \
-v /data/php/test/swoft/Swoft_rpc/:/swoft \
-w /swoft rpc/swoft:2.0.6 sh

# 查看 PHP 版本
php bin/swoft -V
# 返回:PHP: 7.3.4, Swoft: 2.0.8, Swoole: 4.4.5

# 启动 Swoft rpc
swoftcli run -c rpc:start -b bin/swoft

创建数据表

-- 数据库: `myreader`
CREATE TABLE `course_kinds` (
  `item_id` int(11) NOT NULL,
  `kind_name` varchar(50) DEFAULT NULL COMMENT '类别名称',
  `pid` int(11) DEFAULT '0' COMMENT '父级ID',
  `pids` text COMMENT '所有父级ID',
  `ext1` varchar(1000) DEFAULT NULL COMMENT '扩展字段1',
  `ext2` varchar(1000) DEFAULT NULL COMMENT '扩展字段2'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `course_main` (
  `item_id` int(11) NOT NULL,
  `course_title` varchar(200) DEFAULT NULL,
  `course_ltitle` varchar(200) DEFAULT NULL,
  `course_price` decimal(10,2) DEFAULT '0.00',
  `course_disc` tinyint(4) DEFAULT '10' COMMENT '默认10',
  `course_status` tinyint(4) DEFAULT '0' COMMENT '0审核中 1已发布 2已下架',
  `course_intr` text,
  `course_body` longtext,
  `course_pubtime` datetime DEFAULT NULL,
  `course_edittime` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `course_metas` (
  `item_id` int(11) NOT NULL COMMENT '主键',
  `course_id` int(11) DEFAULT NULL COMMENT '课程ID',
  `meta_name` varchar(100) DEFAULT NULL COMMENT '元信息名称 ',
  `meta_value` text COMMENT '元信息内容'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='包含了课程点击量、收藏数、附件名或其他自定义等等';

ALTER TABLE `course_kinds` ADD PRIMARY KEY (`item_id`);
ALTER TABLE `course_main` ADD PRIMARY KEY (`item_id`);
ALTER TABLE `course_metas` ADD PRIMARY KEY (`item_id`),
  ADD UNIQUE KEY `AK_uniquekey` (`course_id`,`meta_name`);

ALTER TABLE `course_kinds` MODIFY `item_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `course_main` MODIFY `item_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `course_metas` MODIFY `item_id` int(11) NOT NULL AUTO_INCREMENT;

INSERT INTO `course_main` (`item_id`, `course_title`, `course_ltitle`, `course_price`, `course_disc`, `course_status`, `course_intr`, `course_body`, `course_pubtime`, `course_edittime`) VALUES
(1, 'test', 'test', 40.00, 10, 0, 'intr', 'body', NULL, NULL);

INSERT INTO `course_metas` (`item_id`, `course_id`, `meta_name`, `meta_value`) VALUES
(1, 1, 'click', '10');

COMMIT;

部署代码

  • 新建接口,之后拷贝给客户端 Swoft_rpc\App\Rpc\Service\ICourceService.php

namespace App\Rpc\Lib;
interface ICourse {
    public function list($size);
    public function get($id);
}
  • 新建 Swoft_rpc\App\Rpc\Lib\ICource.php

namespace App\Rpc\Service;

use App\Models\CourseMain;
use App\Models\CourseMetas;
use App\Models\CourseModel;
use App\Rpc\Lib\ICourse;
use Swoft\Rpc\Server\Annotation\Mapping\Service;

/**
 * Class CourseService
 * @package App\Rpc\Service
 * @Service()
 */
class CourseService implements ICourse {

    public function list($size) {
        return ["list"];
    }

    public function get($id) {
        // 测试
        // return ["get"];
        $main = CourseMain::find($id);
        $metas  = CourseMetas::where("course_id", $id)->get();

        $model = new CourseModel();
        $model->setCourseMain($main);
        $model->setCourseMetas($metas);

        return $model->toArray();
    }

}
  • 测试代码:根目录创建 test.php

# 参考:https://www.swoft.org/documents/v2/core-components/rpc-server/#-swoft-
const RPC_EOL = "\r\n\r\n";

function request($host, $class, $method, $param, $version = '1.0', $ext = []) {
    $fp = stream_socket_client($host, $errno, $errstr);
    if (!$fp) {
        throw new Exception("stream_socket_client fail errno={$errno} errstr={$errstr}");
    }

    $req = [
        "jsonrpc" => '2.0',
        "method" => sprintf("%s::%s::%s", $version, $class, $method),
        'params' => $param,
        'id' => '',
        'ext' => $ext,
    ];
    $data = json_encode($req) . RPC_EOL;
    fwrite($fp, $data);

    $result = '';
    while (!feof($fp)) {
        $tmp = stream_socket_recvfrom($fp, 1024);

        if ($pos = strpos($tmp, RPC_EOL)) {
            $result .= substr($tmp, 0, $pos);
            break;
        } else {
            $result .= $tmp;
        }
    }

    fclose($fp);
    return json_decode($result, true);
}

// 修改为主机地址和端口号
$ret = request('tcp://192.168.60.221:8308', \App\Rpc\Lib\ICourse::class, 'get',  [1], "1.0");

var_dump($ret);
  • 测试结果
// 容器内启动 RPC 服务,本地终端运行 php test.php
// 如下返回,说明接口已经测试通
array(2) {
  ["jsonrpc"]=>
  string(3) "2.0"
  ["result"]=>
  array(1) {
    [0]=>
    string(3) "get"
  }
}
// 连接数据库查询后返回数据
  • 修改 Swoft_rpc\App\bean.php

// 修改数据库配置
'db'  => [
  'class'    => Database::class,
   'dsn'      => 'mysql:dbname=myreader;host=192.168.60.221;port=3306',
   'username' => 'root',
   'password' => 'asdf',
   'charset'  => 'utf8mb4',
   'config'   => [
       'collation' => 'utf8mb4_general_ci',
       'strict'    => false,
       'timezone'  => '+8:00',
       'modes'     => 'NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES',
       'fetchMode' => PDO::FETCH_ASSOC,
   ],
],
  • 生成模型
# 进入容器
docker exec -it swoft_rpc sh
# 生成模型(或者可以本地直接运行生成)
php bin/swoft entity:create --table=course_kinds,course_main,course_metas --path=@app/Models
  • 创建 Swoft_rpc\App\Models\CourseModel.php


namespace App\Models;

class CourseModel {

    // 定义好变量后 Alt + Ins 生成 get 和 set 方法

    /** @var $course_main CourseMain */
    protected $course_main;

    /** @var $course_metas CourseMetas */
    protected $course_metas;
    
    // 返回数组 
    public function toArray(){
        return [
            "course" => $this->course_main->toArray(),
            "metas" => $this->course_metas->toArray()
        ];
    }
    
    /**
     * @return mixed
     */
    public function getCourseMain()
    {
        return $this->course_main;
    }

    /**
     * @param mixed $course_main
     */
    public function setCourseMain($course_main): void
    {
        $this->course_main = $course_main;
    }

    /**
     * @return mixed
     */
    public function getCourseMetas()
    {
        return $this->course_metas;
    }

    /**
     * @param mixed $course_metas
     */
    public function setCourseMetas($course_metas): void
    {
        $this->course_metas = $course_metas;
    }
}

1.2 创建 RPC 网关;

需要做一个定制化的网关

  • 之前完成了一个 RPC 编写的 API,写代码调用的时候(test.php)是通过官方写一个 request() 函数,使用 stream_socket_client 进行调用,传参数的时候,手动拼凑一个 JSONRPC 的协议,这个协议就可以完成服务的调用。然后把值返回出来
  • 调用的时候是用官方的方式,使用 request() 函数,传入 RPC 地址,传进的 class 是完整的接口。method 是 get() 方法,传的参数是以数组的方式
  • 现在代码端没有问题,如果写完 API 后,有很多 API 需要调用,那就不方便了。其次是 API 给其它部门和合作伙伴使用,第三方(传统 PHP、go、java 等)调用就很不方便,这时候有两种方案
    • 第一种就是它们自己写一个类似 test.php 去调用
    • 如果合作伙伴没有能力去写,我们就只能去写一个网关,这个网关通过客户端请求,把这个请求转发给(调用 request() 方法就相当于是转发过程),由它们的客户端去调用我们写的网关,网关再去访问真实 API,然后得到值之后,通过 HTTP API 的方式,返回给客户端。
    • 还有就是我们的很多 API,都有一些公用的功能(鉴权,限流),如果这些功能写在单独的 API 里,这就非常不方便,要写很多套同样的代码。此时要把公用的功能抽出来,变成一个专门的网关来完成。内部 API 调用依然可以通过 RPC 来调用。外部 API,或者给前端 view 都是可以通过网关来调用,不一定要直接去访问 RPC API。其实 view 和 js 端也是可以直接访问 RPC 的,但是一般不会这么做
    • 待续

你可能感兴趣的:(Swoft)