分类 成长笔记 下的文章

起因

周五在公司加班的时候,调试了一个用户验证短信的一个 Bug,为了尽量做到接口的调用限制,对发送过的手机号设置了一定时长的「黑名单」机制,原理是将发送过的手机号存入缓存中,发送短信的时候获取缓存是否存在,如果存在就说明超频发送了。说实话很简单的一个功能,我却破天荒的调试了半天没搞明白。

后来,老大就坐在旁边,我俩一起调,从生成验证码到发送日志,全给丢到 debug 日志,挂着 tail 命令监听。

然鹅!模拟用户注册居然没有打印验证码日志,只有发送日志!

没记录日志,还始终报验证码错误,真是莫名其妙。

调试

首先,我将视线转移到缓存时间上,发现缓存时间只有 1 分钟,也就是说如果用户填写资料比较慢的话,验证码很可能就过期了!调试嘛,改到 10 分钟先。提交后更新生产环境代码。还是报错!

emmm…难道缓存系统出问题了?还是说 Redis 写不进去了?发送完验证码后,使用 Laravelartisan tinker 打印缓存,居然不是验证码,是手机号!诶,我这儿不是改了吗?(没错,刚开始的时候我勿将手机号存到 values 去了)。代码里加日志,想打印下看下什么情况。可是新加的打印日志死活打印不出来。

不行了,我要冷静下,慢慢的开始分析修改前和修改后的逻辑。

修改前:用户传入手机号->系统生成验证码(这里验证码是手机号)->将发送任务丢到队列->用户收到短信后填写->后端校验。

修改后:用户传入手机号->系统生成验证码(这里新加了日志输出)->将发送任务丢到队列->用户收到短信后填写->后端校验。

而现在的问题是:系统生成的验证码还是手机号,尽管我已经修改了;调试日志不打印!

也就是说。。队列没更新!

解决

分析出队列没更新,我立马就去查了 supervisor 的文档。之前更新队列都只使用了 rereadupdate 命令,只是更新了配置文件,但是队列并没有重新启!而 Laravel 的文档 队列 中提到:

注意一旦 queue:work 命令开始执行,它会一直运行直到它被手动停止或终端被关闭。

supervisor 是不会被关闭的,何况我给了 4 个子进程。而文档中又提到:

记住,队列处理器是一个常驻的进程并且在内存中保存着已经启动的应用状态。因此,它们并不会在启动后注意到你代码的更改。所以,在你的重新部署过程中,请记得 重启你的队列处理器.

也就是说,队列还是保持第一次执行的状态。难怪缓存值修改了没用,日志打印不出来也是这个原因。

重启 supervisor 队列命令:

supervisorctl restart laravel-queue

问题得到解决,真是被自己写的 Bug 蠢哭了。

最近公司分配了一个项目给我,时间相对充裕,所以也就直接用 Laravel 5.6 练手了。参照慕课网实战课程「Laravel 快速开发简书」和 Laravel China 社区出的 「Web 开发实战入门 (Laravel 5.5)」以及 Laravel China 社区翻译的「Laravel 5.6 中文文档」直接开怼。

上线前服务器资源成了一个问题,因为这个项目并没提到正式流程中,所有曲线救国,直接上线在官网的服务器。

配置

  • 阿里云 2 核 4 GB * 3(两台生产使用SLB,一台测试)
  • CentOS 7.4 x86_64
  • Nginx 1.12 + PHP 7.0.9

此次升级共三台服务器,两台生产环境和一台测试环境。

查看 Laravel 5.6 服务器要求 PHP 版本为 PHP >= 7.1.3,因为开发环境装的 7.1.5,所以就直接上 7.1.5 了。

评估影响范围

首先,生产环境还运行着一个官网,不过流量可以忽略不计,所以短暂的宕机是可以接受的,不然我要等到晚上才能升级。

连上服务器,根据需求判断是否需要备份之前的数据。是否安装了其他拓展(Redis、Mencache、Swoole、Yaf 等)有的话需要将 php.ini 文件备份下来。

由于官网 PHP 一直没用过,也没装过什么拓展,所以也就直接覆盖升级了。

升级

