​​​​ php 开发记录 | 苏生不惑的博客

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
try {
$a = 5 / 0;
} catch (Exception $e) {
$e->getMessage();
$a = -1; // 通过异常来处理 $a 为 0 的情况,但是实际上,捕获不到该异常
}

echo $a;
// PHP Warning: Division by zero
PHP7 开始统一这两者,使错误也可以像异常那样抛出
PHP 中的错误可理解为 使脚本不运行不正常的情况,根据错误级别从高到低可划分为五类

Parse error 或 Syntax Error - 语法解析错误,触发该错误后,脚本完全无法运行;
Fatal Error - 致命错误,触发该错误后,后面的脚本无法继续执行;
Warning Error - 出现比较不恰当的地方,脚本可继续执行;
Notice Error - 出现不恰当的地方,但是程度比 Warning Error 低,脚本可继续执行;
Deprecated Error - 不推荐这么使用,未来可能会废弃,脚本可继续执行;
// E_ALL - 处理全部错误类型
set_error_handler('customError', E_ALL);

/**
* @param int $errno 错误的级别
* @param string $errstr 错误的信息
* @param string $errfile 错误的文件名(可选)
* @param string $errline 错误发生的行号(可选)
*/
function customError(int $errno, string $errstr, string $errfile, string $errline)
{
echo sprintf('错误消息为 %s', $errstr);
}
当错误类型为 E_USER_DEPRECATED 时,需要添加 @

@trigger_error("foo", E_USER_DEPRECATED);
$a = 5 / 0; // 错误消息为 Division by zero
function division($a, $b) {
if($b == 0){
@trigger_error("0 不能作为除数", E_USER_NOTICE);
return -1;
}
return $a / $b;
}

echo division(10, 0);
开发环境推荐配置

display_errors = On
display_startup_error = On
error_reporting = -1
log_errors = On
生产环境推荐配置

display_errors = Off
display_startup_error = Off
; 报告 Notice 以外的所有错误
error_reporting = E_ALL & ~E_NOTICE
log_errors = On
https://learnku.com/articles/36521

对象的浅拷贝与深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Bar {
public $name;
}

class Foo
{
public $bar;
function __construct()
{
$this->bar = new Bar();
}
}

$foo = new Foo();
$copyFoo = clone $foo;

var_dump($foo->bar === $copyFoo->bar); // bool(true)
克隆的实例与原有的实例的 bar 成员都指向同一个实例 https://github.com/myclabs/DeepCopy
https://learnku.com/articles/36876

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
其他编程语言数组是由相同类型的元素(element)的集合所组成的数据结构,而 PHP 数组元素可以为不同类型的元素。因此说 PHP 数组不是纯粹的数组,而是哈希 (字典) 更为恰当.
注意的是字符串类型假如是有效十进制数字的话,则会转换为整型. "8"->8. 若不想转换则在数字前添加 "+" 符号.

// 02不是有效十进制数字
$arr = ['1' => 'a', '+1' => 'b', '02' => 'c'];
(array)$scalarValue 与 array ($scalarValue) 一样。而对象 (object) 类型转换为 array. 其单元为该对象的属性。键名将为成员变量名,不过有几点例外:整数属性不可访问;私有变量前会加上类名作前缀;保护变量前会加上一个 '*' 做前缀。这些前缀的前后都各有一个 NULL 字符。
var_dump(array_fill(0, 0, 1)); // []
var_dump(array_fill(1, 3, 'red')); // [1=>'red', 2=>'red', 3=>'red']
$a = ['a' => 1, 'B' => 2, 3 => 'c', 'A' => 4, 'b' => 6];
print_r(array_chunk($a, 2)); // [[1, 2], ['c', 4], [6]]
print_r(array_chunk($a, 2, true)); // [['a' => 1, 'B' => 2], [3 => 'c', 'A' => 4], ['b' => 6]]

var_dump(array_slice($a, -1, 1, true)); [4 => 'f']

print_r(array_count_value([1, 'hello', 1, 'hello', 'world'])) // [1=>2, 'hello' => 2, 'world' => 1]
var_dump(array_product([])); // 1
var_dump(array_sum([])); // 0
$a = ['a' => 1, 'b' => 2, 'c' => 3];
var_dump(array_keys($a)); // ['a', 'b', 'c']
var_dump(array_keys($a, '1')); // 'a'
var_dump(array_keys($a, '1', true)); // []
var_dump(array_rand([1, 2, 3], 2)); // [1, 3]
var_dump(array_search(false, $a, true)); // false
var_dump(in_array('1', $os, true)); // false
var_dump(array_diff($array1, $array2, $array3)); // ['blue']

$arr1 = ['color' => ['favorite' => 'red', 5]];
$arr2 = ['color' => ['favorite' => 'green', 6, 7]];
var_dump(array_merge_recursive($arr1, $arr2)); // ['color' => ['favorite' => ['red', 'green'], 5, 6, 7]]
$arr1 = ['color' => ['favorite' => 'red', 5]];
$arr2 = ['color' => ['favorite' => 'green', 6, 7]];
var_dump(array_merge($arr1, $arr2)); // ['color' => ['favorite' => 'green', 6, 7]];
var_dump($arr1 + $arr2); // ['color' => ['favorite' => 'red', 5]]
$ar1 = [10, 100, 100, 0];
$ar2 = [1, 3, 2, 4];
array_multisort($ar1, $ar2); // [0, 10, 100, 100] [4, 1, 2, 3]
array_multisort($ar1, SORT_DESC, SORT_NUMERIC, $ar2, SORT_ASC, SORT_NUMERIC); // [100, 100, 10, 0] [2, 3, 1, 4]
// 排序针对的是值, 而且会保存原先索引
$fruits = array("d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple");
arsort($fruits);
print_r($fruits); // ['a' => 'orange', 'd' => 'lemon', 'b' => 'banana', 'c' => 'apple']

// 语言结构, 并非函数
$info = ['coffee', 'brown', 'caffeine'];
list($drink, $color, $power) = $info;
echo "$drink, $color, $power"; // coffee, brown, caffeine
// 可以省略不想取的
list($drink, , $power) = $info;
echo "$drink, $power"; // coffee, caffeine
// 7.0 以上是从左到右赋值, 7.0以下是从右到左赋值
list($a[0], $a[1], $a[2]) = $info;
// 7.0 以上
var_dump($a); // ['coffee', 'brown', 'caffeine']
// 7.0 以下
var_dump($a); // ['caffeine', 'brown', 'coffee']
// 7.1 支持索引数组
$info = ['a' => 'coffee', 'b' => 'brown', 'c' => 'caffeine'];
list('a' => $drink, 'c' => $power) = $info;
echo "$drink, $power"; // coffee, caffeine
$a = [1, 2, 3];
echo count($a); // 3
$b = [$a, 4, 5, 6];
echo count($b, COUNT_RECURSIVE) // 7

$a = ['php', 4.0, ['green', 'red']];
var_dump(array_reverse($a)); // [['green', 'red'], 7.1, 'php']
var_dump(array_reverse($a, true)); // [2 => ['green', 'red'], 1 => 7.1, 0 => 'php']

$sweet = ['a' => 'apple', 'b' => 'banana'];

function testPrint($item, $key, $userData) {
echo "$key holds $item and $userData\n";
}
/**
* a holds apple and php
* b holds banana and php
* sour holds lemon and php
*/
array_walk_recursive($fruits, 'testPrint', 'php');
function odd($var)
{
return $var & 1;
}
// 自然排序, 以人们认知方式排序,区分大小写
$arr = ['a1', 'A1', 'c2', 'c3', 'b4', 'b5', 'B1'];
natsort($arr); // true
var_dump($arr); // ['A1', 'B1', 'a1', 'b4', 'b5', 'c2', 'c3']
// 数字比较项目, 'a1' 转换数字为 0, 所以排序顺序如下
$arr1 = ['a1', 0, 2, '1a'];
sort($arr1, SORT_NUMERIC);
var_dump($arr1); // ['a1', 0, '1a', 2]
// 被作为字符串来比较
$fruits = array("lemon", "orange", "banana", "apple", 1);
sort($fruits, SORT_STRING);
var_dump($fruits); // [1, 'apple', 'banana', 'lemon', 'orange']
// 和 natsort() 类似对每个单元以“自然的顺序”对字符串进行排序
$arr = ['a1', 'A1', 'c2', 'c3', 'b4', 'b5', 'B1'];
sort($arr, SORT_NATURAL); // true
var_dump($arr); // ['A1', 'B1', 'a1', 'b4', 'b5', 'c2', 'c3']
https://learnku.com/articles/36866

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
https://gitee.com/obamajs/php-base64-implemention/blob/master/Base64.php
base64 的作用是把任意的字符序列转换为只包含特殊字符集的序列
每个 Base64 字符用 6 位来表示,但是一个字节是 8 位,所以 3 个字节刚好可以生成 4 个 Base64 字符,这应该很容易计算出来,下面我给大家举个例子,假如说现在有个字符串为 "123"1 的 ASCII 为 49,那么转换为二进制就是 001100012 的 ASCII 为 50,那么转换为二进制就是 001100103 的 ASCII 为 51,那么转换为二进制就是 00110011,这三个二进制组合在一起就是这样:001100010011001000110011
按照 4 个 Base64 字符转换为 38 位的字节算法就可以了,4 个字符组合起来就是 24 位,按照 8 位一个字节,就是三个字节。
function normalToBase64Char($num)
{
if ($num >= 0 && $num <= 25) {
return chr(ord('A') + $num);
} else if ($num >= 26 && $num <= 51) {
return chr(ord('a') + ($num - 26));
} else if ($num >= 52 && $num <= 61) {
return chr(ord('0') + ($num - 52));
} else if ($num == 62) {
return '+';
} else {
return '/';
}
}
function base64CharToInt($num)
{
if ($num >= 65 && $num <= 90) {
return ($num - 65);
} else if ($num >= 97 && $num <= 122) {
return ($num - 97) + 26;
} else if ($num >= 48 && $num <= 57) {
return ($num - 48) + 52;
} else if ($num == 43) {
return 62;
} else {
return 63;
}
}
https://learnku.com/articles/36655

