​​​​ php 代码整理(续) | 苏生不惑的博客

php 代码整理(续)

Excel 导入导出工具

1
2
3
4
5
6
7
8
9
10
11
12
13
composer require phpoffice/phpspreadsheet
require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Hello World !');

$writer = new Xlsx($spreadsheet);
$writer->save('hello world.xlsx');
https://learnku.com/laravel/t/33677

FFmpeg 截取视频中多个图片,然后拼成一张图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 //获取文件路径
fwrite(STDOUT, "哪个视频文件:");
$video_path = trim(fgets(STDIN));

fwrite(STDOUT, "多少秒截取1张图:");
$s = trim(fgets(STDIN));

//设置输出路径
fwrite(STDOUT, "输出到哪里:");
$export_path = trim(fgets(STDIN));

$command = system('ffmpeg -y -i '.$video_path.' -vf "fps=1/'.$s.',scale=iw/4:-1,tile=2x2" -an '.$export_path.'_%d.png');
https://learnku.com/laravel/t/33066
// fps = 1/2 每2秒截一张图,如果是每秒截一张 参数就是 fps=1
// scale 截图大小,上面的代码是设置宽为原始的1/4大小,高度自动,也可以设置成固定值如:120:80
// tile 网格化,自动将100张图合并成一张大图
截图会输出很多张大图,这不是我想要的,我只想从视频中提取 4 张图片,然后拼接成一张大图怎么操作;
获得视频总时长然后除以 4 就好了 (如果要 6 张图就除以 6)
代码如下:
$duration = system('ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 -i '.$video_path.'');
$s = $duration/4;
$command = system('ffmpeg -y -i '.$videopath.' -vf "fps=1/'.$s.',scale=iw/4:-1,tile=2x2" -an 1%d.png');

MySQL 事务最全详解

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
// 我们先查看表中的数据,id为1的age字段是12
mysql root@127.0.0.1:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 张三 | 12 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.013s
// 开启事务
mysql root@127.0.0.1:test> begin;
Query OK, 0 rows affected
Time: 0.001s
// 将id为1的age字段改为10
mysql root@127.0.0.1:test> update user set age=10 where id=1;
Query OK, 1 row affected
Time: 0.001s
// 再次查询数据时,发现数据改为修改后的值
mysql root@127.0.0.1:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 张三 | 10 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.012s
// 此时我们进行回滚操作
mysql root@127.0.0.1:test> rollback;
Query OK, 0 rows affected
Time: 0.001s
// 再次查询发现数据回到最初状态
mysql root@127.0.0.1:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 张三 | 12 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.019s
// 我们再次对数据进行修改
mysql root@127.0.0.1:test> update user set age=15 where id=1;
Query OK, 1 row affected
Time: 0.001s
// 此时将事务进行提交
mysql root@127.0.0.1:test> commit;
Query OK, 0 rows affected
Time: 0.000s
// 发现此时的数据变为我们最终提交的值
mysql root@127.0.0.1:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 张三 | 15 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.012s
// 我们尝试用刚才回滚的方式进行还原数据
mysql root@127.0.0.1:test> rollback;
Query OK, 0 rows affected
Time: 0.000s
// 发现数据无法回退了,仍然是提交后的数据
mysql root@127.0.0.1:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 张三 | 15 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.017s
PHP 实现事务实例代码

<?php
// 连接MySQL
$mysqli = new mysqli('127.0.0.1', 'root', '123456', 'test', 3306);
// 关闭事务自动提交
$mysqli->autocommit(false);
// 1.开启事务
$mysqli->begin_transaction();
// 2.修改数据
$mysqli->query("update user set age=10 where id=1");
// 3.查看数据
$mysqli->query("select * from user");
// 4.事务回滚
$mysqli->rollback();
// 5.查看数据
$mysqli->query("select * from user");
// 7.修改数据
$mysqli->query("update user set age=15 where id=1");
// 8.事务提交
$mysqli->commit();
// 9.事务回滚
$mysqli->rollback();
// 10.查看数据
$mysqli->query("select * from user");
如何设置事务的隔离级别
// 查看当前的事务隔离级别
mysql root@127.0.0.1:test> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set
Time: 0.015s
// 设置隔离级别
set session transaction isolation level 隔离级别(上面事务隔离级别中的英文单词);
https://learnku.com/articles/33749

二进制转图片

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
<?php
/**
* Created by 独自等待
* Date: 2014/11/12
* Time: 10:10
* Name: decode.php
* 独自等待博客:https://www.waitalone.cn/11-game.html
*/
$bin = <<<binary
01001000 00110100 01110011 01001001 01000001 01000011 01001010 01001011 #第1行
#为阅读方便,把第1行和最后1行之间的省略了,大家测试的时候请把二进制代码放到这里
01001010 01000001 01000001 01101111 01000001 01000001 01000001 00111101 #最后1行
binary;
$bin_arr = explode(' ',str_replace("\r\n"," ",$bin));
#print_r($bin_arr);
$dec = '';
foreach ($bin_arr as $bin_code) {
$dec .= chr(bindec($bin_code));
}
echo $dec;
fwrite(fopen('key.tar.gz','wb'),base64_decode($dec));

chr(bindec('01110011'))//s
import base64
bin = '''
01001000 00110100 01110011 01001001 01000001 01000011 01001010 01001011 #第1行
#为阅读方便,把第1行和最后1行之间的省略了,大家测试的时候请把二进制代码放到这里
01001010 01000001 01000001 01101111 01000001 01000001 01000001 00111101 #最后1行
'''
binTochar = [chr(int(x, 2)) for x in bin.strip().split()]
print ''.join(binTochar)
keyFile = open('key.tar.gz', 'wb')
keyFile.write(base64.b64decode(''.join(binTochar)))
keyFile.close()

windos 安装 Laravel/horizon

1
composer.json 中增加以下两个字段:"platform": { "ext-pcntl": "7.2", "ext-posix": "7.2"},增加至 config 字段中,再次运行安装命令即可~

回调函数

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
$array = [1, 2, 3, 4];
$str = array_reduce($array, function ($return_str, $value) {
$return_str = $return_str . $value; //层层迭代
return $return_str;
});
// 一个基本的购物车,包括一些已经添加的商品和每种商品的数量。
// 其中有一个方法用来计算购物车中所有商品的总价格,该方法使
// 用了一个 closure 作为回调函数。
class Cart
{
const PRICE_BUTTER = 1.00;
const PRICE_MILK = 3.00;
const PRICE_EGGS = 6.95;

protected $products = array();

public function add($product, $quantity)
{
$this->products[$product] = $quantity;
}

public function getQuantity($product)
{
return isset($this->products[$product]) ? $this->products[$product] :
FALSE;
}

public function getTotal($tax)
{
$total = 0.00;

$callback =
function ($quantity, $product) use ($tax, &$total)
{
//定义一个回调函数 取出 当前商品的价格
$pricePerItem = constant(__CLASS__ . "::PRICE_" .
strtoupper($product));
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};

array_walk($this->products, $callback);
return round($total, 2);;
}
}

$my_cart = new Cart;

// 往购物车里添加条目
$my_cart->add('butter', 1);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);

// 打出出总价格,其中有 5% 的销售税.
print $my_cart->getTotal(0.05) . "\n";
// 最后结果是 54.29
https://learnku.com/articles/33848

并发问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 //正常写法,获取最后一条数据,新的单据编号+1,有并发问题
$last = Article::orderByDesc('id')->first();
$data = [
'code' => $last->code + 1,
];
$article = Article::create($data);

//---------------------------------------------------------------------------------//
//解决并发问题 测试ab -t 6 -c 20 http://study.local/xxx
DB::beginTransaction();
$last = Article::lockForUpdate()->orderByDesc('id')->first();
$data = [
'code' => $last->code + 1,
];
$article = Article::create($data);
DB::commit();
https://learnku.com/articles/33809
高性能分布式并发锁, 行为限流https://github.com/zhaocong6/lock

intervention/image慢

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
// 记录开始时间
$startTimestamp = microtime(true);

$url = 'http://wx.qlogo.cn/mmopen/XxT9TiaJ1ibf06TNRCMjQADS4opDHvQLguLZHpqkRlvuJYZicvJW4iaOalPsKIs0kpZ3F6864ZzibyObYiaucUQSrdp4pFTNDyIpxw/0';

$avatar = \Image::make($url);

// 记录结束时间
$endTimestamp = microtime(true);

info($startTimestamp);
info($endTimestamp);
info($endTimestamp - $startTimestamp);

$startTimestamp = microtime(true);

$client = new \GuzzleHttp\Client();

$url = 'http://wx.qlogo.cn/mmopen/XxT9TiaJ1ibf06TNRCMjQADS4opDHvQLguLZHpqkRlvuJYZicvJW4iaOalPsKIs0kpZ3F6864ZzibyObYiaucUQSrdp4pFTNDyIpxw/0';

$avatarResponse = $client->get($url);

$avatar = \Image::make($avatarResponse->getBody()->getContents());

$endTimestamp = microtime(true);

info($startTimestamp);
info($endTimestamp);
info($endTimestamp - $startTimestamp);
https://tianyong90.com/posts/intervention-image-zhong-de-yi-ge-xiao-keng-ji-qi-po-jie-zhi-fa/
在这里我先使用 GuzzleHttp 获取头像,再使用 Image::make($data) 创建头像

laravel5.5 中读写分离

1
明明刚刚写入了数据,但查询时却报 No query result ,而且只是偶然性出现,没啥规律。 在没有启用 sticky 的时候,使用 write 连接写入数据后立即读取,读取时使用的是 read 连接,这样就有可能出问题。将 sticky 设置为 true 后,在与这个写入操作相同的请求周期内的后续读取操作,仍然使用原来的 write 连接,就不会有这麻烦了。

puppeteer 采集异步加载

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
composer require spatie/browsershot
npm i puppeteer --save
use Spatie\Browsershot\Browsershot;