注意:是覆盖升级!并非多版本切换。

首先需要找到 PHP 老版本的安装目录。一般路径为 /usr/local/php,现在需要获取当初安装时的配置情况。

使用命令:

$ php -i | grep configure | sed -e "s/Configure Command => //; s/'//g"
 ./configure  --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-gd --with-iconv --with-zlib --enable-xml --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curlwrappers --enable-mbregex --enable-fpm --enable-mbstring --enable-ftp --enable-gd-native-ttf --with-openssl --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --with-pear --with-gettext --enable-session --with-mcrypt --with-curl --with-jpeg-dir=/usr --with-freetype-dir=/usr

若提示 command not found: php 则需要进入到 /usr/local/php/bin/ 使用 ./php 来执行命令。

将以上配置给复制下来,保存好。

然后下载对应升级的 PHP 版本,这里我选择 7.1.5 如果需要其他版本的只需要在下面命令修改对应版本即可。

$ cd ~
$ wget -c http://cn2.php.net/get/php-7.1.5.tar.gz/from/this/mirror -O php-7.1.5.tar.gz
$ tar zxvf php-7.1.5.tar.gz
$ cd php-7.1.5

解压完成,现在开始配置 PHP,这时需要复制上刚刚保存的 ./configure 命令。

$ ./configure  --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-gd --with-iconv --with-zlib --enable-xml --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curlwrappers --enable-mbregex --enable-fpm --enable-mbstring --enable-ftp --enable-gd-native-ttf --with-openssl --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --with-pear --with-gettext --enable-session --with-mcrypt --with-curl --with-jpeg-dir=/usr --with-freetype-dir=/usr
$ make && make install

如果命令有提示 Permission denied 请使用 sudo 来运行命令。

优化

至此,升级已经完成了。但是仍然有优化的地方。

增加或替换 /usr/bin/ 目录下相关 PHP 的命令脚本。

$ cd /usr/local/php
$ cp -f php phpize php-config php /usr/bin/
$ php -v
PHP 7.1.5 (cli) (built: Jun  5 2017 18:00:45) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

重启 PHP-FPM

现在需要将 php-fpm 重启使其生效。命令:

$ killall php-fpm
$ sudo /usr/local/php/sbin/php-fpm

运营同学有一个需求提给了我,说是每周一和周五需要到系统手动导出所有成交报告,而系统是根据城市进行分库的,导完一个城市的成交报告需要切换系统的登录城市。这个步骤异常繁琐,希望我能给个解决方案来解放他的双手。

在项目拉取了一个 hotfix 分支就开搞,通过命令触发接口导出数据生成 CSV 文件并通过附件发送给我。

测试完成,数据完整。发给运营同学准备测试数据,然后气氛有点奇怪。

运营同学:乱码。

我:试试导入?

运营同学:还是乱码。

乱码?UTF-8 还乱码?这 Office 用得啥编码格式。

搜索一通,有说是 Excel 的 Bug,可以通过加 BOM 头搞定。

BOM 头是啥呢?

在 UCS 编码中有一个叫做 "Zero Width No-Break Space" ,中文译名作 “零宽无间断间隔” 的字符,它的编码是 FEFF。而 FFFE 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 "Zero Width No-Break Space"。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到 FFFE,就表明这个字节流是 Little- Endian 的。因此字符 "Zero Width No-Break Space" (“零宽无间断间隔”)又被称作 BOM。

类似 WINDOWS 自带的记事本等软件,在保存一个以 UTF-8 编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即 BOM)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以 UTF-8 编码。对于一般的文件,这样并不会产生什么麻烦。但对于 PHP 来说,BOM 是个大麻烦。

摘自 百度百科 BOM(Byte Order Mark)

再补充一份不同编码的字节顺序标记的表。