一致性 hash 算法

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
// 一致性哈希算法
class ConsistentHashing
{
protected $nodes = array(); //真实节点
protected $position = array(); //虚拟节点
protected $mul = 64; // 每个节点对应64个虚拟节点

/**
* 把字符串转为32位符号整数
*/
public function hash($str)
{
return sprintf('%u', crc32($str));
}

/**
* 核心功能
*/
public function lookup($key)
{
$point = $this->hash($key);

//先取圆环上最小的一个节点,当成结果
$node = current($this->position);

// 循环获取相近的节点
foreach ($this->position as $key => $val) {
if ($point <= $key) {
$node = $val;
break;
}
}

reset($this->position); //把数组的内部指针指向第一个元素,便于下次查询从头查找

return $node;
}

/**
* 添加节点
*/
public function addNode($node)
{
if(isset($this->nodes[$node])) return;

// 添加节点和虚拟节点
for ($i = 0; $i < $this->mul; $i++) {
$pos = $this->hash($node . '-' . $i);
$this->position[$pos] = $node;
$this->nodes[$node][] = $pos;
}

// 重新排序
$this->sortPos();
}

/**
* 删除节点
*/
public function delNode($node)
{
if (!isset($this->nodes[$node])) return;

// 循环删除虚拟节点
foreach ($this->nodes[$node] as $val) {
unset($this->position[$val]);
}

// 删除节点
unset($this->nodes[$node]);
}

/**
* 排序
*/
public function sortPos()
{
ksort($this->position, SORT_REGULAR);
}
}

// 测试
$con = new ConsistentHashing();

$con->addNode('a');
$con->addNode('b');
$con->addNode('c');
$con->addNode('d');

$key1 = 'www.zhihu.com';
$key2 = 'www.baidu.com';
$key3 = 'www.google.com';
$key4 = 'www.testabc.com';

echo 'key' . $key1 . '落在' . $con->lookup($key1) . '号节点上!<br>';
echo 'key' . $key2 . '落在' . $con->lookup($key2) . '号节点上!<br>';
echo 'key' . $key3 . '落在' . $con->lookup($key3) . '号节点上!<br>';
echo 'key' . $key4 . '落在' . $con->lookup($key4) . '号节点上!<br>';
https://learnku.com/articles/30269

异常处理

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
try {
$a = 5 % 0;
} catch (Exception $e) {
echo $e->getMessage();
$a = -1; // 通过异常来处理 $a 为 0 的情况,但是实际上,捕获不到该异常
}

echo $a; // 无法执行
try {
$a = 5 % 0;
// 注意,DivisionByZeroError 错误只能捕捉到 % 运算,无法捕捉 / 运算
} catch (DivisionByZeroError $e) {
echo $e->getMessage();
$a = -1;
}

echo $a; // -1
异常将错误处理与常规代码进行分离,能够让业务流程更加清晰。

try {
// 业务流程
} catch (FileNotFoundException $e) {

} catch (FileTypeException $e) {

} catch (Exception $e) {

}
// 只处理 404 异常
public function actionType($username)
{
try {
$user = $client->get(sprintf("/api/user/%s", $username));
} catch (RequestException $e) {
if (404 === $e->getResponse()->getStatusCode()) {
return "create";
}

throw $e;
}

return "update";
}
set_exception_handler(function($exception){
echo '发生了未知错误';
log($exception->getMessage());
});https://learnku.com/articles/37029

秒读 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
class ReaderCsv
{

/**
* 数组键值
* @var int
*/
protected $row = 0;

/**
* Excel源数据
* @var array
*/
protected $excelData = [];

/**
* 文件路径
* @var
*/
protected $path;

/**
* ReaderCsv constructor.
* @param $path
*/
public function __construct($path)
{
$this->path = $path;
}

/**
* 读取CSV文件
*/
public function readCsv()
{
//数组键值
$row = $this->row;

//Excel数组
$excelData = [];

//打开文件
$file = fopen($this->path, 'r');

//从文件指针中读入一行并解析 CSV 字段
//fgetcsv 解析读入的行并找出 CSV 格式的字段然后返回一个包含这些字段的数组。
while ($data = fgetcsv($file)) {
//统计一行数据有多少列
$columnSize = count($data);

for ($i = 0; $i < $columnSize; $i++) {
//转换字符的编码 && 赋值Excel源数据
$excelData[$row][$i] = mb_convert_encoding(
$data[$i],
'UTF-8',
'gb2312'
);
}
$row++;
}

$this->row = $row;

//关闭一个已打开的文件指针
fclose($file);

$this->excelData = $excelData;
}

/**
* 获取Excel源数据
* @return array
*/
public function getExcelData()
{
return $this->excelData;
}

/**
* 获取总行数
* @return int
*/
public function getRow()
{
return $this->row;
}
}
//传入文件路径
$readerCsv = new ReaderCsv($filePath);

//读取csv文件
$readerCsv->readCsv();

//获取Excel数据
$excelData = $readerCsv->getExcelData();
https://learnku.com/articles/37002
https://phpspreadsheet.readthedocs.io/en/latest/
https://github.com/rap2hpoutre/fast-excel
https://github.com/viest/php-ext-xlswriter

关键词提取高亮

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

$content = '<p>周杰伦传世之作《晴天》</p>:从前从前,<div><img class="img" src="htt://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike150%2C5%2C5%2C150%2C50/sign=3137f6103c4e251ff6faecaac6efa272/38dbb6fd5266d0167927ca029b2bd40735fa35d9.jpg"></div>有个人<strong>爱你</strong>很久,<p>风渐渐,把你吹得好远</p>';
echo $content;
$tag = [
['name' => '周杰伦','url'=>'https://baike.baidu.com/item/%E5%91%A8%E6%9D%B0%E4%BC%A6/129156?fr=aladdin'],
['name'=>'晴天','url'=>'https://baike.baidu.com/item/%E6%99%B4%E5%A4%A9/5429222'],
];
preg_match_all('/<a[^>]*>.*?<\/a>|<\/?[a-zA-Z]+[^>]*>|<!--.*?-->/', $content, $match);
echo '<pre>';
$html = $match[0];
$no_html = preg_split('/<a[^>]*>.*?<\/a>|<\/?[a-zA-Z]+[^>]*>|<!--.*?-->/', $content);

print_r($html);

print_r($no_html);
$tags = [[],[],[]];
foreach ($tag as $k=>$v){
$tags[0][] = '#'.$v['name'].'#';
$tags[1][] = '#'.md5($v['name']). '#';
$tags[2][] = "<a href={$v['url']}>{$v['name']}</a>";
}
print_r($tags);
foreach ($tags[0] as $k=>$v){
foreach ($no_html as $key=>$value){
if (!trim($value)) continue;
if(preg_match($v,$value)){
$no_html[$key] = preg_replace($v,$tags[1][$k],$value,1);
break;
}
}
}print_r($no_html);
foreach ($no_html as $key=>$value){
if (!trim($value)) continue;
$no_html[$key] = str_replace($tags[1],$tags[2],$value);
}
$res = [];
$num = count($no_html);
print_r($no_html);
for ($i=0;$i<$num;$i++){
$res[] = $no_html[$i];
if (isset($html[$i])){
$res[]=$html[$i];
}
}

echo implode('',$res);
<p><a href=https://baike.baidu.com/item/%E5%91%A8%E6%9D%B0%E4%BC%A6/129156?fr=aladdin>周杰伦</a>传世之作《<a href=https://baike.baidu.com/item/%E6%99%B4%E5%A4%A9/5429222>晴天</a>》</p>:从前从前,<div><img class="img" src="htt://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike150%2C5%2C5%2C150%2C50/sign=3137f6103c4e251ff6faecaac6efa272/38dbb6fd5266d0167927ca029b2bd40735fa35d9.jpg"></div>有个人<strong>爱你</strong>很久,<p>风渐渐,把你吹得好远</p>

/**
* 对字符串执行指定次数替换
* @param Mixed $search 查找目标值
* @param Mixed $replace 替换值
* @param Mixed $subject 执行替换的字符串/数组
* @param Int $limit 允许替换的次数,默认为-1,不限次数
* @return Mixed
*/
function str_replace_limit($search, $replace, $subject, $limit=-1){
if(is_array($search)){
foreach($search as $k=>$v){
$search[$k] = '`'. preg_quote($search[$k], '`'). '`';
}
}else{
$search = '`'. preg_quote($search, '`'). '`';
}
return preg_replace($search, $replace, $subject, $limit);
}

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
ReflectionClass
ReflectionFunction
ReflectionMethod
ReflectionParameter
class Printer
{
}

class Student
{
private $name;
private $year;

public function __construct($name, $year)
{
$this->name = $name;
$this->year = $year;
}

public function getValue()
{
return $this->name;
}

public function setBase(Printer $printer, $name, $year = 10)
{
$this->name = $name;
$this->year = $year;
}
}
$refl_class = new ReflectionClass(Student::class);
$object = $refl_class->newInstanceArgs(["obama", 100]);
echo get_class($object) . "\n";"Student"
echo $object->getValue();obama
$refl_method = $refl_class->getMethod("setBase");
echo get_class($refl_method) . "\n";
$parameters = $refl_method->getParameters();
foreach ($parameters as $parameter) {
echo $parameter->getName() . "\n";
if ($parameter->getClass() != null) {
echo $parameter->getClass()->getName() . "\n";
}
if ($parameter->isDefaultValueAvailable()) {
echo $parameter->getDefaultValue() . "\n";
}
}
printer
Printer
name
year
10
https://learnku.com/articles/37162

经度和纬度计算距离两地距离

1
2
3
4
5
6
7
8
9
10
11
12
13
function getDistance($lng1, $lat1, $lng2, $lat2)
{
// 将角度转为狐度https://www.qqdeveloper.com/2019/09/26/longitude-and-langitude/
//deg2rad()函数将角度转换为弧度
$radLat1 = deg2rad($lat1);
$radLat2 = deg2rad($lat2);
$radLng1 = deg2rad($lng1);
$radLng2 = deg2rad($lng2);
$a = $radLat1 - $radLat2;
$b = $radLng1 - $radLng2;
$s = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6378.137 * 1000;
return $s;
}

雪花算法

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
1bit:一般是符号位,不做处理
41bit:用来记录时间戳,这里可以记录 69 年,如果设置好起始时间比如今年是 2018 年,那么可以用到 2089 年,到时候怎么办?要是这个系统能用 69 年,我相信这个系统早都重构了好多次了。
10bit:10bit 用来记录机器 ID,总共可以记录 1024 台机器,一般用前 5 位代表数据中心,后面 5 位是某个数据中心的机器 ID
12bit:循环位,用来对同一个毫秒之内产生不同的 ID,12 位可以最多记录 4095 个,也就是在同一个机器同一毫秒最多记录 4095 个,多余的需要进行等待下毫秒。上面只是一个将 64bit 划分的标准,当然也不一定这么做,可以根据不同业务的具体场景来划分,比如下面给出一个业务场景:

服务目前 QPS10 万,预计几年之内会发展到百万。
当前机器三地部署,上海,北京,深圳都有。
当前机器 10 台左右,预计未来会增加至百台。
适用场景:当我们需要无序不能被猜测的 ID,并且需要一定高性能,且需要 long 型,那么就可以使用我们雪花算法。比如常见的订单 ID,用雪花算法别人就无法猜测你每天的订单量是多少。
PHP uniqid()函数可用于生成不重复的唯一标识符,该函数基于微秒级当前时间戳。在高并发或者间隔时长极短(如循环代码)的情况下,会出现大量重复数据。即使使用了第二个参数,也会重复,最好的方案是结合 md5 函数来生成唯一 ID。
$units = array();
for($i=0;$i<1000000;$i++){
$units[] = uniqid();
}
$values = array_count_values($units);
$duplicates = [];
foreach($values as $k=>$v){
if($v>1){
$duplicates[$k]=$v;
}
}
echo '<pre>';
print_r($duplicates);
$units = array();
for($i=0;$i<1000000;$i++){
$units[] = uniqid('',true);
}
$values = array_count_values($units);
$duplicates = [];
foreach($values as $k=>$v){
if($v>1){
$duplicates[$k]=$v;
}
}
echo '<pre>';
print_r($duplicates);
$units = array();
for($i=0;$i<1000000;$i++){
$units[]=md5(uniqid(md5(microtime(true)),true));
}
$values = array_count_values($units);
$duplicates = [];
foreach($values as $k=>$v){
if($v>1){
$duplicates[$k]=$v;
}
}
echo '<pre>';
print_r($duplicates);
使用 session_create_id()函数生成唯一标识符,经过实际测试发现,即使循环调用 session_create_id()一亿次,都没有出现过重复。
php session_create_id()是 php 7.1 新增的函数,用来生成 session id,低版本无法使用。
https://www.qqdeveloper.com/2019/09/12/snowflake/
https://www.qqdeveloper.com/2019/09/16/php-unique-id/

跨域认证解决方案jwt

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
(图一)Session与Cookie认证与鉴权
1.客户端向服务端发送一个http请求。
2.服务端在收到客户端的请求时,生成一个唯一的 sessionid,这里需要将该生成的 session 存储在服务端,这个 sessionid 存储具体的 session 内容,默认的是文件存储,当然我们可以修改具体的存储方式,例如数据库存储。 3.客户端在接受到这个 sessionid 时,存在 cookie 里面,每次请求时携带该 sessionid。 4.服务端在接收到客户端的请求之后,根据客户端发送的 sessionid 来进行认证与授权。
这里也推荐一下自己之前分享的一篇有关 session 于 cookie 的知识点。session 与 cookie 详解


(图二)传统的token授权
1.客户端向服务端发送一个http请求。
2.服务端在收到客户端的请求之后,生成一个唯一 token,这里需要将该生成的 token 存储在服务端,至于怎么存,可以和上面 session 与 cookie 的方式一致。也可以存在缓存数据库中,如 redis,memcached。 3.服务端将该 token 返回给客户端,客户端存在本地,可以存请求头 header 中,也可以存在 cookie 中,同时也可以存在 localstorage 中。 4.向服务端发送请求时,携带该 token,服务端进行认证或者授权。


(图三)JWT认证模式
1.客户端向服务端发送一个http请求。
2.服务端根据 jwt 的生成规则,生成一个 token,并返回给客户端,这里服务端是不需要存储的。 3.客户端在接受到该 token 时,存在客户端。 4.客户端向服务端发送请求时,服务端对请求的 token 进行解析,如果发现解析出来的数据和生成的数据是一致的代表是一个合法的 token,则进行相应的操作。
基于 session 都是需要服务端存储的,而 JWT 是不需要服务端来存储的。针对以上几点,总结如下:
一、缺点 1.容易遇到跨域问题。不同域名下是无法通过 session 直接来做到认证和鉴权的。 2.分布式部署的系统,需要使用共享 session 机制 3.容易出现 csrf 问题。

二、优点 1.方便灵活,服务器端直接创建一个 sessionid,下发给客户端,客户端请求携带 sessionid 即可。
2.session 存储在服务端,更加安全。 3.便于服务端清除 session,让用户重新授权一次。
JWT 是基于客户端存储的一种认证方式,然而 session 是基于服务端存储的一种认证方式。JWT 虽然不用服务端存储了,也可以避免跨域、csrf 等情况。但也存在如下几个不太好的地方。 1.无法清除认证 token。由于 JWT 生成的 token 都是存储在客户端的,不能有服务端去主动清除,只有直到失效时间到了才能清除。除非服务端的逻辑做了改变。 2.存储在客户端,相对服务端,安全性更低一些。当 JWT 生成的 token 被破解,我们不便于清除该 token。
composer require firebase/php-jwt
private $key = 'jwtKey';

// 生成JWT
public function createJwt()
{
$time = time();
$key = $this->key;
$token = [
'iss' => 'https://www.qqdeveloper.com',// 签发人
'exp' => $time + 86400,// 过期时间(这里的有效期时间为1天)
'sub' => '主题内容',// 主题
'aud' => '受众内容',// 受众
'nbf' => $time,// 生效时间
'iat' => $time,// 签发时间
'jti' => 123,// 编号
// 额外自定义的数据
'data' => [
'userName' => '编程浪子走四方'
]];
// 调用生成加密方法('Payloadn内容','加密的键',['加密算法'],['加密的可以'],['JWT的header头'])
$jwt = JWT::encode($token, $key);
return json(['data' => $jwt]);
}

// 解析JWT
public function analysisJwt()
{
try {
$key = $this->key;
$jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZyIsImV4cCI6MTU2ODA5NjE4MCwic3ViIjoiXHU0ZTNiXHU5ODk4XHU1MTg1XHU1YmI5IiwiYXVkIjoiXHU1M2Q3XHU0ZjE3XHU1MTg1XHU1YmI5IiwibmJmIjoxNTY4MDA5NzgwLCJpYXQiOjE1NjgwMDk3ODAsImp0aSI6MTIzLCJkYXRhIjp7InVzZXJOYW1lIjoiXHU3ZjE2XHU3YTBiXHU2ZDZhXHU1YjUwXHU4ZDcwXHU1NmRiXHU2NWI5In19.kHb_9Np0zjE25YE9czUEGvmFPYtqMJT9tuZzJTuMZl0';
// 调用解密方法('JWT内容','解密的键,和加密时的加密键一直','加密算法')
$decoded = JWT::decode($jwt, $key, array('HS256'));
return json(['message' => $decoded]);
} catch (\Exception $exception) {
return json(['message' => $exception->getMessage()]);
}

}
}
https://www.qqdeveloper.com/2019/09/09/Jwt/

加密算法

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
MD5 结果是 128 位摘要,SHa1 是 160 位摘要。那么 MD5 的速度更快,而 SHA1 的强度更高。

常用的对称加密算法有:AES 和 DES。
DES:比较老的算法,一共有三个参数入口(原文,密钥,加密模式)。而 3DES 只是 DES 的一种模式,是以 DES 为基础更安全的变形,对数据进行了三次加密,也是被指定为 AES 的过渡算法。
AES:高级加密标准,新一代标准,加密速度更快,安全性更高(不用说优先选择)
$content = "123456";
// 该函数是获取有哪些加密方式
$encryptMethod = openssl_get_cipher_methods();
$method1 = "AES-256-ECB";
$key = "123";

$encrypt2 = openssl_encrypt($content,$method1,$key);
echo $encrypt2."\n";
$decrypt2 = openssl_decrypt($encrypt2,$method1,$key);
echo $decrypt2."\n";

$method2 = "DES-EDE3-CFB";
$encrypt3 = openssl_encrypt($content,$method2,$key);
echo $encrypt3."\n";
$decrypt3 = openssl_decrypt($encrypt3,$method2,$key);
echo $decrypt3."\n";
公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称密码体制的特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。对称密码体制中只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。所以保证其安全性就是保证密钥的安全,而非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。这样安全性就大了很多。
1.A 要向 B 发送信息,A 和 B 都要产生一对用于加密的公钥和私钥。

2.A 的私钥保密,A 的公钥告诉 B;B 的私钥保密,B 的公钥告诉 A。

3.A 要给 B 发送信息时,A 用 B 的公钥加密信息,因为 A 知道 B 的公钥。

4.A 将这个消息发给 B(已经用 B 的公钥加密消息)。

