会点 Vue.js ThinkPHP Workerman Swoole 的搬运工

0%

RPC 依赖Swoole环境

安装

1
composer require topthink/think-swoole

创建接口

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace app\service;

interface OperationInterface
{
/**
* 加法运算
*
* @param [type] $a
* @param [type] $b
* @return void
*/
public function add($a, $b);
}

创建服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace app\service;

/**
* 这是个运算服务
*
*/
class OperationService implements OperationInterface
{
/**
* 执行加法运算
*
* @param [type] $a
* @param [type] $b
* @return void
*/
public function add($a, $b)
{
return $a + $b;
}
}

配置

swoole.php

1
2
3
4
5
6
7
8
9
'rpc'        => [
'server' => [
'enable' => true,
'port' => 9000,
'services' => [
'OperationService' => \app\service\OperationService::class
],
]
],

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

namespace app\controller;

use app\BaseController;
use think\swoole\rpc\client\Client;

/**
* @package app\controllers
*
*/
class Index extends BaseController
{

public function index(OperationService $service)
{
$service = $service->add([1, 2]);
var_dump($service);
}
}

启动

1
php think swoole

访问 http://localhost:9000

客户端生成

必要参数值

  • token
  • 时间戳
  • 随机字符
1
2
3
4
5
6
7

$params = sort([
token值, 时间戳, 随机字符, ...更多自定义参数
], SORT_STRING);

$tmpStr = implode($params);
$sign = sha1($tmpStr);

服务端验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

$signature = $data['signature'];

unset($data['signature'])

$tmpArr= array_values($data);
sort($tmpArr, SORT_STRING);
$tmpStr = implode($tmpArr);
$tmpStr = sha1($tmpStr);

if ($tmpStr == $signature) {
return true;
} else {
return false;
}

前言

传统的FTP文件上传,程序更新代码时,如果文件夹深度超过两层,并且要更新的文件很多的时候,是不是觉得要疯掉了?
当然,你也可以一次性覆盖整个项目根目录。

除此之外,系统在线更新服务也需要这种补丁包的方式来更新。
当然,你也可以一次性下载源程序,进行覆盖。

以下得有些git的基础。

版本差异

以下按release包简单说下具体要点
生成补丁包,需要使用git diff命令。
有两个 release 版本

  • v1.0.0
  • v1.0.1

命令详情 git diff 旧版本 新版本

对比两个版本的代码差异,可以看到 v1.0.0v1.0.1 差异之后的文件路径,把差异之后的路径压缩成一个包,就可以实现补丁包。

1
git diff v1.0.0 v1.0.1 --name-only

如何找

上面已经提到如何将两个版本进行对比,但如何找到最新的版本跟之前上一个版本呢?

上一版本

1
git tag | awk '{a[NR]=$0} END{print a[NR-1]}'

最新版本

1
git tag | awk "END{print}"

完整例子

xargs zip update.zip 把差异结果的文件路径,进行zip压缩生成补丁包。

1
git diff $(git tag | awk '{a[NR]=$0} END{print a[NR-1]}') $(git tag | awk "END{print}") --name-only | xargs zip update.zip

前言

实现 php 项目带composer.json配置的,想发布release时,自动下载所需要的包,供其它人下载。

注意配置

  • before_deploy

  • on:tags

  • overwrite

    deploy:file 必须在 before_deploy 时生成,不然travis会找不到。

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# travis.yml

before_deploy:
- echo 'start'
- composer self-update
- composer install --no-dev --no-interaction --ignore-platform-reqs
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' Project_Full.zip .

deploy:
provider: releases
api_key:
secure: your_sercure_key
file: /path/file
skip_cleanup: true
overwrite: true
on:
tags: true

前言

在使用协程中,新手一般都会遇到以下报错,多个协程使用【已占用】的连接导致

Fatal error: Uncaught SwooleError: Socket#4 has already been bound to another coroutine#505, reading of the same socket in coroutine#506

Channel

使用Channel(管道)解决以上的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

<?php

// 开启20个通道(可以理解为20个连接)
$chan = new co\Channel(20);