编码表示 (十六进制)表示 (十进制)
UTF-8EF BB BF239 187 191
UTF-16(大端序)FE FF254 255
UTF-16(小端序)FF FE255 254
UTF-32(大端序)00 00 FE FF0 0 254 255
UTF-32(小端序)FF FE 00 00255 254 0 0
UTF-72B 2F 76 和以下的一个字节:[38 / 39 / 2B / 2F]43 47 118 和以下的一个字节:[56 / 57 / 43 / 47]
en:UTF-1F7 64 4C247 100 76
en:UTF-EBCDICDD 73 66 73221 115 102 115
en:Standard Compression Scheme for Unicode0E FE FF14 254 255
en:BOCU-1FB EE 28及可能跟随着FF251 238 40及可能跟随着255
GB-1803084 31 95 33132 49 149 51

也就是说,只需要在 CSV 头部加一个 BOM 头就可以解决乱码。

<?php
    // 设置 UTF-8 BOM 头
    $bom = chr(239).chr(187).chr(191);
    $filecontents = $bom . $filecontents;
...

嗯,__记一笔,运营同学欠我一顿饭。__

Xdebug 是一个开放源代码的 PHP 程序调试器 (即一个 Debug 工具),可以用来跟踪,调试和分析 PHP 程序的运行状况。Xdebug 现在的最新版本添加了对 PHP7 的支持。——摘自『xdebug_百度百科

如果有学习过 C 的话,应该使用过 VC++ 的断点调试,可以对执行中的程序进行跟踪,分析程序的执行流程和影响性能缓慢的问题。不过在 PHP 这,却变成了 var_dump();die(); 一类的暴力手工断点。Xdebug 将很好的改变这一现状。

环境

  • 网络环境:局域网
  • 本地环境:Mac OSX 10.13.4 + Visual Studio Code 1.22.2
  • 开发环境:CentOS 7.4 x64、PHP 7.17 NTS x64
  • 代码同步:Visual Studio Code(SFTP)

Window 环境

安装拓展之前需要知道运行环境的 PHP 版本信息,以便下载正确的拓展。

首先,拓展分运行架构,x64 和 x86;其次 PHP 分版本,不同版本的拓展程序不同,以及 TS 和 NTS 版本。

通过右击计算机,属性中可以了解到环境的运行架构。

执行命令来获取 PHP 版本:

$ php -v
PHP 7.1.7 (cli) (built: Mar 15 2018 11:08:04) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
    with Xdebug v2.7.0alpha1, Copyright (c) 2002-2018, by Derick Rethans

PHP 版本为 7.1.7,NTS 版本。

来到 XDebug 的官方站点下载对应拓展:[Xdebug:[Downloads]](https://xdebug.org/download.php)。

Unix 环境

Unix 环境下,测试服务器下载拓展:

$ wget https://xdebug.org/files/xdebug-2.7.0alpha1.tgz

解压拓展包并进入解压目录:

$ tar zxvf xdebug-2.7.0alpha1.tgz
$ cd xdebug-2.7.0alpha1

执行编译:

$ phpize
$ ./configure --enable-xdebug --with-php-config=/usr/local/php/bin/php-config # php 配置目录请以实际环境修改
# 省略...
$ make && make install
# 省略...
Installing shared extensions:     /usr/local/php/lib/php/extensions/no-debug-non-zts-20160303/
# 省略...

复制上拓展的存放目录,这很重要。

现在打开编辑 php.ini 文件:

$ /usr/local/php/etc/php.ini

底部追加 Xdebug 配置:

zend_extension=/usr/local/php/lib/php/extensions/no-debug-non-zts-20160303/xdebug.so
[XDebug]
xdebug.remote_enable = on
xdebug.remote_autostart = 1
;xdebug.remote_host = 192.168.10.1
xdebug.remote_port = 9000
xdebug.remote_connect_back = 1
xdebug.auto_trace = 1
xdebug.collect_includes = 1
xdebug.collect_params = 1
xdebug.remote_log = /tmp/xdebug.log

以上配置中已经开启了远程调试。若是本机环境则配置 remote_enable0 即可。

配置完成后,重启 PHP:

$ service php-fpm restart

然后查看配置是否成功:

$ php -m
tokenizer
xdebug
xml
xmlreader
xmlrpc
xmlwriter
xsl
yaf
zip
zlib

[Zend Modules]
Xdebug

如果能看见 Xdebug 字样则配置成功,否则重新执行安装。

配置远程调试

这里我使用 Visual Studio Code 编辑器的 Xdebug 调试工具(需要安装)。打开一个项目,选择右边的蟑螂图标,进入 Xdebug界面,配置 Xdebug 远程调试,配置文件 configurations 新增一个项目配置:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Test",
            "type": "php",
            "request": "launch",
            "port": 9000,
            "serverSourceRoot": "/home/wwwroot/pulin_openapi",
            "localSourceRoot": "${workspaceRoot}"
        },
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9000,
        }
    ]
}