5.B 收到这个消息后,B 用自己的私钥解密 A 的消息。其他所有收到这个报文的人都无法解密,因为只有 B 才有 B 的私钥。
// 公钥内容
$publicKey = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA448HrtB/ORfECv/owcAB
fN/PfAy2G7BGPkIy96QuFQhgf7pTF+X4x4wRZgRtdmhnMgtpessx6UXaNzRaDsa8
IWozFTrNu3P7xcJxdcWbdlHdMYASmp4xOe6ct/tGURI8HBn4yjxTES4IFAoPxNCt
dgs31dsJnq0uUa3C1VTWbPtdZX+GX/GOsl2hbJOhn/WrRlOIGzI2oSjhdaPXGD1w
xXKCZM1RhcjQoWQLnFerlOeqNRhW+qgoJ7nRD+PhEajA0LhUMHvyeFR+A37DuFdI
KBD58b2EnBZmYyZL7umeNjjhruSyeD1Y5qOvuKOoL7vQOVzAdVwokcMqDmXurR4s
kQIDAQAB
-----END PUBLIC KEY-----
";
// 私钥内容
$privateKey = "-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDjjweu0H85F8QK
/+jBwAF83898DLYbsEY+QjL3pC4VCGB/ulMX5fjHjBFmBG12aGcyC2l6yzHpRdo3
NFoOxrwhajMVOs27c/vFwnF1xZt2Ud0xgBKanjE57py3+0ZREjwcGfjKPFMRLggU
Cg/E0K12CzfV2wmerS5RrcLVVNZs+11lf4Zf8Y6yXaFsk6Gf9atGU4gbMjahKOF1
o9cYPXDFcoJkzVGFyNChZAucV6uU56o1GFb6qCgnudEP4+ERqMDQuFQwe/J4VH4D
fsO4V0goEPnxvYScFmZjJkvu6Z42OOGu5LJ4PVjmo6+4o6gvu9A5XMB1XCiRwyoO
Ze6tHiyRAgMBAAECggEBALtXC1ouKC8Wk70ChdrMee0WTLixtlMBQjMLSO57abzD
Er7U5IeuOqv9cm1sg7mRpjObFZGUK1kCuu3r2aCEmGliBwAZUzpZ+BMNS19L+frk
1pdj+uz0A07QVJFa+r7PplD0SNAl7bUdEmV9CxJbQhMGlUcodrj9lj6EMPx0Rbud
gBLJbZCK+h8lNgbGYce+J0+CDDnTapsPKFP/DvwZwgE4CkpVciEtbH2RzU45qRVI
If7/3YBB3q+eD4GrIuBgmLUT7bs2vKn/RXxN8YARoEwUqj/Gg2iXQeSPtW0iWuV/
cbCT6Sfb1J7KGXiEDKrsNH+e29TcXsIDbrMHSyj3IeECgYEA8gtQJbHDhcXjDSx1
5SWKpWdhFNEp/ggdFj8hAroVBHPmGelvkY6i4OZ/IGulzIrJY2uxXwU91KMuoZ5/
IdOQSZiehsba1PCn9ZAVY/KZZvfAYX96tUKUmnar1GyhhcSAljPK2zKqYbGnJbnI
TVMpSthdNgEi/XNrEWrpXFWimI0CgYEA8K3nhWBt4xG87x9ifEpJjFlf/8hlmKr9
8SMi4qm/ja3huL4si1VVkO8flqqhkkr+HqtBKOe+bYMZBUDtYnv/netMeQuCdvJY
sfXPeSguXgGhjxP6gNswMYnzOykoGyPT84gOXBuY2M8X9nB6SkAQ+OL/sf0ltQdP
4mdQNQBKjRUCgYEA5sLmbKmoMZfSurKSzB8Yqk1/fytTj8AIizcwr7rdYttkm3u1
RN7qZuUaerxm3DXNfx4jguYqZtoVET1dE9DylVgOe4yHAdFXMIVn/1xB6Kt4HPw+
7yVFLGbLt8DB1hjcR1elpYoOawnGw+72CtKoYZUaeOxogZ9Sis6VIdT7KdkCgYEA
tkmw0fcwI0xbAe2OZT7Kp89Fg3BfapsPzORk1rHkkEVDce4vxLQ0I5rJHQ9NYoUE
JWxl5LppI36oo68CXJY4C36cpA1QmhCBlv/rTQNe4vpvR/PExnW88bhfDc7lPnEL
ZicFYUPRp1xq9M9KABS4Bhm/uipWtd685WiEejAnRuUCgYA4ddYkl6XwA/HQ1Kyr
iwAllDdusuGW62DMeXC2h2qw8yCvG38YAQu8lRDCFm0JW/sD2sgVCtt9c147ZFoB
mUghpFOZRZvbxEdPzIyb4Gn0wN+3Jwjrl9uPiY82q92G0E253PKug2EJYapa411/
fxrEXMi/X+VwggoEnOqXsbyrHA==
-----END PRIVATE KEY-----
";

// 加密字符串
$content = "123456";

/**
* 使用公钥加密,私钥解密
*/
$encrypt = "";
openssl_public_encrypt($content,$encrypt,$publicKey);
echo "加密之后的数据为:\n";
var_dump($encrypt);

$decrypt = "";
openssl_private_decrypt($encrypt,$decrypt,$privateKey);
echo "解密之后的数据为:\n";
var_dump($decrypt);
/**
* 使用私钥加密,公钥解密
*/
$encrypt1 = "";
openssl_private_encrypt($content,$encrypt1,$privateKey);
echo "加密之后的数据为:\n";
var_dump($encrypt1);
$decrypt1 = "";
openssl_public_decrypt($encrypt1,$decrypt1,$publicKey);
echo "解密之后的数据为:\n";
var_dump($decrypt1);
https://www.qqdeveloper.com/2019/08/28/data-encrypt/

常用排序算法之桶排序

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
桶排序其实就是一种归纳排序,他将要进行排序的数组分到有限的桶里面,然后对桶进行归纳排序,可以理解成他是一个归纳结果。
小哼的班上只有 5 个同学,这 5 个同学分别考了 5 分、3 分、 5 分、2 分和 8 分,哎考得真是惨不忍睹(满分是 10
分)。接下来将分数进行从小到大排序, 排序后是 2 3 5 5 8

思路分析
  首先,他的总分是 10 分,也就是说有 11 种情况,所以我们需要 11 个桶,开始的时候我们都不给这些桶加水,也就是每个桶是 0,每个桶都标号从 0 开始到 10 结束。

  然后呢我们可以看到有 5 个同学,所以,我们可以通过循环拿到这五个同学的分数,拿到分数后,比如我先拿到的是 8 分,那么我们就给标号为 8 的桶加 1 刻度水(当然也不一定是刻度为单位,也可以是升,毫升什么的,这里是方便理解),这样下来,标号为 2 的桶里有 1 刻度水,标号为 3 的桶里有 1 刻度水,标号为 5 的桶里有 2 刻度水,标号为 8 的桶里有 1 刻度水。

  最后,我们对这些桶里面的水进行统计,从前往后,有一刻度水的就打印这个桶编号 1 次,两刻度水的就打印桶编号 2
public function index()
{
//需要进行排序的数组
$arr = array(5, 3, 5, 2, 8);
//声明一个空数组
$list = array();
//将空数组赋值0
for ($i = 0; $i <= 10; $i++) {
$list[$i] = 0;
}
//按照相应的进行重新赋值
for ($j = 0; $j < sizeof($arr); $j++) {
$list[$arr[$j]]++;
}
//打印排序后的结果
for ($i = 1; $i <= 10; $i++) {
for ($j = 1; $j <= $list[$i]; $j++) {
var_dump($i);
}
}
}
https://learnku.com/articles/37185
$data = [5,3,5,2,8];
$count = array_count_values($data);
$res = [];
for ($i = 0; $i < 11; $i++) {
if (isset($count[$i])) {
for ($j = 0; $j < $count[$i]; $j++) {
$res[] = $i;
}
}
}

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
Goutte composer require fabpot/goutte
Guzzle composer require guzzlehttp/guzzle:~6.0
php artisan make:command Spider
namespace App\Console\Commands;

use Goutte\Client as GoutteClient;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Pool;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;

class Spider extends Command
{

protected $signature = 'command:spider {concurrency} {keyWords*}'; //concurrency为并发数 keyWords为查询关键词

protected $description = 'php spider';

public function __construct()
{
parent::__construct();
}

public function handle()
{
//
$concurrency = $this->argument('concurrency'); //并发数
$keyWords = $this->argument('keyWords'); //查询关键词
$guzzleClent = new GuzzleClient();
$client = new GoutteClient();
$client->setClient($guzzleClent);
$request = function ($total) use ($client,$keyWords){
foreach ($keyWords as $key){
$url='https://laravel-china.org/search?q='.$key;
yield function () use($client,$url){
return $client->request('GET',$url);
};
}
};
$pool = new Pool($guzzleClent,$request(count($keyWords)),[
'concurrency' => $concurrency,
'fulfilled' => function ($response, $index) use ($client){
$response->filter('h2 > a')->reduce(function($node) use ($client){
if(strlen($node->attr('title'))==0) {
$title = $node->text(); //文章标题
$link = $node->attr('href'); //文章链接
$carwler = $client->request('GET',$link); //进入文章
$content=$carwler->filter('#emojify')->first()->text(); //获取内容
Storage::disk('local')->put($title,$content); //储存在本地
}
});
},
'rejected' => function ($reason, $index){
$this->error("Error is ".$reason);
}
]);
//开始爬取
$promise = $pool->promise();
$promise->wait();
}
}https://learnku.com/articles/6272/php-crawler-crawls-community-article-content

guzzle使用cookies实现模拟登录

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
$client = new \GuzzleHttp\Client(['cookies' => true]);
$r = $client->request('GET', 'http://httpbin.org/cookies');
namespace App\Http\Controllers;
use GuzzleHttp\Client;
class TestController extends Controller
{
public function test()
{
$client = new Client(['cookies' => true]);
// 登录账号https://phpartisan.cn/news/47.html
$login = $client->request('POST','登录地址',['form_params' => ['usename' => '账号','password'=>'密码','remember'=>'false']]);
// 请求需要登录才能访问的页面
$response = $client->request('GET',"");
$data = $response->getBody();
echo $data;
}
}
$response = $client->request('POST', 'http://httpbin.org/post', [
'multipart' => [
[
'name' => 'field_name',
'contents' => 'abc'
],
[
'name' => 'file_name',
'contents' => fopen('/path/to/file', 'r')
],
[
'name' => 'other_file',
'contents' => 'hello',
'filename' => 'filename.txt',
'headers' => [
'X-Foo' => 'this is an extra header to include'
]
]
]
]);
$response = $client->request('POST', 'http://httpbin.org/post', [
'form_params' => [
'field_name' => 'abc',
'other_field' => '123',
'nested_field' => [
'nested' => 'hello'
]
]
]);
$response = $client->request('POST', 'https://credit.baiqishi.com/clweb/api/common/gettoken', ['json' => ['token'=>'']]);
发送并发请求
Guzzle你可以使用Promise和异步请求来同时发送多个请求:

$client = new Client();
// 创建一个请求列表
$promises = [
'baidu' => $client->getAsync('https://www.baidu.com/'),
'jd' => $client->getAsync('http://www.jd.com/'),
'earnp' => $client->getAsync('http://bbs.earnp.com/'),
];
// 等待所有请求完成
$results = Promise\unwrap($promises);
// 查看请求结果
$baidu = $results['baidu']->getHeader('Content-Length');
$jd = $results['jd']->getHeader('Content-Length');
$earnp = $results['earnp']->getHeader('Content-Length');
dd($jd);
上面确定了并发的数量,当你想发送不确定数量的请求时,可以使用GuzzleHttp\Pool对象:

$client = new Client();
$requests = function ($total) {
$uri = 'http://baidu.com';
for ($i = 0; $i < $total; $i++) {
yield new Request('GET', $uri);
}
};
$pool = new Pool($client, $requests(100), [
'concurrency' => 5,
'fulfilled' => function ($response, $index) {
// 成功的响应。
},
'rejected' => function ($reason, $index) {
// 失败的响应
},
]);
// 构建请求https://phpartisan.cn/news/44.html
$promise = $pool->promise();
// 等待请求池完成。
$promise->wait();

3DES加密

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
php7.1开始不再支持mcrypt_encrypt,所有这里会有2个版本的3DES加密并且对接JAVA,具体实施如下:

php版本小于php7.0

使用mcrypt方法即可进行加密解密。

$size = mcrypt_get_block_size('des', 'ecb');
$td = mcrypt_module_open(MCRYPT_3DES, '', 'ecb', '');
$iv = @mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
// 使用MCRYPT_3DES算法,cbc模式
@mcrypt_generic_init($td, $key, $iv);
// 初始处理
$data = mcrypt_generic($td, $input);// 加密
var_dump($data);
$decrypted = mdecrypt_generic($td, $data); // 解密
var_dump($decrypted);
mcrypt_generic_deinit($td);
// 结束
mcrypt_module_close($td);
php版本大于php7.0

大于7.0版本我们开始使用openssl进行加密解密处理了:

# 加密
$data = openssl_encrypt($input,'des-ede3',$key,0);
openssl_encrypt加密会按照加密模式进行加密,之后还会进行base64加密,所以需要进行解密

base64_decode($data);
所以解密方法如下:

# 解密https://phpartisan.cn/news/122.html
$decrypted = openssl_decrypt(base64_decode($data),'des-ede3',$key,OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);

API 系列 - 深入浅出 JSON Web Token jwt

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
JWT 的认证过程如下:

客户端发送登录信息(用户 ID,密码)
服务端基于密钥生成 JWT,返回给客户端
客户端在接下来的请求中将 Token 放在头部中一起发送给服务端
服务端对 JWT 进行验证
JWT 可分为三部分,用 . 符号隔开

Header 头部
Payload 负载
Signature 签名
function base64url(string $string)
{
return str_replace('=', '', strtr(base64_encode($string), '+/', '-_'));
}
$header = [
'alg' => 'HS256',
'typ' => 'JWT',
];
$payload = [
'sub' => 1,
'name' => 'Mind Geek',
'admin' => true,
];
$secret = 'mind-geek-jwt';
// Header
$base64Header = base64url(json_encode($header));

// Payload
$base64Payload = base64url(json_encode($payload));
$encryp = $base64Header.".".$base64Payload;
$signature = hash_hmac('sha256', $encryp, $secret, true);
$base64Signature = base64url($signature);
$token = $base64Header.".".$base64Payload.".".$base64Signature;
$token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsIm5hbWUiOiJNaW5kIEdlZWsiLCJhZG1pbiI6dHJ1ZX0.0_dneYOin4yWRYlD-KmfvGEY6AhjA_zDyyvPhgYq2sU';
list($base64Header, $base64Payload, $base64Signature) = explode('.', $token);
$encryp = $base64Header.".".$base64Payload;
$signature = hash_hmac('sha256', $encryp, $secret, true);
$computedBase64Signature = base64url($signature);
if($computedBase64Signature === $base64Signature){
echo "认证成功!";
}
https://learnku.com/articles/37426

UTC 格式的时间转换

1
2
3
4
5
6
7
8
9
10
11
12
<?php

use Carbon\Carbon;

$input = 2018-01-01T12:00:00.000+0100;

// 实例化一个 Carbon 对象
$carbon = Carbon::make($input);
// 转换时区
$carbon->setTimezone('PRC');
// 输出查看
echo $carbon->toDateTimeString();

Curl 下载文件实时进度条显示

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
class Request
{
protected $bar;
// 是否下载完成
protected $downloaded = false;

public function __construct()
{
// 初始化一个进度条https://github.com/dariuszp/cli-progress-bar
$this->bar = new CliProgressBar(100);
$this->bar->display();
$this->bar->setColorToRed();
}

function download($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// 开启进度条
curl_setopt($ch, CURLOPT_NOPROGRESS, 0);
// 进度条的触发函数
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, [$this,'progress']);
// ps: 如果目标网页跳转,也跟着跳转
// curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

if (false === ($stream = curl_exec($ch))) {
throw new \Exception(curl_errno($ch));
}

curl_close($ch);

return $stream;
}

/**
* 进度条下载.
*
* @param $ch
* @param $countDownloadSize 总下载量
* @param $currentDownloadSize 当前下载量
* @param $countUploadSize
* @param $currentUploadSize
*/
public function progress($ch, $countDownloadSize, $currentDownloadSize, $countUploadSize, $currentUploadSize)
{

// 等于 0 的时候,应该是预读资源不等于0的时候即开始下载
// 这里的每一个判断都是坑,多试试就知道了
if (0 === $countDownloadSize) {
return false;
}
// 有时候会下载两次,第一次很小,应该是重定向下载
if ($countDownloadSize > $currentDownloadSize) {
$this->downloaded = false;
// 继续显示进度条
}
// 已经下载完成还会再发三次请求
elseif ($this->downloaded) {
return false;
}
// 两边相等下载完成并不一定结束,
elseif ($currentDownloadSize === $countDownloadSize) {
return false;
}

// 开始计算
$bar = $currentDownloadSize / $countDownloadSize * 100;
$this->bar->progress($bar);
}
}
(new Request)->download('http://www.shiguopeng.cn/database.sql');
http://www.shiguopeng.cn/archives/249

bindParam的绑定参数是引用

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
<?php

/**
* $dbh是pdo实例化后的对象的句柄。
* 第二个参数格式必须与第一个参数对应
* $db->paramBindExec("select user_name, notes from message where user_name=:user_name", array(':user_name'=>'guest'), false);
*
* @param string $sql 预执行的SQL语句
* @param array $param SQL参数绑定
*/
private function paramBindExec($sql, $param)
{
try
{
$this->statement = $this->dbh->prepare($sql);
// 绑定参数
if (!empty($param))
{
foreach ($param as $key => $value)
{
// 错误的写法
// $this->statement->bindParam($key, $value);
// 正确的写法
$this->statement->bindParam($key, $param[$key]);
}
}

// 遗留下来的$value将会被引用最后一次的值
return $this->statement->execute();
}
catch (PDOException $e)
{
$this->error_num = $e->getCode();
$this->error_message = $e->getMessage();

return false;
}
}
绑定一个PHP变量到用作预处理的SQL语句中的对应命名占位符或问号占位符。 不同于PDOStatement::bindValue(),此 变量作为引用被绑定,并只在PDOStatement::execute()被调用的时候才取其值
看到引用二字方醒悟,并不是循环一次,绑定一次参数。而是循环所有次数,才将参数绑定上去。而我绑定的变量每一次都是$value。
最后将$value改成$param[$key]即可 http://www.shiguopeng.cn/archives/28

寻找数组中重复的值

1
2
3
4
5
6
7
8
function FetchRepeatMemberInArray($array) {
// 获取去掉重复数据的数组
$unique_arr = array_unique ( $array );
// 获取重复数据的数组
$repeat_arr = array_diff_assoc ( $array, $unique_arr );
$repeat_arr = array_unique ( $repeat_arr );
return $repeat_arr;
}

php+redis+lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
$lua = "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}";
$s = $redis->eval($lua,array('key1','key2','first','second'),2);
$lua = "local ret={}; for i,v in pairs(KEYS) do ret[i]=redis.call('hgetall', v) end; return ret";
$obj = new Dao_RedisBase();
$arr_hash_key = ['hash1','hash2','hash3','hash4'];
$hashresult=$obj->getCon()->eval($lua,$arr_hash_key,count($arr_hash_key));

if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
https://bettercuicui.github.io/2017/07/04/REDIS/php+redis+lua/

ob缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ob_start();
echo "aaa";
header('Content-Type:text/html;charset=utf-8');
echo "bbb";
$i =1;
while($i<5){
echo $i++;
sleep(1);
}
$i =1;
while($i<5){
echo $i++;
flush();
sleep(1);
}
flush()这个函数会把程序缓存的内容输出到浏览器中,并且清空程序缓存区的内容。这样就可以做到1-5依次输出到屏幕,每次sleep1秒啦。
ob_start(); //打开缓冲区
php页面的全部输出
$content = ob_get_contents(); //取得php页面输出的全部内容
$fp = fopen("output00001.html", "w"); //创建一个文件,并打开,准备写入
fwrite($fp, $content); //把php页面的内容全部写入output00001.html,然后……
fclose($fp);
https://bettercuicui.github.io/2016/11/29/PHP/%E7%BC%93%E5%AD%98%E6%80%BB%E7%BB%93/

不要直接克隆对象,请使用深拷贝

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
 final class Car {
public $model; // 为了减少代码量,将其声明为公共方法

public function __construct(CarModel $model) {
$this->model = $model;
}
}

final class CarModel {
public $name;
public $year;

public function __construct($name, $year) {
$this->name = $name;
$this->year = $year;
}
}
$bmwX1 = new Car(new CarModel('X1', 2015));
$bmwX5 = clone $bmwX1;

var_dump(spl_object_hash($bmwX1)); // "0000000037e353af0000558c268309ea"
var_dump(spl_object_hash($bmwX5)); // "0000000037e353ac0000558c268309ea"
$bmwX5->model->name = 'X5';
var_dump($bmwX1->model);
var_dump($bmwX5->model);
// object(CarModel)#2 (2) {
// ["name"]=> "X5"
// ["year"]=> int(2015)
// }
https://github.com/myclabs/DeepCopy
use function DeepCopy\deep_copy;

$bmwX1 = new Car(new CarModel('X1', 2015));
$bmwX5 = deep_copy($bmwX1);https://learnku.com/php/t/37604

php请求接口异步化

1
2
3
4
5
6
7
8
9
10
11
12
1、用户每次请求删除的接口,直接把参数、操作保存在redis的list结构中。
2、crontable添加一个cron脚本,每分钟执行一次。
3、cron脚本读取redis的list,有值则fork出子进程进行操作。记得要set_time_limit();
4、如果cron脚本读取出list中的值,可以先标记一下,表示有脚本正在执行这个操作,如果执行失败还可以把这个任务塞回去再次执行。
5、当cron脚本再次发现list有值,先判断有没有脚本正在执行这个操作,有则不管了。
如果php的执行方式是fastcgi,则有fastcgi_finish_request这个api。此函数冲刷(flush)所有响应的数据给客户端并结束请求。这使得客户端结束连接后,需要大量时间运行的任务能够继续运行。
echo "正在进行删除操作,请稍后";
fastcgi_finish_request();
for($i = 1-10000){
删除uid = $i的数据;
}用户通过浏览器请求删除的操作,立马收到’正在进行删除操作,请稍后’的提示,但是数据并没有被删除完,等50s后,才发现数据被删除完毕。由此说明在调用fastcgi_finish_request后,客户端响应就已经结束,但与此同时服务端脚本却继续运行!
https://bettercuicui.github.io/2018/04/01/PHP/php%E8%AF%B7%E6%B1%82%E6%8E%A5%E5%8F%A3%E5%BC%82%E6%AD%A5%E5%8C%96%20/

