0%

经常疑惑在 PHP 中可以以数组和对象的属性两种方式获取某个变量值是如何实现的,原来只要数组实现了ArrayAccess接口便可以了。

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
class Person implements ArrayAccess
{
public function offsetExists($offset)
{
return isset($this->$offset);
}

public function offsetGet($offset)
{
return $this->$offset;
}

public function offsetSet($offset, $value)
{
$this->$offset = $value;
}

public function offsetUnset($offset)
{
unset($this->$offset);
}
}

$person = new Person();
$person->name = "hello";
echo $person['name']; //hello
var_dump((array)$person);//array(1) { 'name' => string(2) "cy"}
var_dump(isset($person));//bool(true)
unset($person['name']);
var_dump(isset($person));//bool(false)

Nginx 将 http 默认跳转 https 的方法有多种,本文提供几种方法以作探讨。

分开处理 http 请求和 https 请求

使 Nginx 在接收到 http 请求时自动返回 307 状态码(永久重定向并且不丢失POST数据)。

1
2
3
4
5
6
7
8
9
10
server {
server_name example.com;
listen 80;
return 307 https://$host$request_uri;
}

server {
server_name example.com;
listen 443 ssl;
}

//Nginx 的497状态码(经测试,该方法无效)

497状态码的解释是497 - normal request was sent to HTTPS。所以指定 497 状态码时跳转到 https 即可。

1
2
3
4
5
6
7
server {
server_name example.com;
listen 80;
listen 443 ssl;

error_page 497 https://$host$request_uri;
}

Nginx 中执行判断

1
2
3
if ($scheme != "https") {
rewrite ^ https://$host$request_uri permanent;
}

上述几种方法都可以成功将 http 请求跳转到 https,不过本人推荐第二种方式,配置简单并且符合规范。

在执行 npm 时,系统会在 PATH 中寻找第一个匹配到的 npm 位置,此位置和 which npm 得到的结果相同。

然而,在执行 sudo npm 时,系统的环境变量会默认重置为 secure_path ,执行 sudo -l 可以查看 sudo 受到的限制。结果最后一行便是使用 sudo 时的环境变量。

1
2
3
4
5
6
$ sudo -l
Matching Defaults entries for root on this host:
!visiblepw, always_set_home, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS",
env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION
LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME
LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY", secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

因此,自己在环境变量中已设置的但不在以上位置的命令将会失效。

解决方法有两种:

  1. 将需要用到的命令软链到上述文件夹中

  2. 修改配置,使 sudo 切换时不重置环境变量

    1
    2
    3
    4
    $sudo visudo 
    # Defaults env_reset # 注释掉原有配置
    # Defaults env_keep="…" # 注释掉指定的变量保持
    Defaults !env_reset # 修改为不重置环境

在一次请求中,代码可能执行了数万行,很难去轻易定位代码执行效率的瓶颈。因此,需要使用 XDebug 去记录请求中各方法的执行时间来精准分析究竟是哪一行的代码执行地很慢。

我司的代码每次在我电脑上运行都要数十秒甚至一两分钟,因此我不得不去分析代码执行的速度,也刚好在这里做个示例

修改 XDebug 配置文件

1
2
3
xdebug.profiler_enable=on
xdebug.trace_output_dir="D:\webserver\xdebug"
xdebug.profiler_output_dir="D:\webserver\xdebug"

下载 wincachegrind

下载地址

运行 PHP 代码并得到输出

Clipboard Image.png

打开对应的输出文件

image.png

然后层层点击进去就可以查看到运行最慢的方法并动手去优化他了。

表达式全集