serverSourceRoot 是你服务器中项目的路径,localSourceRoot 是你本机环境中的项目路径。

现在,在项目中打上一个断点测试,按下 F5 一下吧~

参考

进入 vim

命令描述
vim filename打开或新建文件, 并将光标置于第一行首
vim +n filename打开文件,并将光标置于第 n 行首
vim + filename打开文件,并将光标置于最后一行首
vim +/pattern filename打开文件,并将光标置于第一个与 pattern 匹配的串处
vim -r filename在上次正用 vim 编辑时发生系统崩溃,恢复 filename
vim filename….filename打开多个文件,依次编辑

vim 配置

命令描述
all列出所有选项设置情况
term设置终端类型
ignorance在搜索中忽略大小写
list显示制表位 (Ctrl+I) 和行尾标志($)
number显示行号
report显示由面向行的命令修改过的数目
terse显示简短的警告信息
warn在转到别的文件时若没保存当前文件则显示 NO write 信息
nomagic允许在搜索模式中,使用前面不带 “\” 的特殊字符
nowrapscan禁止 vi 在搜索到达文件两端时,又从另一端开始
mesg允许 vi 显示其他用户用 write 写到自己终端上的信息
:set number / set nonumber显示 / 不显示行号
:set ruler /set noruler显示 / 不显示标尺
:set hlsearch高亮显示查找到的单词
:set nohlsearch关闭高亮显示
:syntax on语法高亮
:set nu显示行号
:set tabstop=8设置 tab 大小, 8 为最常用最普遍的设置
:set softtabstop=84:4 个空格, 8: 正常的制表符, 12: 一个制表符 4 个空格, 16: 两个制表符
:set autoindent自动缩进
:set cindentC 语言格式里面的自动缩进

移动光标