public function getBodyHtml()
{
$newsUrl = 'https://m.toutiao.com/i6546884151050502660/';

$html = Browsershot::url($newsUrl)
->windowSize(480, 800)
->userAgent('Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36')
->mobile()
->touch()
->bodyHtml();

\Log::info($html);
}

use Spatie\Browsershot\Browsershot;

public function getBodyHtml()
{
$newsUrl = 'https://m.toutiao.com/i6546884151050502660/';

Browsershot::url($newsUrl)
->windowSize(480, 800)
->userAgent('Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36')
->mobile()
->touch()
->setDelay(1000)
->save(public_path('images/toutiao.jpg'));
}https://tianyong90.com/posts/laravel-zhong-shi-yong-puppeteer-cai-ji-yi-bu-jia-zai-de-wang-ye-nei-rong/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 cookie运行原理

1. 客户端向服务端发起一个 http 请求.

2. 服务端设置一个创建 cookie 的指令,响应给客户端.

3. 客户端收到服务端响应的指令,根据指令在客户端创建一个 cookie.

4. 挡下一次请求时,客户端携带这个 cookie 向服务端发送请求.
session. 运行原理

1. 客户端向服务端发起请求,建立通信

2. 服务端根据设置的 session 创建指令,在服务端创建一个编号为 sessionid 的文件,里面的值就是 session 具体的值 (组成部分 变量名 | 类型 : 长度:值).

3. 服务端将创建好的 sessionid 编号响应给客户端,客户则将该编号存在 cookie 中 (一般我们在浏览器存储的调试栏中会发现 cookie 中有一个 PHPSESSID 的键,这就是 sessionid,当然这个名称,我可以通过设置服务端是可以改变的).

. 当下一次请求时,客户端将这个 sessionid 携带在请求中,发送给服务端,服务端根据这个 sessionid 来做一些业务判断

PHP-Casbin 的 ABAC 权限控制

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
https://github.com/php-casbin/php-casbin
ABAC 的官方实例如下:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == r.obj.owner
这是 r.obj 类的定义:

$data1 = new \stdClass();
$data1->name = 'data1';
$data1->owner = 'alice';

$data2 = new \stdClass();
$data2->name = 'data2';
$data2->owner = 'bob';
然后使用决策器进行决策:

$e->enforce('alice', $data1, 'read'); // true
$e->enforce('alice', $data2, 'read'); // false

$e->enforce('bob', $data1, 'read'); // false
$e->enforce('bob', $data2, 'read'); // true
https://learnku.com/articles/33921

php artisan tinker edit

1
2
3
4
5
6
7
php artisan tinker
>>>edit

进入了 Vim 文本状态,能够正常的编写你想要的代码 编辑完之后,我们保存并退出这个文件,tinker 会自动执行我们刚才写好的代码,并将结果进行输出。
也可以考虑使用 spatie/laravel-web-tinker
还有一种方法是 直接把代码写到 php 文件里,然后 php artisan tinker your-file.php 也是我常用的一种解决方法。
psysh 更多使用可以查看文档 https://psysh.org/ https://learnku.com/laravel/t/33957

使用 PHP-version 切换 PHP 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[qian@bogon ~ ]$ mkdir $HOME/.local
[qian@bogon ~ ]$ cd $HOME/.local
[qian@bogon .local ]$ git clone https://github.com/wilmoore/php-version.git
[qian@bogon .local ]$ source $HOME/.local/php-version/php-version.sh
//测试 查看当前php版本
[qian@bogon .local ]$ php-version
* 7.1.18
7.2.22
[qian@bogon ~ ]$ echo "source $HOME/.local/php-version/php-version.sh" >> ~/.zshrc
[qian@bogon ~ ]$ source ~/.zshrc
切换 php 版本

php-version 7.1

php-version 7.2

动态绘制会员卡图片

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

/**
* PHP GD 动态绘制会员卡图片
* https://github.com/kasuganosoras/SomeCodes/blob/master/v2ex_600721.php
* 帖子:https://www.v2ex.com/t/600721
* 更新:现在背景图黑色部分不会变成透明了
* 测试:https://cdn.zerodream.net/api/card/
* 使用:Clone 到本地后,将 v2ex_600721.php 和 v2ex_600721 目录复制到你的网站目录下即可
*/
// 获取图片大小
list($width, $height, $type) = getimagesize(__DIR__ . '/v2ex_600721/images/bg.png');
// 文字内容设定
$text = "钢板卡号:4040 2333 114 514";
// 加载资源
$font = __DIR__ . '/v2ex_600721/fonts/mono.ttf';
$card = imagecreatefrompng(__DIR__ . '/v2ex_600721/images/bg.png');
$image = imagecreatetruecolor($width, $height);
$bgColor = imagecolorallocatealpha($image, 0, 0, 0, 127);
$white = imagecolorallocate($image, 255, 255, 255);
// 执行绘图
imagefill($image, 0, 0, $bgColor);
imagecopyresampled($image, $card, 0, 0, 0, 0, $width, $height, $width, $height);
imagefttext($image, 12, 0, 36, 215, $white, $font, $text);
imagesavealpha($image, true);
// 输出并销毁
Header("Content-type: image/png");
imagepng($image);
imagedestroy($image);

PHP不支持Unicode编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//文件编码UTF-8
echo strlen("中文"); // 6
echo substr("中文",0,1) // 乱码
echo substr("中文",0,3) // 中
PHP原生字符串函数只能操作单字节字符!就是把一个字节当做一个字符来处理!
//文件编码UTF-8
echo bin2hex("中文"); // 可以看到,"中文"对应的二进制就是:e4b8ade69687
echo strlen("中文"); // 所以按照单字节来统计长度,就是6
echo substr("中文",0,1) // 取0到1个字节,也就是e4,并不对应某个字符的编码,所以乱码
echo substr("中文",0,3) // 取0到3个字节,刚好把`中`的编码取出来
// 脚本类型为UTF-8
echo strlen("中文"); // 6
echo mb_strlen("中文","UTF-8"); //2 使用mb_strlen ,并传入编码 utf-8, 就会把二进制E4B8ADE69687当做utf-8的处理能正确处理
echo mb_strlen("中文"); //2 如果不传编码UTF-8,则函数会自动确定编码,文档说:如果省略,则使用内部字符编码。所以这里也当做UTF-8来处理。
echo mb_strlen("中文","GBK"); //3,如果传入编码GBK,则:e4b8ade69687会被当做gbk来处理,一个gbk字符占2字节,所以为:3
http://webkit.cc/post/php-and-unicode.html

curl乱码

php curl乱码 curl_setopt ($ch, CURLOPT_ENCODING ,’gzip’);
是告诉 Curl 使用 Gzip 进行解析。 Header 里 Accept-Encoding:gzip 是告诉对方服务器使用 Gzip 进行传输。 ​​​​

curl_setopt($ch, CURLOPT_ENCODING, ‘’);
加了这句话,会在请求时自动加上请求头Accept-Encoding,并且返回内容会自动解压,不会乱码了

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
匿名类的支持
new class($i) {
public function __construct($i) {
$this->i = $i;
}
}
// 使用 <=> (飞船符)之后
function order_func($a, $b) {
return $a <=> $b;
}
// 分组使用语法:

use FooLibrary\Bar\Baz\{ ClassA, ClassB, ClassC, ClassD as Fizbo };

echo "\u{202E}Reversed text"; //输出反转文本
echo "mañana"; // "ma\u{00F1}ana"
echo "mañana"; // "man\u{0303}ana" "n" 结合 ~ 字符 (U+0303)

$array = [1, 2, 3];
//为 $a,$b 和 $c 按键值从 0 开始的方式分配 $array 数组元素的值
[$a, $b, $c] = $array;

// 使用 “a”,“b” 和 “c” 键分别为 $a,$b 和 $c 分配 $array 中数组元素的值
["a" => $a, "b" => $b, "c" => $c] = $array;

try {
// 部分代码...
} catch (ExceptionType1 | ExceptionType2 $e) {
// 处理异常的代码
} catch (\Exception $e) {
// ...
}

https://learnku.com/php/t/33829

单例模式

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
/**

* 单例模式

*/

class Single

{

public static $attribute = '';

public static $instance = '';

private function __construct($attribute = '浪子编程走四方')

{

self::$attribute = $attribute;

}

public static function getInstance($attribute = '浪子编程走四方1')

{

if (!(self::$instance instanceof self)) self::$instance = new self($attribute);

return self::$instance;

}

}

/**

* 工厂模式

*/

class Factory

{

public static function createObj()

{

return Single::getInstance('浪子编程走四方');

}

}

/**

* 注册模式

* 含义:就是将对象放在一个对象池中,使用的时候直接去对象池查找.

* 需要如下几个操作:

* 1.注册

* 2.存放对象池

* 3.获取

* 4.销毁

*/

Class Register

{

// 用一个数组来当做对象池,键当做对象别名,值存储具体对象

public static $objTree = [];

// 将对象放在对象池

public static function set($key, $val)

{

return self::$objTree[$key] = $val;

}

// 通过对象别名在对象池中获取到对象别名

public static function get($key)

{

return self::$objTree[$key];

}

\

// 通过对象别名将对象从对象池中注销https://learnku.com/articles/34160

public static function _unset($key)

{

unset(self::$objTree[$key]);

}

}

Register::set('single', Factory::createObj());

$single = Register::get('single');

print_r($single);

echo $single::$attribute;

guzzle 绑定 host

1
2
3
4
5
6
7
8
$client = new Client();
$response = $client->get('http://1.2.3.4',["timeout"=>1,"connect_timeout"=>1,'headers'=>['host'=>'xxx.com.cn']]);
$status = $response->getStatusCode();
if ($status == 200)
{
$data=json_decode((string)$response->getBody(), true);
}
return false;