人民币小写转大写

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
/**
* 人民币小写转大写
*
* @param string $number 数值
* @param string $intUnit 币种单位,默认"元"
* @param bool $isRound 是否对小数进行四舍五入
* @param bool $isExtraZero 是否对整数部分以0结尾,小数存在的数字附加0,比如1960.30,
* 有的需要输出"壹仟玖佰陆拾元零叁角",实际上"壹仟玖佰陆拾元叁角"也是对的,随意,默认第一种
* @return string
*/
public static function formatMoney($number, $intUnit = '元', $isRound = true, $isExtraZero = true)
{
$money = $number / 100;
$parts = explode('.', $money, 2);
$int = isset($parts[0]) ? strval($parts[0]) : '0';
$dec = isset($parts[1]) ? strval($parts[1]) : '';

// 如果小数点后多于2位,不四舍五入就直接截,否则就处理
$decLen = strlen($dec);

if (isset($parts[1]) && $decLen > 2) {
$dec = $isRound
? substr(strrchr(strval(round(floatval("0.".$dec), 2)), '.'), 1)
: substr($parts[1], 0, 2);
}

// 当number为0.001时,小数点后的金额为0元
if (empty($int) && empty($dec)) {
return '零';
}

// 定义
$chs = ['0','壹','贰','叁','肆','伍','陆','柒','捌','玖'];
$uni = ['','拾','佰','仟'];
$decUnit = ['角', '分'];
$exp = ['', '万'];
$res = '';

// 整数部分从右向左找
for ($i = strlen($int) - 1, $k = 0; $i >= 0; $k++) {
$str = '';
// 按照中文读写习惯,每4个字为一段进行转化,i一直在减
for ($j = 0; $j < 4 && $i >= 0; $j++, $i--) {
$u = $int{$i} > 0 ? $uni[$j] : '';
$str = $chs[$int{$i}] . $u . $str;
}

$str = rtrim($str, '0');
$str = preg_replace("/0+/", "零", $str);
if (!isset($exp[$k])) {
$exp[$k] = $exp[$k - 2] . '亿';
}
$u2 = $str != '' ? $exp[$k] : '';
$res = $str . $u2 . $res;
}

// 如果小数部分处理完之后是00,需要处理下
$dec = rtrim($dec, '0');

// 小数部分从左向右找
if (!empty($dec)) {
$res .= $intUnit;

// 是否要在整数部分以0结尾的数字后附加0,有的系统有这要求
if ($isExtraZero) {
if (substr($int, -1) === '0') {
$res.= '零';
}
}

for ($i = 0, $cnt = strlen($dec); $i < $cnt; $i++) {
$u = $dec{$i} > 0 ? $decUnit[$i] : '';
$res .= $chs[$dec{$i}] . $u;
}
$res = rtrim($res, '0');
$res = preg_replace("/0+/", "零", $res);
} else {
$res .= $intUnit . '整';
}

return $res;
}https://blog.11010.net/archives/9/

不用内置函数取代var_dump()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function dump($var, $label = '', $return = false)
{
if(ini_get('html_errors')){
$content = "<pre>\n";
if($label != ''){
$content .= "<strong>{$label}</strong>\n";
}
$content .= htmlspecialchars(print_r($var, true));
$content .= "\n</pre>\n";
}else{
$content .= $label . ":\n" . print_r($var,true);
}
if($return) return $content;
echo $content;
return null;
}

json转换

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
https://3v4l.org/JSStn
<?php

$json_str='{
"code": 200,
"message": "操作成功",
"data": [
{
"id": 1,
"goods_id": 1,
"property_name_id": 1,
"property_value_id": 1,
"property_name": {
"title": "份量",
"is_sale": true
},
"property_value": {
"id": 1,
"value": "小份",
"image": ""
}
},
{
"id": 2,
"goods_id": 1,
"property_name_id": 1,
"property_value_id": 2,
"property_name": {
"title": "份量",
"is_sale": true
},
"property_value": {
"id": 2,
"value": "中份",
"image": ""
}
},
{
"id": 3,
"goods_id": 1,
"property_name_id": 2,
"property_value_id": 4,
"property_name": {
"title": "温度",
"is_sale": true
},
"property_value": {
"id": 4,
"value": "常温",
"image": ""
}
},
{
"id": 4,
"goods_id": 1,
"property_name_id": 2,
"property_value_id": 5,
"property_name": {
"title": "温度",
"is_sale": true
},
"property_value": {
"id": 5,
"value": "加冰",
"image": ""
}
}
]
}';
$obj = json_decode($json_str);

$data = [];
foreach($obj->data as $v){

$nid=$v->property_name_id;
if(!array_key_exists($nid,$data)){
$o = new stdclass;//new Class{}
$o->property_id = $v->property_name_id;
$o->property_name = $v->property_name->title;
$o->is_sale= $v->property_name->is_sale ;
$data[$nid]=$o;
}
$data[$nid]->items[]=$v->property_value;
}

$obj->data = array_values($data);
print_r($obj);
//echo json_encode($obj); https://learnku.com/articles/37803#reply121107

无限级菜单排序

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
$temp = [
['id' => 1, 'pid' => 0, 'name' => '商品管理'],
['id' => 2, 'pid' => 1, 'name' => '平台自营'],
['id' => 3, 'pid' => 2, 'name' => '图书品类'],
['id' => 4, 'pid' => 2, 'name' => '3C品类'],
['id' => 5, 'pid' => 0, 'name' => '第三方'],
['id' => 6, 'pid' => 5, 'name' => '家私用品'],
['id' => 7, 'pid' => 5, 'name' => '书法品赏'],
['id' => 8, 'pid' => 7, 'name' => '行书'],
['id' => 9, 'pid' => 8, 'name' => '行楷'],
['id' => 10, 'pid' => 9, 'name' => '张山行楷字帖'],
['id' => 11, 'pid' => 22, 'name' => '李四行楷字帖'],
];
function te(array $arr, $node = 0)
{
$tem = [];
foreach ($arr as $val) {
$pid = $val['pid']; // 当前菜单的父级ID
$id = $val['id']; // 当前菜单ID
// 当前的父级ID和node节点匹配,说明当前子菜单属于node节点。
if ($pid == $node) {
$tem[$id] = $val; // 保存当前的菜单。
$son = te($arr, $id); // 递归 当前子菜单是否有子菜单?
// 当有的时候,保存 。
if (!empty($son)) {
$tem[$id]['son'] = $son;
}
}
}
return $tem;
}

function show(array $arr, $node = 0)
{
foreach ($arr as $val) {
if ($val['pid'] == 0) {
$node = 0;
}
for ($i = 0; $i < $node; $i++) {
echo '----';
}
echo $val['name'] . "\n";
if (isset($val['son'])) {
$node += 1;
show($val['son'], $node);
}
}
}

$menus = te($temp);
show($menus);
function traverseMenu(array $menus, array &$result, $pid = 0)
{
foreach ($menus as $child_menu) {
if ($child_menu['pid'] == $pid) {
$item = ['id' => $child_menu['id'], 'name' => $child_menu['name'], 'children' => []];
traverseMenu($menus, $item['children'], $child_menu['id']);
$result[] = $item;
} else {
continue;
}
}
}

$result = [];
traverseMenu($temp, $result, 0);
https://learnku.com/laravel/t/37883

静态变量 缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class App
{
public function getData()
{
static $data = [];

if (!$data) {
$path = __DIR__.'/data.json';
$data = json_decode(file_get_contents($path), true);
}

return $data;
}
}

// info1.php
$app = new App;
$info = $app->getData();

// info2.php
$app = new App;
$info = $app->getData();
https://learnku.com/lk/t/34710

composer 内存不足

1
2
3
4
5
6
7
8
9
10
11
12
13
free -m
total used free shared buff/cache available
Mem: 864 372 306 50 185 296
Swap: 0 0 0
# 如上发现Swap实际都为0
/bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
/sbin/mkswap /var/swap.1
/sbin/swapon /var/swap.1
# 再次使用free -m,发现已经有了Swap内存配置
total used free shared buff/cache available
Mem: 864 383 67 49 413 267
Swap: 1023 0 1023
# 再次运行composer install即可

PHP 仿 Word 统计文章字数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function comment_count_word($str){
//$str =characet($str);
//判断是否存在替换字符
$is_tihuan_count=substr_count($str,"龘");
try {
//先将回车换行符做特殊处理
$str = preg_replace('/(\r\n+|\s+| +)/',"龘",$str);
//处理英文字符数字,连续字母、数字、英文符号视为一个单词
$str = preg_replace('/[a-z_A-Z0-9-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",\'<>~`\?:;|]/',"m",$str);
//合并字符m,连续字母、数字、英文符号视为一个单词
$str = preg_replace('/m+/',"*",$str);
//去掉回车换行符
$str = preg_replace('/龘+/',"",$str);
//返回字数
return mb_strlen($str)+$is_tihuan_count;
} catch (Exception $e) {
return 0;
}
}https://learnku.com/articles/37939

Tesseract 图片识别

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
composer require thiagoalessio/tesseract_ocr
class ImageService
{
public function read(string $url)
{
$path = $this->save($url);

return $this->tesseract($path);
}

/**
* @Task
*/
public function tesseract(string $path)
{
return (new TesseractOCR($path))->run();
}

protected function save(string $url): string
{
$client = di()->get(ClientFactory::class)->create();
$content = $client->get($url)->getBody()->getContents();

$path = BASE_PATH . '/runtime/' . uniqid();
file_put_contents($path, $content);

return $path;
}
}https://learnku.com/articles/32096

imagepng 返回的图片流直接上传

1
2
3
4
5
6
7
8
9
10
ob_start();

imagepng($image_data);

$string_data = ob_get_contents(); // read from buffer

ob_end_clean(); // delete buffer

Oss::uploadByString($string_data)
https://learnku.com/php/t/38225

PHP 方法重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
php 方法重写,参数不同,报错: Declaration of should be compatible with that

Strict standards: Declaration of ... should be compatible with that of ...

解决方法:

<?php
abstract class A {
// 方法无参数
public static function foo(){ echo 'bar'; }
}

abstract class B extends A {
// 方法有参数
public static function foo($str = NULL){ echo $str; }
}
https://learnku.com/articles/38351

最全的县级以上信息包

1
2
3
4
5
6
7
8
9
10
11
12
13
composer require medz/gb-t-2260^2.0