命令描述
k nk上 向上移动 n 行
j nj下 向下移动 n 行
h nh左 向左移动 n 行
l nl右 向右移动 n 行
Space光标右移一个字符
Backspace光标左移一个字符
Enter光标下移一行
w/W光标右移一个字至字首
b/B光标左移一个字至字首
e 或 E光标右移一个字至字尾
)光标移至句尾
(光标移至句首
}光标移至段落开头
{光标移至段落结尾
n$光标移至第 n 行尾
H光标移至屏幕顶行
M光标移至屏幕中间行
L光标移至屏幕最后行
0(注意是数字零)光标移至当前行首
^移动光标到行首第一个非空字符上去
$光标移至当前行尾
gg移到第一行
G移到最后一行
f移动光标到当前行的字符 a 上
F相反
%移动到与制匹配的括号上去(),{},[],<> 等
nG移动到第 n 行上
G到最后一行

屏幕滚动

命令描述
Ctrl+u向文件首翻半屏
Ctrl+d向文件尾翻半屏
Ctrl+f向文件尾翻一屏
Ctrl+b向文件首翻一屏
nz将第 n 行滚至屏幕顶部,不指定 n 时将当前行滚至屏幕顶部

插入文本类

命令描述
i在光标前
I在当前行首
a光标后
A在当前行尾
o在当前行之下新开一行
O在当前行之上新开一行
r替换当前字符
R替换当前字符及其后的字符,直至按 ESC 键
s从当前光标位置处开始,以输入的文本替代指定数目的字符
S删除指定数目的行,并以所输入文本代替之
ncw/nCW修改指定数目的字
nCC修改指定数目的行

删除命令

命令描述
x/X删除一个字符,x 删除光标后的,而 X 删除光标前的
dw删除一个单词 (删除光标位置到下一个单词开始的位置)
dnw删除 n 个单词
dne也可,只是删除到单词尾
do删至行首
d$删至行尾
dd删除一行
ndd删除当前行及其后 n-1 行
dnl向右删除 n 个字母
dnh向左删除 n 个字母
dnj向下删除 n 行, 当前行 + 其上 n 行
dnk向上删除 n 行, 当期行 + 其下 n 行
cnw[word]将 n 个 word 改变为 word
C$改变到行尾
cc改变整行
shift+j删除行尾的换行符,下一行接上来了

复制粘贴

命令描述
p粘贴用 x 或 d 删除的文本
ynw复制 n 个单词
yy复制一行
ynl复制 n 个字符
y$复制当前光标至行尾处
nyy拷贝 n 行

撤销

命令描述
u撤销前一次的操作
shif+u(U)撤销对该行的所有操作

搜索及替换

命令描述
/pattern从光标开始处向文件尾搜索 pattern
?pattern从光标开始处向文件首搜索 pattern
n在同一方向重复上一次搜索命令
N在反方向上重复上一次搜索命令
cw newword替换为 newword
n继续查找
.执行替换
:s/p1/p2/g将当前行中所有 p1 均用 p2 替代, g 表示执行 用 c 表示需要确认
:n1,n2 s/p1/p2/g将第 n1 至 n2 行中所有 p1 均用 p2 替代
:g/p1/s//p2/g将文件中所有 p1 均用 p2 替换
:1,$ s/string1/string2/g在全文中将 string1 替换为 string2

书签

命令描述
m[a-z]在文中做标记,标记号可为 a-z 的 26 个字母
`a移动到标记 a 处

visual 模式

命令描述
v进入 visual 模式
V进入行的 visual 模式
ctrl+v进如块操作模式用 o 和 O 改变选择的边的大小
在所有行插入相同的内容如 include<将光标移到开始插入的位置,按 CTRL+V 进入 VISUAL 模式,选择好模块后按 I(shift+i),后插入要插入的文本,按 [ESC] 完成

行方式命令

命令描述
:n1,n2 co n3 或者 :n1,n2 copy n3将 n1 行到 n2 行之间的内容拷贝到第 n3 行下
:n1,n2 m n3 或者 :n1,n2 move n3将 n1 行到 n2 行之间的内容移至到第 n3 行下
:n1,n2 d将 n1 行到 n2 行之间的内容删除
:n1,n2 w!command将文件中 n1 行至 n2 行的内容作为 command 的输入并执行之
若不指定 n1,n2,则表示将整个文件内容作为 command 的输入

命令描述
q[a-z]开始记录但前开始的操作为宏,名称可为【a-z】,然后用 q 终止录制宏
reg显示当前定义的所有的宏,用 @[a-z] 来在当前光标处执行宏 [a-z]

窗口操作

命令描述
:split分割一个窗口
:split file.c为另一个文件 file.c 分隔窗口
:nsplit file.c为另一个文件 file.c 分隔窗口,并指定其行数
ctrl+w在窗口中切换
:close关闭当前窗口

文件及其他

命令描述
:q退出 vi
:q!不保存文件并退出 vi
:e filename打开文件 filename 进行编辑
:e!放弃修改文件内容,重新载入该文件编辑
:w保存当前文件
:wq存盘退出
:ZZ保存当前文档并退出 VIM
:!command执行 shell 命令 command
:r!command将命令 command 的输出结果放到当前行
:n1,n2 write temp.c
:read file.c将文件 file.c 的内容插入到当前光标所在的下面

常用正则

  • 删除行尾空格::%s/\s+$//g
  • 删除行首多余空格:%s/^\s*// 或者 %s/^ *//
  • 删除沒有內容的空行:%s/^$// 或者 g/^$/d
  • 删除包含有空格组成的空行:%s/^\s*$// 或者 g/^\s*$/d
  • 删除以空格或TAB开头到结尾的空行:%s/^[ |\t]*$// 或者 g/^[ |\t]*$/d
  • 清空某一行或多行文本::n1,n2 s/\w//g
  • 给一行或多行首字符添加注释:n1,n2 s/^/#/g
  • 给一行或多行首字符删除注释:n1,n2 s/^#//g