指定顺序插入数组元素

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
在数组第2,4个位置插入一个数组
$insert = [
['index'=> 2, 'data' => '第2'],
['index'=> 4, 'data' => '第4'],
];
$data = [
['a' => 'a'],
['b' => 'b'],
['c' => 'c'],
['d' => 'd'],
['e' => 'e'],
['f' => 'f'],
];
$i = 0;
foreach ($insert as $value) {
if (!$value) {
continue;
}
array_splice($data, $value['index'] - 1 + $i, 0 ,[$value]);
$i++;
}

print_r($data);

[
[
"a" => "a",
],
[
"index" => 2,
"data" => "第2",
],
[
"b" => "b",
],
[
"c" => "c",
],
[
"index" => 4,
"data" => "第4",
],
[
"d" => "d",
],
[
"e" => "e",
],
[
"f" => "f",
],
]

guzzle 并行请求接口

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
composer require "guzzlehttp/guzzle: 5.3.1"
use GuzzleHttp\Pool;
use GuzzleHttp\Message\Response;
$requests = [];
$url = 'http://xxxx.com';
$url2 = "http://xxx2.com";
$client = new Client();
$request = $client->createRequest('get', $url, ["timeout"=>1,"connect_timeout"=>1]);
$request2 = $client->createRequest('get', $url2, ["timeout"=>1,"connect_timeout"=>1]);
$requests[] = $request;
$requests[] = $request2;
$results = Pool::batch($client, $requests);
if(!$results->getResult($request) instanceof Response && !$results->getResult($request2) instanceof Response){
return [];
}

if($results->getFailures()){
$data = [];
$data2 = [];
}else{
$data = $results->getResult($request)->json();
$data2 = $results->getResult($request2)->json();
}

return [$data,$data2];
异步请求
try{
$client = new Client();
$req = $client->createRequest('GET', 'http://xxx.com?', ['future' => true,"timeout"=>1,"connect_timeout"=>1]);
$client->send($req)->then(function ($response){
//dump($response->json());
});
} catch (\Exception $e){

}

杀掉过期进程

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
use Carbon\Carbon;
try {
$pids = $this->getPids(["php", "lock"]);
foreach ($pids as $pid) {
$startTime = $this->getProcessStartTime($pid);
$this->ifKill($startTime, $pid);
}
} catch (Exception $e) {

}

protected function getPids($arr)
{
$command = "ps aux | grep -v grep ";
foreach ($arr as $word) {
$command .= "| grep {$word}";
}
$command .= " | awk '{print $2}'";
$result = shell_exec($command);
$result = trim($result);
if (empty($result)) {
throw new Exception("Cannot find such process " . implode(",", $arr));
}
return explode("\n", $result);
}

protected function getProcessStartTime($pid)
{
$command = "ps -p {$pid} -o lstart";
$result = shell_exec($command);
$result = trim(str_replace("STARTED", "", $result));
if (empty($result)) {
throw new Exception("Cannot get process start time, process id is {$pid}");
}
$ctime = Carbon::createFromFormat("D M j H:i:s Y", $result);
return $ctime;
}

protected function ifKill(Carbon $ctime, $pid)
{
if ($ctime->lt(Carbon::now()->addHours(-1))) {
$cmd = file_get_contents("/proc/{$pid}/cmdline");

shell_exec("kill -9 {$pid}");
}
}

根据name和pcode合并职位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$temp = [];
foreach ($arr as $v) {
if (isset($temp[$v['Name'].'_'.$v['Pcode']])) {
$temp[$v['Name'].'_'.$v['Pcode']]['Duty'] .= ' '.$v['Duty'];
} else {
$temp[$v['Name'].'_'.$v['Pcode']] = $v;
}
}
//职位排序
$manager = ['董事长', '董事会主席', '总裁', '总经理'];
$res = [];
foreach ($manager as $value) {
foreach ($temp as $k=>$v) {
$t = explode(' ', $v['Duty']);
if (in_array($value, $t)) {
$res[$k] = $v;
}
}
}
//dump($res,$res+array_diff_key($temp,$res),$res+$temp);
return $res ? $res + $temp : $temp;

unicode encode

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
function unicode_encode($name)
{
$name = iconv('UTF-8', 'UCS-2', $name);
$len = strlen($name);
$str = '';
for ($i = 0; $i < $len - 1; $i = $i + 2)
{
$c = $name[$i];
$c2 = $name[$i + 1];
if (ord($c) > 0)
{ //两个字节的文字
$str .= '\u'.base_convert(ord($c), 10, 16).str_pad(base_convert(ord($c2), 10, 16), 2, 0, STR_PAD_LEFT);
}
else
{
$str .= $c2;
}
}
return $str;
}

//将UNICODE编码后的内容进行解码
function unicode_decode($name)
{
//转换编码,将Unicode编码转换成可以浏览的utf-8编码
$pattern = '/([\w]+)|(\\\u([\w]{4}))/i';
preg_match_all($pattern, $name, $matches);
if (!empty($matches))
{
$name = '';
for ($j = 0; $j < count($matches[0]); $j++)
{
$str = $matches[0][$j];
if (strpos($str, '\\u') === 0)
{
$code = base_convert(substr($str, 2, 2), 16, 10);
$code2 = base_convert(substr($str, 4), 16, 10);
$c = chr($code).chr($code2);
$c = iconv('UCS-2', 'UTF-8', $c);
$name .= $c;
}
else
{
$name .= $str;
}
}
}
return $name;
}

二维数组排序

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//对存储数字的二维数组,按照每个一维数组数字之和的平均值重新排序
$arr = array(
array(1,18,3,6,2147483648),
array(4,16,2,10,1),
array(11,2,3,5,9),
array(3,8,12,4,8)
);
// 方法一
function vhall_sort($arr){
$tmp = $new = array();
$len = count($arr);
if ($len == 0 || !is_array($arr)) {
return false;
}
foreach ($arr as $k=>$v) {
$num = floatval(array_sum($v));
$tmp[$k] = $num / $len;
}
arsort($tmp);//倒序排列
foreach ($tmp as $k=>$v){
$new[$k] = $arr[$k];//保持key
}
return $new;
}
echo '<pre>';
print_r($arr);
print_r(vhall_sort($arr));
// 方法二 require php5.3+
uasort($arr, function($v1, $v2){
return (array_sum($v1)/count($v1) > array_sum($v2)/count($v2)) ? -1 : 1;
});
print_r($arr);
//二维数组去掉重复值
function array_unique_fb($array2D){
foreach ($array2D as $v){
$v=join(',',$v); //降维,也可以用implode,将一维数组转换为用逗号连接的字符串
$temp[]=$v;
}
$temp=array_unique($temp); //去掉重复的字符串,也就是重复的一维数组
foreach ($temp as $k => $v){
$temp[$k]=explode(',',$v); //再将拆开的数组重新组装
}
return $temp;
}
//二维数组去掉重复值,并保留键值
function array_unique_fb($array2D){
foreach ($array2D as $k=>$v){
$v=join(',',$v); //降维,也可以用implode,将一维数组转换为用逗号连接的字符串
$temp[$k]=$v;
}
$temp=array_unique($temp); //去掉重复的字符串,也就是重复的一维数组
foreach ($temp as $k => $v){
$array=explode(',',$v); //再将拆开的数组重新组装
//下面的索引根据自己的情况进行修改即可
$temp2[$k]['id'] =$array[0];
$temp2[$k]['title'] =$array[1];
$temp2[$k]['keywords'] =$array[2];
$temp2[$k]['content'] =$array[3];
}
return $temp2;
}
/*
*二维数组按指定的键值排序
*/
function array_sort($array,$keys,$type='asc'){
if(!isset($array) || !is_array($array) || empty($array)){
return '';
}
if(!isset($keys) || trim($keys)==''){
return '';
}
if(!isset($type) || $type=='' || !in_array(strtolower($type),array('asc','desc'))){
return '';
}
$keysvalue=array();
foreach($array as $key=>$val){
$val[$keys] = str_replace('-','',$val[$keys]);
$val[$keys] = str_replace(' ','',$val[$keys]);
$val[$keys] = str_replace(':','',$val[$keys]);
$keysvalue[] =$val[$keys];
}
asort($keysvalue); //key值排序
reset($keysvalue); //指针重新指向数组第一个
foreach($keysvalue as $key=>$vals) {
$keysort[] = $key;
}
$keysvalue = array();
$count=count($keysort);
if(strtolower($type) != 'asc'){
for($i=$count-1; $i>=0; $i--) {
$keysvalue[] = $array[$keysort[$i]];
}
}else{
for($i=0; $i<$count; $i++){
$keysvalue[] = $array[$keysort[$i]];
}
}
return $keysvalue;
}
$array=array(
0=>array('id'=>8,'username'=>'phpernote'),
1=>array('id'=>9,'username'=>'com'),
2=>array('id'=>5,'username'=>'www')
);
array_sort($array,'id','asc');
$trans = array ("a" => 1, "b" => 1, "c" => 2);
$trans = array_flip (array_flip($trans));
print_r($trans);
$arr = array(
array('id' => 1,'name' => 'aaa'),
array('id' => 2,'name' => 'bbb'),
array('id' => 3,'name' => 'ccc'),
array('id' => 4,'name' => 'ddd'),
array('id' => 5,'name' => 'ccc'),
array('id' => 6,'name' => 'aaa'),
array('id' => 7,'name' => 'bbb'),
);
function assoc_unique(&$arr, $key)
{
$rAr=array();
for($i=0;$i<count($arr);$i++)
{
if(!isset($rAr[$arr[$i][$key]]))
{
$rAr[$arr[$i][$key]]=$arr[$i];
}
}
$arr=array_values($rAr);
}
assoc_unique($arr,'name');
print_r($arr);
function array_sort($arr,$keys,$type='asc'){
$keysvalue= $new_array= array();
foreach($arras$k=>$v){
$keysvalue[$k] = $v[$keys];
}
if($type== 'asc'){
asort($keysvalue);
}else{
arsort($keysvalue);
}
reset($keysvalue);
foreach($keysvalueas$k=>$v){
$new_array[$k] = $arr[$k];
}
return$new_array;
}
$newArray= array_sort($array,'price');
function hasort($arr) {
$a = $b = array();
foreach($arr as $v => $k) $a[$k][] = $v;
ksort($a);
print_r($a);
foreach($a as $v => $i)
foreach($i as $k) $b[$k] = $v;
return $b;
}
$a = array('a' => 10, 'z' => 10, 'c' => 10, 'b' => 11, 'd' => 10);
$a = hasort($a);
print_r($a);
$a = array('a' => 10, 'z' => 10, 'c' => 10, 'b' => 11, 'd' => 10);
asort($a, SORT_NUMERIC);
/**
* @param array $weight 权重 例如array('a'=>10,'b'=>20,'c'=>50)
* @return string key 键名
*/
function roll($weight = array()) {
$roll = rand ( 1, array_sum ( $weight ) );
$_tmpW = 0;
$rollnum = 0;
foreach ( $weight as $k => $v ) {
$min = $_tmpW;
$_tmpW += $v;
$max = $_tmpW;
if ($roll > $min && $roll <= $max) {
$rollnum = $k;
break;
}
}
return $rollnum;
}