// 主协程
go(function() use(&$chan){

// mysql服务
$swoole_mysql = new Swoole\Coroutine\MySQL();
$swoole_mysql->connect([
'host' => 'mysql56',
'port' => 3306,
'user' => 'root',
'password' => '1234',
'database' => 'test',
]);

// 子协程 - 保存数据到数据库
go(function() use(&$chan, $swoole_mysql){
while(true) {
// 从Channel中拿出一个数据
// Channel必须要拿数据,不然20个管道全占用了
$data = $chan->pop();

// 执行保存
$swoole_mysql->query();
}
});

// 创建多个子协程 - 模拟创建保存任务
for($i = 0; $i < 20; $i++) {
go(function() use(&$chan, $i){
// 检查Channel(管道)是否已满
while ($chan->isFull()) {
// Channel(管道)已满
// 进行休眠0.1秒等待空闲
co::sleep(0.1);
}

// Channel空闲了,可以进行业务
// ...其它业务代码

// 可以传入一些自定义参数到Channel中
$chan->push(['job' => $i]);
});
}
});

了解

主要使用Channel来控制协程什么时间执行业务代码

  • $chan->push
  • $chan->pop
  • $chan->isFull()

前言

大概过了下ThinkPHP6文档,相比以前的tp5, 多了个订阅功能。

对比

订阅事件(subscribe)

  • 支持订阅多个事件(监听)

事件监听(listener)

  • 只能监听一个事件(跟TP5的行为大致一样)

应用

立马体验一波!

场景: 订单发货后 触发事件 完成发送短信通信

注册事件/监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// 事件定义文件
return [
'bind' => [
// 绑定订单发货类
'OrderShipped' => 'app\event\OrderShipped',
],

'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],

// 监听器【订单发货】事件
'OrderShipped' => ['app\listener\OrderShipped']
],

'subscribe' => [
// 订阅【订单】事件 此文件可订阅(监听)【订单类】事件等
'app\subscribe\Order'
],
];

绑定订单发货类

app\event\OrderShipped

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29


namespace app\event;

use app\Model\Order;