$getter = \Medz\GBT2260\Getter::instance();

## 获取一个代码的省信息
$province = $getter->province('511304'); // 四川省

## 获取市级地区,注意,直辖地区是返回 null
$city = $getter->city('511304'); // 南充市

## 获取区级地区
$county = $getter->county('511304'); // 嘉陵区
https://github.com/medz/gb-t-2260

this、self、static 的区别

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
$this 调用类实例

self 类自身

static 调用类
class A {
public static function foo() {
echo 'A-foo<br>';
}
}

class B extends A {

public static function test() {
A::foo(); // 结果:A-foo
static::foo(); // 结果:C-foo
self::foo(); // 结果:B-foo
}

public static function foo()
{
echo 'B-foo<br>';
}
}

class C extends B {

public static function foo()
{
echo 'C-foo<br>';
}
}
https://learnku.com/articles/38406
C::test();

零宽空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 正常的优惠券
b4c44af562

// urlencode() 之后
b4c44af562


// 有问题的优惠券
b4c44af562​​​​​​​​​​​​​​

// urlencode() 之后
b4c44af562%e2%80%8b%e2%80%8b%e2%80%8b%e2%80
谷歌了下,这个东西叫做 零宽空格。一般的空格是有宽度的,使用光标可以将它选中。但零宽空格没有宽度,看不见,也难以选中,就像不存在一样。
https://zh.wikipedia.org/wiki/%E9%9B%B6%E5%AE%BD%E7%A9%BA%E6%A0%BC
http://sakyavarro.cn/post/%E9%9B%B6%E5%AE%BD%E7%A9%BA%E6%A0%BC
控制台
copy(decodeURI('%E2%80%8B'))

数组转utf-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 /**
* 数组转utf-8
* @param string $data
* @return array
*/
function gbk_to_utf8($data){
if(is_array($data)){
foreach($data as $key => $value){
$data[$key]=call_user_func(__FUNCTION__,$value);
}
return $data;
}
return iconv("gbk","utf-8",$data);
}

echo date('Y/m/d', strtotime('last day of -1 month'));
echo date('Y/m/d', strtotime('-1 month', strtotime('2019/03/31')));
today()->subMonthNoOverflow()

阿拉伯数字和中文数字的相互转换

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
function numToWord($num){
$chiNum = array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九');
$chiUni = array('','十', '百', '千', '万', '十', '百', '千', '亿');
$chiStr = '';
$num_str = (string)$num;
$count = strlen($num_str);
$last_flag = true; //上一个 是否为0
$zero_flag = true; //是否第一个
$temp_num = null; //临时数字
$chiStr = '';//拼接结果
if ($count == 2) {//两位数
$temp_num = $num_str[0];
$chiStr = $temp_num == 1 ? $chiUni[1] : $chiNum[$temp_num].$chiUni[1];
$temp_num = $num_str[1];
$chiStr .= $temp_num == 0 ? '' : $chiNum[$temp_num];
}else if($count > 2){
$index = 0;
for ($i=$count-1; $i >= 0 ; $i--) {
$temp_num = $num_str[$i];
if ($temp_num == 0) {
if (!$zero_flag && !$last_flag ) {
$chiStr = $chiNum[$temp_num]. $chiStr;
$last_flag = true;
}

if($index == 4 && $temp_num == 0){
$chiStr = "万".$chiStr;
}
}else{
if($i == 0 && $temp_num == 1 && $index == 1 && $index == 5){
$chiStr = $chiUni[$index%9] .$chiStr;
}else{
$chiStr = $chiNum[$temp_num].$chiUni[$index%9] .$chiStr;
}
$zero_flag = false;
$last_flag = false;
}
$index ++;
}
}else{
$chiStr = $chiNum[$num_str[0]];
}
return $chiStr;
}
function cn2num($string){

if(is_numeric($string)){
return $string;
}
// '仟' => '千','佰' => '百','拾' => '十',
$string = str_replace('仟', '千', $string);
$string = str_replace('佰', '百', $string);
$string = str_replace('拾', '十', $string);
$num = 0;
$wan = explode('万', $string);
if (count($wan) > 1) {
$num += cn2num($wan[0]) * 10000;
$string = $wan[1];
}
$qian = explode('千', $string);
if (count($qian) > 1) {
$num += cn2num($qian[0]) * 1000;
$string = $qian[1];
}
$bai = explode('百', $string);
if (count($bai) > 1) {
$num += cn2num($bai[0]) * 100;
$string = $bai[1];
}
$shi = explode('十', $string);
if (count($shi) > 1) {
$num += cn2num($shi[0] ? $shi[0] : '一') * 10;
$string = $shi[1] ? $shi[1] : '零';
}
$ling = explode('零', $string);
if (count($ling) > 1) {
$string = $ling[1];
}
$d = array(
'一' => '1','二' => '2','三' => '3','四' => '4','五' => '5','六' => '6','七' => '7','八' => '8','九' => '9',
'壹' => '1','贰' => '2','叁' => '3','肆' => '4','伍' => '5','陆' => '6','柒' => '7','捌' => '8','玖' => '9',
'零' => 0, '0' => 0, 'O' => 0, 'o' => 0,
'两' => 2
);
return $num + @$d[$string];
}https://learnku.com/articles/39405

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
//https://github.com/Maatwebsite/Laravel-Excel
//不限制执行时间
set_time_limit(0);
//最大内存
ini_set('memory_limit', '-1');

//导出txt逻辑
$file_path = $file_name.".txt";
$str = "";//拼接表头字符
Storage::disk('local')->put($file_path, $str);

//以二进制的方式追加读写
$file = fopen(storage_path().$file_path, "a+b");

$str = "";
$rows = [];//数据源
foreach ($rows as $row) {
//耗时处理操作得到$str
...

//每处理1条数据就写入一次,数据量大时,建议批量写入
fwrite($file, $str."\n");
}

fclose($file);
https://learnku.com/articles/38828#reply125534

bcadd 的精度

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
1、数值
1) 4位小数,且前4位为0
\Log::info('bcadd:' . bcadd(0.00002200, 0, 8));
// 结果:bcadd:0.00000000
2) 5位小数,且前4位有大于0
\Log::info('bcadd:' . bcadd(0.00012200, 0, 8));
// 结果:bcadd:0.00012200
2、字符串
\Log::info('bcadd:' . bcadd('0.00002200', 0, 8));
// 等同于 \Log::info('bcadd:' . bcadd((string)0.00012200, 0, 8));
// 结果:bcadd:0.00002200
3、科学记数法
1) 5位小数,且前4位有大于0
\Log::info('bcadd:' . bcadd(2.2E-4, 0, 8));
// 结果:bcadd:0.00022000
2) 5位小数,且第4位等于0
\Log::info('bcadd:' . bcadd(2.2E-5, 0, 8));
// 结果:bcadd:0.00000000
3) 5为小数,第4位大于0
\Log::info('bcadd:' . bcadd(12.2E-5, 0, 8));
// 结果:bcadd:0.00012200
结论是超过 4 位小数,且前 4 位都为 0 时,如不转为字符串进行 bcadd 计算,那么精度会有问题,结果会按 0 来计算;高精度计算,建议先转为字符串再进行计算。

根据需要,提前转为精度+1的字符串类型参与计算,如需保留6位,就取7位出来
$value = 0.000011;
$value = sprintf('%.7f', $value);
保存数据时,可转再转为6
$model->value = bcadd($value, 0, 6);
$model->save();
https://learnku.com/articles/39459

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
 $process_num = 50; //开50个进程,模拟50个用户
for ($i = 0; $i < $process_num; $i++) {
MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
if (Db::name('product')->where('id', 1)->value('count') > 0) {
$res = Db::name('product')->where('id', 1)->setDec('count');
if ($res) {
dump('获取到更新资源权限:' . $i);
}
}
});
}
这就是高并发带来的问题,同一时刻有多个进程读取同一条数据,同一时刻有多个进程更新同一条数据
mysql 提供的行级锁 select ... lock in share mode(阻塞写),select ... for update(阻塞读写,悲观锁),所以 for update 机制能满足我们的原子要求。编辑代码如下:
$process_num = 50; //开50个进程,模拟50个用户
for ($i = 0; $i < $process_num; $i++) {
MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
Db::startTrans(); //行级锁必须在事务中才能生效
//设置for update,进程会阻塞在这里,只能允许一个进程获取到行锁,其他等待获取
if (Db::name('product')->where('id', 1)->lock('for update')->value('count') > 0) {
$res = Db::name('product')->where('id', 1)->setDec('count');
if ($res) {
dump('获取到更新资源权限:' . $i);
}
}
Db::commit();
});
}

update
$process_num = 50; //开50个进程,模拟50个用户
for ($i = 0; $i < $process_num; $i++) {
MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
//合并两条语句为一条更新语句
$res = Db::name('product')->where('id', 1)->where('count', '>', 0)->setDec('count');
if ($res) {
dump('获取到更新资源权限:' . $i);
}
});
}
文件锁
$process_num = 50; //开50个进程,模拟50个用户
for ($i = 0; $i < $process_num; $i++) {
MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
$filename = app()->getRootPath() . 'runtime/lock';
$file = fopen($filename, 'w'); //打开文件
$lock = flock($file, LOCK_EX);
// $lock=flock($handle, LOCK_EX|LOCK_NB); (异步非阻塞,所有进程如果出现获取不到锁,不等待跳过,加锁失败)
//获取文件排他锁:LOCK_EX(异步阻塞,只有一个进程获得锁,其他竞争进程等待)
//还有一种共享锁:LOCK_SH(所有进程都可以获取共享锁,读取文件,当且只有一个锁时,才允许写操作,否则操作失败,容易出现死锁问题)
if ($lock) {
try {
if (Db::name('product')->where('id', 1)->lock('for update')->value('count') > 0) {
$res = Db::name('product')->where('id', 1)->setDec('count');
if ($res) {
dump('获取到更新资源权限:' . $i);
}
}
} catch (\Exception $e) {
dump($e->getMessage());
} finally {
flock($file, LOCK_UN); //无论如何都要释放锁
}
}
fclose($file); //关闭文件句柄
});
}
在集群架构、分布式等多机联网结构中就是掩耳盗铃了,所以适应性更好地锁机制还是要使用分布式锁,分布式锁最常用和最易用就是 redis 的 setnx 锁了。
编辑代码如下:

$process_num = 50; //开50个进程,模拟50个用户
for ($i = 0; $i < $process_num; $i++) {
MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
//获取redis锁
//关于CacheHelper::getRedisLock是怎样获取锁的,注意几个点就行:1.如何避免死锁;2.如何设置过期时间;3.如何设置抢占条件;4.如何循环等待判断。这些不在本文讨论范围,可自行研究,以后有空我也可以写一篇博文
$lock = CacheHelper::getRedisLock('redis_lock');
if ($lock) {
try {
if (Db::name('product')->where('id', 1)->lock('for update')->value('count') > 0) {
$res = Db::name('product')->where('id', 1)->setDec('count');
if ($res) {
dump('获取到更新资源权限:' . $i);
}
}
} catch (\Exception $e) {
dump($e->getMessage());
}
} else {
// dump('获取redis锁失败');
}
});
}

from https://learnku.com/articles/39244

并发下安全读写文件函数

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

<?php

/**
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/
https://mkblog.cn/1831/ 孟坤大佬

/**
* 安全读取文件,避免并发下读取数据为空
*
* @param $file 要读取的文件路径
* @param $timeout 读取超时时间
* @return 读取到的文件内容 | false - 读取失败
*/
function file_read_safe($file, $timeout = 5) {
if (!$file || !file_exists($file)) return false;
$fp = @fopen($file, 'r');
if (!$fp) return false;
$startTime = microtime(true);

// 在指定时间内完成对文件的独占锁定
do {
$locked = flock($fp, LOCK_EX | LOCK_NB);
if (!$locked) {
usleep(mt_rand(1, 50) * 1000); // 随机等待1~50ms再试
}
}
while ((!$locked) && ((microtime(true) - $startTime) < $timeout));

if ($locked && filesize($file) >= 0) {
$result = @fread($fp, filesize($file));
flock($fp, LOCK_UN);
fclose($fp);
if (filesize($file) == 0) {
return '';
}
return $result;
} else {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
}

/**
* 安全写文件,避免并发下写入数据为空
*
* @param $file 要写入的文件路径
* @param $buffer 要写入的文件二进制流(文件内容)
* @param $timeout 写入超时时间
* @return 写入的字符数 | false - 写入失败
*/
function file_write_safe($file, $buffer, $timeout = 5) {
clearstatcache();
if (strlen($file) == 0 || !$file) return false;

// 文件不存在则创建
if (!file_exists($file)) {
@file_put_contents($file, '');
}
if(!is_writeable($file)) return false; // 不可写

// 在指定时间内完成对文件的独占锁定
$fp = fopen($file, 'r+');
$startTime = microtime(true);
do {
$locked = flock($fp, LOCK_EX);
if (!$locked) {
usleep(mt_rand(1, 50) * 1000); // 随机等待1~50ms再试
}
}
while ((!$locked) && ((microtime(true) - $startTime) < $timeout));

if ($locked) {
$tempFile = $file.'.temp';
$result = file_put_contents($tempFile, $buffer, LOCK_EX);

if (!$result || !file_exists($tempFile)) {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
@unlink($tempFile);

ftruncate($fp, 0);
rewind($fp);
$result = fwrite($fp, $buffer);
flock($fp, LOCK_UN);
fclose($fp);
clearstatcache();
return $result;
} else {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
}

随机图像

1
2
3
4
5
6
7
<?php
$img_array = glob('images/*.{gif,jpg,png,jpeg,webp,bmp}', GLOB_BRACE);
if(count($img_array) == 0) die('没找到图片文件。请先上传一些图片到 '.dirname(__FILE__).'/images/ 文件夹');
header('Content-Type: image/png');
echo(file_get_contents($img_array[array_rand($img_array)]));
?>
https://mkblog.cn/1836/

PHP读取exe软件版本号

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 读取 exe\dll 文件版本号
*
* @auth @腾讯电脑管家(https://zhidao.baidu.com/question/246143241010222924.html)
* @param $filename 目标文件
* @return 读取到的版本号https://mkblog.cn/1712/
*/
function getFileVersion($filename)
{
$fileversion = '';
$fpFile = @fopen($filename, "rb");
$strFileContent = @fread($fpFile, filesize($filename));
fclose($fpFile);
if($strFileContent)
{
$strTagBefore = 'F\0i\0l\0e\0V\0e\0r\0s\0i\0o\0n\0\0\0\0\0'; // 如果使用这行,读取的是 FileVersion
// $strTagBefore = 'P\0r\0o\0d\0u\0c\0t\0V\0e\0r\0s\0i\0o\0n\0\0'; // 如果使用这行,读取的是 ProductVersion
$strTagAfter = '\0\0';
if (preg_match("/$strTagBefore(.*?)$strTagAfter/", $strFileContent, $arrMatches))
{
if(count($arrMatches) == 2)
{
$fileversion = str_replace("\0", '', $arrMatches[1]);
}
}
}
return $fileversion;
}

百度当图床

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
<?php

/**
* 上传图片到百度识图接口,获取图片外链
*
* @param $file 图片文件
* @return 图片链接(上传成功) NULL(上传失败)
* @copyright (c) mengkun(https://mkblog.cn/1619/)
*/
function uploadToBaidu($file) {
// API 接口地址
$url = 'http://image.baidu.com/pcdutu/a_upload?fr=html5&target=pcSearchImage&needJson=true';

// 文件不存在
if(!file_exists($file)) return '';

// POST 文件
if (class_exists('CURLFile')) { // php 5.5
$post['file'] = new CURLFile(realpath($file));
} else {
$post['file'] = '@'.realpath($file);
}

// CURL 模拟提交
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL , $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$output = curl_exec($ch);
curl_close($ch);

// 返回结果为空(上传失败)
if($output == '') return '';

// 解析数据
$output = json_decode($output, true);
if(isset($output['url']) && $output['url'] != '') {
return $output['url'];
}
return '';
}
在获取到的链接前面加上
https://image.baidu.com/search/down?tn=download&url=
可以绕过防盗链,并且 https 一步到位
// 使用示例:https://mkblog.cn/1619/comment-page-1/#comments
$url = uploadToBaidu('1.jpg');
echo $url;

变量的作用域

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
方法一:使用 global 关键字
function updateCounter()
{
global $counter;
$counterr++;
}

$counter = 10;
updateCounter();
echo $counter; // 11
方法二:使用 PHP 中的 $GLOBALS 数组https://learnku.com/articles/39670
function updateCounter()
{
$GLOBALS[counter]++:
}

$counter = 10;
updateCounter();
echo $counter; // 11
静态变量 ( static )
静态变量在一个函数中被多次调用时,其值不会丢失,但此变量仅在函数内时可见的。

function updateCounter()
{
static $counter = 0;
$counter++;

echo "Static counter is now {$counter}\n";
}

$counter = 10;
updateCounter(); // Static counter is now 1
updateCounter(); // Static counter is now 2 // 不会再被付初始值
echo "Global couter is {$couter}\n"; // Global counter is 10

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

$dateTimeOne = '00:00:40';
$dateTimeTwo = '00:00:50';
$timeOne = explode(':', $dateTimeOne);
$timeTwo = explode(':', $dateTimeTwo);

$hour = $timeOne[0] + $timeTwo[0];
$min = $timeOne[1] + $timeTwo[1];
$sec = $timeOne[2] + $timeTwo[2];

$newS = $sec / 60; //得到分钟数
$newS1 = $sec % 60; //得到秒数

$newM = $min / 60; // 得到小时数
$newM1 = $min % 60; //得到分钟

$newH = $hour / 60; // 得到天数
$newH1 = $hour % 60; // 得到小时数

$min = floor($newS + $newM1);
$sec = floor($newS1);
$hour = floor($newM + $newH1);

echo $hour . ':' . $min . ':' . $sec;

https://learnku.com/articles/39760

php-fpm 502

1
2
3
4
5
6
7
8
9
10
11
php-fpm.conf 的配置 ,静态模式,动态模式,内存模式
1:静态适合 大内存的,
2: 适合内存适合 4g,2g
3:微内存的模式 适合 512256 的内存

进程数 的配置 根据 cpu 核数 和内存 / 30M 的设置 一般一个 worker 进程占 30m 大小

502504 错误处理
502 网关错误,php-fpm 进程无法处理 大量的请求,导致的
504 而是 请求过去,php-fpm 处理超时,无法返回请求导致的
https://learnku.com/articles/39903

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
memory_get_peak_usage();

function formatBytes($bytes, $precision = 2) {
$units = array("b", "kb", "mb", "gb", "tb");

$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= (1 << (10 * $pow));

return round($bytes, $precision) . " " . $units[$pow];
}
function readTheFile($path) {
$lines = [];
$handle = fopen($path, "r");

while(!feof($handle)) {
$lines[] = trim(fgets($handle));
}

fclose($handle);
return $lines;
}

readTheFile("shakespeare.txt");

function readTheFile($path) {
$handle = fopen($path, "r");

while(!feof($handle)) {
yield trim(fgets($handle));
}

fclose($handle);
}
$zip = new ZipArchive();
$filename = "filters-1.zip";

$zip->open($filename, ZipArchive::CREATE);
$zip->addFromString("shakespeare.txt", file_get_contents("shakespeare.txt"));
$zip->close();
https://learnku.com/php/t/39751

phpdocker 镜像网站

docker 神器之 「lnmp 环境篇」

PHP 学习笔记之正则表达式

阿里云视频点播转码

京东大商户对接 API

阿里云 OSS 上传

PHP 略缩图裁剪插件

composer自动加载原理

对话机器人框架 CommuneChatbot

数组增强组件

开源文档管理系统 Wizard

PhpStorm 中使用 PHP Inspections 进行代码静态分析

php开发技术栈导图

聊天系统代码

php添加水印和缩略图

获取 AppStore 信息

PHP Excel 扩展 xlswriter

PHP权限库支持 ACL,RBAC,ABAC 等权限控制

Excel读写工具php excel

Leetcode 二叉树题目集合

使用简单实用的语义化接口快速读写Excel文件composer

PHP中的危险函数和伪协议

一次获取客户端 IP 记录

PHP 开发者实践

php websocket

PHP启示录

腾讯优图 OCR Composer 包

阿里巴巴开放平台php sdk

机器学习PHP库

ThinkPHP6.0 核心分析汇总

PHP通过反射实现自动注入参数

PHP中常见的设计模式

php leetCode 的学习

《PHP 微服务练兵》系列教程

php excel扩展

laravel guzzle爬虫

PHP7 的垃圾回收机制

微博机器人

大型网站技术架构

在谷歌浏览器输入 URL 回车之后发生了什么

电商秒杀超卖解决方案

高并发设计笔记

php爬虫