$row=roll(array('a'=>10,'b'=>20,'c'=>50));
echo $row;
function utf8_array_asort(&$array) {
if(!isset($array) || !is_array($array)) {
return false;
}
foreach($array as $k=>$v) {
$array[$k] = iconv('UTF-8', 'GBK//IGNORE',$v);
}
asort($array);
foreach($array as $k=>$v) {
$array[$k] = iconv('GBK', 'UTF-8//IGNORE', $v);
}
return true;
}
$abc = array('a'=>'猜', 'b'=>'我','c'=>'哦','d'=>'棍','e'=>'f','f'=>'爸','z'=>'州');
utf8_array_asort($abc);
print_r($abc);

报错

1
2
3
4
5
PHP 里这样不会报错:<?php var_dump(NoFound::class); ?>,不注意的话可能会掉坑。 ​​​​

还有 http://xxx

preg_match_all('@\p{Han}@u','微博',$m);这也能匹配汉字

并发场景下的重复插入

1
2
3
4
5
6
7
8
9
10
11
12
$lock = Cache::lock('key', 60);
abort_if(!$lock->get(), 422, '操作过于频繁,请稍后再试!');



$lock->release();
一个用户只能一条吗?用 redis 集合,把用户 id 扔集合里,返回已存在的,就证明已插过了。扔成功的,就是没插入过的。
$lock->block(3,function(){
// todo
// 在这闭包下写业务逻辑,并且不用手动释放锁,框架会为你自动释放,感觉方便些
});
https://learnku.com/laravel/t/34295

小数点 00

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$num = [
"a" => 20.00,
"b" => 39.78,
];
echo json_encode($num);
// {"a":20,"b":39.78}
echo 66.00;
// 66
$num = sprintf("%1\$.2f", 66.00);
var_dump($num);
// string(4) "66.00"
或者

$num = number_format(66.00,2,'.','');
var_dump($num);
// string(5) "66.00"

$output = array('x' => 'y', 'price' => '30.00');
$json = json_encode($output);
$json = str_replace('"price":"'.$output['price'].'"', '"price":'.$output['price'].'', $json);
print $json;
// {"x":"y","price":30.00}

https://laravelcode.cn/posts/104/a-brief-talk-on-the-return-value-of-the-decimal-point-00-of-the-output-functions-of-various-languages

反射之动态代理

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
基于动态代理,可以有更多的想象空间,如实现拦截器,属性方法增加,裁剪等等
class Mysql
{
function connect($db){
echo "connecting database ${db[0]}\r\n";
}
}
class SqlProxy
{
private $target;
function __construct($tar){
$this->target[] = new $tar();
}

function __call($name, $args){
if($method = $r->getMethod($name)){
if($method->isPublic() && !$method->isAbstract()){
echo "method before record \r\n";
$method->invoke($obj,$args);
echo "method after record\r\n";
}
}
}
}
}
$obj = new SqlProxy('Mysql');
$obj->connect('member');
https://learnku.com/articles/34288
反射可以探知类的内部结构 可以用它做 hook 实现插件功能,或者做动态代理

PhpSpreadsheet 导出图片到 Excel

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
 public function export($data)
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
//设置sheet的名字 两种方法
$sheet->setTitle('phpspreadsheet——demo');
$spreadsheet->getActiveSheet()->setTitle('Hello');
//设置第一行小标题
$k = 1;
$sheet->setCellValue('A' . $k, '问题');
$sheet->setCellValue('B' . $k, '选项');
$sheet->setCellValue('C' . $k, '答案');
$sheet->setCellValue('D' . $k, '图片');

// 设置个表格宽度
$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(16);
$spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(80);
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(15);
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(20);

// 垂直居中
$spreadsheet->getActiveSheet()->getStyle('A')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('B')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('C')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('D')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);

$info = $data;
// 设置A单元格的宽度 同理设置每个
$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(20);
// 设置第三行的高度
$spreadsheet->getActiveSheet()->getRowDimension('3')->setRowHeight(50);
// A1水平居中
$styleArray = [
'alignment' => [
'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
],
];
$sheet->getStyle('A1')->applyFromArray($styleArray);
// 将A3到D4合并成一个单元格
$spreadsheet->getActiveSheet()->mergeCells('A3:D4');
// 拆分合并单元格
$spreadsheet->getActiveSheet()->unmergeCells('A3:D4');
// 将A2到D8表格边框 改变为红色
$styleArray = [
'borders' => [
'outline' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK,
'color' => ['argb' => 'FFFF0000'],
],
],
];
// $sheet->getStyle('A2:E8')->applyFromArray($styleArray);
// 设置超链接
// $sheet->setCellValue('D6', 'www.baidu.com');
// $spreadsheet->getActiveSheet()->setCellValue('E6', 'www.baidu.com');
// 循环赋值
$k = 2;
foreach ($info as $key => $value) {
$sheet->setCellValue('A' . $k, $value['question']);
$sheet->setCellValue('B' . $k, $value['question_options']);
$sheet->setCellValue('C' . $k, $value['answer']);

$img = self::curlGet($value['img']);
$dir = public_path('/temp/image/');
$file_info = pathinfo($value['img']);
if (!empty($file_info['basename'])) { //过滤非文件类型
$basename = $file_info['basename'];
is_dir($dir) OR mkdir($dir, 0777, true); //进行检测文件是否存在
file_put_contents($dir . $basename, $img);

$drawing[$k] = new Drawing();
$drawing[$k]->setName('Logo');
$drawing[$k]->setDescription('Logo');
$drawing[$k]->setPath($dir . $basename);
$drawing[$k]->setWidth(80);
$drawing[$k]->setHeight(80);
$drawing[$k]->setCoordinates('D'.$k);
$drawing[$k]->setOffsetX(12);
$drawing[$k]->setOffsetY(12);
$drawing[$k]->setWorksheet($spreadsheet->getActiveSheet());
} else {
$sheet->setCellValue('D' . $k, '');
}
$sheet->getRowDimension($k)->setRowHeight(80);
$k++;
}
$file_name = date('Y-m-d', time()) . rand(1000, 9999);
// 第一种保存方式
/*$writer = new Xlsx($spreadsheet);
//保存的路径可自行设置
$file_name = '../'.$file_name . ".xlsx";
$writer->save($file_name);*/
// 第二种直接页面上显示下载
$file_name = $file_name . ".xls";
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="' . $file_name . '"');
header('Cache-Control: max-age=0');
$writer = IOFactory::createWriter($spreadsheet, 'Xls');
// 注意createWriter($spreadsheet, 'Xls') 第二个参数首字母必须大写
$writer->save('php://output');
}

public function getClient(){
$client = new Client();
return $client;
}