字符 描述
\\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,”n“匹配字符”n“。”\n“匹配一个换行符。序列”\“匹配”\“而”(“则匹配”(“。
^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配”\n“或”\r“之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配”\n“或”\r“之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo能匹配”z“、”zo“以及”zoo“。等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,”zo+“能匹配”zo“以及”zoo“,但不能匹配”z“。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,”do(es)?“可以匹配”do“或”does“中的”do“。?等价于{0,1}。
{n} n是一个非负整数。匹配确定的n次。例如,”o{2}“不能匹配”Bob“中的”o“,但是能匹配”food“中的两个o。
{n,} n是一个非负整数。至少匹配n次。例如,”o{2,}“不能匹配”Bob“中的”o“,但能匹配”foooood“中的所有o。”o{1,}“等价于”o+“。”o{0,}“则等价于”o“。
{n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,”o{1,3}“将匹配”fooooood“中的前三个o。”o{0,1}“等价于”o?“。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串”oooo“,”o+?“将匹配单个”o“,而”o+“将匹配所有”o“。
. 匹配除”\n“之外的任何单个字符。要匹配包括”\n“在内的任何字符,请使用像”`(.
(pattern) 匹配pattern并获取这一匹配的子字符串。该子字符串用于向后引用。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0...$9属性。要匹配圆括号字符,请使用”(“或”)“。
(?:pattern) 匹配pattern但不获取匹配的子字符串,也就是说这是一个非获取匹配,不存储匹配的子字符串用于向后引用。这在使用或字符”`(
(?=pattern) 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,”`Windows(?=95
(?!pattern) 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如”`Windows(?!95
(?<=pattern) 反向肯定预查,与正向肯定预查类似,只是方向相反。例如,”`(?<=95
(?<!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。例如”`(?<!95
`x y`
[xyz] 字符集合(character class)。匹配所包含的任意一个字符。例如,”[abc]“可以匹配”plain“中的”a“。特殊字符仅有反斜线\\保持特殊含义,用于转义字符。其它特殊字符如星号、加号、各种括号等均作为普通字符。脱字符^如果出现在首位则表示负值字符集合;如果出现在字符串中间就仅作为普通字符。连字符 - 如果出现在字符串中间表示字符范围描述;如果如果出现在首位则仅作为普通字符。
[^xyz] 排除型(negate)字符集合。匹配未列出的任意字符。例如,”[^abc]“可以匹配”plain“中的”plin“。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,”[a-z]“可以匹配”a“到”z“范围内的任意小写字母字符。
[^a-z] 排除型的字符范围。匹配任何不在指定范围内的任意字符。例如,”[^a-z]“可以匹配任何不在”a“到”z“范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,”er\b“可以匹配”never“中的”er“,但不能匹配”verb“中的”er“。
\B 匹配非单词边界。”er\B“能匹配”verb“中的”er“,但不能匹配”never“中的”er“。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的”c“字符。
\d 匹配一个数字字符。等价于[0-9]
\D 匹配一个非数字字符。等价于[^0-9]
\f 匹配一个换页符。等价于\x0c\cL
\n 匹配一个换行符。等价于\x0a\cJ
\r 匹配一个回车符。等价于\x0d\cM
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]
\S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]
\t 匹配一个制表符。等价于\x09\cI
\v 匹配一个垂直制表符。等价于\x0b\cK
\w 匹配包括下划线的任何单词字符。等价于”[A-Za-z0-9]“。
\W 匹配任何非单词字符。等价于”[^A-Za-z0-9]“。
\xn 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,”\x41“匹配”A“。”\x041“则等价于”\x04&1“。正则表达式中可以使用ASCII编码。.
\num 向后引用(back-reference)一个子字符串(substring),该子字符串与正则表达式的第num个用括号围起来的子表达式(subexpression)匹配。其中num是从1开始的正整数,其上限可能是99。例如:”(.)\1“匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。
\nml 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。

职责链模式:避免请求发送者和请求接收者的耦合关系。将接受者连成一条链,直到有接收者处理请求。
例如,假设你要请假,请假3天以内部门领导可以批准,7天以内需要部门领导和总监批准,7天以上需要部门领导、总监和董事长批准。 你只需向部门领导,部门领导如果察觉超出权限,自动向上级报告并交由上级处理。
职责链模式使客户传递请求后,层层递进,直到有人去处理请求。职责链模式适用于请求接收动态变化、多个对象可能会根据请求不同分别进行处理的情况。

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
<?php
class Request
{
/**
* @var string
*/
public $requester;
/**
* @var int
*/
public $dayCount;
/**
* @var string
*/
public $reason;

/**
* Request constructor.
* @param $requester string
* @param $dayCount int
* @param $reason string
*/
public function __construct($requester, $dayCount, $reason)
{
$this->requester = $requester;
$this->dayCount = $dayCount;
$this->reason = $reason;
}
}

interface Manager
{
/**
* @param Request $request
* @return mixed
*/
public function handle(Request $request);
}


class DepartmentManager implements Manager
{
/**
* @var Manager
*/
private $supervisor;
/**
* @var string
*/
private $name;

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

public function handle(Request $request)
{
if ($request->dayCount > 3) {
$this->supervisor->handle($request);
} else {
echo $this->name . "批准了" . $request->requester . "的请假, 共" . $request->dayCount . "天";
echo "\r\n原因是:" . $request->reason;
}
}

public function setSupervisor(Manager $manager)
{
$this->supervisor = $manager;
}
}

class CompanyManager implements Manager
{
/**
* @var Manager
*/
private $supervisor;
/**
* @var string
*/
private $name;

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

public function handle(Request $request)
{
if ($request->dayCount > 7) {
$this->supervisor->handle($request);
} else {
echo $this->name . "批准了" . $request->requester . "的请假, 共" . $request->dayCount . "天";
echo "\r\n原因是:" . $request->reason;
}
}

public function setSupervisor(Manager $manager)
{
$this->supervisor = $manager;
}
}

class SuperManager implements Manager
{
/**
* @var Manager
*/
private $supervisor;
/**
* @var string
*/
private $name;

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

public function handle(Request $request)
{
echo $this->name . "批准了" . $request->requester . "的请假, 共" . $request->dayCount . "天";
echo "\r\n原因是:" . $request->reason;
}

public function setSupervisor(Manager $manager)
{
$this->supervisor = $manager;
}
}

$departmentManager = new DepartmentManager("部门领导");
$companyManager = new CompanyManager("公司领导");
$superManager = new SuperManager("董事长");
$departmentManager->setSupervisor($companyManager);
$companyManager->setSupervisor($superManager);

$request = new Request("小明", 3, "我想回家回家回家");
$departmentManager->handle($request);

CURL 是一个强大的向服务器发送请求的工具, 尤其是在测试 API 的时候。
很多人像寻常表单一样使用了 -X POST 方式来使用 CURL 去上传文件,但实际上这是错误的。
正确的方式是使用 -F (–form) 来上传文件,这样才会给请求添加 enctype="multipart/form-data" 参数。

1
$ curl -F 'data=@path/to/local/file’ UPLOAD_ADDRES

例如, 如果我想向服务器 [http://localhost/upload](http://localhost/upload) 上传位于/home/petehouston/hello.txt的文件,并将上传的文件的参数命名为 img_avatar, 我可以这样发送请求,

1
$ curl -F 'img_avatar=@/home/petehouston/hello.txt' http://localhost/upload

上传多个文件

想要同时上传多个文件的话,只需要添加多个 -F 选项就可以了。

1
$ curl -F 'fileX=@/path/to/fileX' -F 'fileY=@/path/to/fileY' ... http://localhost/upload

上传文件数组

想要上传文件数组的话,只需要添加多个 -F 选项并命名成相同名字的数组就可以了。

1
$ curl -F 'files[]=@/path/to/fileX' -F 'files[]=@/path/to/fileY' ... http://localhost/upload

就是这么简单,开始享用吧:)

翻译自 Upload files with CURL, 版权归原文所有

根据15位身份证号或者身份证号前17位获取完整身份证号从而完成身份证号是否有效的校验。

javascript 写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
//javascript
function getIDCard(str){
let modulus = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
let mods = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2', '1']
if(str.length == 15){
str = str.substr(0,6) + "19" + str.substr(6, str.length - 6)
}
let total = 0
modulus.forEach(function(value, index){
total += value * str[index]
})
return str + mods[total % 11]
}

go 写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func getIDCard(str string) string {
modulus := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
mods := []rune{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2', '1'}
if len(str) == 15 {
str = str[:6] + "19" + str[6:]
}
total := 0
for i, v := range modulus {
strHere, _ := strconv.Atoi(string(str[i]))
total += v * strHere
}

return str + string(mods[total%11])
}

MySQL常见的优化手段

  1. 主从分离、读写分离、负载均衡。
  2. 分库、分表。
  3. 合理设置索引。
  4. 适当数据冗余,减少 join 次数。
  5. 使用 memcache、redis 等缓存技术,减少 MySQL的查询次数和压力。

MySQL 索引类型

数据结构角度

  1. B-tree 索引
  2. hash 索引
  3. FullText 索引
  4. R-tree 索引,主要用于地理位置

物理角度

  1. 聚集索引
  2. 非聚集索引

逻辑角度

  1. 主键索引,主键索引是一种特殊的唯一索引,不允许有空值
  2. 唯一索引
  3. 单列索引
  4. 复合索引(多列索引)
  5. 空间索引

快速排序就是找出数组的某个值,将数组分为左右两部分,小于该值的放左边大于该值的放右边,再将左右两部分循环执行拆分,直至结束。
例如 1, 11, 9, 7, 5, 6
select = 9
left = [1,7,5,6]
right = [11]
左侧可以继续拆分
select = 7
left = [1,5,6]
right = []
合起来的顺序的变为[[1,5,6], [7], [], [9], [11]]
左侧继续拆分select = 5
left = [1]
right = [6]
合起来的顺序的变为[[1],[5],[6], [7],[] [9], [11]]
至此不可再次拆分。 从底部顺序合并,
1, 5, 6, 7, 9, 11 。
上一层可再分的每次都替换为 进一步拆分的结果。
由此思路使用 golang 进行实现:

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
func quickSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
//首次拆分
left, selectValue, right := split(arr)
list := [][]int{left, selectValue, right}
for i := 0; i < len(list); i++ {
v := list[i]
if len(v) <= 1 {
continue
}
A, B, C := split(v)
temp := make([][]int, len(list[i+1:]))
copy(temp, list[i+1:])
//替换长度大于1的
list = append(append(list[:i], A, B, C), temp...)
i--
}
var result []int
for _, v := range list {
if len(v) > 0 {
result = append(result, v[0])
}
}
return result
}

func split(arr []int) ([]int, []int, []int) {
selectIndex := len(arr) / 2
var left, right []int
for i, v := range arr {
if selectIndex == i {
continue
}
if v <= arr[selectIndex] {
left = append(left, v)
continue
}
right = append(right, v)
}
return left, []int{arr[selectIndex]}, right
}