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
<?php
namespace App\Rpc\Lib;
interface ICourse {
public function list($size);
public function get($id);
}
- 新建
Swoft_rpc\App\Rpc\Lib\ICource.php
<?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
<?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
<?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
<?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 的,但是一般不会这么做
- 待续