public static function curlGet($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 这个是重点 请求https。
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
https://learnku.com/articles/26965
在其他地方调用 export 方法,传入 $data 数组,数组中有四个字段:question,question_options,answer,img ,其中 img 就是图片的远程地址,剩下的再看一下代码

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
include __DIR__ . '/vendor/autoload.php';//引入自动加载类

use Goutte\Client;//使用第三方采集库

$client = new Client();

$links = [
'http://www.nipic.com/topic/show_27192_1.html',
'http://www.nipic.com/topic/show_27054_1.html',
'http://www.nipic.com/topic/show_27085_1.html',
];//要爬的三个分类下的图片

$pids = [];//定义一个子进程的数组

foreach ($links as $url) {
$pid = pcntl_fork();//fork 一个子进程
switch ($pid) {
case -1:
die("Fork failed\n");
case 0:

$id = posix_getpid();//得到子进程id
$pids[$id] = $id;//存入到子进程数组id 中
$data = [];

$crawler = $client->request('GET', $url);
$crawler->filter('.search-works-thumb')->each(function($node) use ($client, $id, &$data) {
$url = $node->link()->getUri();

$crawler = $client->request('GET', $url);
$crawler->filter('#J_worksImg')->each(function($node) use ($id, &$data) {
$src = $node->image()->getUri();

$data[$id][] = $src;//存入消息队列 或者redis 都可以
//echo $src . PHP_EOL;
});
});

print_r($data);

exit;//结束子进程

break;
default:
sleep(2);
break;
}
}

while ( count($pids) ) {
if (($id = pcntl_wait($status, WUNTRACED)) > 0) {//主进程 阻塞等待子进程完成任务
unset($pids[$id]);
}
}
https://learnku.com/articles/26756

larvel excel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function drawings()
{
//这里的数据自己组装
$draw_arr = [1 =>'detail1.jpg', 2 => 'detail2.jpg'];
$result = [];
foreach ($draw_arr as $k => $v) {
${'drawing'.$k} = new Drawing();//变量还能这样用
${'drawing'.$k}->setName('Other image');
${'drawing'.$k}->setDescription('This is a second image');
//图片路径
${'drawing'.$k}->setPath(public_path($v));
${'drawing'.$k}->setHeight(50);
//设置图片列
${'drawing'.$k}->setCoordinates('U'.$k);
$result[] = ${'drawing'.$k};
}
return $result;
}https://learnku.com/articles/32391

权重随机算法

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
class WeightedRoundRobin
{
private static $_weightArray = array();

private static $_i = -1;//代表上一次选择的服务器
private static $_gcd;//表示集合S中所有服务器权值的最大公约数
private static $_cw = 0;//当前调度的权值
private static $_max;
private static $_n;//agent个数

public function init()
{

}

public function initParam(array $weightArray)
{
self::$_weightArray = $weightArray;
self::$_gcd = self::getGcd(self::$_weightArray);
self::$_max = self::getMaxWeight(self::$_weightArray);
self::$_n = count($weightArray);
}

private static function getGcd(array $weightArray)
{
$temp = array_shift($weightArray);
$min = $temp['weight'];
$status = false;
foreach ($weightArray as $val) {
$min = min($val['weight'],$min);
}

if($min == 1){
return 1;
}else{

for ($i = $min; $i>1; $i--) {

foreach ($weightArray as $val) {
if (is_int($val['weight']/$i)) {
$status = true;
}else{
$status = false;
break;
}
}
if ($status) {
return $i;
}else {
return 1;
}

}
}

}

private static function getMaxWeight(array $weightArray)
{
if(empty($weightArray)){
return false;
}
$temp = array_shift($weightArray);
$max = $temp['weight'];
foreach ($weightArray as $val) {
$max = max($val['weight'],$max);
}
return $max;
}

public function getWeight()
{
while (true){

self::$_i = ((int)self::$_i+1) % (int)self::$_n;

if (self::$_i == 0) {

self::$_cw = (int)self::$_cw - (int)self::$_gcd;
if (self::$_cw <= 0) {
self::$_cw = (int)self::$_max;

if (self::$_cw == 0) {
return null;
}
}
}

if ((int)(self::$_weightArray[self::$_i]['weight']) >= self::$_cw) {
return self::$_weightArray[self::$_i]['id'];
}
}
}
}
https://learnku.com/articles/34449

base64原理

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
数据先做一个 Base64 编码,统统变成可见字符,降低错误率。基于 A-Z、a-z、0-9 以及 '+''/'64 个字符的编码方式,因为 26 次方等于 64,所以说只需要 6 个比特即可表示一个 base64 的字符。

编码表:'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
核心原理是将二进制数据进行分组,以 6 位一组进行分组,并在每组前面都填两个高位 0,然后将 8 bit 的字节转换成十进制,对照 BASE64 编码表 (上表),得到对应编码后的字符。
以对6666P进行base64编码的步骤说明
每个字符转化为8bit:
6----->00110110
6----->00110110
6----->00110110
6----->00110110
P----->01010000
补位----->00000000
整体拼接结果:
001101100011011000110110001101100101000000000000
6bit一组进行分割:
001101 100011 011000 110110 001101 100101 000000 000000
转化为base64编码脚标:
13 35 24 54 13 37 0 0
获取对应的base64编码:
N j Y 2 N l A A
得到结果:string(8) "NjY2NlA="

$a = "6666P";

$decbinStr= '';
//计算补位
$end = (3 - strlen($a)%3)%3;
for($i=0;$i<strlen($a);$i++){
//每个字符转化为8bit
$decbin = str_pad(decbin(ord($a[$i])),8,"0",STR_PAD_LEFT);
$decbinStr .= $decbin ;
}
//增加补位
$decbinStr = $decbinStr. str_pad("",$end*8,"0");
//以6bit一组进行分割
$arr = str_split($decbinStr,6);
//转化成对应的base64编码
$result = implode("", array_map("getBase64Str",$arr));
//末尾补位的数据处理
for($j=0;$j<$end;$j++){
if($result[strlen($result)-1-$j] == 'A'){
$result[strlen($result)-1-$j] = "=";
}
}
//验证下跟自带的base64_encode结果是否一致
var_dump($result);
var_dump(base64_encode($a));

exit();

function getBase64Str($sixStr){
$number = bindec($sixStr);
$baseHash = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
return $baseHash[$number];
}

https://learnku.com/articles/34439

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
class ExportService
{

public static $outPutFile = '';

/**
* 导出文件
* @param string $fileName
* @param $data
* @param array $formFields
* @return mixed
*/
public static function exportData($fileName = '', $data, $formFields = [])
{
$fileArr = [];
$tmpPath = \Yii::$app->params['excelSavePath'];

foreach (array_chunk($data, 10000) as $key => $value) {
self::$outPutFile = '';
$subject = !empty($fileName) ? $fileName : 'data_';
$subject .= date('YmdHis');
if (empty($value) || empty($formFields)) {
continue;
}

self::$outPutFile = $tmpPath . $subject . $key . '.csv';
if (!file_exists(self::$outPutFile)) {
touch(self::$outPutFile);
}
$index = array_keys($formFields);
$header = array_values($formFields);
self::outPut($header);

foreach ($value as $k => $v) {
$tmpData = [];
foreach ($index as $item) {
$tmpData[] = isset($v[$item]) ? $v[$item] : '';
}
self::outPut($tmpData);
}
$fileArr[] = self::$outPutFile;
}

$zipFile = $tmpPath . $fileName . date('YmdHi') . '.zip';
$zipRes = self::zipFile($fileArr, $zipFile);
return $zipRes;
}

/**
* 向文件写入数据
* @param array $data
*/
public static function outPut($data = [])
{
if (is_array($data) && !empty($data)) {
$data = implode(',', $data);
file_put_contents(self::$outPutFile, iconv("UTF-8", "GB2312//IGNORE", $data) . PHP_EOL, FILE_APPEND);
}
}

/**
* 压缩文件
* @param $sourceFile
* @param $distFile
* @return mixed
*/
public static function zipFile($sourceFile, $distFile)
{
$zip = new \ZipArchive();
if ($zip->open($distFile, \ZipArchive::CREATE) !== true) {
return $sourceFile;
}

$zip->open($distFile, \ZipArchive::CREATE);
foreach ($sourceFile as $file) {
$fileContent = file_get_contents($file);
$file = iconv('utf-8', 'GBK', basename($file));
$zip->addFromString($file, $fileContent);
}
$zip->close();
return $distFile;
}

/**
* 下载文件
* @param $filePath
* @param $fileName
*/
public static function download($filePath, $fileName)
{
if (!file_exists($filePath . $fileName)) {
header('HTTP/1.1 404 NOT FOUND');
} else {
//以只读和二进制模式打开文件
$file = fopen($filePath . $fileName, "rb");

//告诉浏览器这是一个文件流格式的文件
Header("Content-type: application/octet-stream");
//请求范围的度量单位
Header("Accept-Ranges: bytes");
//Content-Length是指定包含于请求或响应中数据的字节长度
Header("Accept-Length: " . filesize($filePath . $fileName));
//用来告诉浏览器,文件是可以当做附件被下载,下载后的文件名称为$file_name该变量的值
Header("Content-Disposition: attachment; filename=" . $fileName);

//读取文件内容并直接输出到浏览器
echo fread($file, filesize($filePath . $fileName));
fclose($file);
exit();
}
}
} https://learnku.com/articles/34512

24 小时排行榜实时更新

1
2
3
4
5
6
7
8
ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN
利用 ZADD 按小时划分添加用户的积分信息,然后用 ZUNIONSTORE 并集实现 24 小时的游戏积分总和,实现 “24 小时排行榜”
Redis 在遇到分数相同时是按照集合成员自身的字典顺序来排序,这里即是按照”user2″和”user3″这两个字符串进行排序,以逆序排序的话 user3 自然排到了前面。要解决这个问题,我们可以考虑在分数中加入时间戳,计算公式为:
带时间戳的分数 = 实际分数*10000000000 + (9999999999 – timestamp)
ZUNIONSTORE destination numkeys key [key ...]
Redis Zunionstore 命令计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并 将该并集(结果集)储存到 destination 。
默认情况下,结果集中某个成员的分数值是所有给定集下该成员分数值之和 。
https://learnku.com/articles/30279

数组操作

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
* 数组层级缩进转换
* @param array $array 源数组
* @param int $pid
* @param int $level
* @return array
*/
function Array2level($array, $pid = 0, $level = 1)
{
static $list = [];
foreach ($array as $v) {
if ($v['pid'] == $pid) {
$v['level'] = $level;
$list[] = $v;
Array2level($array, $v['id'], $level + 1);
}
}
return $list;
}
/**
* 把返回的数据集转换成Tree
* @access public
* @param array $list 要转换的数据集
* @param string $pid parent标记字段
* @param string $level level标记字段
* @return array
*/
function list_to_tree($list, $pk='id', $pid = 'pid', $child = 'son', $root = 0, $is_count = false) {
// 创建Tree
$tree = [];
if(is_array($list)) {
// 创建基于主键的数组引用
$refer = [];
foreach ($list as $key => $data) {
$refer[$data[$pk]] =& $list[$key];
}

foreach ($list as $key => $data) {
// 判断是否存在parent
$parentId = $data[$pid];
if ($root == $parentId) {
$tree[] =& $list[$key];
}else{
if (isset($refer[$parentId])) {
$parent =& $refer[$parentId];
$parent[$child][] =& $list[$key];
if($is_count==true) $parent['_count'] = count($parent[$child]);
}
}
}
}
return $tree;
}
/**
* 将数据格式化成树形结构
* @param array $items
* @return array
*/
function genTree($items,$pk='id',$pid = 'pid', $child = '_child') {
$tree = []; //格式化好的树
foreach ($items as $item)
if (isset($items[$item[$pid]]))
$items[$item[$pid]][$child][] = &$items[$item[$pk]];
else
$tree[] = &$items[$item[$pk]];
return $tree;
}
/**
* 多个数组的笛卡尔积
*
* @param unknown_type $data
*/
function combineDika() {
$data = func_get_args();
$data = current($data);
$cnt = count($data);
$result = array();
$arr1 = array_shift($data);
foreach($arr1 as $key=>$item)
{
$result[] = array($item);
}

foreach($data as $key=>$item)
{
$result = combineArray($result,$item);
}
return $result;
}
/**
* 两个数组的笛卡尔积
* @param array $arr1 [description]
* @param array $arr2 [description]
* @return [type] [description]
* @date 2017-08-07
* @author 赵俊峰 <981248356@qq.com>
*/
function combineArray($arr1 =[],$arr2=[]) {
$result = [];
foreach ($arr1 as $item1)
{
foreach ($arr2 as $item2)
{
$temp = $item1;
$temp[] = $item2;
$result[] = $temp;
}
}
return $result;
}
/**
* 将二维数组以元素的某个值作为键 并归类数组
* array( array('name'=>'aa','type'=>'pay'), array('name'=>'cc','type'=>'pay') )
* array('pay'=>array( array('name'=>'aa','type'=>'pay') , array('name'=>'cc','type'=>'pay') ))
* @param $arr 数组
* @param $key 分组值的key
* @return array
*/
function group_same_key($arr,$key){
$new_arr = array();
foreach($arr as $k=>$v ){
$new_arr[$v[$key]][] = $v;
}
return $new_arr;
}
/**
* @param $arr
* @param $key_name
* @return array
* 将数据库中查出的列表以指定的 id 作为数组的键名
*/
function convert_arr_key($arr, $key_name='id')
{
$arr2 = [];
foreach($arr as $key => $val){
$arr2[$val[$key_name]] = $val;
}
return $arr2;
}
/**
* 数组 转 对象
*
* @param array $arr 数组
* @return object
*/
function array_to_object($arr) {
if (gettype($arr) != 'array') {
return;
}
foreach ($arr as $k => $v) {
if (gettype($v) == 'array' || getType($v) == 'object') {
$arr[$k] = (object)array_to_object($v);
}
}

return (object)$arr;
}
/**
* 对象 转 数组
* @param object $obj 对象
* @return array
*/
function object_to_array($obj) {
$obj = (array)$obj;
foreach ($obj as $k => $v) {
if (gettype($v) == 'resource') {
return;
}
if (gettype($v) == 'object' || gettype($v) == 'array') {
$obj[$k] = (array)object_to_array($v);
}
}

return $obj;
}
//将 xml数据转换为数组格式。
function xml_to_array($xml){
$reg = "/<(\w+)[^>]*>([\\x00-\\xFF]*)<\\/\\1>/";
if(preg_match_all($reg, $xml, $matches)){
$count = count($matches[0]);
for($i = 0; $i < $count; $i++){
$subxml= $matches[2][$i];
$key = $matches[1][$i];
if(preg_match( $reg, $subxml )){
$arr[$key] = xml_to_array( $subxml );
}else{
$arr[$key] = $subxml;
}
}
}
return $arr;
}

/**
* array_delete 删除数组中的某个值
* @param $array
* @param $value
* @return mixed
*/
function array_delete($array, $value)
{
$key = array_search($value, $array);
if ($key !== false)
array_splice($array, $key, 1);
return $array;
}
// 分析枚举类型配置值 格式 a:名称1,b:名称2
function parse_config_attr($value, $type = null) {
switch ($type) {
default: //解析"1:1\r\n2:3"格式字符串为数组
$array = preg_split('/[,;\r\n]+/', trim($value, ",;\r\n"));
if (strpos($value,':')) {
$value = array();
foreach ($array as $val) {
list($k, $v) = explode(':', $val);
$value[$k] = $v;
}
} else {
$value = $array;
}
break;
}
return $value;
}
//array_column()函数兼容低版本PHP
if (!function_exists('array_column')) {
function array_column($input, $columnKey, $indexKey = null) {
$columnKeyIsNumber = (is_numeric($columnKey)) ? true : false;
$indexKeyIsNull = (is_null($indexKey)) ? true : false;
$indexKeyIsNumber = (is_numeric($indexKey)) ? true : false;
$result = array();
foreach ((array) $input as $key => $row) {
if ($columnKeyIsNumber) {
$tmp = array_slice($row, $columnKey, 1);
$tmp = (is_array($tmp) && !empty($tmp)) ? current($tmp) : null;
} else {
$tmp = isset($row[$columnKey]) ? $row[$columnKey] : null;
}
if (!$indexKeyIsNull) {
if ($indexKeyIsNumber) {
$key = array_slice($row, $indexKey, 1);
$key = (is_array($key) && !empty($key)) ? current($key) : null;
$key = is_null($key) ? 0 : $key;
} else {
$key = isset($row[$indexKey]) ? $row[$indexKey] : 0;
}
}
$result[$key] = $tmp;
}
return $result;
}
}
http://www.jackhhy.cn/article/29/5.html

PHP-fpm MongoDB 连接数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
php7 的 MongoDB 扩展中,php 客户端是长连接(及时请求结束,只要 php-fpm 进程没有杀掉,连接就一直保持,目的是下次请求时减少连接带来的性能消耗),并且没有关闭连接的函数。 https://learnku.com/laravel/t/34780 

解决办法

而我们 php-fpm 配置中,pm.min_spare_servers = 100,pm.max_spare_servers = 200,空闲进程是至少开了 100, 所以大量空闲连接占用了 MongoDB 连接。

然后我们根据实际情况,配置改为 pm.min_spare_servers = 5,pm.max_spare_servers = 10。重新部署后解决问题。连接数降了下了,没有超过 100.

总结

在 PHP7 中,MongoDB 是长连接,一个请求完,对应得 php-fpm 进程没有被 kill 掉化,这个连接不会断开,会一直保持。所以要减少连接个数,需要合理设置 php-fpm 空闲进程数
php7 之后 采用 mongodb 扩展 这个扩展内置的 API 没有关闭连接的方法 所以只能通过 fpm 进程来控制了 实际连接数是由 pm.max_children 控制的 有多少 fpm 进程就会有多少连接 pm.min_spare_servers & pm.min_spare_servers 控制空闲进程数 减少连接占用

如果是高负载服务器 pm.max_children & pm.min_spare_servers & pm.min_spare_servers 这几项值又不会设置太低 而持久连接对于高并发来说可以减少 IO 开销 但是 mongodb 连接池是由 fpm 进程维护 这样持久连接数会很高 不知道以后会不会专门为 mongodb 开辟进程连接池 优化这块的功能

批量更新

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
$materials = $app->material->list($type, 0); // 获取素材列表
$page = $materials['total_count'] / 20;
for ($x = 0; $x <= $page; $x++) {
$offset = $x * 20;
$materials = $app->material->list($type, $offset);
collect($materials['item'])->map(function ($v) use ($platformId, $type, $user) {
WeChatMaterial::query()
->updateOrCreate([
'platform_id' => $platformId,
'media_id' => $v['media_id'],
], [
'type' => $type,
'user_id' => $user->id,
'name' => $v['name'],
'update_time' => $v['update_time'],
'url' => $v['url'],
]);
});
}

$startId = 0;
$limit = 20;
while (true) {
$materials = $app->material->list($type, $startId); // 获取素材列表
if (empty($materials) || empty($materials['item'])) {
break;
}
foreach ($materials['item'] as $k => $v) {
WeChatMaterial::query()
->updateOrCreate([
'platform_id' => $platformId,
'media_id' => $v['media_id'],
], [
'type' => $type,
'user_id' => $user->id,
'name' => $v['name'],
'update_time' => $v['update_time'],
'url' => $v['url'],
]);
}
$startId = $startId + $limit;
usleep(200);
}
https://learnku.com/laravel/t/34751

php7 mongodb 分批删除

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
123
124
$limit = 1000;
$skip = 0;
$i = 1;
$log = new \App\Models\Log;
while (true) {
$list = $log->query([], ['skip' => $skip, 'limit' => $limit, 'projection' => ['_id' => 1],'sort' => ['_id' => 1]]);
if (!$list) {
break;
}
dump('第'.$i.'次');
if ($i > 2000) {
break;
}
$ids = array_column($list, '_id');
$messageIds = array_map(function ($value){
return new \MongoDB\BSON\ObjectID($value);
}, $ids);
try {
$c=$log->delete(['_id' => ['$in' => $messageIds]]);dump($c);
} catch (\Exception $e) {
\Log::error('delete message fail', ['ids' => $ids, 'msg' => $e->getMessage()]);
}
$skip += $limit;
$i++;
}

public function __construct(array $attributes=[]) {
parent::__construct($attributes);
if(version_compare(PHP_VERSION,'7.0','>=')){
$dsn=$this->getDNS();
$this->manager=new \MongoDB\Driver\Manager($dsn);
$this->writeConcern = new \MongoDB\Driver\WriteConcern(1, 100);
} else {
\DB::connection('mongodb')->enableQueryLog();
}
}
private function getDNS(){
$name=$this->connection;
$connections=app()->config['database.connections'];
if (is_null($config = array_get($connections, $name)))
{
throw new \Exception("Database [$name] not configured.");
}
$this->namespace=$config['database'].'.'.$this->collection;
if ($config['username'] && $config['password']) {
return 'mongodb://'.$config['username'].":".rawurlencode($config['password']).'@'.$config['host'].':'.$config['port']."/".$config['database'].(isset($config['options']['replicaSet'])?'?replicaSet='.$config['options']['replicaSet']:'');
} else {
return 'mongodb://'.$config['host'].':'.$config['port']."/".$config['database'].(isset($config['options']['replicaSet'])?'?replicaSet='.$config['options']['replicaSet']:'');
}
}

public function create(array $attributes){
if(count($attributes)<1){
return '';
}
$_id=new \MongoDB\BSON\ObjectID();
$attributes=array_merge($attributes,['_id'=>$_id]);

$bulk=new \MongoDB\Driver\BulkWrite(['ordered' => true]);
$bulk->insert($attributes);
try{
$result = $this->manager->executeBulkWrite($this->namespace, $bulk, $this->writeConcern);
}catch(\Exception $e){
return false;
}
return (string)$_id;
}

public function insert(array $attributes){
if(count($attributes)<1){
return '';
}
$bulk=new \MongoDB\Driver\BulkWrite(['ordered' => true]);
foreach($attributes as $at){
$bulk->insert($at);
}

try{
$result = $this->manager->executeBulkWrite($this->namespace, $bulk, $this->writeConcern);
}catch(\Exception $e){
return false;
}
return $result->getInsertedCount();
}

public function update($where = [], $update = [], $upsert = false) {
$bulk = new \MongoDB\Driver\BulkWrite(['ordered' => true]);
$bulk->update($where, ['$set' => $update], ['multi' => true, 'upsert' => $upsert]);
$result = $this->manager->executeBulkWrite($this->namespace, $bulk, $this->writeConcern);

return $result->getModifiedCount();
}

public function delete($where = [], $limit = 0) {
$bulk = new \MongoDB\Driver\BulkWrite(['ordered' => true]);
$bulk->delete($where, ['limit' => $limit]);//limit 为 0 时,删除所有匹配数据
$result = $this->manager->executeBulkWrite($this->namespace, $bulk, $this->writeConcern);

return $result->getDeletedCount();
}

public function count($where = []) {
$command = new \MongoDB\Driver\Command(['count' => $this->collection, 'query' => $where]);
$result = $this->manager->executeCommand(explode('.', $this->namespace)[0], $command);
$res = $result->toArray();
$count = 0;
if ($res) {
$count = $res[0]->n;
}

return $count;
}

public function query($where = [], $option = []) {
$query = new \MongoDB\Driver\Query($where, $option);//http://php.net/manual/zh/mongodb-driver-manager.executequery.php
$result = $this->manager->executeQuery($this->namespace, $query);
$res = [];
foreach ($result as $value){
$v = (array)$value;
$v['_id'] = (string)$v['_id'];
$res[] = $v;
}
return $res;
}

头部插入内容

1
2
3
4
5
如果是图片开头,在图片后面插入文字,如果不是图片开头,直接插入文字到头部
preg_match('#^(<img class="img" src=.*"/>)*(.*)#',$data, $m);
if ($m && isset($m[1]) && isset($m[2])) {
$data = $m[1].$ad.$m[2];
}

生成按时间增长的全局唯一主键

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

function getGenerateId ($len = 12) {

$temp = random_bytes($len);

return bin2hex($temp);
}
使用 mongo 扩展 new \MongoDB\BSON\ObjectID ()
function generate_id_hex()
{
static $i = 0;
$i OR $i = mt_rand(1, 0x7FFFFF);

return sprintf("%08x%06x%04x%06x",
/* 4-byte value representing the seconds since the Unix epoch. */
time() & 0xFFFFFFFF,

/* 3-byte machine identifier.
*
* On windows, the max length is 256. Linux doesn't have a limit, but it
* will fill in the first 256 chars of hostname even if the actual
* hostname is longer.
*
* From the GNU manual:
* gethostname stores the beginning of the host name in name even if the
* host name won't entirely fit. For some purposes, a truncated host name
* is good enough. If it is, you can ignore the error code.
*
* crc32 will be better than Times33. */
crc32(substr((string)gethostname(), 0, 256)) >> 8 & 0xFFFFFF,

/* 2-byte process id. */
getmypid() & 0xFFFF,

/* 3-byte counter, starting with a random value. */
$i = $i > 0xFFFFFE ? 1 : $i + 1
);
}

for ($j=0; $j <100 ; $j++) {
echo generate_id_hex();
echo "<br>";
# code...
}
https://learnku.com/articles/34849

array_search 和 in_array 函数效率问题

1
2
3
4
5
6
7
8
9
采用 array_flip 翻转后,用 isset 代替 in_array 函数,用 $array[key] 替代 array_search, 这样能解决大数组超时耗时问题
$needle = 'test for this';

$flipped_haystack = array_flip($haystack);

if ( isset($flipped_haystack[$needle]) )
{
print "Yes it's there!";
}

把array或json以array字符串的形式输出

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
class Utils
{
/**
* 把array或json以array字符串的形式输出 等同于var_export
* @param $str
* @return string
*/
public static function outPutArray($str): string
{
if (is_string($str)) {
$arr = json_decode($str, true);
if (is_null($arr)) {
$ret_str = '';
if (is_numeric($str)) {
$ret_str .= '[' .PHP_EOL. $str .PHP_EOL. ']';
} else {
$ret_str .= '[' .PHP_EOL. '\''.$str .'\''.PHP_EOL. ']';
}
return $ret_str;
}
} else {
$arr = $str;
}
$str = '[' . PHP_EOL;
foreach ($arr as $key => $value) {
if (!is_numeric($key)) {
$str .= "'{$key}' => ";
}
if (is_array($value)) {
$str .= self::outPutArray($value);
} else {
if (is_string($value)) {
$str .= "'{$value}'," . PHP_EOL;
} else {
$str .= "{$value}," . PHP_EOL;
}
}
}
$str .= '],' . PHP_EOL;
return $str;
}
}
echo Utils::outPutArray('shiwenyuan');
/*
* [
* 'shiwenyuan'
* ]
* */
echo Utils::outPutArray(['name'=>'zhangsan', 'age'=>18, 'friend'=>[['name'=>'lisi', 'age'=>19], ['name'=>'wangwu', 'age'=>20]]]);
/**
* [
*'name' => 'zhangsan',
* 'age' => 18,
* 'friend' => [
* [
* 'name' => 'lisi',
* 'age' => 19,
* ],
* [
* 'name' => 'wangwu',
* 'age' => 20,
* ],
* ],
* ]
*/
echo Utils::outPutArray('{"name":"zhangsan","age":18,"friend":[{"name":"lisi","age":19},{"name":"wangwu","age":21}]}');
/**
* [
*'name' => 'zhangsan',
* 'age' => 18,
* 'friend' => [
* [
* 'name' => 'lisi',
* 'age' => 19,
* ],
* [
* 'name' => 'wangwu',
* 'age' => 20,
* ],
* ],
* ]
*/

https://shiwenyuan.github.io/post/ck1irc5wh000fq2fyv61xflc6.html

unicode 转中文

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
function UnicodeEncode($str){
//split word
preg_match_all('/./u',$str,$matches);

$unicodeStr = "";
foreach($matches[0] as $m){
//拼接
$unicodeStr .= "&#".base_convert(bin2hex(iconv('UTF-8',"UCS-4",$m)),16,10);
}
return $unicodeStr;
}

$str = "新浪微博";
echo UnicodeEncode($str)

function unicodeDecode($unicode_str){
$json = '{"str":"'.$unicode_str.'"}';
$arr = json_decode($json,true);
if(empty($arr)) return '';
return $arr['str'];
}

$unicode_str = "\u65b0\u6d6a\u5fae\u535a";
echo unicodeDecode($unicode_str);

js:
function convert2Unicode(str) {
return str.replace(/[\u0080-\uffff]/g,
function($0) {
var tmp = $0.charCodeAt(0).toString(16);
return "\u" + new Array(5 - tmp.length).join('0') + tmp;
});
function unicode2utf8($str){
if(!$str) return $str;
$decode = json_decode($str);
if($decode) return $decode;
$str = '["' . $str . '"]';
$decode = json_decode($str);
if(count($decode) == 1){
return $decode[0];
}
return $str;

function decodeUnicode($str){
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', create_function('$matches', 'return iconv("UCS-2BE","UTF-8",pack("H*", $matches[1]));'), $str);
$unicodeChar = "\u56de\u590d\uff1a";
echo json_decode('"'.$unicodeChar.'"');
https://segmentfault.com/a/1190000003020776
/**
* utf8字符转换成Unicode字符
* @param [type] $utf8_str Utf-8字符
* @return [type] Unicode字符
*/
function utf8_str_to_unicode($utf8_str) {
$unicode = 0;
$unicode = (ord($utf8_str[0]) & 0x1F) << 12;
$unicode |= (ord($utf8_str[1]) & 0x3F) << 6;
$unicode |= (ord($utf8_str[2]) & 0x3F);
return dechex($unicode);
}

/**
* Unicode字符转换成utf8字符
* @param [type] $unicode_str Unicode字符
* @return [type] Utf-8字符
*/
function unicode_to_utf8($unicode_str) {
$utf8_str = '';
$code = intval(hexdec($unicode_str));
//这里注意转换出来的code一定得是整形,这样才会正确的按位操作
$ord_1 = decbin(0xe0 | ($code >> 12));
$ord_2 = decbin(0x80 | (($code >> 6) & 0x3f));
$ord_3 = decbin(0x80 | ($code & 0x3f));
$utf8_str = chr(bindec($ord_1)) . chr(bindec($ord_2)) . chr(bindec($ord_3));
return $utf8_str;
}

开放API接口签名验证

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
AccessKey&SecretKey (开放平台)

请求身份

为开发者分配AccessKey(开发者标识,确保唯一)和SecretKey(用于接口加密,确保不易被穷举,生成算法不易被猜测)。

防止篡改

参数签名

按照请求参数名的字母升序排列非空请求参数(包含AccessKey),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA;
在stringA最后拼接上Secretkey得到字符串stringSignTemp;
对stringSignTemp进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值。
请求携带参数AccessKey和Sign,只有拥有合法的身份AccessKey和正确的签名Sign才能放行。这样就解决了身份验证和参数篡改问题,即使请求参数被劫持,由于获取不到SecretKey(仅作本地加密使用,不参与网络传输),无法伪造合法的请求。

重放攻击

虽然解决了请求参数被篡改的隐患,但是还存在着重复使用请求参数伪造二次请求的隐患。

timestamp+nonce方案

nonce指唯一的随机字符串,用来标识每个被签名的请求。通过为每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用(记录所有用过的nonce以阻止它们被二次使用)。

然而,对服务器来说永久存储所有接收到的nonce的代价是非常大的。可以使用timestamp来优化nonce的存储。

假设允许客户端和服务端最多能存在15分钟的时间差,同时追踪记录在服务端的nonce集合。当有新的请求进入时,首先检查携带的timestamp是否在15分钟内,如超出时间范围,则拒绝,然后查询携带的nonce,如存在已有集合,则拒绝。否则,记录该nonce,并删除集合内时间戳大于15分钟的nonce(可以使用redis的expire,新增nonce的同时设置它的超时失效时间为15分钟)。

实现

请求接口:http://api.test.com/test?name=hello&home=world&work=java

客户端

生成当前时间戳timestamp=now和唯一随机字符串nonce=random
按照请求参数名的字母升序排列非空请求参数(包含AccessKey)
stringA="AccessKey=access&home=world&name=hello&work=java&timestamp=now&nonce=random";
拼接密钥SecretKey
stringSignTemp="AccessKey=access&home=world&name=hello&work=java&timestamp=now&nonce=random&SecretKey=secret";
MD5并转换为大写
sign=MD5(stringSignTemp).toUpperCase();
最终请求
http://api.test.com/test?name=hello&home=world&work=java&timestamp=now&nonce=nonce&sign=sign;
https://blog.csdn.net/qq_18495465/article/details/79248608

PhpSpreadsheet 小教程

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
> composer require phpoffice/phpspreadsheet
<?php
require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Hello World !');

$writer = new Xlsx($spreadsheet);
$writer->save('hello world.xlsx');
实例

包中带了实例代码,位置 vendor/phpoffice/phpspreadsheet/samples 下

> php -S localhost:8000 -t vendor/phpoffice/phpspreadsheet/samples

$inputFileType = 'Xls';
$inputFileName = './sampleData/example1.xls';

$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
/** 只要数据 **/
$reader->setReadDataOnly(true);

$spreadsheet = $reader->load($inputFileName);

$spreadsheet = PhpOffice\PhpSpreadsheet\IOFactory::load("new.xls");

$data = $spreadsheet
->getSheet(0) // 指定第一个工作表为当前
->toArray(); // 转为数组

// 或者得到全部工作表的数据数组
$cells=array();
// 工作表对象有迭代器实现
foreach ( $spreadsheet->getWorksheetIterator() as $data ) {
$cells = $data->toArray();
}
https://learnku.com/articles/30048

进制转换

1
2
3
4
5
$res=bin2hex(openssl_encrypt('test','AES-128-ECB',$key,OPENSSL_RAW_DATA));
通过以上函数把 test 转换成 16 进制

openssl_decrypt(pack('H*',$res),'AES-128-ECB',$key,OPENSSL_RAW_DATA);
再用以上函数把 16 进制转换回来成 test

https证书

1
2
3
4
5
wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto
假设你的域名 mengdodo.com,想获取根域名 https 证书
./certbot-auto certonly -d mengdodo.com -d *.mengdodo.com --manual --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory
然后按照提示完成两个 txt 解析即可 https,使用 acme.sh 可以申请免费了,我好几项目用了。

composer

1
2
3
4
Composer 国内镜像及多线程下载利器 hirak/prestissimo composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

composer global require hirak/prestissimo
composer clearcache

Composer 提示 zlib_decode (): data error

1
2
3
4
5
6
7
$ composer config repo.packagist composer http://packagist.phpcomposer.com
composer config -g --unset repos.packagist
composer config --unset repos.packagist
composer clearcache
composer diagnose
composer install -vvv
https://learnku.com/articles/35106

swoole 进程退出

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
swoole_process::signal(SIGALRM,function (){
echo "1\n";
});

swoole_process::alarm(100*1000);

swoole_timer_tick(2000, function ($timer_id) {
echo "执行 $timer_id" . PHP_EOL;
});
root@687d4c789947:/var/www/swoole2# php timer.php
执行 1
执行 1
执行 1

use Swoole\Process;//引入类的命名空间【对应根目录】
//安装信号处理器 SIGALRM定时信号 【可以由定时器产生,或是进程发出如pcntl_kill【c是kill】】或是在终端输入kill -s 信号编号或-n 信号值 进程标识符号【进程标识符号可以在代码中通过posix_getpid或是在/proc/PID/目录下查看进程详细数据】
Process::signal(SIGALRM, function () {
static $i = 0;
echo "#{$i}\talarm\n";
$i++;
if ($i > 20) {
Process::alarm(-1);//定时器清0操作
}
});

//100ms
Process::alarm(100 * 1000); //定时器,swoole已经做了封装,默认会阻塞进程【当时间到的时候,系统会唤醒该进程,并发出中断请求,中断处理函数将会收到定时中断,并运行中断处理函数,运行到20后,自动清0,同时进程阻塞。

阻塞:是指操作系统把当前进程释放cpu使用权,此时cpu干其它事情了,产生阻塞的场景如等待网络IO,读写请求等操作
非阻塞:执行请求后,立马返回

swoole 版本多少,4.4+ signal 监听不会加入事件循环了,也就是程序会自动退出。。。

https://learnku.com/laravel/t/35076

常用正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$
http://yyeer.com/%E7%BC%96%E7%A8%8B/2019/06/30/%E6%94%B6%E9%9B%86-%E5%B8%B8%E7%94%A8%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/

图片加水印

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
/* 
* 图片加水印函数
* $dst_file : 处理完成后保存到此文件路径
* $src_file :待处理的图片文件路径
* $logo_file:水印的图片文件路径
* $top :上间距
* $left :左间距
* $alpha :透明度(0-100)【特别注意:若logo_file本身已有透明效果,请务必传false】
*/
public static function fullFillLogos($dst_file, $src_file, $logo_file, $top=0, $left=0, $alpha=false) {
try {
//创建图片的实例
$dst = imagecreatefromstring(file_get_contents($src_file));
$logo = imagecreatefromstring(file_get_contents($logo_file));
//获取宽高
list($logo_w, $logo_h) = getimagesize($logo_file);
list($dst_w, $dst_h) = getimagesize($src_file);
//循环铺上水印
for($off_y=$top; $off_y+$top<$dst_h; $off_y+=$logo_h+$top) {
for($off_x=$left; $off_x+$left<$dst_w; $off_x+=$logo_w+$left) {
$width = ($off_x+$logo_w+$left>$dst_w) ? ($dst_w-$off_x-$left) : $logo_w;
$height = ($off_y+$logo_h+$top>$dst_h) ? ($dst_h-$off_y-$top) : $logo_h;
if($alpha === false) {
imagecopy($dst, $logo, $off_x, $off_y, 0, 0, $width, $height);
} else {
imagecopymerge($dst, $logo, $off_x, $off_y, 0, 0, $width, $height, $alpha);
}
}
}
//水印完成, 存档...
header('Content-Type: image/jpeg');
imagejpeg($dst, $dst_file);
imagedestroy($dst);
imagedestroy($logo);
} catch (Exception $e) {
return false;
}
return true;
}http://yyeer.com/%E7%BC%96%E7%A8%8B/2017/12/08/%E4%BD%BF%E7%94%A8GD%E5%BA%93%E7%BB%99%E5%9B%BE%E7%89%87%E9%93%BA%E6%BB%A1%E6%B0%B4%E5%8D%B0/

素数while循环的分析

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
function isprime($num) {
$count = (int) sqrt($num);
while ($count > 1) {
if ($num % $count == 0) {
return false;
}
$count-- ;
}
return true;
}

function isprime($num) {
$count = (int) sqrt($num);
$i = 2;
while ($i <= $count) {
if ($num % $i == 0) {
return false;
}
$i++ ;
}
}

$start = microtime(true);
for ($i = 2; $i < 100000; $i++) {
isprime($i);
}
echo (microtime(true) - $start) * 1000 ."\n";

http://blog.qicunshang.com/2018/02/09/%E4%B8%80%E6%AC%A1%E5%85%B3%E4%BA%8E%E7%B4%A0%E6%95%B0while%E5%BE%AA%E7%8E%AF%E7%9A%84%E5%88%86%E6%9E%90/

繁体简体互转 OpenCC-PHP 扩展

超大文件上传

AetherUpload 视频上传过程

高德地图 SDK https://github.com/Hanson/gaode-sdk

云爬虫

Laravel 插件 PhpSpreadSheet 使用总结 https://phpspreadsheet.readthedocs.io/en/latest/topics/autofilters/ https://learnku.com/articles/29608
https://learnku.com/articles/30048

PHP 速查表 https://free-andy.github.io/php-dict https://github.com/free-andy/php-dict

plaidCTF两道web题目writeup http://blog.wonderkun.cc/2019/04/15/plaidCTF%E4%B8%A4%E9%81%93web%E9%A2%98%E7%9B%AEwriteup/

ctf https://www.yof3ng.xyz/

PHP中的危险函数和伪协议https://anemone.top/php-PHP%E4%B8%AD%E7%9A%84%E5%8D%B1%E9%99%A9%E5%87%BD%E6%95%B0%E5%92%8C%E4%BC%AA%E5%8D%8F%E8%AE%AE/

几个比较有意思的逻辑问题 https://www.xuejiayuan.net/blog/1d15a4dfae8747a3b263052ec024aae2

Laravel-sms 阿里云短信扩展包

91porn爬虫php版本

快速、简洁且强大的PHP爬虫框架

CentOS7 轻松部署 Laravel 应用

高性能优化 PHP-FPM

开发自己的composer包

高性能优化 PHP-FPM

大文件传输解决方案:分片上传 / 下载限速

开源访问控制框架

权限控制库 Casbin

数据结构之链表

深入理解计算机系统学习

laragon 简单好用的 PHP 环境

搭建高性能的私有 Composer 镜像服务

PHP socket 的简单理解

phpstudy apache 配置 https 证书

PHP 三年模拟五年面试之一网打尽系列(4)—– MySQL 高级

PHP 中四大经典排序算法

Laravel-snappy 生成 PDF 踩坑记录

Laravel Ignition 错误页面功能全解析

PHP 7.4在线运行代码

PC 端微信扫码支付全过程

PHP 高性能 Excel 扩展

crontab 定时任务直观地编辑

接入 paypal PHP-sdk 支付宝支付 / 回调 / 退款全流程