/**
* 订单事件绑定类
*
*/
class OrderShipped
{
/**
* 订单对象模型(包含订单的数据)
*
* @var [type]
*/
public $order;

/**
* 构造时 注入订单对象
*
* @param Order $order
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}

监听事件

app\listener\OrderShipped

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25



namespace app\listener;

use app\event\OrderShipped as Event;

/**
* 处理订单发货事件
*
*/
class OrderShipped
{
public function handle(Event $event)
{
echo '收到事件 - {OrderShipped}' . PHP_EOL;

// 获取订单模型 已包含订单详情的数据对象
// echo '<pre>';
// var_dump($event->order);

// 现在可以使用order的信息发邮件 写日志等等

}
}

订阅订单事件

app\subscribe\Order

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

namespace app\subscribe;

use app\event\OrderShipped as Event;

/**
* 订阅订单相关事件
*
*/
class Order
{
/**
* 收到订单发货事件
*
* @param [type] $event
* @return void
*/
public function onOrderShipped(Event $event)
{
echo '收到订阅 - {订单发货事件}' . PHP_EOL;

// 获取订单信息
// echo '<pre>';
// var_dump($event->order);
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


use app\Model\Order;

public function orderShip()
{
// 查找订单
$order = Order::find(1);

// 发货处理 更新订单状态
// ...

// 发布订单发货事件
event('OrderShipped', $order);
}

重点

可能有些小伙伴有这么一个好奇,这个 $order 数据对象是否怎么传入到对应的处理方法,并且使用 $event->order 就可以获取订单信息

注意以下文件内容, 这些文件都有注入事件绑定类

public function handle(Event $event)

public function onOrderShipped(Event $event)

  • listen/OrderShipped.php
  • subscribe/Order.php

当调用 event('OrderShipped', $order) 事件时,系统分发到对应的处理方法(OrderShipped, onOrderShipped)

OrderShippedonOrderShipped 自动注入(反射)OrderShipped 事件类,并且实例化的时候也注入订单数据对象到 OrderShipped 成员变量 order

最后可以使用 $event->order 来获取订单信息来做短信通知等任务

创建多应用

以下创建两个应用,并创建对应的控制器

1
2
3
4
5
6
7
8
.
├── app
│ ├── project
│ │ └── controller
│ │ └── Index.php
│ ├── project2
│ │ └── controller
│ │ └── Index.php

配置

应用一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# /project/controller/Index.php
namespace app\project\controller;

use app\BaseController;

class Index extends BaseController
{
public function index()
{
return 'hello,' . 'project';
}

public function say()
{
return 'say hello,' . 'project';
}
}

应用二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# /project2/controller/Index.php
namespace app\project2\controller;

use app\BaseController;

class Index extends BaseController
{
public function index()
{
return 'hello,' . 'project2';
}

public function say()
{
return 'say hello,' . 'project2';
}
}

开启多应用

1
2
# /config/app.php
'auto_multi_app' => true

运行

访问以下地址,你就可以看到已经成功了。

  • 你的域名/project
  • 你的域名/projec2

多应用 路由配置

按以下路径创建文件就行

  • /route/project/xxx.php
  • /route/project2/xxx.php

重点! 重点! 重点!

多应用的路由,路由不用再配置应用名

例如 有这么一条路由 /project/say 那配置是这样

1
2
3
4
5
6
7
use think\facade\Route;

// 错的
// Route::rule('project/say', 'index/index/say');

// 对的
Route::rule('say', 'index/index/say');

前言

闲着学习下,利用swoole下载github对应项目的全部包

目录

1
2
3
4
5
6
7
.
├── composer.json
├── packages
├── src
│ ├── Dom.php
│ └── Scraper.php
└── start.php

流程

  1. 注入http服务类 Saber public function __construct(Saber $saber, string $savePath)
  2. 配置要下载的包r public function scrape(array $options)
  3. 开始爬取任务 public function run()
  4. 获取项目分页信息(一直循环到没有包为止) protected function fetchPagination(string $package, string $lastVersion = '')
  5. 下载包 protected function download(string $version, string $url, string $ext)

使用包

创建项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# composer.json
{
"name": "yourname/github-package-downloader",
"authors": [
{
"name": "yourname",
"email": "yourname@live.com"
}
],
"require": {
"symfony/dom-crawler": "^4.2",
"symfony/css-selector": "^4.3",
"swlib/saber": "^1.0"
},
"autoload": {
"psr-4": {
"App\\": "./src"
}
}
}
1
composer install

编写

  • Scraper.php
  • Dom.php
  • start.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# src/Scraper.php

namespace App;

use App\Dom;
use Swlib\Saber;

final class Scraper
{
private $saber;

private $package;

private $savePath;

const HOST = 'https://github.com';

const DOWNLOAD = 'https://codeload.github.com';

public function __construct(Saber $saber, string $savePath)
{
$this->saber = $saber;
$this->savePath = $savePath;
}

/**
* 配置
*
* @param array $packages
* @return void
*/
public function scrape(array $packages)
{
$this->packages = $packages;

return $this;
}

/**
* 开始运行
*
* @return void
*/
public function run()
{
foreach ($this->packages as $package) {
go(function () use ($package) {
$this->fetchPagination($package);
});
}
}

/**
* 拉取信息
*
* @param string $package
* @param string $lastVersion
* @return void
*/
protected function fetchPagination(string $package, string $lastVersion = '')
{
$url = $package . '/tags?after=' . $lastVersion;
try {
echo "=============================================\n";
echo "开始拉取:{$url}\n";
echo "=============================================\n";
$html = $this->saber->get(Scraper::HOST . '/' . $url, ['max_co' => 5, 'timeout' => 500]);


$dom = new Dom((string)$html);

if (true === $dom->hasPackage()) {
$lastVersion = $dom->getLastVersion();
$data = $dom->getVersionUrls();

foreach($data as $item) {
foreach($item['urls'] as $ext => $url) {
$this->download($item['version'], $url, $ext);
}
}

$this->fetchPagination($package, $lastVersion);

} else {
echo "拉取完成\n";
}
} catch (\Exception $e) {
echo $e->getMessage();
}
}

/**
* 下载、保存包
*
* @return void
*/
protected function download(string $version, string $url, string $ext)
{
go(function () use ($version, $url, $ext) {
list($tmp, $user, $package, $archive, $file) = explode('/', $url);
$dir = $this->savePath . "/{$user}/{$package}";

if (!file_exists($dir)) {
mkdir($dir, 0777, true);
}

$savePath = $dir . '/' . $file;
try {
$response = $this->saber->download(
Scraper::DOWNLOAD . '/' . $user.'/'. $package .'/'. $ext .'/' . $version,
$savePath
);

if ($response->success) {
echo "下载完成\n";
}
} catch (\Exception $e) {
$this->download($version, $url, $ext);
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

# src/Dom.php

namespace App;

use Symfony\Component\DomCrawler\Crawler;

class Dom
{
protected $html;

public function __construct(string $html)
{
$this->crawler = new Crawler((string)$html);
}

/**
* 是否存在包
*
* @return boolean
*/
public function hasPackage()
{
return count($this->crawler->filter('.blankslate')) ? false : true;
}

/**
* 是否分页
*
* @return boolean
*/
public function hasPagination()
{
try {
$this->crawler->filter('.pagination a')->text();
return true;
} catch(\Exception $e) {
return false;
}
}

/**
* 获取版本号与下载链接
*
* @return void
*/
public function getVersionUrls()
{
$data = [];
$this->crawler->filter('.Box-row')->each(function (Crawler $node, $i) use(&$data) {
$data[] = [
'version' => $this->getVersion($node),
'urls' => $this->getDownloadLink($node),
];
});

return $data;
}

/**
* 获取版本号
*
* @param Crawler $node
* @return void
*/
protected function getVersion(Crawler $node)
{
return trim($node->filter('.commit .d-flex h4 a')->text());
}

/**
* 获取下载链接
*
* @param Crawler $node
* @return void
*/
protected function getDownloadLink(Crawler $node)
{
$urls = [
'zip' => $node->filter('ul.list-style-none li:nth-child(3) a')->attr('href'),
'tar.gz' => $node->filter('ul.list-style-none li:nth-child(4) a')->attr('href')
];

return $urls;
}

/**
* 获取最后一个版本
*
* @return void
*/
public function getLastVersion()
{
$version = $this->crawler->filter('.Box-row:last-child .d-flex a')->text();

return trim($version);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# start.php

use App\Scraper;
use Swlib\Saber;

require_once './vendor/autoload.php';

$saber = Saber::create([
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3824.6 Safari/537.36',
'use_pool' => true
]);

// 注入http库,配置保存路径
$scraper = new Scraper($saber, __DIR__ . '/packages');

// 配置需要爬取的项目并运行
$scraper->scrape([
'swlib/saber',
])->run();

运行

1
php start.php

前言

有时可能项目上或域名资源紧缺的情况,可以一个域名下有多个项目运行。

以下就按部署ThinkPHP项目为例,将后端代码部署到二级目录

配置

要注意以下几个环节

  • location
  • rewrite
  • alias 指定到后端项目 public 路径上
  • fastcgi_param 不能加document_root配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

server {

# ...其它代码

# 匹配到以 www.domain.com/api/ 开头的url
location /api/ {
# 指定后端代码位置 并指向到public文件夹 具体位置请按照你实际路径填写就行
alias /var/www/html/www.domain.com/think/public/;
index index.php;

# 注意下方 fastcgi_param的值
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
}

# 必须加上rewrite 不然你的子链接全404, 必须rewrite到think文件夹,具体位置请按照你实际路径填写就行
if (!-e $request_filename) {
rewrite ^/think/(.*)$ /think/index.php?s=$1 last;
}
}
}

注意

静态资源文件 static 不能带绝对路径 /

前言

在实际开发ThinkPHP5的时候,【控制器】、【模型】两者的参数传递,通过 use class就能在方法里面使用到其实例化的类,这上我十分好奇它是怎么实现的

1
2
3
4
5
6
7
8
9
use think\Request;

class User
{
public function hello(Request $request)
{
$request->balabala();
}
}

发现

ReflectionClass

查看了源码发现,它是使用一个叫【ReflectionClass】反射类来实现

  1. 通过反射类获取一个类的信息
  2. 获取类的某个方法
  3. 获取某个方法的参数信息,参数如果是一个类的话,系统自动帮你实例化
  4. 最后实例化控制器,执行目标方法 并且把参数带上

自己实现一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

<?php

class User
{
public function say()
{
return 'hello';
}
}

class Test
{
public function sayHello(User $user)
{
return $user->say();
}
}

// 使用反射类获取 `Test`类
$class = new ReflectionClass('Test');

// 获取Test类的`syHello`方法
$method = $class->getMethod('sayHello');

// 获取`SayHello`方法中的参数
$parameters = $method->getParameters();

$args = [];
foreach($parameters as $item) {

// 获取类
$ReflectionClass = $item->getClass();

// 获致类名
$ReflectionClass = $ReflectionClass->getName();

// 实例化
$args[] = new $ReflectionClass;
}

// 实例化`Test`类
$instance = $class->newInstanceArgs();

// 执行 Test类中的`sayHello`方法 并且把上面参数带上
var_dump($method->invokeArgs($instance, $args));