经验总结

经验总结

Yii2框架如何重写POST或者GET参数?

回复

Yii框架zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 15 次浏览 • 2019-01-11 16:08 • 来自相关话题

MySql如何将13位的时间戳展示成易读格式?

回复

数据库zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 21 次浏览 • 2019-01-10 19:20 • 来自相关话题

PHP如何计算两个经纬度地理位置之间的距离?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 25 次浏览 • 2019-01-04 19:24 • 来自相关话题

PHP如何在数据的头部插入新元素?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 37 次浏览 • 2018-12-19 17:50 • 来自相关话题

PHP如何在数组特定位置处插入新元素?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 49 次浏览 • 2018-12-19 17:43 • 来自相关话题

服务保障经验谈之服务熔断

架构思想zkbhj 发表了文章 • 0 个评论 • 40 次浏览 • 2018-12-18 10:56 • 来自相关话题

什么是服务熔断?

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

这种牺牲局部,保全整体的措施就叫做熔断。

如果不采取熔断措施,我们的系统会怎样呢?我们来看一个栗子。

当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游。它们的调用链如下:





 
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮。




像这种调用链路的连锁故障,叫做雪崩。

正所谓刮骨疗毒,壮士断腕。在这种时候,就需要我们的熔断机制来挽救整个系统。熔断机制的大体流程和刚才所讲的考试策略如出一辙:





 
这里需要解释两点:

1.开启熔断

在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。


2.熔断回复

熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用。
 
更多服务降级熔断限流,参考:https://www.cnblogs.com/raosha ... .html 查看全部
什么是服务熔断?

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

这种牺牲局部,保全整体的措施就叫做熔断。

如果不采取熔断措施,我们的系统会怎样呢?我们来看一个栗子。

当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游。它们的调用链如下:

20180507103456708.png

 
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮。
20180507103502138.png

像这种调用链路的连锁故障,叫做雪崩

正所谓刮骨疗毒,壮士断腕。在这种时候,就需要我们的熔断机制来挽救整个系统。熔断机制的大体流程和刚才所讲的考试策略如出一辙:

20180507103507143.png

 
这里需要解释两点:

1.开启熔断

在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。


2.熔断回复

熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用。
 
更多服务降级熔断限流,参考:https://www.cnblogs.com/raosha ... .html

给小米手机线刷新系统时,出现‘err:'MySQL' 不是内部或外部命令’问题怎么解决?

回复

工具软件zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 44 次浏览 • 2018-12-13 16:22 • 来自相关话题

Linux执行shell脚本报Could not open input file什么原因?

回复

服务器zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 70 次浏览 • 2018-12-06 16:31 • 来自相关话题

CSV文件数据导入MySQL数据库时中文乱码如何解决?

回复

数据库zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 59 次浏览 • 2018-12-04 10:13 • 来自相关话题

PHP如何运行时设置错误报告输出到页面上?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 80 次浏览 • 2018-11-21 14:35 • 来自相关话题

条新动态, 点击查看
zkbhj

zkbhj 回答了问题 • 2017-06-02 10:43 • 1 个回复 不感兴趣

如何生成全局唯一ID并能大致有序?

赞同来自:

Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同。

Snowflake算法核... 显示全部 »
Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同。

Snowflake算法核心

把时间戳,工作机器id,序列号组合在一起。

133
 
除了最高位bit标记为不可用以外,其余三组bit占位均可浮动,看具体的业务需求而定。默认情况下41bit的时间戳可以支持该算法使用到2082年,10bit的工作机器id可以支持1023台机器,序列号支持1毫秒产生4095个自增序列id。下文会具体分析。
 结构为:
0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---0000000000 00
在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。

这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。 
/**
* ID 生成策略
* 毫秒级时间41位+机器ID 10位+毫秒内序列12位。
* 0 41 51 64
+-----------+------+------+
|time |pc |inc |
+-----------+------+------+
* 最高位bit标记为不可用
* 前41bits是以微秒为单位的timestamp。
* 接着10bits是事先配置好的机器ID。
* 最后12bits是累加计数器。
* macheine id(10bits)标明最多只能有1024台机器同时产生ID,sequence number(12bits)也标明1台机器1ms中最多产生4096个ID,
*
*/
class SnowFlake{
private static $epoch = 1462264156000;


public function createID($machineId){
/*
* Time - 41 bits
*/
$time = floor(microtime(true) * 1000);


/*
* Substract custom epoch from current time
*/
$time -= SnowFlake::$epoch;

/*
* flag number - 1 bits - can not change, beacause is shoule be a positive number
*/
$suffix = 0;

/*
* Create a base and add time to it
*/
$base = decbin(pow(2,40) - 1 + $time);
//$base = sprintf("1s", decbin($time));
/*
* Configured machine id - 10 bits - up to 512 machines
*/
$machineid = decbin(pow(2,9) - 1 + $machineId);
//$machineid = sprintf("0s", decbin($machineId));
/*
* sequence number - 12 bits - up to 2048 random numbers per machine
*/
$random = mt_rand(1, pow(2,11)-1);
$random = decbin(pow(2,11)-1 + $random);
//$random = mt_rand(1, pow(2, 12) - 1);
//$random = sprintf("2s", decbin($random));

/*
* 拼装$base
*/
$base = $suffix.$base.$machineid.$random;
/*
* 讲二进制的base转换成long
*/
$base = bindec($base);


$id = sprintf('%.0f', $base);


return $id;
}
}
我这里的PHP代码序列号是随机产生的,因为我们的业务还不达不到需要有序生成的必要, 而且对于PHP来说要序列化生成必须把这个序列存储到缓存里面去。
 
GIT源码:https://github.com/search?l=PHP&q=snowflake&type=Repositories&utf8=%E2%9C%93

PHP用wkhtmltopdf及扩展实现网页生成图片或PDF

PHPzkbhj 发表了文章 • 0 个评论 • 678 次浏览 • 2017-11-21 10:55 • 来自相关话题

我们经常会在实际的项目开发中遇到这种需求,将一个特定的网页生成图片,用于分享等用途。这时候,我们可以用这个PHP扩展,很方便的将网页转换生成为各种格式的图片或者pdf文件。下面就来看下如何实现?

本教程的主角是wkhtmltopdf,下面就先简单了解下它:

wkhtmltopdf 是一个开源、简单而有效的命令行 shell 程序,它可以将任何 HTML (网页)转换为 PDF 文档或图像(jpg、png 等)。

wkhtmltopdf 是用 C++ 编写的,并在 GNU/GPL (通用公共许可证)下发布。它使用 WebKit 渲染引擎将 HTML 页面转换为 PDF 文档且不会丢失页面的质量。这是一个用于实时创建和存储网页快照的非常有用且可信赖的解决方案。

 
这个程序的功能特点有以下几点:
开源并且跨平台。使用 WebKit 引擎将任意 HTML 网页转换为 PDF 文件。添加页眉和页脚的选项目录生成 (TOC) 选项。提供批量模式转换。通过绑定 libwkhtmltox 来支持 PHP 或 Python。





 
首先,我们需要在我们的服务器上安装 libwkhtmltox,linux下的webkit内核,根据自己的服务器配置,选择合适的安装包:

需要说明的是,libwkhtmltox现在的版本已经是0.13了,但是本人在CentOS 6.2下安装了libwkhtmltox-0.11之后,在将网页转换成图片时会出错,最终选择了0.10版本,这个版本还是相当稳定的。

13版本安装方法:wget https://bitbucket.org/wkhtmlto ... 4.rpm然后执行:[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
如果安装过程中出现下列报错,可以通过下面方法解决,安装缺失的依赖即可![root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
error: Failed dependencies:
icu is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64
xorg-x11-fonts-75dpi is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64









[root@KaiBoss_4_45 ~]# yum search icu
[root@KaiBoss_4_45 ~]# yum install icu.x86_64

[root@KaiBoss_4_45 ~]# yum search 75dpi
[root@KaiBoss_4_45 ~]# yum install xorg-x11-fonts-75dpi.noarch









[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm
Preparing... ########################################### [100%]
1:wkhtmltox ########################################### [100%]
[root@KaiBoss_4_45 ~]#









看到如上结果,就代表安装成功了!
 
10稳定版本安装方式:[root@KaiBoss_4_45 ~]#mkdir libwkhtmtox
[root@KaiBoss_4_45 ~]#cd libwkhtmtox
[root@KaiBoss_4_45 libwkhtmtox]# tar jxvf libwkhtmltox-0.10.0_rc2-amd64.tar.bz2
[root@KaiBoss_4_45 libwkhtmtox]# cd lib
[root@KaiBoss_4_45 lib]# cp libwkhtmltox.so /usr/local/lib/
[root@KaiBoss_4_45 lib]# cd ../include/
[root@KaiBoss_4_45 include]# cp -R wkhtmltox /usr/local/include/






 
接下来,需要安装phpwkhtmltox, php扩展,可调用webkit内核将网页转换成各种格式图片或者pdf。
这里需要注意,如果是PHP7,需要安装PHP7版本对应的扩展包。[root@KaiBoss_4_45 ~]#unzip php7-wkhtmltox-master.zip
[root@KaiBoss_4_45 ~]#cd php7-wkhtmltox-master
[root@KaiBoss_4_45 ~]#phpize
[root@KaiBoss_4_45 ~]#./configure
[root@KaiBoss_4_45 ~]#make install





 接下来,我们修改们的php.ini配置文件,将扩展文件加进来:extension=phpwkhtmltox.so 然后,重新加载系统动态链接库(重要,否则PHP无法完成扩展phpwkhtmltox),并检查是否添加成功:[root@platform tmp]#ldconfig
[root@platform tmp]#php -m然后,就可以写一个php脚本,来测试功能啦:
 
测试生成图片:<?php
wkhtmltox_convert(
'image',
array(
'out' => 'zk_pic.jpg',
'in' => 'http://www.baidu.com/'
)
);
?>
下面是百度网页的生成结果











 测试生成pdf:<?php
wkhtmltox_convert('pdf',
array(
'out' => '/tmp/zk_pdf.pdf',
'imageQuality' => '95'
), // global settings
array(
array(
'page' => 'http://www.ziroom.com/'
),
array(
'page' => 'http://www.baidu.com/'
)
)// object settings
);
?>
如果发现生成的图片或pdf中,中文是乱码,则需要让CentOS支持中文:[root@KaiBoss_4_45 ~]#yum groupinstall chinese-support把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如msyh[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/
[root@KaiBoss_4_45 ~]#mkdir msyh
[root@KaiBoss_4_45 ~]#cd msyh建立字体缓存[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv
PS:让Linux CentOS支持Consolas字体(技术类博客可能会发布一个示例代码,大部分wordpress技术博客都会安装SyntaxHighlighter插件,而该插件代码显示字体首选的是Consolas字体,所以为了html转换成图片时示例代码显示得好看,我们也需要让linux支持Consolas字体)
 
下载或者从本地windows拷贝Consolas(注意从windows系统里面拷贝出来应该是有4个文件),并上传到linux服务器。把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如Consolas[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/
[root@KaiBoss_4_45 ~]#mkdir Consolas
[root@KaiBoss_4_45 ~]#cd Consolas
建立字体缓存[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv
大功告成!
 
wkhtmtox官网:https://wkhtmltopdf.org/
 
所有历史版本下载:https://github.com/wkhtmltopdf ... ME.md

wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64 下载地址(百度网盘):https://pan.baidu.com/s/1mi3P9g8 
 
稳定版0.10版本libwkhtmltox-0.10.0_rc2-amd64.tar.bz2 下载地址(百度网盘):https://pan.baidu.com/s/1c2B74PU

(非PHP7)php-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1jHHkFTO
 
(PHP7)php7-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1i5J4Mhf 查看全部
我们经常会在实际的项目开发中遇到这种需求,将一个特定的网页生成图片,用于分享等用途。这时候,我们可以用这个PHP扩展,很方便的将网页转换生成为各种格式的图片或者pdf文件。下面就来看下如何实现?

本教程的主角是wkhtmltopdf,下面就先简单了解下它:


wkhtmltopdf 是一个开源、简单而有效的命令行 shell 程序,它可以将任何 HTML (网页)转换为 PDF 文档或图像(jpg、png 等)。

wkhtmltopdf 是用 C++ 编写的,并在 GNU/GPL (通用公共许可证)下发布。它使用 WebKit 渲染引擎将 HTML 页面转换为 PDF 文档且不会丢失页面的质量。这是一个用于实时创建和存储网页快照的非常有用且可信赖的解决方案。


 
这个程序的功能特点有以下几点:
  1. 开源并且跨平台。
  2. 使用 WebKit 引擎将任意 HTML 网页转换为 PDF 文件。
  3. 添加页眉和页脚的选项
  4. 目录生成 (TOC) 选项。
  5. 提供批量模式转换。
  6. 通过绑定 libwkhtmltox 来支持 PHP 或 Python。


banner.jpg

 
首先,我们需要在我们的服务器上安装 libwkhtmltox,linux下的webkit内核,根据自己的服务器配置,选择合适的安装包:


需要说明的是,libwkhtmltox现在的版本已经是0.13了,但是本人在CentOS 6.2下安装了libwkhtmltox-0.11之后,在将网页转换成图片时会出错,最终选择了0.10版本,这个版本还是相当稳定的。


13版本安装方法:
wget https://bitbucket.org/wkhtmlto ... 4.rpm
然后执行:
[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm

如果安装过程中出现下列报错,可以通过下面方法解决,安装缺失的依赖即可!
[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm 
error: Failed dependencies:
icu is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64
xorg-x11-fonts-75dpi is needed by wkhtmltox-1:0.13.0_alpha_7b36694-1.x86_64









[root@KaiBoss_4_45 ~]# yum search icu
[root@KaiBoss_4_45 ~]# yum install icu.x86_64

[root@KaiBoss_4_45 ~]# yum search 75dpi
[root@KaiBoss_4_45 ~]# yum install xorg-x11-fonts-75dpi.noarch









[root@KaiBoss_4_45 ~]# rpm -ivh wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64.rpm 
Preparing... ########################################### [100%]
1:wkhtmltox ########################################### [100%]
[root@KaiBoss_4_45 ~]#









看到如上结果,就代表安装成功了!
 
10稳定版本安装方式:
[root@KaiBoss_4_45 ~]#mkdir libwkhtmtox
[root@KaiBoss_4_45 ~]#cd libwkhtmtox
[root@KaiBoss_4_45 libwkhtmtox]# tar jxvf libwkhtmltox-0.10.0_rc2-amd64.tar.bz2
[root@KaiBoss_4_45 libwkhtmtox]# cd lib
[root@KaiBoss_4_45 lib]# cp libwkhtmltox.so /usr/local/lib/
[root@KaiBoss_4_45 lib]# cd ../include/
[root@KaiBoss_4_45 include]# cp -R wkhtmltox /usr/local/include/






 
接下来,需要安装phpwkhtmltox, php扩展,可调用webkit内核将网页转换成各种格式图片或者pdf。
这里需要注意,如果是PHP7,需要安装PHP7版本对应的扩展包。
[root@KaiBoss_4_45 ~]#unzip php7-wkhtmltox-master.zip
[root@KaiBoss_4_45 ~]#cd php7-wkhtmltox-master
[root@KaiBoss_4_45 ~]#phpize
[root@KaiBoss_4_45 ~]#./configure
[root@KaiBoss_4_45 ~]#make install


QQ截图20171121115044.jpg

 接下来,我们修改们的php.ini配置文件,将扩展文件加进来:
extension=phpwkhtmltox.so 
然后,重新加载系统动态链接库(重要,否则PHP无法完成扩展phpwkhtmltox),并检查是否添加成功:
[root@platform tmp]#ldconfig
[root@platform tmp]#php -m
然后,就可以写一个php脚本,来测试功能啦:
 
测试生成图片:
<?php
wkhtmltox_convert(
'image',
array(
'out' => 'zk_pic.jpg',
'in' => 'http://www.baidu.com/'
)
);
?>

下面是百度网页的生成结果

QQ截图20171121161815.jpg


baiducreate.jpg


 测试生成pdf:
<?php
wkhtmltox_convert('pdf',
array(
'out' => '/tmp/zk_pdf.pdf',
'imageQuality' => '95'
), // global settings
array(
array(
'page' => 'http://www.ziroom.com/'
),
array(
'page' => 'http://www.baidu.com/'
)
)// object settings
);
?>

如果发现生成的图片或pdf中,中文是乱码,则需要让CentOS支持中文:
[root@KaiBoss_4_45 ~]#yum groupinstall chinese-support
把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如msyh
[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/
[root@KaiBoss_4_45 ~]#mkdir msyh
[root@KaiBoss_4_45 ~]#cd msyh
建立字体缓存
[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv

PS:让Linux CentOS支持Consolas字体(技术类博客可能会发布一个示例代码,大部分wordpress技术博客都会安装SyntaxHighlighter插件,而该插件代码显示字体首选的是Consolas字体,所以为了html转换成图片时示例代码显示得好看,我们也需要让linux支持Consolas字体)
 
下载或者从本地windows拷贝Consolas(注意从windows系统里面拷贝出来应该是有4个文件),并上传到linux服务器。把字体文件拷贝到/usr/share/fonts/xxx,其中xxx为新增字体文件夹,如Consolas
[root@KaiBoss_4_45 ~]#cd /usr/share/fonts/
[root@KaiBoss_4_45 ~]#mkdir Consolas
[root@KaiBoss_4_45 ~]#cd Consolas

建立字体缓存
[root@KaiBoss_4_45 ~]#mkfontscale
[root@KaiBoss_4_45 ~]#mkfontdir
[root@KaiBoss_4_45 ~]#fc-cache -fv

大功告成!
 
wkhtmtox官网:https://wkhtmltopdf.org/
 
所有历史版本下载:https://github.com/wkhtmltopdf ... ME.md

wkhtmltox-0.13.0-alpha-7b36694_linux-centos6-amd64 下载地址(百度网盘):https://pan.baidu.com/s/1mi3P9g8 
 
稳定版0.10版本libwkhtmltox-0.10.0_rc2-amd64.tar.bz2 下载地址(百度网盘):https://pan.baidu.com/s/1c2B74PU

(非PHP7)php-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1jHHkFTO
 
(PHP7)php7-wkhtmltox-master 下载地址(百度网盘):https://pan.baidu.com/s/1i5J4Mhf

vim常用命令总结

工具软件zkbhj 发表了文章 • 0 个评论 • 324 次浏览 • 2017-02-16 15:36 • 来自相关话题

 





 vim 选择文本,删除,复制,粘贴

文本的选择,对于编辑器来说,是很基本的东西,也经常被用到,总结如下:

v 从光标当前位置开始,光标所经过的地方会被选中,再按一下v结束。

V 从光标当前行开始,光标经过的行都会被选中,再按一下V结束。

Ctrl + v 从光标当前位置开始,选中光标起点和终点所构成的矩形区域,再按一下Ctrl + v结束。

ggVG 选中全部的文本, 其中gg为跳到行首,V选中整行,G末尾

选中后就可以用编辑命令对其进行编辑,如
d 删除

y 复制 (默认是复制到"寄存器)

p 粘贴 (默认从"寄存器取出内容粘贴)

"+y 复制到系统剪贴板(也就是vim的+寄存器)

"+p 从系统剪贴板粘贴

vim命令总结

1.删除字符

要删除一个字符,只需要将光标移到该字符上按下"x"。

2.删除一行

删除一整行内容使用"dd"命令。删除后下面的行会移上来填补空缺。

3.删除换行符

在Vim中你可以把两行合并为一行,也就是说两行之间的换行符被删除了:命令是"J"。

4.撤销

如果你误删了过多的内容。显然你可以再输入一遍,但是命令"u" 更简便,它可以撤消上一次的操作。

5.重做

如果你撤消了多次,你还可以用CTRL-R(重做)来反转撤消的动作。换句话说,它是对撤消的撤消。撤消命令还有另一种形式,"U"命令,它一次撤消对一行的全部操作。第二次使用该命令则会撤消前一个"U"的操作。用"u"和CTRL-R你可以找回任何一个操作状态。

6.追加

"i"命令可以在当前光标之前插入文本。
"a"命令可以在当前光标之后插入文本。
"o"命令可以在当前行的下面另起一行,并使当前模式转为Insert模式。
"O"命令(注意是大写的字母O)将在当前行的上面另起一行。

7.使用命令计数

假设你要向上移动9行。这可以用"kkkkkkkkk"或"9k"来完成。事实上,很多命令都可以接受一个数字作为重复执行同一命令的次数。比如刚才的例子,要在行尾追加三个感叹号,当时用的命令是"a!!!"。另一个办法是用"3a!"命令。3说明该命令将被重复执行3次。同样,删除3个字符可以用"3x"。指定的数字要紧挨在它所要修饰的命令前面。

8.退出

要退出Vim,用命令"ZZ"。该命令保存当前文件并退出Vim。

9.放弃编辑

丢弃所有的修改并退出,用命令":q!"。用":e!"命令放弃所有修改并重新载入该文件的原始内容。

10.以Word为单位的移动

使用"w"命令可以将光标向前移动一个word的首字符上;比如"3w"将光标向前移动3个words。"b"命令则将光标向后移动到前一个word的首字符上。
"e"命令会将光标移动到下一个word的最后一个字符。命令"ge",它将光标移动到前一个word的最后一个字符上。、

11.移动到行首或行尾

"$"命令将光标移动到当前行行尾。如果你的键盘上有一个键,它的作用也一样。"^"命令将光标移动到当前行的第一个非空白字符上。"0"命令则总是把光标移动到当前行的第一个字符上。键也是如此。"$"命令还可接受一个计数,如"1$"会将光标移动到当前行行尾,"2$"则会移动到下一行的行尾,如此类推。"0"命令却不能接受类似这样的计数,命令"^"前加上一个计数也没有任何效果。

12.移动到指定字符上

命令"fx"在当前行上查找下一个字符x(向右方向),可以带一个命令计数"F"命令向左方向搜索。"tx"命令形同"fx"命令,只不过它不是把光标停留在被搜索字符上,而是在它之前的一个字符上。提示:"t"意为"To"。该命令的反方向版是"Tx"。这4个命令都可以用";"来重复。以","也是重复同样的命令,但是方向与原命令的方向相反。

13.以匹配一个括号为目的移动

用命令"%"跳转到与当前光标下的括号相匹配的那一个括号上去。如果当前光标在"("上,它就向前跳转到与它匹配的")"上,如果当前在")"上,它就向后自动跳转到匹配的"("上去.

14.移动到指定行

用"G"命令指定一个命令计数,这个命令就会把光标定位到由命令计数指定的行上。比如"33G"就会把光标置于第33行上。没有指定命令计数作为参数的话, "G"会把光标定位到最后一行上。"gg"命令是跳转到第一行的快捷的方法。
另一个移动到某行的方法是在命令"%"之前指定一个命令计数比如"50%"将会把光标定位在文件的中间. "90%"跳到接近文件尾的地方。
命令"H","M","L",分别将光标跳转到第一行,中间行,结尾行部分。

15.告诉你当前的位置

使用CTRL-G命令。"set number"在每行的前面显示一个行号。相反关闭行号用命令":set nonumber"。":set ruler"在Vim窗口的右下角显示当前光标位置。

16.滚屏

CTRL-U显示文本的窗口向上滚动了半屏。CTRL-D命令将窗口向下移动半屏。一次滚动一行可以使用CTRL-E(向上滚动)和CTRL-Y(向下滚动)。要向前滚动一整屏使用命令CTRL-F。另外CTRL-B是它的反向版。"zz"命令会把当前行置为屏幕正中央,"zt"命令会把当前行置于屏幕顶端,"zb"则把当前行置于屏幕底端.

17.简单搜索

"/string"命令可用于搜索一个字符串。要查找上次查找的字符串的下一个位置,使用"n"命令。如果你知道你要找的确切位置是目标字符串的第几次出现,还可以在"n"之前放置一个命令计数。"3n"会去查找目标字符串的第3次出现。
"?"命令与"/"的工作相同,只是搜索方向相反."N"命令会重复前一次查找,但是与最初用"/"或"?"指定的搜索方向相反。
如果查找内容忽略大小写,则用命令"set ignorecase", 返回精确匹配用命令"set noignorecase" 。

18.在文本中查找下一个word

把光标定位于这个word上然后按下""键。Vim将会取当前光标所在的word并将它作用目标字符串进行搜索。"#"命令是""的反向版。还可以在这两个命令前加一个命令计数:"3*"查找当前光标下的word的第三次出现。

19.查找整个word

如果你用"/the"来查找Vim也会匹配到"there"。要查找作为独立单词的"the"使用如下命令:"/the>"。">"是一个特殊的记法,它只匹配一个word的结束处。近似地,"\<"匹配到一个word的开始处。这样查找作为一个word的"the"就可以用:"/"。

20.高亮显示搜索结果

开启这一功能用":set hlsearch",关闭这一功能:":set nohlsearch"。如果只是想去掉当前的高亮显示,可以使用下面的命令:":nohlsearch"(可以简写为noh)。

21.匹配一行的开头与结尾

^ 字符匹配一行的开头。$字符匹配一行的末尾。
所以"/was$"只匹配位于一行末尾的单词was,所以"/^was"只匹配位于一行开始的单词was。

22.匹配任何的单字符

.这个字符可以匹配到任何字符。比如"c.m"可以匹配任何前一个字符是c,后一个字符是m的情况,不管中间的字符是什么。

23.匹配特殊字符

放一个反斜杠在特殊字符前面。如果你查找"ter。",用命令"/ter\。"

24.使用标记

当你用"G"命令从一个地方跳转到另一个地方时,Vim会记得你起跳的位置。这个位置在Vim中是一个标记。使用命令" "可以使你跳回到刚才的出发点。命令可以在两点之间来回跳转。CTRL-O命令是跳转到你更早些时间停置光标的位置(提示:O意为older). CTRL-I则是跳回到后来停置光标的更新的位置(提示:I在键盘上位于O前面)。
注:使用CTRL-I 与按下键一样。

25.具名标记

命令"ma"将当前光标下的位置名之为标记"a"。从a到z一共可以使用26个自定义的标记。要跳转到一个你定义过的标记,使用命令" marks "marks就是定义的标记的名字。命令" 'a "使你跳转到a所在行的行首,"a "会精确定位a所在的位置。命令:":marks"用来查看标记的列表。
命令delm!删除所有标记。

26.操作符命令和位移

"dw"命令可以删除一个word,"d4w"命令是删除4个word,依此类推。类似有"d2e"、"d$"。此类命令有一个固定的模式:操作符命令+位移命令。首先键入一个操作符命令。比如"d"是一个删除操作符。接下来是一个位移命。比如"w"。这样任何移动光标命令所及之处,都是命令的作用范围。

27.改变文本

操作符命令是"c",改变命令。它的行为与"d"命令类似,不过在命令执行后会进入Insert模式。比如"cw"改变一个word。或者,更准确地说,它删除一个word并让你置身于Insert模式。
"cc"命令可以改变整行。不过仍保持原来的缩进。
"c$"改变当前光标到行尾的内容。
快捷命令:x 代表dl(删除当前光标下的字符)
X 代表dh(删除当前光标左边的字符)
D 代表d$(删除到行尾的内容)
C 代表c$(修改到行尾的内容)
s 代表cl(修改一个字符)
S 代表cc(修改一整行)
命令"3dw"和"d3w"都是删除3个word。第一个命令"3dw"可以看作是删除一个word的操作执行3次;第二个命令"d3w"是一次删除3个word。这是其中不明显的差异。事实上你可以在两处都放上命令记数,比如,"3d2w"是删除两个word,重复执行3次,总共是6个word。

28.替换单个字符

"r"命令不是一个操作符命令。它等待你键入下一个字符用以替换当前光标下的那个字符。"r"命令前辍以一个命令记数是将多个字符都替换为即将输入的那个字符。要把一个字符替换为一个换行符使用"r"。它会删除一个字符并插入一个换行符。在此处使用命令记数只会删除指定个数的字符:"4r"将把4个字符替换为一个换行符。

29.重复改动

"."命令会重复上一次做出的改动。"."命令会重复你做出的所有修改,除了"u"命令CTRL-R和以冒号开头的命令。"."需要在Normal模式下执行,它重复的是命令,而不是被改动的内容,

30.Visual模式

按"v"可以进入Visual模式。移动光标以覆盖你想操纵的文本范围。同时被选中的文本会以高亮显示。最后键入操作符命令。

31.移动文本

以"d"或"x"这样的命令删除文本时,被删除的内容还是被保存了起来。你还可以用p命令把它取回来。"P"命令是把被去回的内容放在光标之前,"p"则是放在光标之后。对于以"dd"删除的整行内容,"P"会把它置于当前行的上一行。"p"则是至于当前行的后一行。也可以对命令"p"和"P"命令使用命令记数。它的效果是同样的内容被取回指定的次数。这样一来"dd"之后的"3p"就可以把被删除行的3 份副本放到当前位置。
命令"xp"将光标所在的字符与后一个字符交换。

32.复制文本(VIM编辑器内复制)

"y"操作符命令会把文本复制到一个寄存器3中。然后可以用"p"命令把它取回。因为"y"是一个操作符命令,所以你可以用"yw"来复制一个word. 同样可以使用命令记数。如下例中用"y2w"命令复制两个word,"yy"命令复制一整行,"Y"也是复制整行的内容,复制当前光标至行尾的命令是"y$"。

33.文本对象

"diw" 删除当前光标所在的word(不包括空白字符) "daw" 删除当前光标所在的word(包括空白字符)

34.快捷命令

x 删除当前光标下的字符("dl"的快捷命令)
X 删除当前光标之前的字符("dh"的快捷命令)
D 删除自当前光标至行尾的内容("d$"的快捷命令)
dw 删除自当前光标至下一个word的开头
db 删除自当前光标至前一个word的开始
diw 删除当前光标所在的word(不包括空白字符)
daw 删除当前光标所在的word(包括空白字符)
dG 删除当前行至文件尾的内容
dgg 删除当前行至文件头的内容
如果你用"c"命令代替"d"这些命令就都变成更改命令。使用"y"就是yank命令,如此类推。

35.编辑另一个文件

用命令":edit foo.txt",也可简写为":e foo.txt"。

36.文件列表

可以在启动Vim时就指定要编辑多个文件,用命令"vim one.c two.c three.c"。Vim将在启动后只显示第一个文件,完成该文件的编辑后,可以用令:":next"或":n"要保存工作成果并继续下一个文件的编辑,命令:":wnext"或":wn"可以合并这一过程。

37.显示当前正在编辑的文件

用命令":args"。

38.移动到另一个文件

用命令":previous" ":prev"回到上一个文件,合并保存步骤则是":wprevious" ":wprev"。要移到最后一个文件":last",到第一个":first".不过没有":wlast"或者":wfirst"这样的命令。可以在":next"和":previous"命令前面使用一个命令计数。

39.编辑另一个文件列表

不用重新启动Vim,就可以重新定义一个文件列表。命令":args five.c six.c seven.h"定义了要编辑的三个文件。

39.自动存盘

命令":set autowrite","set aw"。自动把内容写回文件: 如果文件被修改过,在每个:next、:rewind、:last、:first、:previous、:stop、:suspend、:tag、:!、:make、CTRL-] 和 CTRL-^命令时进行。
命令":set autowriteall","set awa"。和 'autowrite' 类似,但也适用于":edit"、":enew"、":quit"、":qall"、":exit"、":xit"、":recover" 和关闭 Vim 窗口。置位本选项也意味着 Vim 的行为就像打开 'autowrite' 一样。

40.切换到另一文件

要在两个文件间快速切换,使用CTRL-^。

41.文件标记

以大写字母命名的标记。它们是全局标记,它们可以用在任何文件中。比如,正在编辑"fab1.Java",用命令"50%mF"在文件的中间设置一个名为F的标记。然后在"fab2.java"文件中,用命令"GnB"在最后一行设置名为B的标记。在可以用"F"命令跳转到文件"fab1.java"的半中间。或者编辑另一个文件,"'B"命令会再把你带回文件"fab2.java"的最后一行。
要知道某个标记所代表的位置是什么,可以将该标记的名字作为"marks"命令的参数":marks M"或者连续跟上几个参数":marks MJK"
可以用CTRL-O和CTRL-I可以跳转到较早的位置和靠后的某位置。

42.查看文件

仅是查看文件,不向文件写入内容,可以用只读形式编辑文件。用命令:
vim -R file。如果是想强制性地避免对文件进行修改,可以用命令:
vim -M file。

43.更改文件名

将现有文件存成新的文件,用命令":sav(eas) move.c"。如果想改变当前正在编辑的文件名,但不想保存该文件,就可以用命令:":f(ile) move.c"。

44.分割一个窗口

打开一个新窗口最简单的办法就是使用命令:":split"。CTRL-W 命令可以切换当前活动窗口。

45.关闭窗口

用命令:"close".可以关闭当前窗口。实际上,任何退出文件编辑的命令":quit"和"ZZ"都会关闭窗口,但是用":close"可以阻止你关闭最后一个Vim,以免以意外地整个关闭了Vim。

46.关闭除当前窗口外的所有其他窗口

用命令:":only",关闭除当前窗口外的所有其它窗口。如果这些窗口中有被修改过的,你会得到一个错误信息,同时那个窗口会被留下来。

47.为另一个文件分隔出一个窗口

命令":split two.c"可以打开第二个窗口同时在新打开的窗口中开始编辑作为
参数的文件。如果要打开一个新窗口并开始编辑一个空的缓冲区,使用命令:":new"。

48.垂直分割

用命令":vsplit或::vsplit two.c"。同样有一个对应的":vnew"命令,用于垂直分隔窗口并在其中打开一个新的空缓冲区。

49.切换窗口

CTRL-W h 到左边的窗口
CTRL-W j 到下面的窗口
CTRL-W k 到上面的窗口
CTRL-W l 到右边的窗口
CTRL-W t 到顶部窗口
CTRL-W b 到底部窗口

50.针对所有窗口操作的命令

":qall"放弃所有操作并退出,":wall"保存所有,":wqall"保存所有并退出。

51.为每一个文件打开一个窗口

使用"-o"选项可以让Vim为每一个文件打开一个窗口:
"vim -o one.txt two.txt three.txt"。

52.使用vimdiff查看不同

"vimdiff main.c~ main.c",另一种进入diff模式的办法可以在Vim运行中操作。编辑文件"main.c",然后打开另一个分隔窗口显示其不同:
":edit main.c"
":vertical diffpatch main.c.diff"。

53.页签

命令":tabe(dit) thatfile"在一个窗口中打开"thatfile",该窗口占据着整个的Vim显示区域。命令":tab split/new"结果是新建了一个拥有一个窗口的页签。以用"gt"命令在不同的页签间切换。

这是我总结的一些基本用法,可能对初用者会有帮助,独乐乐不如众乐乐,是吧!

说明:以下黑色为vi和vim均有的一般功能,而红色为Vim(Vi Improved)所特有功能。Vim一般的Unix和Linux下均有安装。

三种状态

Command: 任何输入都会作为编辑命令,而不会出现在屏幕上,任何输入都引起立即反映
Insert: 任何输入的数据都置于编辑寄存器,按ESC,可跳回command方式
Escape: 以“:”或者“/”为前导的指令,出现在屏幕的最下一行,任何输入都被当成特别指令。

离开vi

:q! 离开vi,并放弃刚在缓冲区内编辑的内容。
:wq 将缓冲区内的资料写入磁盘中,并离开vi。
:x 同wq。
(注意—— :X 是文件加密,一定要与:x存盘退出相区别)

进入输入模式

a (append) 由游标之后加入资料。
A 由该行之末加入资料。
i (insert) 由游标之前加入资料。
I 由该行之首加入资料。
o (open) 新增一行於该行之下供输入资料之用。
O 新增一行於该行之上供输入资料之用。

删除与修改

x 删除游标所在该字元。
X 删除游标所在之前一字元。
r 用接於此指令之后的字元取代(replace)游标所在字元。如:ra将游标所在字元以 a 取代之。
R 进入取代状态,直到《ESC》为止。
s 删除游标所在之字元,并进入输入模式直到《ESC》。
S 删除游标所在之该行资料,并进入输入模式直到《ESC》。

光标的移动

0 移至该行之首
$ 移至该行之末。
e 移动到下个字的最後一个字母
w 移动到下个字的第一个字母。
b 移动到上个字的第一个字母。
^ 移至该行的第一个字元处。
H 移至视窗的第一行。
M 移至视窗的中间那行。
L 移至视窗的最后一行。
G 移至该文件的最后一行。
+ 移至下一列的第一个字元处。
- 移至上一列的第一个字元处。
:n 移至该文件的第 n 列。
n+ 移至游标所在位置之后的第 n 列。
n- 移至游标所在位置之前的第 n 列。
Ctrl+g 显示该行之行号、文件名称、文件中最末行之行号、游标所在行号占总行号之百分比。

视窗的移动

Ctrl+f 视窗往下卷一页。
Ctrl+b 视窗往上卷一页。
Ctrl+d 视窗往下卷半页。
Ctrl+u 视窗往上卷半页。
Ctrl+e 视窗往下卷一行。
Ctrl+y 视窗往上卷一行。

剪切、复制、删除

Operator + Scope = command
Operator
d 剪切
y 复制。
p 粘帖,与 d 和 y 配和使用。可将最后d或y的资料放置於游标所在位置之行列下。
c 修改,类似delete与insert的组和。删除一个字组、句子等之资料,并插入新建资料。
Scope
e 由游标所在位置至该字串的最后一个字元。
w 由游标所在位置至下一个字串的第一个字元。
b 由游标所在位置至前一个字串的第一个字元。
$ 由游标所在位置至该行的最后一个字元。
0 由游标所在位置至该行的第一个字元。

整行动作

dd 删除整行。
D 以行为单位,删除游标后之所有字元。
cc 修改整行的内容。
yy 使游标所在该行复制到记忆体缓冲区。

取消前一动作(Undo)

u 恢复最后一个指令之前的结果。
U 恢复游标该行之所有改变。
u 可以多次撤消指令,一次撤消一个操作,直至本次操作开始为止。
Ctrl+r 可以恢复撤消前内容,按多次可恢复多次。

查找与替换

/字串 往游标之后寻找该字串。
?字串 往游标之前寻找该字串。
n 往下继续寻找下一个相同的字串。
N 往上继续寻找下一个相同的字串。
% 查找“(”,“)”,“{”,“}”的配对符。
s 搜寻某行列范围。
g 搜寻整个编辑缓冲区的资料。
:1,$s/old/new/g 将文件中所有的『old』改成『new』。
:10,20s/^/ / 将第10行至第20行资料的最前面插入5个空白。
(vim)
/字符串 后边输入查询内容可保存至缓冲区中,可用↑↓进行以往内容选择。
另外:将光标移动在选定单词下方按*,则可以选中此单词作为查询字符,可以避免输入一长串字符的麻烦。

大小写替换

首先用按v开启选择功能,然后用↑↓←→键来选定所要替换的字符,若是小写变大写,则按U;反之按u;
如果是选择单词,则可以在按v后,按w,最后按U/u,这样就可以将字符随意的改变大小写了,而不用删除后重新敲入。

资料的连接

句子的连接。将游标所在之下一行连接至游标该行的后面。
环境的设定
:set all 可设置的环境变量列表
:set 环境变量的当前值
:set nu 设定资料的行号。
:set nonu 取消行号设定。
:set ai 自动内缩。
:set noai 取消自动内缩。
:set ruler 会在屏幕右下角显示当前光标所处位置,并随光移动而改变,占用屏幕空间较小,使用也比较方便,推荐使用。
:set hlsearch 在使用查找功能时,会高亮显示所有匹配的内容。
:set nohlsearch 关闭此功能。
:set incsearch 使Vim在输入字符串的过程中,光标就可定位显示匹配点。
:set nowrapscan 关闭查找自动回环功能,即查找到文件结尾处,结束查找;默认状态是自动回环

ex指令
读写资料

:10,20w test 将第10行至第20行的资料写入test文件。
:10,20w>>test 将第10行至第20行的资料加在test文件之后。
:r test 将test文件的资料读入编辑缓冲区的最后。
:e [filename] 编辑新的文件。
:e! [filename] 放弃当前修改的文件,编辑新的文件。
:sh 进入shell环境,使用exit退出,回到编辑器中。

:!cmd 运行命令cmd后,返回到编辑器中。

删除、复制及搬移

:10,20d 删除第10行至第20行的资料。
:10d 删除第10行的资料。
:%d 删除整个编辑缓冲区。
:10,20co30 将第10行至第20行的资料复制至第30行之后。
:10,20mo30 将第10行至第20行的资料搬移至第30行之后。 查看全部

555482-20161202001047365-2061100822.jpg

 

555482-20161202000652615-1023421454.png

 vim 选择文本,删除,复制,粘贴

文本的选择,对于编辑器来说,是很基本的东西,也经常被用到,总结如下:

v 从光标当前位置开始,光标所经过的地方会被选中,再按一下v结束。

V 从光标当前行开始,光标经过的行都会被选中,再按一下V结束。

Ctrl + v 从光标当前位置开始,选中光标起点和终点所构成的矩形区域,再按一下Ctrl + v结束。

ggVG 选中全部的文本, 其中gg为跳到行首,V选中整行,G末尾

选中后就可以用编辑命令对其进行编辑,如
d 删除

y 复制 (默认是复制到"寄存器)

p 粘贴 (默认从"寄存器取出内容粘贴)

"+y 复制到系统剪贴板(也就是vim的+寄存器)

"+p 从系统剪贴板粘贴

vim命令总结

1.删除字符

要删除一个字符,只需要将光标移到该字符上按下"x"。

2.删除一行

删除一整行内容使用"dd"命令。删除后下面的行会移上来填补空缺。

3.删除换行符

在Vim中你可以把两行合并为一行,也就是说两行之间的换行符被删除了:命令是"J"。

4.撤销

如果你误删了过多的内容。显然你可以再输入一遍,但是命令"u" 更简便,它可以撤消上一次的操作。

5.重做

如果你撤消了多次,你还可以用CTRL-R(重做)来反转撤消的动作。换句话说,它是对撤消的撤消。撤消命令还有另一种形式,"U"命令,它一次撤消对一行的全部操作。第二次使用该命令则会撤消前一个"U"的操作。用"u"和CTRL-R你可以找回任何一个操作状态。

6.追加

"i"命令可以在当前光标之前插入文本。
"a"命令可以在当前光标之后插入文本。
"o"命令可以在当前行的下面另起一行,并使当前模式转为Insert模式。
"O"命令(注意是大写的字母O)将在当前行的上面另起一行。

7.使用命令计数

假设你要向上移动9行。这可以用"kkkkkkkkk"或"9k"来完成。事实上,很多命令都可以接受一个数字作为重复执行同一命令的次数。比如刚才的例子,要在行尾追加三个感叹号,当时用的命令是"a!!!"。另一个办法是用"3a!"命令。3说明该命令将被重复执行3次。同样,删除3个字符可以用"3x"。指定的数字要紧挨在它所要修饰的命令前面。

8.退出

要退出Vim,用命令"ZZ"。该命令保存当前文件并退出Vim。

9.放弃编辑

丢弃所有的修改并退出,用命令":q!"。用":e!"命令放弃所有修改并重新载入该文件的原始内容。

10.以Word为单位的移动

使用"w"命令可以将光标向前移动一个word的首字符上;比如"3w"将光标向前移动3个words。"b"命令则将光标向后移动到前一个word的首字符上。
"e"命令会将光标移动到下一个word的最后一个字符。命令"ge",它将光标移动到前一个word的最后一个字符上。、

11.移动到行首或行尾

"$"命令将光标移动到当前行行尾。如果你的键盘上有一个键,它的作用也一样。"^"命令将光标移动到当前行的第一个非空白字符上。"0"命令则总是把光标移动到当前行的第一个字符上。键也是如此。"$"命令还可接受一个计数,如"1$"会将光标移动到当前行行尾,"2$"则会移动到下一行的行尾,如此类推。"0"命令却不能接受类似这样的计数,命令"^"前加上一个计数也没有任何效果。

12.移动到指定字符上

命令"fx"在当前行上查找下一个字符x(向右方向),可以带一个命令计数"F"命令向左方向搜索。"tx"命令形同"fx"命令,只不过它不是把光标停留在被搜索字符上,而是在它之前的一个字符上。提示:"t"意为"To"。该命令的反方向版是"Tx"。这4个命令都可以用";"来重复。以","也是重复同样的命令,但是方向与原命令的方向相反。

13.以匹配一个括号为目的移动

用命令"%"跳转到与当前光标下的括号相匹配的那一个括号上去。如果当前光标在"("上,它就向前跳转到与它匹配的")"上,如果当前在")"上,它就向后自动跳转到匹配的"("上去.

14.移动到指定行

用"G"命令指定一个命令计数,这个命令就会把光标定位到由命令计数指定的行上。比如"33G"就会把光标置于第33行上。没有指定命令计数作为参数的话, "G"会把光标定位到最后一行上。"gg"命令是跳转到第一行的快捷的方法。
另一个移动到某行的方法是在命令"%"之前指定一个命令计数比如"50%"将会把光标定位在文件的中间. "90%"跳到接近文件尾的地方。
命令"H","M","L",分别将光标跳转到第一行,中间行,结尾行部分。

15.告诉你当前的位置

使用CTRL-G命令。"set number"在每行的前面显示一个行号。相反关闭行号用命令":set nonumber"。":set ruler"在Vim窗口的右下角显示当前光标位置。

16.滚屏

CTRL-U显示文本的窗口向上滚动了半屏。CTRL-D命令将窗口向下移动半屏。一次滚动一行可以使用CTRL-E(向上滚动)和CTRL-Y(向下滚动)。要向前滚动一整屏使用命令CTRL-F。另外CTRL-B是它的反向版。"zz"命令会把当前行置为屏幕正中央,"zt"命令会把当前行置于屏幕顶端,"zb"则把当前行置于屏幕底端.

17.简单搜索

"/string"命令可用于搜索一个字符串。要查找上次查找的字符串的下一个位置,使用"n"命令。如果你知道你要找的确切位置是目标字符串的第几次出现,还可以在"n"之前放置一个命令计数。"3n"会去查找目标字符串的第3次出现。
"?"命令与"/"的工作相同,只是搜索方向相反."N"命令会重复前一次查找,但是与最初用"/"或"?"指定的搜索方向相反。
如果查找内容忽略大小写,则用命令"set ignorecase", 返回精确匹配用命令"set noignorecase" 。

18.在文本中查找下一个word

把光标定位于这个word上然后按下""键。Vim将会取当前光标所在的word并将它作用目标字符串进行搜索。"#"命令是""的反向版。还可以在这两个命令前加一个命令计数:"3*"查找当前光标下的word的第三次出现。

19.查找整个word

如果你用"/the"来查找Vim也会匹配到"there"。要查找作为独立单词的"the"使用如下命令:"/the>"。">"是一个特殊的记法,它只匹配一个word的结束处。近似地,"\<"匹配到一个word的开始处。这样查找作为一个word的"the"就可以用:"/"。

20.高亮显示搜索结果

开启这一功能用":set hlsearch",关闭这一功能:":set nohlsearch"。如果只是想去掉当前的高亮显示,可以使用下面的命令:":nohlsearch"(可以简写为noh)。

21.匹配一行的开头与结尾

^ 字符匹配一行的开头。$字符匹配一行的末尾。
所以"/was$"只匹配位于一行末尾的单词was,所以"/^was"只匹配位于一行开始的单词was。

22.匹配任何的单字符

.这个字符可以匹配到任何字符。比如"c.m"可以匹配任何前一个字符是c,后一个字符是m的情况,不管中间的字符是什么。

23.匹配特殊字符

放一个反斜杠在特殊字符前面。如果你查找"ter。",用命令"/ter\。"

24.使用标记

当你用"G"命令从一个地方跳转到另一个地方时,Vim会记得你起跳的位置。这个位置在Vim中是一个标记。使用命令" "可以使你跳回到刚才的出发点。命令可以在两点之间来回跳转。CTRL-O命令是跳转到你更早些时间停置光标的位置(提示:O意为older). CTRL-I则是跳回到后来停置光标的更新的位置(提示:I在键盘上位于O前面)。
注:使用CTRL-I 与按下键一样。

25.具名标记

命令"ma"将当前光标下的位置名之为标记"a"。从a到z一共可以使用26个自定义的标记。要跳转到一个你定义过的标记,使用命令" marks "marks就是定义的标记的名字。命令" 'a "使你跳转到a所在行的行首,"a "会精确定位a所在的位置。命令:":marks"用来查看标记的列表。
命令delm!删除所有标记。

26.操作符命令和位移

"dw"命令可以删除一个word,"d4w"命令是删除4个word,依此类推。类似有"d2e"、"d$"。此类命令有一个固定的模式:操作符命令+位移命令。首先键入一个操作符命令。比如"d"是一个删除操作符。接下来是一个位移命。比如"w"。这样任何移动光标命令所及之处,都是命令的作用范围。

27.改变文本

操作符命令是"c",改变命令。它的行为与"d"命令类似,不过在命令执行后会进入Insert模式。比如"cw"改变一个word。或者,更准确地说,它删除一个word并让你置身于Insert模式。
"cc"命令可以改变整行。不过仍保持原来的缩进。
"c$"改变当前光标到行尾的内容。
快捷命令:x 代表dl(删除当前光标下的字符)
X 代表dh(删除当前光标左边的字符)
D 代表d$(删除到行尾的内容)
C 代表c$(修改到行尾的内容)
s 代表cl(修改一个字符)
S 代表cc(修改一整行)
命令"3dw"和"d3w"都是删除3个word。第一个命令"3dw"可以看作是删除一个word的操作执行3次;第二个命令"d3w"是一次删除3个word。这是其中不明显的差异。事实上你可以在两处都放上命令记数,比如,"3d2w"是删除两个word,重复执行3次,总共是6个word。

28.替换单个字符

"r"命令不是一个操作符命令。它等待你键入下一个字符用以替换当前光标下的那个字符。"r"命令前辍以一个命令记数是将多个字符都替换为即将输入的那个字符。要把一个字符替换为一个换行符使用"r"。它会删除一个字符并插入一个换行符。在此处使用命令记数只会删除指定个数的字符:"4r"将把4个字符替换为一个换行符。

29.重复改动

"."命令会重复上一次做出的改动。"."命令会重复你做出的所有修改,除了"u"命令CTRL-R和以冒号开头的命令。"."需要在Normal模式下执行,它重复的是命令,而不是被改动的内容,

30.Visual模式

按"v"可以进入Visual模式。移动光标以覆盖你想操纵的文本范围。同时被选中的文本会以高亮显示。最后键入操作符命令。

31.移动文本

以"d"或"x"这样的命令删除文本时,被删除的内容还是被保存了起来。你还可以用p命令把它取回来。"P"命令是把被去回的内容放在光标之前,"p"则是放在光标之后。对于以"dd"删除的整行内容,"P"会把它置于当前行的上一行。"p"则是至于当前行的后一行。也可以对命令"p"和"P"命令使用命令记数。它的效果是同样的内容被取回指定的次数。这样一来"dd"之后的"3p"就可以把被删除行的3 份副本放到当前位置。
命令"xp"将光标所在的字符与后一个字符交换。

32.复制文本(VIM编辑器内复制)

"y"操作符命令会把文本复制到一个寄存器3中。然后可以用"p"命令把它取回。因为"y"是一个操作符命令,所以你可以用"yw"来复制一个word. 同样可以使用命令记数。如下例中用"y2w"命令复制两个word,"yy"命令复制一整行,"Y"也是复制整行的内容,复制当前光标至行尾的命令是"y$"。

33.文本对象

"diw" 删除当前光标所在的word(不包括空白字符) "daw" 删除当前光标所在的word(包括空白字符)

34.快捷命令

x 删除当前光标下的字符("dl"的快捷命令)
X 删除当前光标之前的字符("dh"的快捷命令)
D 删除自当前光标至行尾的内容("d$"的快捷命令)
dw 删除自当前光标至下一个word的开头
db 删除自当前光标至前一个word的开始
diw 删除当前光标所在的word(不包括空白字符)
daw 删除当前光标所在的word(包括空白字符)
dG 删除当前行至文件尾的内容
dgg 删除当前行至文件头的内容
如果你用"c"命令代替"d"这些命令就都变成更改命令。使用"y"就是yank命令,如此类推。

35.编辑另一个文件

用命令":edit foo.txt",也可简写为":e foo.txt"。

36.文件列表

可以在启动Vim时就指定要编辑多个文件,用命令"vim one.c two.c three.c"。Vim将在启动后只显示第一个文件,完成该文件的编辑后,可以用令:":next"或":n"要保存工作成果并继续下一个文件的编辑,命令:":wnext"或":wn"可以合并这一过程。

37.显示当前正在编辑的文件

用命令":args"。

38.移动到另一个文件

用命令":previous" ":prev"回到上一个文件,合并保存步骤则是":wprevious" ":wprev"。要移到最后一个文件":last",到第一个":first".不过没有":wlast"或者":wfirst"这样的命令。可以在":next"和":previous"命令前面使用一个命令计数。

39.编辑另一个文件列表

不用重新启动Vim,就可以重新定义一个文件列表。命令":args five.c six.c seven.h"定义了要编辑的三个文件。

39.自动存盘

命令":set autowrite","set aw"。自动把内容写回文件: 如果文件被修改过,在每个:next、:rewind、:last、:first、:previous、:stop、:suspend、:tag、:!、:make、CTRL-] 和 CTRL-^命令时进行。
命令":set autowriteall","set awa"。和 'autowrite' 类似,但也适用于":edit"、":enew"、":quit"、":qall"、":exit"、":xit"、":recover" 和关闭 Vim 窗口。置位本选项也意味着 Vim 的行为就像打开 'autowrite' 一样。

40.切换到另一文件

要在两个文件间快速切换,使用CTRL-^。

41.文件标记

以大写字母命名的标记。它们是全局标记,它们可以用在任何文件中。比如,正在编辑"fab1.Java",用命令"50%mF"在文件的中间设置一个名为F的标记。然后在"fab2.java"文件中,用命令"GnB"在最后一行设置名为B的标记。在可以用"F"命令跳转到文件"fab1.java"的半中间。或者编辑另一个文件,"'B"命令会再把你带回文件"fab2.java"的最后一行。
要知道某个标记所代表的位置是什么,可以将该标记的名字作为"marks"命令的参数":marks M"或者连续跟上几个参数":marks MJK"
可以用CTRL-O和CTRL-I可以跳转到较早的位置和靠后的某位置。

42.查看文件

仅是查看文件,不向文件写入内容,可以用只读形式编辑文件。用命令:
vim -R file。如果是想强制性地避免对文件进行修改,可以用命令:
vim -M file。

43.更改文件名

将现有文件存成新的文件,用命令":sav(eas) move.c"。如果想改变当前正在编辑的文件名,但不想保存该文件,就可以用命令:":f(ile) move.c"。

44.分割一个窗口

打开一个新窗口最简单的办法就是使用命令:":split"。CTRL-W 命令可以切换当前活动窗口。

45.关闭窗口

用命令:"close".可以关闭当前窗口。实际上,任何退出文件编辑的命令":quit"和"ZZ"都会关闭窗口,但是用":close"可以阻止你关闭最后一个Vim,以免以意外地整个关闭了Vim。

46.关闭除当前窗口外的所有其他窗口

用命令:":only",关闭除当前窗口外的所有其它窗口。如果这些窗口中有被修改过的,你会得到一个错误信息,同时那个窗口会被留下来。

47.为另一个文件分隔出一个窗口

命令":split two.c"可以打开第二个窗口同时在新打开的窗口中开始编辑作为
参数的文件。如果要打开一个新窗口并开始编辑一个空的缓冲区,使用命令:":new"。

48.垂直分割

用命令":vsplit或::vsplit two.c"。同样有一个对应的":vnew"命令,用于垂直分隔窗口并在其中打开一个新的空缓冲区。

49.切换窗口

CTRL-W h 到左边的窗口
CTRL-W j 到下面的窗口
CTRL-W k 到上面的窗口
CTRL-W l 到右边的窗口
CTRL-W t 到顶部窗口
CTRL-W b 到底部窗口

50.针对所有窗口操作的命令

":qall"放弃所有操作并退出,":wall"保存所有,":wqall"保存所有并退出。

51.为每一个文件打开一个窗口

使用"-o"选项可以让Vim为每一个文件打开一个窗口:
"vim -o one.txt two.txt three.txt"。

52.使用vimdiff查看不同

"vimdiff main.c~ main.c",另一种进入diff模式的办法可以在Vim运行中操作。编辑文件"main.c",然后打开另一个分隔窗口显示其不同:
":edit main.c"
":vertical diffpatch main.c.diff"。

53.页签

命令":tabe(dit) thatfile"在一个窗口中打开"thatfile",该窗口占据着整个的Vim显示区域。命令":tab split/new"结果是新建了一个拥有一个窗口的页签。以用"gt"命令在不同的页签间切换。

这是我总结的一些基本用法,可能对初用者会有帮助,独乐乐不如众乐乐,是吧!

说明:以下黑色为vi和vim均有的一般功能,而红色为Vim(Vi Improved)所特有功能。Vim一般的Unix和Linux下均有安装。

三种状态

Command: 任何输入都会作为编辑命令,而不会出现在屏幕上,任何输入都引起立即反映
Insert: 任何输入的数据都置于编辑寄存器,按ESC,可跳回command方式
Escape: 以“:”或者“/”为前导的指令,出现在屏幕的最下一行,任何输入都被当成特别指令。

离开vi

:q! 离开vi,并放弃刚在缓冲区内编辑的内容。
:wq 将缓冲区内的资料写入磁盘中,并离开vi。
:x 同wq。
(注意—— :X 是文件加密,一定要与:x存盘退出相区别)

进入输入模式

a (append) 由游标之后加入资料。
A 由该行之末加入资料。
i (insert) 由游标之前加入资料。
I 由该行之首加入资料。
o (open) 新增一行於该行之下供输入资料之用。
O 新增一行於该行之上供输入资料之用。

删除与修改

x 删除游标所在该字元。
X 删除游标所在之前一字元。
r 用接於此指令之后的字元取代(replace)游标所在字元。如:ra将游标所在字元以 a 取代之。
R 进入取代状态,直到《ESC》为止。
s 删除游标所在之字元,并进入输入模式直到《ESC》。
S 删除游标所在之该行资料,并进入输入模式直到《ESC》。

光标的移动

0 移至该行之首
$ 移至该行之末。
e 移动到下个字的最後一个字母
w 移动到下个字的第一个字母。
b 移动到上个字的第一个字母。
^ 移至该行的第一个字元处。
H 移至视窗的第一行。
M 移至视窗的中间那行。
L 移至视窗的最后一行。
G 移至该文件的最后一行。
+ 移至下一列的第一个字元处。
- 移至上一列的第一个字元处。
:n 移至该文件的第 n 列。
n+ 移至游标所在位置之后的第 n 列。
n- 移至游标所在位置之前的第 n 列。
Ctrl+g 显示该行之行号、文件名称、文件中最末行之行号、游标所在行号占总行号之百分比。

视窗的移动

Ctrl+f 视窗往下卷一页。
Ctrl+b 视窗往上卷一页。
Ctrl+d 视窗往下卷半页。
Ctrl+u 视窗往上卷半页。
Ctrl+e 视窗往下卷一行。
Ctrl+y 视窗往上卷一行。

剪切、复制、删除

Operator + Scope = command
Operator
d 剪切
y 复制。
p 粘帖,与 d 和 y 配和使用。可将最后d或y的资料放置於游标所在位置之行列下。
c 修改,类似delete与insert的组和。删除一个字组、句子等之资料,并插入新建资料。
Scope
e 由游标所在位置至该字串的最后一个字元。
w 由游标所在位置至下一个字串的第一个字元。
b 由游标所在位置至前一个字串的第一个字元。
$ 由游标所在位置至该行的最后一个字元。
0 由游标所在位置至该行的第一个字元。

整行动作

dd 删除整行。
D 以行为单位,删除游标后之所有字元。
cc 修改整行的内容。
yy 使游标所在该行复制到记忆体缓冲区。

取消前一动作(Undo)

u 恢复最后一个指令之前的结果。
U 恢复游标该行之所有改变。
u 可以多次撤消指令,一次撤消一个操作,直至本次操作开始为止。
Ctrl+r 可以恢复撤消前内容,按多次可恢复多次。

查找与替换

/字串 往游标之后寻找该字串。
?字串 往游标之前寻找该字串。
n 往下继续寻找下一个相同的字串。
N 往上继续寻找下一个相同的字串。
% 查找“(”,“)”,“{”,“}”的配对符。
s 搜寻某行列范围。
g 搜寻整个编辑缓冲区的资料。
:1,$s/old/new/g 将文件中所有的『old』改成『new』。
:10,20s/^/ / 将第10行至第20行资料的最前面插入5个空白。
(vim)
/字符串 后边输入查询内容可保存至缓冲区中,可用↑↓进行以往内容选择。
另外:将光标移动在选定单词下方按*,则可以选中此单词作为查询字符,可以避免输入一长串字符的麻烦。

大小写替换

首先用按v开启选择功能,然后用↑↓←→键来选定所要替换的字符,若是小写变大写,则按U;反之按u;
如果是选择单词,则可以在按v后,按w,最后按U/u,这样就可以将字符随意的改变大小写了,而不用删除后重新敲入。

资料的连接

句子的连接。将游标所在之下一行连接至游标该行的后面。
环境的设定
:set all 可设置的环境变量列表
:set 环境变量的当前值
:set nu 设定资料的行号。
:set nonu 取消行号设定。
:set ai 自动内缩。
:set noai 取消自动内缩。
:set ruler 会在屏幕右下角显示当前光标所处位置,并随光移动而改变,占用屏幕空间较小,使用也比较方便,推荐使用。
:set hlsearch 在使用查找功能时,会高亮显示所有匹配的内容。
:set nohlsearch 关闭此功能。
:set incsearch 使Vim在输入字符串的过程中,光标就可定位显示匹配点。
:set nowrapscan 关闭查找自动回环功能,即查找到文件结尾处,结束查找;默认状态是自动回环

ex指令
读写资料

:10,20w test 将第10行至第20行的资料写入test文件。
:10,20w>>test 将第10行至第20行的资料加在test文件之后。
:r test 将test文件的资料读入编辑缓冲区的最后。
:e [filename] 编辑新的文件。
:e! [filename] 放弃当前修改的文件,编辑新的文件。
:sh 进入shell环境,使用exit退出,回到编辑器中。

:!cmd 运行命令cmd后,返回到编辑器中。

删除、复制及搬移

:10,20d 删除第10行至第20行的资料。
:10d 删除第10行的资料。
:%d 删除整个编辑缓冲区。
:10,20co30 将第10行至第20行的资料复制至第30行之后。
:10,20mo30 将第10行至第20行的资料搬移至第30行之后。

58到家数据库30条军规解读

数据库zkbhj 发表了文章 • 0 个评论 • 261 次浏览 • 2017-02-16 10:29 • 来自相关话题

一、基础规范

(1)必须使用InnoDB存储引擎

解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高

 

(2)必须使用UTF8字符集

解读:万国码,无需转码,无乱码风险,节省空间

 

(3)数据表、数据字段必须加入中文注释

解读:N年后谁tm知道这个r1,r2,r3字段是干嘛的

 

(4)禁止使用存储过程、视图、触发器、Event

解读:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,CPU计算还是上移吧

 

(5)禁止存储大文件或者大照片

解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数据库里存URI多好

 

二、命名规范

(6)只允许使用内网域名,而不是ip连接数据库

 

(7)线上环境、开发环境、测试环境数据库内网域名遵循命名规范

业务名称:xxx

线上环境:dj.xxx.db

开发环境:dj.xxx.rdb

测试环境:dj.xxx.tdb

从库在名称后加-s标识,备库在名称后加-ss标识

线上从库:dj.xxx-s.db

线上备库:dj.xxx-sss.db

 

(8)库名、表名、字段名:小写,下划线风格,不超过32个字符,必须见名知意,禁止拼音英文混用

 

(9)表名t_xxx,非唯一索引名idx_xxx,唯一索引名uniq_xxx

 

三、表设计规范

(10)单实例表数目必须小于500

 

(11)单表列数目必须小于30

 

(12)表必须有主键,例如自增主键

解读:

a)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用

b)主键要选择较短的数据类型, Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率

c) 无主键的表删除,在row模式的主从架构,会导致备库夯住

 

(13)禁止使用外键,如果有外键完整性约束,需要应用程序控制

解读:外键会导致表与表之间耦合,update与delete操作都会涉及相关联的表,十分影响sql 的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先

 

四、字段设计规范

(14)必须把字段定义为NOT NULL并且提供默认值

解读:

a)null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化

b)null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多

c)null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识

d)对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录

 

(15)禁止使用TEXT、BLOB类型

解读:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能

 

(16)禁止使用小数存储货币

解读:使用整数吧,小数容易导致钱对不上

 

(17)必须使用varchar(20)存储手机号

解读:

a)涉及到区号或者国家代号,可能出现+-()

b)手机号会去做数学运算么?

c)varchar可以支持模糊查询,例如:like“138%”

 

(18)禁止使用ENUM,可使用TINYINT代替

解读:

a)增加新的ENUM值要做DDL操作

b)ENUM的内部实际存储就是整数,你以为自己定义的是字符串?

 

五、索引设计规范

(19)单表索引建议控制在5个以内

 

(20)单索引字段数不允许超过5个

解读:字段超过5个时,实际已经起不到有效过滤数据的作用了

 

(21)禁止在更新十分频繁、区分度不高的属性上建立索引

解读:

a)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能

b)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似

 

(22)建立组合索引,必须把区分度高的字段放在前面

解读:能够更加有效的过滤数据

 

六、SQL使用规范

(23)禁止使用SELECT *,只获取必要的字段,需要显示说明列属性

解读:

a)读取不需要的列会增加CPU、IO、NET消耗

b)不能有效的利用覆盖索引

c)使用SELECT *容易在增加或者删除字段后出现程序BUG

 

(24)禁止使用INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性

解读:容易在增加或者删除字段后出现程序BUG

 

(25)禁止使用属性隐式转换

解读:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中phone索引,猜猜为什么?(这个线上问题不止出现过一次)

 

(26)禁止在WHERE条件的属性上使用函数或者表达式

解读:SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 会导致全表扫描

正确的写法是:SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')

 

(27)禁止负向查询,以及%开头的模糊查询

解读:

a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描

b)%开头的模糊查询,会导致全表扫描

 

(28)禁止大表使用JOIN查询,禁止大表使用子查询

解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能

 

(29)禁止使用OR条件,必须改为IN查询

解读:旧版本Mysql的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化呢?

 

(30)应用程序必须捕获SQL异常,并有相应处理


总结:大数据量高并发的互联网业务,极大影响数据库性能的都不让用,不让用哟。 查看全部
一、基础规范

(1)必须使用InnoDB存储引擎

解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高

 

(2)必须使用UTF8字符集

解读:万国码,无需转码,无乱码风险,节省空间

 

(3)数据表、数据字段必须加入中文注释

解读:N年后谁tm知道这个r1,r2,r3字段是干嘛的

 

(4)禁止使用存储过程、视图、触发器、Event

解读:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,CPU计算还是上移吧

 

(5)禁止存储大文件或者大照片

解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数据库里存URI多好

 

二、命名规范

(6)只允许使用内网域名,而不是ip连接数据库

 

(7)线上环境、开发环境、测试环境数据库内网域名遵循命名规范

业务名称:xxx

线上环境:dj.xxx.db

开发环境:dj.xxx.rdb

测试环境:dj.xxx.tdb

从库在名称后加-s标识,备库在名称后加-ss标识

线上从库:dj.xxx-s.db

线上备库:dj.xxx-sss.db

 

(8)库名、表名、字段名:小写,下划线风格,不超过32个字符,必须见名知意,禁止拼音英文混用

 

(9)表名t_xxx,非唯一索引名idx_xxx,唯一索引名uniq_xxx

 

三、表设计规范

(10)单实例表数目必须小于500

 

(11)单表列数目必须小于30

 

(12)表必须有主键,例如自增主键

解读:

a)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用

b)主键要选择较短的数据类型, Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率

c) 无主键的表删除,在row模式的主从架构,会导致备库夯住

 

(13)禁止使用外键,如果有外键完整性约束,需要应用程序控制

解读:外键会导致表与表之间耦合,update与delete操作都会涉及相关联的表,十分影响sql 的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先

 

四、字段设计规范

(14)必须把字段定义为NOT NULL并且提供默认值

解读:

a)null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化

b)null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多

c)null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识

d)对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录

 

(15)禁止使用TEXT、BLOB类型

解读:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能

 

(16)禁止使用小数存储货币

解读:使用整数吧,小数容易导致钱对不上

 

(17)必须使用varchar(20)存储手机号

解读:

a)涉及到区号或者国家代号,可能出现+-()

b)手机号会去做数学运算么?

c)varchar可以支持模糊查询,例如:like“138%”

 

(18)禁止使用ENUM,可使用TINYINT代替

解读:

a)增加新的ENUM值要做DDL操作

b)ENUM的内部实际存储就是整数,你以为自己定义的是字符串?

 

五、索引设计规范

(19)单表索引建议控制在5个以内

 

(20)单索引字段数不允许超过5个

解读:字段超过5个时,实际已经起不到有效过滤数据的作用了

 

(21)禁止在更新十分频繁、区分度不高的属性上建立索引

解读:

a)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能

b)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似

 

(22)建立组合索引,必须把区分度高的字段放在前面

解读:能够更加有效的过滤数据

 

六、SQL使用规范

(23)禁止使用SELECT *,只获取必要的字段,需要显示说明列属性

解读:

a)读取不需要的列会增加CPU、IO、NET消耗

b)不能有效的利用覆盖索引

c)使用SELECT *容易在增加或者删除字段后出现程序BUG

 

(24)禁止使用INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性

解读:容易在增加或者删除字段后出现程序BUG

 

(25)禁止使用属性隐式转换

解读:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中phone索引,猜猜为什么?(这个线上问题不止出现过一次)

 

(26)禁止在WHERE条件的属性上使用函数或者表达式

解读:SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 会导致全表扫描

正确的写法是:SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')

 

(27)禁止负向查询,以及%开头的模糊查询

解读:

a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描

b)%开头的模糊查询,会导致全表扫描

 

(28)禁止大表使用JOIN查询,禁止大表使用子查询

解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能

 

(29)禁止使用OR条件,必须改为IN查询

解读:旧版本Mysql的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化呢?

 

(30)应用程序必须捕获SQL异常,并有相应处理


总结:大数据量高并发的互联网业务,极大影响数据库性能的都不让用,不让用哟。

Yii2框架如何重写POST或者GET参数?

回复

Yii框架zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 15 次浏览 • 2019-01-11 16:08 • 来自相关话题

MySql如何将13位的时间戳展示成易读格式?

回复

数据库zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 21 次浏览 • 2019-01-10 19:20 • 来自相关话题

PHP如何计算两个经纬度地理位置之间的距离?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 25 次浏览 • 2019-01-04 19:24 • 来自相关话题

PHP如何在数据的头部插入新元素?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 37 次浏览 • 2018-12-19 17:50 • 来自相关话题

PHP如何在数组特定位置处插入新元素?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 49 次浏览 • 2018-12-19 17:43 • 来自相关话题

给小米手机线刷新系统时,出现‘err:'MySQL' 不是内部或外部命令’问题怎么解决?

回复

工具软件zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 44 次浏览 • 2018-12-13 16:22 • 来自相关话题

Linux执行shell脚本报Could not open input file什么原因?

回复

服务器zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 70 次浏览 • 2018-12-06 16:31 • 来自相关话题

CSV文件数据导入MySQL数据库时中文乱码如何解决?

回复

数据库zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 59 次浏览 • 2018-12-04 10:13 • 来自相关话题

PHP如何运行时设置错误报告输出到页面上?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 80 次浏览 • 2018-11-21 14:35 • 来自相关话题

HTTP中有哪些方法可供使用?

回复

网络zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 68 次浏览 • 2018-11-14 16:41 • 来自相关话题

服务保障经验谈之服务熔断

架构思想zkbhj 发表了文章 • 0 个评论 • 40 次浏览 • 2018-12-18 10:56 • 来自相关话题

什么是服务熔断?

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

这种牺牲局部,保全整体的措施就叫做熔断。

如果不采取熔断措施,我们的系统会怎样呢?我们来看一个栗子。

当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游。它们的调用链如下:





 
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮。




像这种调用链路的连锁故障,叫做雪崩。

正所谓刮骨疗毒,壮士断腕。在这种时候,就需要我们的熔断机制来挽救整个系统。熔断机制的大体流程和刚才所讲的考试策略如出一辙:





 
这里需要解释两点:

1.开启熔断

在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。


2.熔断回复

熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用。
 
更多服务降级熔断限流,参考:https://www.cnblogs.com/raosha ... .html 查看全部
什么是服务熔断?

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

这种牺牲局部,保全整体的措施就叫做熔断。

如果不采取熔断措施,我们的系统会怎样呢?我们来看一个栗子。

当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游。它们的调用链如下:

20180507103456708.png

 
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮。
20180507103502138.png

像这种调用链路的连锁故障,叫做雪崩

正所谓刮骨疗毒,壮士断腕。在这种时候,就需要我们的熔断机制来挽救整个系统。熔断机制的大体流程和刚才所讲的考试策略如出一辙:

20180507103507143.png

 
这里需要解释两点:

1.开启熔断

在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。


2.熔断回复

熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用。
 
更多服务降级熔断限流,参考:https://www.cnblogs.com/raosha ... .html

一点点学习Linux:如何添加crontab计划任务?

服务器zkbhj 发表了文章 • 0 个评论 • 45 次浏览 • 2018-11-13 11:53 • 来自相关话题

crond 是linux用来定期执行程序的命令。当安装完成操作系统之后,默认便会启动此任务调度命令。crond命令每分锺会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。可以用以下的方法启动、关闭这个服务:
/sbin/service crond start //启动服务

/sbin/service crond stop //关闭服务

/sbin/service crond restart //重启服务

/sbin/service crond reload //重新载入配置1.linux任务调度的工作主要分为以下两类:

系统执行的工作:系统周期性所要执行的工作,如备份系统数据、清理缓存

个人执行的工作:某个用户定期要做的工作,例如每隔10分钟检查邮件服务器是否有新信,这些工作可由每个用户自行设置。


2.crontab命令选项:

cron服务提供crontab命令来设定cron服务的,以下是这个命令的一些参数与说明:

crontab -u //设定某个用户的cron服务,一般root用户在执行这个命令的时候需要此参数

crontab -l //列出某个用户cron服务的详细内容

crontab -r //删除某个用户的cron服务

crontab -e //编辑某个用户的cron服务

比如说root查看自己的cron设置:crontab -u root -l

再例如,root想删除fred的cron设置:crontab -u fred -r

在编辑cron服务时,编辑的内容有一些格式和约定,输入:crontab -u root -e

进入vi编辑模式,编辑的内容一定要符合下面的格式:*/1 * * * * ls >> /tmp/ls.txt

3.cron文件语法 分 小时 日 月 星期 命令

0-59 0-23 1-31 1-12 0-6 command (取值范围,0表示周日一般一行对应一个任务)4.记住几个特殊符号的含义:

"*"代表取值范围内的数字,

"/"代表"每",

"-"代表从某个数字到某个数字,

","分开几个离散的数字
 
5.举几个例子
5 * * * * ls //指定每小时的第5分钟执行一次ls命令

30 5 * * * ls //指定每天的 5:30 执行ls命令

30 7 8 * * ls //指定每月8号的7:30分执行ls命令

30 5 8 6 * ls //指定每年的6月8日5:30执行ls命令

30 6 * * 0 ls //指定每星期日的6:30执行ls命令[注:0表示星期天,1表示星期1,以此类推,也可以用英文来表示,sun表示星期天,mon表示星期一等。]

30 3 10,20 * * ls //每月10号及20号的3:30执行ls命令[注:”,”用来连接多个不连续的时段]

25 8-11 * * * ls //每天8-11点的第25分钟执行ls命令[注:”-”用来连接连续的时段]

*/15 * * * * ls //每15分钟执行一次ls命令 [即每个小时的第0 15 30 45 60分钟执行ls命令 ]

30 6 */10 * * ls //每个月中,每隔10天6:30执行一次ls命令[即每月的1、11、21、31日是的6:30执行一次ls命令。 ]

50 7 * * * root run-parts /etc/cron.daily //每天7:50以root 身份执行/etc/cron.daily目录中的所有可执行文件[ 注:run-parts参数表示,执行后面目录中的所有可执行文件。 ]6.新增调度任务可用两种方法:

a.在命令行输入: crontab -e 然后添加相应的任务,wq存盘退出。

b.直接编辑/etc/crontab 文件,即vi /etc/crontab,添加相应的任务。 查看全部
crond 是linux用来定期执行程序的命令。当安装完成操作系统之后,默认便会启动此任务调度命令。crond命令每分锺会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。可以用以下的方法启动、关闭这个服务:
/sbin/service crond start //启动服务

/sbin/service crond stop //关闭服务

/sbin/service crond restart //重启服务

/sbin/service crond reload //重新载入配置
1.linux任务调度的工作主要分为以下两类:


系统执行的工作:系统周期性所要执行的工作,如备份系统数据、清理缓存

个人执行的工作:某个用户定期要做的工作,例如每隔10分钟检查邮件服务器是否有新信,这些工作可由每个用户自行设置。



2.crontab命令选项:

cron服务提供crontab命令来设定cron服务的,以下是这个命令的一些参数与说明:

crontab -u //设定某个用户的cron服务,一般root用户在执行这个命令的时候需要此参数

crontab -l //列出某个用户cron服务的详细内容

crontab -r //删除某个用户的cron服务

crontab -e //编辑某个用户的cron服务

比如说root查看自己的cron设置:crontab -u root -l

再例如,root想删除fred的cron设置:crontab -u fred -r

在编辑cron服务时,编辑的内容有一些格式和约定,输入:crontab -u root -e

进入vi编辑模式,编辑的内容一定要符合下面的格式:*/1 * * * * ls >> /tmp/ls.txt

3.cron文件语法
  分     小时      日       月       星期      命令

0-59 0-23 1-31 1-12 0-6 command (取值范围,0表示周日一般一行对应一个任务)
4.记住几个特殊符号的含义:

"*"代表取值范围内的数字,

"/"代表"每",

"-"代表从某个数字到某个数字,

","分开几个离散的数字
 
5.举几个例子
5       *       *       *      *     ls              //指定每小时的第5分钟执行一次ls命令

30 5 * * * ls //指定每天的 5:30 执行ls命令

30 7 8 * * ls //指定每月8号的7:30分执行ls命令

30 5 8 6 * ls //指定每年的6月8日5:30执行ls命令

30 6 * * 0 ls //指定每星期日的6:30执行ls命令[注:0表示星期天,1表示星期1,以此类推,也可以用英文来表示,sun表示星期天,mon表示星期一等。]

30 3 10,20 * * ls //每月10号及20号的3:30执行ls命令[注:”,”用来连接多个不连续的时段]

25 8-11 * * * ls //每天8-11点的第25分钟执行ls命令[注:”-”用来连接连续的时段]

*/15 * * * * ls //每15分钟执行一次ls命令 [即每个小时的第0 15 30 45 60分钟执行ls命令 ]

30 6 */10 * * ls //每个月中,每隔10天6:30执行一次ls命令[即每月的1、11、21、31日是的6:30执行一次ls命令。 ]

50 7 * * * root run-parts /etc/cron.daily //每天7:50以root 身份执行/etc/cron.daily目录中的所有可执行文件[ 注:run-parts参数表示,执行后面目录中的所有可执行文件。 ]
6.新增调度任务可用两种方法:

a.在命令行输入: crontab -e 然后添加相应的任务,wq存盘退出。

b.直接编辑/etc/crontab 文件,即vi /etc/crontab,添加相应的任务。

类设计的六大基本原则

架构思想zkbhj 发表了文章 • 0 个评论 • 51 次浏览 • 2018-11-12 20:12 • 来自相关话题

一.单一职责原则

Single Responsibility Principle, 简称SRP。

定义:There should never be more than one reason for a class to change.

应该有且仅有一个原因引起类的变更。
 
二.里氏替换原则

Liskov Substitution Principle, 简称LSP。

定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

(所有引用基类的地方必须能透明地使用其子类的对象)
 
三.依赖倒置原则

Dependence Inversion Principle, 简称DIP

定义:High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

翻译过来,包含三层含义:

1.高层模块不应该依赖低层模块,两者都应该依赖其抽象。2.抽象不应该依赖细节。3.细节应该依赖抽象。

精简的定义: 面向接口编程。
 
四.接口隔离原则:
接口--这里指用interface关键字定义的接口。
定义:
1.Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口)
2.The dependency of one class to anther one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)

概括:建立单一接口,不要建立臃肿庞大的接口。通俗来讲:接口尽量细化,同时接口中的方法尽量少。
 
五.迪米特法则
Law of Demeter, LOD。又称最少知识原则(Least Knowledge Principle, LKP)。
通俗来讲:一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没有关系,那是你的事情,我就调用你提供的public方法,其他一概不关心。
 
六.开闭原则
Software entities like classes, modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭) 查看全部
一.单一职责原则

Single Responsibility Principle, 简称SRP。

定义:There should never be more than one reason for a class to change.

应该有且仅有一个原因引起类的变更。
 
二.里氏替换原则

Liskov Substitution Principle, 简称LSP。

定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

(所有引用基类的地方必须能透明地使用其子类的对象)
 
三.依赖倒置原则

Dependence Inversion Principle, 简称DIP

定义:High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

翻译过来,包含三层含义:

1.高层模块不应该依赖低层模块,两者都应该依赖其抽象。2.抽象不应该依赖细节。3.细节应该依赖抽象。

精简的定义: 面向接口编程。
 
四.接口隔离原则:
接口--这里指用interface关键字定义的接口。
定义:
1.Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口)
2.The dependency of one class to anther one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)

概括:建立单一接口,不要建立臃肿庞大的接口。通俗来讲:接口尽量细化,同时接口中的方法尽量少。
 
五.迪米特法则
Law of Demeter, LOD。又称最少知识原则(Least Knowledge Principle, LKP)。
通俗来讲:一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没有关系,那是你的事情,我就调用你提供的public方法,其他一概不关心。
 
六.开闭原则
Software entities like classes, modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭)

#原创#API接口设计要考虑的几个重要原则和方法总结

架构思想zkbhj 发表了文章 • 0 个评论 • 100 次浏览 • 2018-11-12 11:04 • 来自相关话题

这里想和大家讨论的是在后台接口设计过程中,还有哪些方面需要考虑,以及还有哪些优秀的技术实践方案可以借鉴。

 【规范和最佳实践】

1.合理的接口命名;

接口的命名必须规范优雅,在未看到接口文档时,就可以根据接口的URL明白接口的功能是什么?

如下面的例子://好的接口命名示例
/customer/cert/search.json

//不好的接口命名示例
/customer/info/get.json
2.入参和出参的规范化定义,有统一的风格;

一个项目内的所有接口,必须有统一的风格,统一返回格式,约定业务层错误编码,每个编码可以携带明确的错误信息。出入参字段含义明确,采用统一的命名规范,如驼峰命名等。返回格式统一采用json格式。举一个例子:{
"status": "failure",
"error_code": 100003,
"error_message": "未获取到用户信息",
"data":
}status标识接口是否逻辑处理成功;error_code为不同类型错误信息对应的唯一错误码,error_message为错误信息的简要描述信息(注意某些数据或者信息是否可直接展示给用户),data则为需要返回给调用方的数据信息。
 
另外,每个参数一定要有明确且固定的数据格式,int就是int,string就是string,array就是array,object就是object,因为对于一些对数据类型要求比较严格的使用方,不明确的数据格式返回,可能会造成不可预知的错误。
 
下面给大家列一下Json里的六种基本数据格式: 

Number:整数或浮点数
String:字符串
Boolean:true 或 false
Array:数组包含在方括号中
Object:对象包含在大括号{}中
Null:空类型

 
3.接口的功能定义要具备单一性;

单一性是指接口要做的事情应该是一个比较单一的事情,比如登陆接口,登陆完成应该只是返回登陆成功以后一些用户信息如uid即可,但很多人为了减少接口交互,返回一大堆额外的数据。

但有时候对于一些内部系统接口来讲,为了实现通用性,可能会提供一些通用的查询接口,即在同一个接口内返回尽可能多的信息,但也不建议这么做,至少不是一个好的实践;


4.明确接口支持的协议;

接口要明确所支持的协议,是POST/GET/PUT/DELETE等的哪一个。一般来讲,同一个接口而言,尽量只支持一种协议,并且在接口被调用时,如果参数传递非接口定义协议,要明确提示返回错误信息,这样可以减少很多类似于“为啥我调接口参数都对还调不通”的问题的沟通成本。同时,严格的协议规范也可以避免一些意料之外的问题出现。例如:您请求的资源不支持 http 方法“POST
5.是否支持幂等;

这是作为一个接口而言,很需要明确的一点,尤其是在一些特殊的应用场景下,是否支持幂等是需要首先明确的。比如下面这个例子:

发放卡券的接口:/coupon/card/handOut.json POST

这是一个卡券系统里发放租金卡的接口,支持POST协议传参。由于很多种原因,同一类卡券被某个人领取时,都可能会产生接口被调用不支持一次的情形,比如网络抖动、用户快速点击、甚至是恶意刷接口等,我们希望,对于“同一个调用”,我们给用户返回的结果应该是一致的,这就是幂等。实现幂等的方式有几种,比如卡券系统就是通过生成订单号的形式完成的接口幂等;


6、充分考虑接口的可扩展性,避免做大而全的接口;

要根据实际业务场景定义接口,充分考虑接口的可扩展性。比如自如的APP首页数据接口,我们可以设计成整个首页就一个大接口,但是假如这样,未来再次改版APP,我们可能就需要完全重新写这个接口,但假如我们按模块区分接口,可能我们仅需要开发新增加的模块的接口,对于以前有的,在数据结构不调整只做样式改变的需求里,就可以减少工作量的开发。
另外这么做还有一个优点,尤其对移动端作用尤为突出,由于移动端对带宽有限,所以,接口中尽量不要返回无用的信息,只返回真正需要的数据,进而减少由于过多的数据量影响处理速度,最重要的是影响传输效率。


7、接口里尽量不做客户端可以处理的逻辑,减少服务端压力;

接口主要是提供给客户端数据的,对于能够在客端完成的逻辑处理,尽量由客端来处理(当然APP比较特殊,如果改动这部分逻辑需要发版,还是需要放在后端),进而减轻后端服务器的压力,让后端接口更加“专心”做好数据服务;


8、清晰的日志分类、记录以及归档规范;

接口日志很重要,无论对于追溯问题还是解决bug,都有着举足轻重的作用。所以,好的日志规范,是一个很好的习惯。日志主要有info和error两种(warning一般不做记录,或者很少用到),info日志一般用于记录现场,用来追溯问题;error日志一般用于协作我们查找bug,定位代码问题。

另外日志也需要定期做归档处理,防止机器磁盘被日志文件大量占用。

更高级的,可以接入一些日志搜集和分析工具,如ELK等,将日志信息持久化存储以及可视化展示,更加方便的对日志信息进行使用。


9、版本控制:
 
这一点,对于接口来讲,非常重要。在实际的场景中,维护多个版本是非常常见的事情,在系统的迭代升级过程中,无可避免的会增减返回参数及入参,修改返回数据的结构,甚至会废弃原有接口改为新的数据接口。所以,为了不影响老版本应用的正常使用,大部分应用后台都会针对性的维护多个接口版本。
一般来讲,有2中常用的方式:

1.每个接口有各自的版本,一般为接口添加个version的参数。 
2.整个接口系统有统一的版本,一般在URL中添加版本号,比如 http://api.zkbhj.com/v2。 

 

【性能和高可用】

1、接口的平均响应时长、支持的并发数、TPS;

这个很重要,无论是我们自己设计的接口,还是我们在使用第三方提供的接口时,我们都需要明确接口的平均响应时长,因为这直接关系到你系统的安全性问题!如果在接口调用时,没有设置合理的超时时间甚至都没有设置超时时间,那么一旦所以依赖的接口出现问题甚至服务不可用时,对你调用方系统来讲,将是致命的,雪崩式的系统崩溃,很大一部分就是由于这个原因造成的。所以为了不害人害己,设计以及最终提供的接口,一定要提供一个明确的接口平均响应时长,而且要在接口文档中写明并强烈建议接口调用方设置合理的超时时间,防止由于接口超时而造成雪崩式的连锁反应。


2、数据库和缓存的选择;

为了提高接口性能,合理的选择数据库和缓存很是总要。一般情况下,关系型存储我们一般都会选择MySQL数据库,缓存一般都选择Redis。当然,MySQL数据库的分库分表,加索引用事务、读写分离等,redis作为缓存使用时的缓存时长、缓存数据类型等,都有他们的使用原则和最佳实践,这里不做赘述。我们这里只讨论在何种场景下要使用缓存。比如查询类型的接口,如果要查询的数据并不是实时性要求很高的接口,那我们可以进行缓存处理,比如APP首页接口,一般都是CMS里面配置的一些图文信息,我们有必要做缓存处理。当然可能里面有一部分数据是需要实时的,比如自如寓的管家信息,那我们可以把这一部分内容做实时的处理。
 

3、限流、熔断和降级;

对于一些特殊的应用场景,比如抢红包、秒杀等,要对接口进行限流处理,方式短时间内的高并发请求将接口搞死;

接口熔断和降级,是为了解决系统不被拖死,不影响核心业务流程而采取的措施,比如获取用户信息列表,实时获取用户头像和昵称的接口暂时不可用(比如根据设置,10个请求里6个以上都超时,则判定为服务不可用,触发熔断机制),我们可以主动放弃调用(熔断),只返回核心数据uid等,昵称和头像暂时返回默认数据(降级);


4、消除单点,负载均衡;

对于任何一个接口服务,我们至少要有2台机器对外提供服务,禁止单点服务,单点一旦出问题,会直接造成服务不可用;

对于访问量很大的API服务,为了提供更加快速的接口响应,我们往往不是单台机器提供服务,而是有多台机器组成一个分布式集群对外提供服务。这个时候就会涉及到负载均衡,比如我们就会由nginx来做负载均衡,根据一定的策略机制,将接口请求平均的分发到不同的应用机器上进行处理和响应。进而提高接口的性能。


5、是否有第三方服务接口?

如果接口依赖了第三方服务接口,能用缓存就用缓存。这样可以进一步降低由于第三方接口不稳定给我们自己系统造成的波动。当然,也有一些第三方接口无法做缓存,比如就是要实时进行身份验证等,这个时候,超时时间的设置就尤为重要!


6、能异步处理的异步处理:

其实有很多场景下,一个接口里面的很多逻辑是可以异步处理的。举个例子:

比如用户注册场景,用户注册成功之后会给客户的邮箱发送一封激活邮件。常规的逻辑流程应该是,前段提交用户信息到注册接口,注册接口做各种校验,校验通过后,发送邮件,发送成功后,返回给前端告诉用户注册成功,请进入邮箱激活账号。其实,这个流程里的“发送邮件”就是可以拿出来异步处理的部分,当校验通过而且注册完成之后,我们把发送邮件这件事抛出去,交给另外一个就负责发邮件的任务进行处理(如我们现在有的补偿队列,或者是发一个MQ消息),然后直接返回给用户注册成功。这样,注册接口的平均响应时间一定会比第一种方案提高很多。


7、更高要求的高可用,可以采用异地机房部署;

在物理地域上就分开部署,两地同时崩溃的概率还是比较低的;


8、监控和报警;

在对接口建立高效的监控和报警机制,能够及时发现问题并通知到相应的人员进行第一时间的处理和跟进。


【稳定和安全】

1、身份验证;

在一些接口场景中,是要依赖于用户身份的,比如通过token还实现用户身份的验证;


2、接口防抓取和串改数据;

防止数据被轻易抓取到,我们可以采用https作为接口的网络传输协议,进而保证数据包不被轻易的就抓取和分析。即使这种情况下,依然被抓取到,我们还可以对传输的数据进行我们自己的加密处理,比如用对称加密算法AES或者非对称加密算法RSA,亦或是我们系统内部自己商定好的加密算法,对数据进行加密处理,这样,即使抓取到数据包,也很难分析出数据的原始信息。

对于防止数据被串改,可以使用sign验签,进一步防止接口参数被串改的可能性。


3、防刷;

接口防刷会有一些策略,根据实际的应用场景进行选择,比如增加图形验证码、接入智能验证码、时间戳限制单位时间内的调用次数、ip限制等;

另外监控很重要,及时发现异常的调用,进行封禁处理;


【其他】

1、是否需要支持跨域;

这一点是针对于H5提供接口时需要考虑的,因为一般情况下,实际的应用和接口所在的域并不是同一个域,基于浏览器的安全策略,对于XHR请求来讲,是不允许进行跨域请求的,所以,一般提供给H5的接口要支持跨域请求。当然解决跨域的方法也不在本次讨论的范围之内,目前主流的方式就是在服务器配置的header头信息中增加两项参数。



2、基于H5提供接口的一些安全性问题;

比如常见的CSRF攻击,我们可以在接口里验证 HTTP Referer字段、x-requested-with字段、header中增加token等,从一定程度上提高被CSRF攻击的门槛。 
 
3、在代码结构层面,尽量和其他部分分开;
 
API集中由同一个系统“模块”提供,尽量不要和页面等其他功能混合开发。例如下面的项目分层模式就是一个较好的实践方案:





 
即所有API接口均分布在api内部,不与pc(PC站页面)、mobile(M站页面)等混合在一起。 

4、文档:
 
好的接口,还有一项优点,就是会有为之配套的接口文档。如果希望降低接口文档的维护成本等,也可以使用开源的第三方自动化接口文档工具,比如swagger等。 查看全部
这里想和大家讨论的是在后台接口设计过程中,还有哪些方面需要考虑,以及还有哪些优秀的技术实践方案可以借鉴。

 【规范和最佳实践】

1.合理的接口命名;

接口的命名必须规范优雅,在未看到接口文档时,就可以根据接口的URL明白接口的功能是什么?

如下面的例子:
//好的接口命名示例
/customer/cert/search.json

//不好的接口命名示例
/customer/info/get.json

2.入参和出参的规范化定义,有统一的风格;

一个项目内的所有接口,必须有统一的风格,统一返回格式,约定业务层错误编码,每个编码可以携带明确的错误信息。出入参字段含义明确,采用统一的命名规范,如驼峰命名等。返回格式统一采用json格式。举一个例子:
{
"status": "failure",
"error_code": 100003,
"error_message": "未获取到用户信息",
"data":
}
status标识接口是否逻辑处理成功;error_code为不同类型错误信息对应的唯一错误码,error_message为错误信息的简要描述信息(注意某些数据或者信息是否可直接展示给用户),data则为需要返回给调用方的数据信息。
 
另外,每个参数一定要有明确且固定的数据格式,int就是int,string就是string,array就是array,object就是object,因为对于一些对数据类型要求比较严格的使用方,不明确的数据格式返回,可能会造成不可预知的错误。
 
下面给大家列一下Json里的六种基本数据格式: 


Number:整数或浮点数
String:字符串
Boolean:true 或 false
Array:数组包含在方括号中
Object:对象包含在大括号{}中
Null:空类型


 
3.接口的功能定义要具备单一性;

单一性是指接口要做的事情应该是一个比较单一的事情,比如登陆接口,登陆完成应该只是返回登陆成功以后一些用户信息如uid即可,但很多人为了减少接口交互,返回一大堆额外的数据。

但有时候对于一些内部系统接口来讲,为了实现通用性,可能会提供一些通用的查询接口,即在同一个接口内返回尽可能多的信息,但也不建议这么做,至少不是一个好的实践;


4.明确接口支持的协议;

接口要明确所支持的协议,是POST/GET/PUT/DELETE等的哪一个。一般来讲,同一个接口而言,尽量只支持一种协议,并且在接口被调用时,如果参数传递非接口定义协议,要明确提示返回错误信息,这样可以减少很多类似于“为啥我调接口参数都对还调不通”的问题的沟通成本。同时,严格的协议规范也可以避免一些意料之外的问题出现。例如:
您请求的资源不支持 http 方法“POST

5.是否支持幂等;

这是作为一个接口而言,很需要明确的一点,尤其是在一些特殊的应用场景下,是否支持幂等是需要首先明确的。比如下面这个例子:

发放卡券的接口:/coupon/card/handOut.json POST

这是一个卡券系统里发放租金卡的接口,支持POST协议传参。由于很多种原因,同一类卡券被某个人领取时,都可能会产生接口被调用不支持一次的情形,比如网络抖动、用户快速点击、甚至是恶意刷接口等,我们希望,对于“同一个调用”,我们给用户返回的结果应该是一致的,这就是幂等。实现幂等的方式有几种,比如卡券系统就是通过生成订单号的形式完成的接口幂等;


6、充分考虑接口的可扩展性,避免做大而全的接口;

要根据实际业务场景定义接口,充分考虑接口的可扩展性。比如自如的APP首页数据接口,我们可以设计成整个首页就一个大接口,但是假如这样,未来再次改版APP,我们可能就需要完全重新写这个接口,但假如我们按模块区分接口,可能我们仅需要开发新增加的模块的接口,对于以前有的,在数据结构不调整只做样式改变的需求里,就可以减少工作量的开发。
另外这么做还有一个优点,尤其对移动端作用尤为突出,由于移动端对带宽有限,所以,接口中尽量不要返回无用的信息,只返回真正需要的数据,进而减少由于过多的数据量影响处理速度,最重要的是影响传输效率。


7、接口里尽量不做客户端可以处理的逻辑,减少服务端压力;

接口主要是提供给客户端数据的,对于能够在客端完成的逻辑处理,尽量由客端来处理(当然APP比较特殊,如果改动这部分逻辑需要发版,还是需要放在后端),进而减轻后端服务器的压力,让后端接口更加“专心”做好数据服务;


8、清晰的日志分类、记录以及归档规范;

接口日志很重要,无论对于追溯问题还是解决bug,都有着举足轻重的作用。所以,好的日志规范,是一个很好的习惯。日志主要有info和error两种(warning一般不做记录,或者很少用到),info日志一般用于记录现场,用来追溯问题;error日志一般用于协作我们查找bug,定位代码问题。

另外日志也需要定期做归档处理,防止机器磁盘被日志文件大量占用。

更高级的,可以接入一些日志搜集和分析工具,如ELK等,将日志信息持久化存储以及可视化展示,更加方便的对日志信息进行使用。


9、版本控制:
 
这一点,对于接口来讲,非常重要。在实际的场景中,维护多个版本是非常常见的事情,在系统的迭代升级过程中,无可避免的会增减返回参数及入参,修改返回数据的结构,甚至会废弃原有接口改为新的数据接口。所以,为了不影响老版本应用的正常使用,大部分应用后台都会针对性的维护多个接口版本。
一般来讲,有2中常用的方式:


1.每个接口有各自的版本,一般为接口添加个version的参数。 
2.整个接口系统有统一的版本,一般在URL中添加版本号,比如 http://api.zkbhj.com/v2。 


 

【性能和高可用】

1、接口的平均响应时长、支持的并发数、TPS;

这个很重要,无论是我们自己设计的接口,还是我们在使用第三方提供的接口时,我们都需要明确接口的平均响应时长,因为这直接关系到你系统的安全性问题!如果在接口调用时,没有设置合理的超时时间甚至都没有设置超时时间,那么一旦所以依赖的接口出现问题甚至服务不可用时,对你调用方系统来讲,将是致命的,雪崩式的系统崩溃,很大一部分就是由于这个原因造成的。所以为了不害人害己,设计以及最终提供的接口,一定要提供一个明确的接口平均响应时长,而且要在接口文档中写明并强烈建议接口调用方设置合理的超时时间,防止由于接口超时而造成雪崩式的连锁反应。


2、数据库和缓存的选择;

为了提高接口性能,合理的选择数据库和缓存很是总要。一般情况下,关系型存储我们一般都会选择MySQL数据库,缓存一般都选择Redis。当然,MySQL数据库的分库分表,加索引用事务、读写分离等,redis作为缓存使用时的缓存时长、缓存数据类型等,都有他们的使用原则和最佳实践,这里不做赘述。我们这里只讨论在何种场景下要使用缓存。比如查询类型的接口,如果要查询的数据并不是实时性要求很高的接口,那我们可以进行缓存处理,比如APP首页接口,一般都是CMS里面配置的一些图文信息,我们有必要做缓存处理。当然可能里面有一部分数据是需要实时的,比如自如寓的管家信息,那我们可以把这一部分内容做实时的处理。
 

3、限流、熔断和降级;

对于一些特殊的应用场景,比如抢红包、秒杀等,要对接口进行限流处理,方式短时间内的高并发请求将接口搞死;

接口熔断和降级,是为了解决系统不被拖死,不影响核心业务流程而采取的措施,比如获取用户信息列表,实时获取用户头像和昵称的接口暂时不可用(比如根据设置,10个请求里6个以上都超时,则判定为服务不可用,触发熔断机制),我们可以主动放弃调用(熔断),只返回核心数据uid等,昵称和头像暂时返回默认数据(降级);


4、消除单点,负载均衡;

对于任何一个接口服务,我们至少要有2台机器对外提供服务,禁止单点服务,单点一旦出问题,会直接造成服务不可用;

对于访问量很大的API服务,为了提供更加快速的接口响应,我们往往不是单台机器提供服务,而是有多台机器组成一个分布式集群对外提供服务。这个时候就会涉及到负载均衡,比如我们就会由nginx来做负载均衡,根据一定的策略机制,将接口请求平均的分发到不同的应用机器上进行处理和响应。进而提高接口的性能。


5、是否有第三方服务接口?

如果接口依赖了第三方服务接口,能用缓存就用缓存。这样可以进一步降低由于第三方接口不稳定给我们自己系统造成的波动。当然,也有一些第三方接口无法做缓存,比如就是要实时进行身份验证等,这个时候,超时时间的设置就尤为重要!


6、能异步处理的异步处理:

其实有很多场景下,一个接口里面的很多逻辑是可以异步处理的。举个例子:

比如用户注册场景,用户注册成功之后会给客户的邮箱发送一封激活邮件。常规的逻辑流程应该是,前段提交用户信息到注册接口,注册接口做各种校验,校验通过后,发送邮件,发送成功后,返回给前端告诉用户注册成功,请进入邮箱激活账号。其实,这个流程里的“发送邮件”就是可以拿出来异步处理的部分,当校验通过而且注册完成之后,我们把发送邮件这件事抛出去,交给另外一个就负责发邮件的任务进行处理(如我们现在有的补偿队列,或者是发一个MQ消息),然后直接返回给用户注册成功。这样,注册接口的平均响应时间一定会比第一种方案提高很多。


7、更高要求的高可用,可以采用异地机房部署;

在物理地域上就分开部署,两地同时崩溃的概率还是比较低的;


8、监控和报警;

在对接口建立高效的监控和报警机制,能够及时发现问题并通知到相应的人员进行第一时间的处理和跟进。


【稳定和安全】

1、身份验证;

在一些接口场景中,是要依赖于用户身份的,比如通过token还实现用户身份的验证;


2、接口防抓取和串改数据;

防止数据被轻易抓取到,我们可以采用https作为接口的网络传输协议,进而保证数据包不被轻易的就抓取和分析。即使这种情况下,依然被抓取到,我们还可以对传输的数据进行我们自己的加密处理,比如用对称加密算法AES或者非对称加密算法RSA,亦或是我们系统内部自己商定好的加密算法,对数据进行加密处理,这样,即使抓取到数据包,也很难分析出数据的原始信息。

对于防止数据被串改,可以使用sign验签,进一步防止接口参数被串改的可能性。


3、防刷;

接口防刷会有一些策略,根据实际的应用场景进行选择,比如增加图形验证码、接入智能验证码、时间戳限制单位时间内的调用次数、ip限制等;

另外监控很重要,及时发现异常的调用,进行封禁处理;


【其他】

1、是否需要支持跨域;

这一点是针对于H5提供接口时需要考虑的,因为一般情况下,实际的应用和接口所在的域并不是同一个域,基于浏览器的安全策略,对于XHR请求来讲,是不允许进行跨域请求的,所以,一般提供给H5的接口要支持跨域请求。当然解决跨域的方法也不在本次讨论的范围之内,目前主流的方式就是在服务器配置的header头信息中增加两项参数。



2、基于H5提供接口的一些安全性问题;

比如常见的CSRF攻击,我们可以在接口里验证 HTTP Referer字段、x-requested-with字段、header中增加token等,从一定程度上提高被CSRF攻击的门槛。 
 
3、在代码结构层面,尽量和其他部分分开;
 
API集中由同一个系统“模块”提供,尽量不要和页面等其他功能混合开发。例如下面的项目分层模式就是一个较好的实践方案:

QQ截图20181112160646.jpg

 
即所有API接口均分布在api内部,不与pc(PC站页面)、mobile(M站页面)等混合在一起。 

4、文档:
 
好的接口,还有一项优点,就是会有为之配套的接口文档。如果希望降低接口文档的维护成本等,也可以使用开源的第三方自动化接口文档工具,比如swagger等。

为什么Linux的Crond最小只支持分钟级别的定时任务?Crontab工作机制探究

服务器zkbhj 发表了文章 • 0 个评论 • 94 次浏览 • 2018-10-17 15:21 • 来自相关话题

在开发过程中,经常会有用到计划任务的时候。一般来讲,常见的大部分需求也很好实现,Linux的定时任务简单地配置到crontab里面就可以了,设置方法也很简单,如下所示,6个参数依次表示为“分、时、日、月、周和具体任务”(示例为每天凌晨3点执行freshclam工作)。
[root@student ~]# crontab -l
0 3 * * * freshclam然而,从5个时间参数来看最小是精确到某一分钟执行。如果要想实现秒级的定时任务,那么该怎么做呢? 
首先,我们先探究下Linux的Crond工作的机制是怎么样的。
 cron 是一个可以用来根据时间、日期、月份、星期的组合来调度对重复任务的执行的守护进程。

cron 假定系统持续运行。如果当某任务被调度时系统不在运行,该任务就不会被执行。

要使用 cron 服务,你必须安装了 vixie-cron RPM 软件包,而且必须在运行crond 服务。要判定该软件包是否已安装,使用 rpm -q vixie-cron 命令。要判定该服务是否在运行,使用 /sbin/service crond status 命令。
 
配置
 
cron 的主配置文件是 /etc/crontab,它包括下面几行:SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
# run-parts
01 * * * * root run-parts /etc/cron.hourly
02 4 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
42 4 1 * * root run-parts /etc/cron.monthly前四行是用来配置 cron 任务运行环境的变量。SHELL 变量的值告诉系统要使用哪个 shell 环境(在这个例子里是 bash shell);PATH 变量定义用来执行命令的路径。cron 任务的输出被邮寄给MAILTO 变量定义的用户名。如果 MAILTO 变量被定义为空白字符串(MAILTO=""),电子邮件就不会被寄出。HOME 变量可以用来设置在执行命令或脚本时使用的主目录。

/etc/crontab 文件中的每一行都代表一项任务,它的格式是:
minute hour day month dayofweek command 

minute — 分钟,从 0 到 59 之间的任何整数

hour — 小时,从 0 到 23 之间的任何整数

day — 日期,从 1 到 31 之间的任何整数(如果指定了月份,必须是该月份的有效日期)

month — 月份,从 1 到 12 之间的任何整数(或使用月份的英文简写如 jan、feb 等等)

dayofweek — 星期,从 0 到 7 之间的任何整数,这里的 0 或 7 代表星期日(或使用星期的英文简写如 sun、mon 等等)

command — 要执行的命令(命令可以是ls /proc >> /tmp/proc 之类的命令,也可以是执行你自行编写的脚本的命令。)

 
在以上任何值中,星号(*)可以用来代表所有有效的值。譬如,月份值中的星号意味着在满足其它制约条件后每月都执行该命令。

整数间的短线(-)指定一个整数范围。譬如,1-4 意味着整数 1、2、3、4。

用逗号(,)隔开的一系列值指定一个列表。譬如,3, 4, 6, 8 标明这四个指定的整数。

正斜线(/)可以用来指定间隔频率。在范围后加上 /<integer> 意味着在范围内可以跳过 integer。譬如,0-59/2 可以用来在分钟字段定义每两分钟。间隔频率值还可以和星号一起使用。例如,*/3 的值可以用在月份字段中表示每三个月运行一次任务。

开头为井号(#)的行是注释,不会被处理。

如你在 /etc/crontab 文件中所见,它使用run-parts 脚本来执行 /etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly和/etc/cron.monthly 目录中的脚本,这些脚本被相应地每小时、每日、每周、或每月执行。这些目录中的文件应该是 shell 脚本。

如果某 cron 任务需要根据调度来执行,而不是每小时、每日、每周、或每月地执行,它可以被添加到 /etc/cron.d 目录中。该目录中的所有文件使用和 /etc/crontab 中一样的语法。# record the memory usage of the system every monday
# at 3:30AM in the file /tmp/meminfo
30 3 * * mon cat /proc/meminfo >> /tmp/meminfo
# run custom script the first day of every month at 4:10AM
10 4 1 * * /root/scripts/backup.sh根用户以外的用户可以使用 crontab 工具来配置 cron 任务。所有用户定义的 crontab 都被保存在/var/spool/cron 目录中,并使用创建它们的用户身份来执行。要以某用户身份创建一个 crontab 项目,登录为该用户,然后键入crontab -e 命令,使用由 VISUAL 或 EDITOR环境变量指定的编辑器来编辑该用户的 crontab。该文件使用的格式和/etc/crontab 相同。当对 crontab 所做的改变被保存后,该 crontab 文件就会根据该用户名被保存,并写入文件/var/spool/cron/username 中。

cron 守护进程每分钟都检查 /etc/crontab 文件、etc/cron.d/ 目录、以及/var/spool/cron 目录中的改变。如果发现了改变,它们就会被载入内存。这样,当某个 crontab 文件改变后就不必重新启动守护进程了。
 
 控制对 cron 的使用
 
/etc/cron.allow 和/etc/cron.deny 文件被用来限制对 cron 的使用。这两个使用控制文件的格式都是每行一个用户。两个文件都不允许空格。如果使用控制文件被修改了,cron 守护进程(crond)不必被重启。使用控制文件在每次用户添加或删除一项 cron 任务时都会被读取。

无论使用控制文件中的规定如何,根用户都总是可以使用 cron。

如果 cron.allow 文件存在,只有其中列出的用户才被允许使用 cron,并且cron.deny 文件会被忽略。

如果 cron.allow 文件不存在,所有在cron.deny 中列出的用户都被禁止使用 cron。
 
启动和停止服务
 
要启动 cron 服务,使用 /sbin/service crond start 命令。要停止该服务,使用/sbin/service crond stop 命令。推荐你在引导时启动该服务。

由于Cron 是Linux的内置服务,可以用以下的方法启动、关闭这个服务:/sbin/service crond start //启动服务
/sbin/service crond stop //关闭服务
/sbin/service crond restart //重启服务
/sbin/service crond reload //重新载入配置
要实现秒级的计划任务,可以使用开源项目:Quartz
详细:http://www.quartz-scheduler.org/
 参考文章:
https://blog.csdn.net/wenqiang1208/article/details/74853519
https://blog.csdn.net/foxman209/article/details/6759920?utm_source=blogxgwz4
  查看全部
在开发过程中,经常会有用到计划任务的时候。一般来讲,常见的大部分需求也很好实现,Linux的定时任务简单地配置到crontab里面就可以了,设置方法也很简单,如下所示,6个参数依次表示为“分、时、日、月、周和具体任务”(示例为每天凌晨3点执行freshclam工作)。
[root@student ~]# crontab -l
0 3 * * * freshclam
然而,从5个时间参数来看最小是精确到某一分钟执行。如果要想实现秒级的定时任务,那么该怎么做呢? 
首先,我们先探究下Linux的Crond工作的机制是怎么样的。
 cron 是一个可以用来根据时间、日期、月份、星期的组合来调度对重复任务的执行的守护进程。

cron 假定系统持续运行。如果当某任务被调度时系统不在运行,该任务就不会被执行。

要使用 cron 服务,你必须安装了 vixie-cron RPM 软件包,而且必须在运行crond 服务。要判定该软件包是否已安装,使用 rpm -q vixie-cron 命令。要判定该服务是否在运行,使用 /sbin/service crond status 命令。
 
配置
 
cron 的主配置文件是 /etc/crontab,它包括下面几行:
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
# run-parts
01 * * * * root run-parts /etc/cron.hourly
02 4 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
42 4 1 * * root run-parts /etc/cron.monthly
前四行是用来配置 cron 任务运行环境的变量。SHELL 变量的值告诉系统要使用哪个 shell 环境(在这个例子里是 bash shell);PATH 变量定义用来执行命令的路径。cron 任务的输出被邮寄给MAILTO 变量定义的用户名。如果 MAILTO 变量被定义为空白字符串(MAILTO=""),电子邮件就不会被寄出。HOME 变量可以用来设置在执行命令或脚本时使用的主目录。

/etc/crontab 文件中的每一行都代表一项任务,它的格式是:
minute         hour         day         month         dayofweek         command
 


minute — 分钟,从 0 到 59 之间的任何整数

hour — 小时,从 0 到 23 之间的任何整数

day — 日期,从 1 到 31 之间的任何整数(如果指定了月份,必须是该月份的有效日期)

month — 月份,从 1 到 12 之间的任何整数(或使用月份的英文简写如 jan、feb 等等)

dayofweek — 星期,从 0 到 7 之间的任何整数,这里的 0 或 7 代表星期日(或使用星期的英文简写如 sun、mon 等等)

command — 要执行的命令(命令可以是ls /proc >> /tmp/proc 之类的命令,也可以是执行你自行编写的脚本的命令。)


 
在以上任何值中,星号(*)可以用来代表所有有效的值。譬如,月份值中的星号意味着在满足其它制约条件后每月都执行该命令。

整数间的短线(-)指定一个整数范围。譬如,1-4 意味着整数 1、2、3、4。

用逗号(,)隔开的一系列值指定一个列表。譬如,3, 4, 6, 8 标明这四个指定的整数。

正斜线(/)可以用来指定间隔频率。在范围后加上 /<integer> 意味着在范围内可以跳过 integer。譬如,0-59/2 可以用来在分钟字段定义每两分钟。间隔频率值还可以和星号一起使用。例如,*/3 的值可以用在月份字段中表示每三个月运行一次任务。

开头为井号(#)的行是注释,不会被处理。

如你在 /etc/crontab 文件中所见,它使用run-parts 脚本来执行 /etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly和/etc/cron.monthly 目录中的脚本,这些脚本被相应地每小时、每日、每周、或每月执行。这些目录中的文件应该是 shell 脚本。

如果某 cron 任务需要根据调度来执行,而不是每小时、每日、每周、或每月地执行,它可以被添加到 /etc/cron.d 目录中。该目录中的所有文件使用和 /etc/crontab 中一样的语法。
# record the memory usage of the system every monday
# at 3:30AM in the file /tmp/meminfo
30 3 * * mon cat /proc/meminfo >> /tmp/meminfo
# run custom script the first day of every month at 4:10AM
10 4 1 * * /root/scripts/backup.sh
根用户以外的用户可以使用 crontab 工具来配置 cron 任务。所有用户定义的 crontab 都被保存在/var/spool/cron 目录中,并使用创建它们的用户身份来执行。要以某用户身份创建一个 crontab 项目,登录为该用户,然后键入crontab -e 命令,使用由 VISUAL 或 EDITOR环境变量指定的编辑器来编辑该用户的 crontab。该文件使用的格式和/etc/crontab 相同。当对 crontab 所做的改变被保存后,该 crontab 文件就会根据该用户名被保存,并写入文件/var/spool/cron/username 中。

cron 守护进程每分钟都检查 /etc/crontab 文件、etc/cron.d/ 目录、以及/var/spool/cron 目录中的改变。如果发现了改变,它们就会被载入内存。这样,当某个 crontab 文件改变后就不必重新启动守护进程了。
 
 控制对 cron 的使用
 
/etc/cron.allow 和/etc/cron.deny 文件被用来限制对 cron 的使用。这两个使用控制文件的格式都是每行一个用户。两个文件都不允许空格。如果使用控制文件被修改了,cron 守护进程(crond)不必被重启。使用控制文件在每次用户添加或删除一项 cron 任务时都会被读取。

无论使用控制文件中的规定如何,根用户都总是可以使用 cron。

如果 cron.allow 文件存在,只有其中列出的用户才被允许使用 cron,并且cron.deny 文件会被忽略。

如果 cron.allow 文件不存在,所有在cron.deny 中列出的用户都被禁止使用 cron。
 
启动和停止服务
 
要启动 cron 服务,使用 /sbin/service crond start 命令。要停止该服务,使用/sbin/service crond stop 命令。推荐你在引导时启动该服务。

由于Cron 是Linux的内置服务,可以用以下的方法启动、关闭这个服务:
/sbin/service crond start //启动服务
/sbin/service crond stop //关闭服务
/sbin/service crond restart //重启服务
/sbin/service crond reload //重新载入配置

要实现秒级的计划任务,可以使用开源项目:Quartz
详细:http://www.quartz-scheduler.org/
 参考文章:
https://blog.csdn.net/wenqiang1208/article/details/74853519
https://blog.csdn.net/foxman209/article/details/6759920?utm_source=blogxgwz4
 

JWT:完全前后端分离的项目如何做用户身份验证更安全?看这篇就够了!

前端开发zkbhj 发表了文章 • 0 个评论 • 230 次浏览 • 2018-09-19 14:48 • 来自相关话题

在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了。于是我们的程序就不知道谁是谁,就要再验证一次。所以为了保证系统安全,我们就需要验证用户否处于登录状态。

传统方式

前后端分离通过Restful API进行数据交互时,如何验证用户的登录信息及权限。在原来的项目中,使用的是最传统也是最简单的方式,前端登录,后端根据用户信息生成一个token,并保存这个 token 和对应的用户id到数据库或Session中,接着把 token 传给用户,存入浏览器 cookie,之后浏览器请求带上这个cookie,后端根据这个cookie值来查询用户,验证是否过期。

但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。

在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。

httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。但设置 httpOnly 就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。

另外,如果将验证信息保存在数据库中,后端每次都需要根据token查出用户id,这就增加了数据库的查询和存储开销。若把验证信息保存在session中,有加大了服务器端的存储压力。那我们可不可以不要服务器去查询呢?如果我们生成token遵循一定的规律,比如我们使用对称加密算法来加密用户id形成token,那么服务端以后其实只要解密该token就可以知道用户的id是什么了。不过呢,我只是举个例子而已,要是真这么做,只要你的对称加密算法泄露了,其他人可以通过这种加密方式进行伪造token,那么所有用户信息都不再安全了。恩,那用非对称加密算法来做呢,其实现在有个规范就是这样做的,就是我们接下来要介绍的 JWT。

Json Web Token(JWT)

JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:

简洁(Compact)

可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

自包含(Self-contained)

负载中包含了所有用户所需要的信息,避免了多次查询数据库


JWT 组成





Header 头部

头部包含了两部分,token 类型和采用的加密算法
 {
"alg": "HS256",
"typ": "JWT"
}它会使用 Base64 编码组成 JWT 结构的第一部分,如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串。

Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

Payload 负载

这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。
 {
"iss": "lion1ou JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "lion1ou@163.com"
}同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

Signature 签名

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

三个部分通过.连接在一起就是我们的 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

其实到这一步可能就有人会想了,HTTP 请求总会带上 token,这样这个 token 传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2,HTTP2 对头部进行了压缩,相信也解决了这个问题。

签名的目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息暴露

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

JWT 使用




 
首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

和Session方式存储id的差异

Session方式存储用户id的最大弊病在于Session是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员、用户所在的分组等。虽说JWT方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘存储而言可能就不算什么了。具体是否采用,需要在不同场景下用数据说话。

单点登录

Session方式来存储用户id,一开始用户的Session只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:www.taobao.com,nv.taobao.com,nz.taobao.com,login.taobao.com。所以如果要实现在login.taobao.com登录后,在其他的子域名下依然可以取到Session,这要求我们在多台服务器上同步Session。使用JWT的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。

总结

JWT的主要作用在于(一)可附带用户信息,后端直接通过JWT获取相关信息。(二)使用本地保存,通过HTTP Header中的Authorization位提交验证。但其实关于JWT存放到哪里一直有很多讨论,有人说存放到本地存储,有人说存 cookie。个人偏向于放在本地存储,如果你有什么意见和看法欢迎提出。

参考文档:
https://segmentfault.com/a/1190000005783306 
https://ruiming.me/authentication-of-frontend-backend-separate-application/ 
 
总结和摘录自:
https://blog.csdn.net/kevin_lc ... 46723 查看全部
在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了。于是我们的程序就不知道谁是谁,就要再验证一次。所以为了保证系统安全,我们就需要验证用户否处于登录状态。

传统方式

前后端分离通过Restful API进行数据交互时,如何验证用户的登录信息及权限。在原来的项目中,使用的是最传统也是最简单的方式,前端登录,后端根据用户信息生成一个token,并保存这个 token 和对应的用户id到数据库或Session中,接着把 token 传给用户,存入浏览器 cookie,之后浏览器请求带上这个cookie,后端根据这个cookie值来查询用户,验证是否过期。

但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。

在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。

httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。但设置 httpOnly 就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。

另外,如果将验证信息保存在数据库中,后端每次都需要根据token查出用户id,这就增加了数据库的查询和存储开销。若把验证信息保存在session中,有加大了服务器端的存储压力。那我们可不可以不要服务器去查询呢?如果我们生成token遵循一定的规律,比如我们使用对称加密算法来加密用户id形成token,那么服务端以后其实只要解密该token就可以知道用户的id是什么了。不过呢,我只是举个例子而已,要是真这么做,只要你的对称加密算法泄露了,其他人可以通过这种加密方式进行伪造token,那么所有用户信息都不再安全了。恩,那用非对称加密算法来做呢,其实现在有个规范就是这样做的,就是我们接下来要介绍的 JWT。

Json Web Token(JWT)

JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:


简洁(Compact)

可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

自包含(Self-contained)

负载中包含了所有用户所需要的信息,避免了多次查询数据库



JWT 组成

006tNc79gy1fbv54tfilmj31120b2wl9.jpg

Header 头部

头部包含了两部分,token 类型和采用的加密算法
 
{
"alg": "HS256",
"typ": "JWT"
}
它会使用 Base64 编码组成 JWT 结构的第一部分,如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串。


Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。


Payload 负载

这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。
 
{
"iss": "lion1ou JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "lion1ou@163.com"
}
同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

Signature 签名

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

三个部分通过.连接在一起就是我们的 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

其实到这一步可能就有人会想了,HTTP 请求总会带上 token,这样这个 token 传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2,HTTP2 对头部进行了压缩,相信也解决了这个问题。

签名的目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息暴露

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

JWT 使用

006tNc79gy1fbv63pzqocj30pj0h8t9m.jpg
 
  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  2. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。
  3. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
  4. 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
  5. 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  6. 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。


和Session方式存储id的差异

Session方式存储用户id的最大弊病在于Session是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员、用户所在的分组等。虽说JWT方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘存储而言可能就不算什么了。具体是否采用,需要在不同场景下用数据说话。

单点登录

Session方式来存储用户id,一开始用户的Session只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:www.taobao.com,nv.taobao.com,nz.taobao.com,login.taobao.com。所以如果要实现在login.taobao.com登录后,在其他的子域名下依然可以取到Session,这要求我们在多台服务器上同步Session。使用JWT的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。

总结

JWT的主要作用在于(一)可附带用户信息,后端直接通过JWT获取相关信息。(二)使用本地保存,通过HTTP Header中的Authorization位提交验证。但其实关于JWT存放到哪里一直有很多讨论,有人说存放到本地存储,有人说存 cookie。个人偏向于放在本地存储,如果你有什么意见和看法欢迎提出。

参考文档:
https://segmentfault.com/a/1190000005783306 
https://ruiming.me/authentication-of-frontend-backend-separate-application/ 
 
总结和摘录自:
https://blog.csdn.net/kevin_lc ... 46723

PHP升级7.2之后需要注意的“坑”

PHPzkbhj 发表了文章 • 0 个评论 • 245 次浏览 • 2018-09-12 20:25 • 来自相关话题

最近升级了PHP版本,从7.1升级到7.2,升级前版本:
PHP 7.1.14 (cli) (built: Feb 2 2018 08:42:59) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.1.14, Copyright (c) 1999-2018, by Zend Technologies
with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans升级后版本:
PHP 7.2.2 (cli) (built: Feb 24 2018 17:51:12) ( ZTS DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.2.2, Copyright (c) 1999-2018, by Zend Technologies升级完成之后发现有几个框架在使用时都出现了问题,主要原因集中在7.2之后废弃了一些功能,下面列出几个常见的问题:

1、each函数已被废弃:

之前版本写法:<?php
$array = array();
each($array);

// Deprecated: The each() function is deprecated. This message will be suppressed on further calls在7.2版本中会提示过时,可以使用foreach替代each方法,也可以自己修改each方法替代:
<?php
function func_new_each(&$array){
$res = array();
$key = key($array);
if($key !== null){
next($array);
$res[1] = $res['value'] = $array[$key];
$res[0] = $res['key'] = $key;
}else{
$res = false;
}
return $res;
}
2、当传递一个无效参数时,count()函数将抛出warning警告:

之前版本写法<?php
count('');

// Warning: count(): Parameter must be an array or an object that implements Countable在7.2版本中将严格执行类型区分,参数类型不正确,将会出现警告,所以需要在使用count方法时注意参数的值,不过也可以通过自己修改方法来替代(不建议):
<?php
function func_new_count($array_or_countable,$mode = COUNT_NORMAL){
if(is_array($array_or_countable) || is_object($array_or_countable)){
return count($array_or_countable, $mode);
}else{
return 0;
}
}3、create_function被废弃,可以用匿名函数来代替:

之前版本写法:<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $newfunc\n";
echo $newfunc(2, M_E) . "\n";
// outputs
// New anonymous function: lambda_1
// ln(2) + ln(2.718281828459) = 1.6931471805599

// Warning This function has been DEPRECATED as of PHP 7.2.0. Relying on this function is highly discouraged.在7.2版本中会有警告提示,可修改为匿名函数来替代:
<?php
$newfunc = function ($a,$b){
return "ln($a) + ln($b) = " . log($a * $b);
};
echo $newfunc(2, M_E) . "\n";以上就是升级之后暂时遇到的几个问题,其它相关修改可详看链家产品技术团队做的翻译及整理:PHP7.2 版本指南
https://mp.weixin.qq.com/s/60pohj2n7Pxba3G9vY92yg 查看全部
最近升级了PHP版本,从7.1升级到7.2,升级前版本:
PHP 7.1.14 (cli) (built: Feb  2 2018 08:42:59) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.1.14, Copyright (c) 1999-2018, by Zend Technologies
with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans
升级后版本:
PHP 7.2.2 (cli) (built: Feb 24 2018 17:51:12) ( ZTS DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.2.2, Copyright (c) 1999-2018, by Zend Technologies
升级完成之后发现有几个框架在使用时都出现了问题,主要原因集中在7.2之后废弃了一些功能,下面列出几个常见的问题:

1、each函数已被废弃:

之前版本写法:
<?php
$array = array();
each($array);

// Deprecated: The each() function is deprecated. This message will be suppressed on further calls
在7.2版本中会提示过时,可以使用foreach替代each方法,也可以自己修改each方法替代:
<?php
function func_new_each(&$array){
$res = array();
$key = key($array);
if($key !== null){
next($array);
$res[1] = $res['value'] = $array[$key];
$res[0] = $res['key'] = $key;
}else{
$res = false;
}
return $res;
}

2、当传递一个无效参数时,count()函数将抛出warning警告:

之前版本写法
<?php
count('');

// Warning: count(): Parameter must be an array or an object that implements Countable
在7.2版本中将严格执行类型区分,参数类型不正确,将会出现警告,所以需要在使用count方法时注意参数的值,不过也可以通过自己修改方法来替代(不建议):
<?php
function func_new_count($array_or_countable,$mode = COUNT_NORMAL){
if(is_array($array_or_countable) || is_object($array_or_countable)){
return count($array_or_countable, $mode);
}else{
return 0;
}
}
3、create_function被废弃,可以用匿名函数来代替:

之前版本写法:
<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $newfunc\n";
echo $newfunc(2, M_E) . "\n";
// outputs
// New anonymous function: lambda_1
// ln(2) + ln(2.718281828459) = 1.6931471805599

// Warning This function has been DEPRECATED as of PHP 7.2.0. Relying on this function is highly discouraged.
在7.2版本中会有警告提示,可修改为匿名函数来替代:
<?php
$newfunc = function ($a,$b){
return "ln($a) + ln($b) = " . log($a * $b);
};
echo $newfunc(2, M_E) . "\n";
以上就是升级之后暂时遇到的几个问题,其它相关修改可详看链家产品技术团队做的翻译及整理:PHP7.2 版本指南
https://mp.weixin.qq.com/s/60pohj2n7Pxba3G9vY92yg

PHP获取远程文件的mime类型的方法

PHPzkbhj 发表了文章 • 0 个评论 • 125 次浏览 • 2018-09-06 19:45 • 来自相关话题

 /**
* 获取远程或本地文件信息
* @param string $strUrl 远程文件或本地文件地址
* @param integer $intType 调用方式(1:get_headers 2:fsocketopen 3:curl 4:本地文件)
* @param array $arrOptional
* @return array
* @author mengdj<mengdj#outlook.com>
*/
function remote_filesize($strUrl,$intType=1,$arrOptional=array()){
$arrRet=array(
"length"=>0, //大小,字节为单位
"mime"=>"", //mime类型
"filename"=>"", //文件名
"status"=>0 //状态码
);
switch($intType){
case 1:
//利用get_headers函数
if(($arrTmp=get_headers($strUrl,true))){
$arrRet=array("length"=>$arrTmp['Content-Length'],"mime"=>$arrTmp['Content-Type']);
if(preg_match('/filename=\"(.*)\"/si',$arrTmp['Content-Disposition'],$arr)){
$arrRet["filename"]=$arr[1];
}
if(preg_match('/\s(\d+)\s/',$arrTmp[0],$arr)){
$arrRet["status"]=$arr[1];
}
}
break;
case 2:
//利用fsocket
if(($arrUrl=parse_url($strUrl))){
if($fp=@fsockopen($arrUrl['host'],empty($arrUrl['port'])?80:$arrUrl['port'],$error)){
@fputs($fp,"GET ".(empty($arrUrl['path'])?'/':$arrUrl['path'])." HTTP/1.1\r\n");
@fputs($fp,"Host: $arrUrl[host]\r\n");
@fputs($fp,"Connection: Close\r\n\r\n");
while(!feof($fp)){
$tmp=fgets($fp);
if(trim($tmp)==''){
//此行代码只读到头信息即可
break;
}else{
(preg_match('/(HTTP.*)(\s\d{3}\s)/',$tmp,$arr))&&$arrRet['status']=trim($arr[2]);
(preg_match('/Content-Length:(.*)/si',$tmp,$arr))&&$arrRet['length']=trim($arr[1]);
(preg_match('/Content-Type:(.*)/si',$tmp,$arr))&&$arrRet['mime']=trim($arr[1]);
(preg_match('/filename=\"(.*)\"/si',$tmp,$arr))&&$arrRet['filename']=trim($arr[1]);
}
}
@fclose($fp);
}
}
break;
case 3:
//利用curl
if(($ch=curl_init($strUrl))){
curl_setopt($ch,CURLOPT_HEADER,1);
curl_setopt($ch,CURLOPT_NOBODY,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
if(isset($arrOptional['user'])&&isset($arrOptional['password'])){
$headers=array('Authorization: Basic '.base64_encode($arrOptional['user'].':'.$arrOptional['password']));
curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
}
$tmp=curl_exec($ch);
curl_close($ch);
(preg_match('/Content-Length:\s([0-9].+?)\s/',$tmp,$arr))&&$arrRet['length']=trim($arr[1]);
(preg_match('/Content-Type:\s(.*)\s/',$tmp,$arr))&&$arrRet['mime']=trim($arr[1]);
(preg_match('/filename=\"(.*)\"/i',$tmp,$arr))&&$arrRet['filename']=trim($arr[1]);
(preg_match('/(HTTP.*)(\s\d{3}\s)/',$tmp,$arr))&&$arrRet['status']=trim($arr[2]);
}
break;
case 4:
//本地处理
if(file_exists($strUrl)) {
$arrRet=array(
"length"=>filesize($strUrl),
"mime" =>mime_content_type($strUrl),
"filename"=>basename($strUrl),
"status"=>200
);
}else{
$arrRet=array(
"length"=>0,
"mime" =>'',
"filename"=>basename($strUrl),
"status"=>404
);
}
break;
}
if(isset($arrOptional['getimagesize'])&&$arrRet['status']=='200'){
if(($arrTmp=@getimagesize($strUrl))){
$arrRet['width']=$arrTmp[0];
$arrRet['height']=$arrTmp[1];
$arrRet['type']=$arrTmp[2];
$arrRet['tag']=$arrTmp[3];
$arrRet['bits']=$arrTmp['bits'];
$arrRet['channels']=$arrTmp['channels'];
!isset($arrRet['mime'])&&$arrRet['mime']=$arrTmp['mime'];
}
}
return $arrRet;
} 查看全部
 
/**
* 获取远程或本地文件信息
* @param string $strUrl 远程文件或本地文件地址
* @param integer $intType 调用方式(1:get_headers 2:fsocketopen 3:curl 4:本地文件)
* @param array $arrOptional
* @return array
* @author mengdj<mengdj#outlook.com>
*/
function remote_filesize($strUrl,$intType=1,$arrOptional=array()){
$arrRet=array(
"length"=>0, //大小,字节为单位
"mime"=>"", //mime类型
"filename"=>"", //文件名
"status"=>0 //状态码
);
switch($intType){
case 1:
//利用get_headers函数
if(($arrTmp=get_headers($strUrl,true))){
$arrRet=array("length"=>$arrTmp['Content-Length'],"mime"=>$arrTmp['Content-Type']);
if(preg_match('/filename=\"(.*)\"/si',$arrTmp['Content-Disposition'],$arr)){
$arrRet["filename"]=$arr[1];
}
if(preg_match('/\s(\d+)\s/',$arrTmp[0],$arr)){
$arrRet["status"]=$arr[1];
}
}
break;
case 2:
//利用fsocket
if(($arrUrl=parse_url($strUrl))){
if($fp=@fsockopen($arrUrl['host'],empty($arrUrl['port'])?80:$arrUrl['port'],$error)){
@fputs($fp,"GET ".(empty($arrUrl['path'])?'/':$arrUrl['path'])." HTTP/1.1\r\n");
@fputs($fp,"Host: $arrUrl[host]\r\n");
@fputs($fp,"Connection: Close\r\n\r\n");
while(!feof($fp)){
$tmp=fgets($fp);
if(trim($tmp)==''){
//此行代码只读到头信息即可
break;
}else{
(preg_match('/(HTTP.*)(\s\d{3}\s)/',$tmp,$arr))&&$arrRet['status']=trim($arr[2]);
(preg_match('/Content-Length:(.*)/si',$tmp,$arr))&&$arrRet['length']=trim($arr[1]);
(preg_match('/Content-Type:(.*)/si',$tmp,$arr))&&$arrRet['mime']=trim($arr[1]);
(preg_match('/filename=\"(.*)\"/si',$tmp,$arr))&&$arrRet['filename']=trim($arr[1]);
}
}
@fclose($fp);
}
}
break;
case 3:
//利用curl
if(($ch=curl_init($strUrl))){
curl_setopt($ch,CURLOPT_HEADER,1);
curl_setopt($ch,CURLOPT_NOBODY,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
if(isset($arrOptional['user'])&&isset($arrOptional['password'])){
$headers=array('Authorization: Basic '.base64_encode($arrOptional['user'].':'.$arrOptional['password']));
curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
}
$tmp=curl_exec($ch);
curl_close($ch);
(preg_match('/Content-Length:\s([0-9].+?)\s/',$tmp,$arr))&&$arrRet['length']=trim($arr[1]);
(preg_match('/Content-Type:\s(.*)\s/',$tmp,$arr))&&$arrRet['mime']=trim($arr[1]);
(preg_match('/filename=\"(.*)\"/i',$tmp,$arr))&&$arrRet['filename']=trim($arr[1]);
(preg_match('/(HTTP.*)(\s\d{3}\s)/',$tmp,$arr))&&$arrRet['status']=trim($arr[2]);
}
break;
case 4:
//本地处理
if(file_exists($strUrl)) {
$arrRet=array(
"length"=>filesize($strUrl),
"mime" =>mime_content_type($strUrl),
"filename"=>basename($strUrl),
"status"=>200
);
}else{
$arrRet=array(
"length"=>0,
"mime" =>'',
"filename"=>basename($strUrl),
"status"=>404
);
}
break;
}
if(isset($arrOptional['getimagesize'])&&$arrRet['status']=='200'){
if(($arrTmp=@getimagesize($strUrl))){
$arrRet['width']=$arrTmp[0];
$arrRet['height']=$arrTmp[1];
$arrRet['type']=$arrTmp[2];
$arrRet['tag']=$arrTmp[3];
$arrRet['bits']=$arrTmp['bits'];
$arrRet['channels']=$arrTmp['channels'];
!isset($arrRet['mime'])&&$arrRet['mime']=$arrTmp['mime'];
}
}
return $arrRet;
}

PHP如何实现对图片指定位置(可多个)进行打码处理

PHPzkbhj 发表了文章 • 0 个评论 • 166 次浏览 • 2018-09-05 16:43 • 来自相关话题

<?php
/**
* 图片通用处理逻辑类
*/
namespace common\models\logics;

use Yii;
use yii\helpers\Json;


class ImageLogic
{
/**
* PHP将网页上的图片攫取到本地存储
* @param $imgUrl 图片url地址
* @param string $saveDir 本地存储路径 默认存储在当前路径
* @param null $fileName 图片存储到本地的文件名
* @return mix
*/
public static function crabImage($imgUrl, $fileName=null, $saveDir='/tmp/remote/'){
if(empty($imgUrl)){
return false;
}

//获取图片信息大小
$imgSize = getImageSize($imgUrl);
if(!in_array($imgSize['mime'],array('image/jpg', 'image/gif', 'image/png', 'image/jpeg'),true)){
return false;
}

//获取后缀名
$_mime = explode('/', $imgSize['mime']);
$_ext = '.'.end($_mime);

if(empty($fileName)){ //生成唯一的文件名
$fileName = uniqid(time(),true).$_ext;
}else{
$fileName .= $_ext;
}

//判断你是否已经本地有该图片,如果有则直接返回
if(file_exists($saveDir.$fileName)){
return $saveDir.$fileName;
}

//开始攫取
ob_start();
readfile($imgUrl);
$imgInfo = ob_get_contents();
ob_end_clean();

if(!file_exists($saveDir)){
mkdir($saveDir,0777,true);
}
$fp = fopen($saveDir.$fileName, 'a');
$imgLen = strlen($imgInfo); //计算图片源码大小
$_inx = 204800; //每次写入200k
$_time = ceil($imgLen/$_inx);
for($i=0; $i<$_time; $i++){
fwrite($fp,substr($imgInfo, $i*$_inx, $_inx));
}
fclose($fp);

return $saveDir.$fileName;
}


/**
* 将百度AI返回的位置信息转化为大码方法可以是别的位置坐标信息
* 百度:位置数组(坐标0点为左上角)
* left:表示定位位置的长方形左上顶点的水平坐标
* top:表示定位位置的长方形左上顶点的垂直坐标
* width:表示定位位置的长方形的宽度
* height:表示定位位置的长方形的高度
*
* 大码要求的坐标信息(坐标0点为左上角)
* x1:起点横坐标
* y1:起点纵坐标
* x2:终点横坐标
* y2:终点纵坐标
*/
public static function getLocationReal($location = )
{
if(empty($location))
$position = ;

//内容周围padding
$padding = 5;

//循环计算每个位置的坐标数据
foreach($location as $key => $value){
$position[$key]['x1'] = $value['left'] - $padding;
$position[$key]['y1'] = $value['top'] - $padding;
$position[$key]['x2'] = $value['left'] + $value['width'] + $padding;
$position[$key]['y2'] = $value['top'] + $value['height'] + $padding;
}

return $position;

}


/** 图片局部打马赛克
* @param String $source 原图
* @param Stirng $target 生成的图片
* @param int $x1 起点横坐标
* @param int $y1 起点纵坐标
* @param int $x2 终点横坐标
* @param int $y2 终点纵坐标
* @param int $deep 深度,数字越大越模糊
* @return boolean
*/
public static function imageMosaics($source, $target, $x1, $y1, $x2, $y2, $deep = 6){

// 判断原图是否存在
if(!file_exists($source)){
return false;
}

// 获取原图信息
list($o_width, $o_height, $o_type) = getimagesize($source);

// 判断区域是否超出图片
if($x1>$o_width || $x1<0 || $x2>$o_width || $x2<0 || $y1>$o_height || $y1<0 || $y2>$o_height || $y2<0){
return false;
}

switch($o_type){
case 1: $source_img = imagecreatefromgif($source); break;
case 2: $source_img = imagecreatefromjpeg($source); break;
case 3: $source_img = imagecreatefrompng($source); break;
default:
return false;
}

// 打马赛克
for($x=$x1; $x<$x2; $x=$x+$deep){
for($y=$y1; $y<$y2; $y=$y+$deep){
$color = imagecolorat($source_img, $x+round($deep/2), $y+round($deep/2));
imagefilledrectangle($source_img, $x, $y, $x+$deep, $y+$deep, $color);
}
}

// 生成图片
switch($o_type){
case 1: imagegif($source_img, $target); break;
case 2: imagejpeg($source_img, $target); break;
case 3: imagepng($source_img, $target); break;
}

return is_file($target)? true : false;

}

/**
* 在图片固定位置(支持多个位置)打码
* @param String $source 原图
* @param Stirng $target 生成的图片
* @param array $position 大码位置信息,可能有多个需要打码的位置
* @param int $position->x1 起点横坐标
* @param int $position->y1 起点纵坐标
* @param int $position->x2 终点横坐标
* @param int $position->y2 终点纵坐标
* @param int $deep 深度,数字越大越模糊
* return string 返回处理后的本地图片地址,如 /tmp/convert/1a987608924ce098fc8ccd3deaac8d09.png
*/
public static function imageAddMosaics($source, $target, $position, $deep = 6)
{
//默认生成临时文件地址
$target = empty($target) ? '/tmp/
convert/'.md5(Json::encode($position)).'.png' : $target;

//将要打码的位置信息从百度模式转为本地模式
$position = self::getLocationReal($position);

//判断图片是否为远程图片
if(strpos($source,'http') !==false){
$source = self::crabImage($source,md5(Json::encode($position)));
}

//循环处理图片打码
foreach ($position as $key => $value) {

//如果是第一次进行打码操作
if($key == 0){
self::imageMosaics($source, $target, $value['x1'], $value['y1'], $value['x2'], $value['y2'], 6);
}else{
self::imageMosaics($target, $target, $value['x1'], $value['y1'], $value['x2'], $value['y2'], 6);
}
}

return $target;

}

}


//使用
use common\models\logics\ImageLogic;
$source = 'https://qiniu.zkbhj.com/common/images/jiagou/%E5%87%AF%E5%86%B0%E7%A7%91%E6%8A%80%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84.png';
$target = '';
$position = [
0 => [
'width'=>656,
'top'=>452,
'left'=>110,
'height'=>35

],
1 => [
'width'=>748,
'top'=>1400,
'left'=>188,
'height'=>39

]
];

$img = ImageLogic::imageAddMosaics($source, $target, $position);




需要用到百度AI图片文本内容识别接口,参考:http://ai.baidu.com/docs#/OCR-API/0d9adafa

请求百度图片识别接口代码示例:
<?php

/**
* 发起http post请求(REST API), 并获取REST请求的结果
* @param string $url
* @param string $param
* @return - http response body if succeeds, else false.
*/
function request_post($url = '', $param = '')
{
if (empty($url) || empty($param)) {
return false;
}

$postUrl = $url;
$curlPost = $param;
// 初始化curl
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $postUrl);
curl_setopt($curl, CURLOPT_HEADER, 0);
// 要求结果为字符串且输出到屏幕上
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
// post提交方式
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $curlPost);
// 运行curl
$data = curl_exec($curl);
curl_close($curl);

return $data;
}
//token需要通过自己的appid和secretkey获取
$token = '24.6a792a76b5edaf7b8d48f65a8843c3c5.2592000.1538639572.282335-11769895';
$url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general?access_token=' . $token;
$img = file_get_contents('source.png');
$img = base64_encode($img);
$bodys = array(
"image" => $img
);
$res = request_post($url, $bodys);
echo $res;




  查看全部
<?php
/**
* 图片通用处理逻辑类
*/
namespace common\models\logics;

use Yii;
use yii\helpers\Json;


class ImageLogic
{
/**
* PHP将网页上的图片攫取到本地存储
* @param $imgUrl 图片url地址
* @param string $saveDir 本地存储路径 默认存储在当前路径
* @param null $fileName 图片存储到本地的文件名
* @return mix
*/
public static function crabImage($imgUrl, $fileName=null, $saveDir='/tmp/remote/'){
if(empty($imgUrl)){
return false;
}

//获取图片信息大小
$imgSize = getImageSize($imgUrl);
if(!in_array($imgSize['mime'],array('image/jpg', 'image/gif', 'image/png', 'image/jpeg'),true)){
return false;
}

//获取后缀名
$_mime = explode('/', $imgSize['mime']);
$_ext = '.'.end($_mime);

if(empty($fileName)){ //生成唯一的文件名
$fileName = uniqid(time(),true).$_ext;
}else{
$fileName .= $_ext;
}

//判断你是否已经本地有该图片,如果有则直接返回
if(file_exists($saveDir.$fileName)){
return $saveDir.$fileName;
}

//开始攫取
ob_start();
readfile($imgUrl);
$imgInfo = ob_get_contents();
ob_end_clean();

if(!file_exists($saveDir)){
mkdir($saveDir,0777,true);
}
$fp = fopen($saveDir.$fileName, 'a');
$imgLen = strlen($imgInfo); //计算图片源码大小
$_inx = 204800; //每次写入200k
$_time = ceil($imgLen/$_inx);
for($i=0; $i<$_time; $i++){
fwrite($fp,substr($imgInfo, $i*$_inx, $_inx));
}
fclose($fp);

return $saveDir.$fileName;
}


/**
* 将百度AI返回的位置信息转化为大码方法可以是别的位置坐标信息
* 百度:位置数组(坐标0点为左上角)
* left:表示定位位置的长方形左上顶点的水平坐标
* top:表示定位位置的长方形左上顶点的垂直坐标
* width:表示定位位置的长方形的宽度
* height:表示定位位置的长方形的高度
*
* 大码要求的坐标信息(坐标0点为左上角)
* x1:起点横坐标
* y1:起点纵坐标
* x2:终点横坐标
* y2:终点纵坐标
*/
public static function getLocationReal($location = )
{
if(empty($location))
$position = ;

//内容周围padding
$padding = 5;

//循环计算每个位置的坐标数据
foreach($location as $key => $value){
$position[$key]['x1'] = $value['left'] - $padding;
$position[$key]['y1'] = $value['top'] - $padding;
$position[$key]['x2'] = $value['left'] + $value['width'] + $padding;
$position[$key]['y2'] = $value['top'] + $value['height'] + $padding;
}

return $position;

}


/** 图片局部打马赛克
* @param String $source 原图
* @param Stirng $target 生成的图片
* @param int $x1 起点横坐标
* @param int $y1 起点纵坐标
* @param int $x2 终点横坐标
* @param int $y2 终点纵坐标
* @param int $deep 深度,数字越大越模糊
* @return boolean
*/
public static function imageMosaics($source, $target, $x1, $y1, $x2, $y2, $deep = 6){

// 判断原图是否存在
if(!file_exists($source)){
return false;
}

// 获取原图信息
list($o_width, $o_height, $o_type) = getimagesize($source);

// 判断区域是否超出图片
if($x1>$o_width || $x1<0 || $x2>$o_width || $x2<0 || $y1>$o_height || $y1<0 || $y2>$o_height || $y2<0){
return false;
}

switch($o_type){
case 1: $source_img = imagecreatefromgif($source); break;
case 2: $source_img = imagecreatefromjpeg($source); break;
case 3: $source_img = imagecreatefrompng($source); break;
default:
return false;
}

// 打马赛克
for($x=$x1; $x<$x2; $x=$x+$deep){
for($y=$y1; $y<$y2; $y=$y+$deep){
$color = imagecolorat($source_img, $x+round($deep/2), $y+round($deep/2));
imagefilledrectangle($source_img, $x, $y, $x+$deep, $y+$deep, $color);
}
}

// 生成图片
switch($o_type){
case 1: imagegif($source_img, $target); break;
case 2: imagejpeg($source_img, $target); break;
case 3: imagepng($source_img, $target); break;
}

return is_file($target)? true : false;

}

/**
* 在图片固定位置(支持多个位置)打码
* @param String $source 原图
* @param Stirng $target 生成的图片
* @param array $position 大码位置信息,可能有多个需要打码的位置
* @param int $position->x1 起点横坐标
* @param int $position->y1 起点纵坐标
* @param int $position->x2 终点横坐标
* @param int $position->y2 终点纵坐标
* @param int $deep 深度,数字越大越模糊
* return string 返回处理后的本地图片地址,如 /tmp/convert/1a987608924ce098fc8ccd3deaac8d09.png
*/
public static function imageAddMosaics($source, $target, $position, $deep = 6)
{
//默认生成临时文件地址
$target = empty($target) ? '/tmp/
convert/'.md5(Json::encode($position)).'.png' : $target;

//将要打码的位置信息从百度模式转为本地模式
$position = self::getLocationReal($position);

//判断图片是否为远程图片
if(strpos($source,'http') !==false){
$source = self::crabImage($source,md5(Json::encode($position)));
}

//循环处理图片打码
foreach ($position as $key => $value) {

//如果是第一次进行打码操作
if($key == 0){
self::imageMosaics($source, $target, $value['x1'], $value['y1'], $value['x2'], $value['y2'], 6);
}else{
self::imageMosaics($target, $target, $value['x1'], $value['y1'], $value['x2'], $value['y2'], 6);
}
}

return $target;

}

}


//使用
use common\models\logics\ImageLogic;
$source = 'https://qiniu.zkbhj.com/common/images/jiagou/%E5%87%AF%E5%86%B0%E7%A7%91%E6%8A%80%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84.png';
$target = '';
$position = [
0 => [
'width'=>656,
'top'=>452,
'left'=>110,
'height'=>35

],
1 => [
'width'=>748,
'top'=>1400,
'left'=>188,
'height'=>39

]
];

$img = ImageLogic::imageAddMosaics($source, $target, $position);




需要用到百度AI图片文本内容识别接口,参考:http://ai.baidu.com/docs#/OCR-API/0d9adafa

请求百度图片识别接口代码示例:
<?php

/**
* 发起http post请求(REST API), 并获取REST请求的结果
* @param string $url
* @param string $param
* @return - http response body if succeeds, else false.
*/
function request_post($url = '', $param = '')
{
if (empty($url) || empty($param)) {
return false;
}

$postUrl = $url;
$curlPost = $param;
// 初始化curl
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $postUrl);
curl_setopt($curl, CURLOPT_HEADER, 0);
// 要求结果为字符串且输出到屏幕上
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
// post提交方式
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $curlPost);
// 运行curl
$data = curl_exec($curl);
curl_close($curl);

return $data;
}
//token需要通过自己的appid和secretkey获取
$token = '24.6a792a76b5edaf7b8d48f65a8843c3c5.2592000.1538639572.282335-11769895';
$url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general?access_token=' . $token;
$img = file_get_contents('source.png');
$img = base64_encode($img);
$bodys = array(
"image" => $img
);
$res = request_post($url, $bodys);
echo $res;

QQ截图20180905165443.jpg

 

PHP给图片选定区域打上马赛克的方法

PHPzkbhj 发表了文章 • 0 个评论 • 86 次浏览 • 2018-09-04 21:11 • 来自相关话题

原理:

对图片中选定区域的每一像素,增加若干宽度及高度,生成矩型。而每一像素的矩型重叠在一起,就形成了马赛克效果。

本例使用GD库的imagecolorat获取像素颜色,使用imagefilledrectangle画矩型。<?php
/** 图片局部打马赛克
* @param String $source 原图
* @param Stirng $dest 生成的图片
* @param int $x1 起点横坐标
* @param int $y1 起点纵坐标
* @param int $x2 终点横坐标
* @param int $y2 终点纵坐标
* @param int $deep 深度,数字越大越模糊
* @return boolean
*/
function imageMosaics($source, $dest, $x1, $y1, $x2, $y2, $deep){

// 判断原图是否存在
if(!file_exists($source)){
return false;
}

// 获取原图信息
list($owidth, $oheight, $otype) = getimagesize($source);

// 判断区域是否超出图片
if($x1>$owidth || $x1<0 || $x2>$owidth || $x2<0 || $y1>$oheight || $y1<0 || $y2>$oheight || $y2<0){
return false;
}

switch($otype){
case 1: $source_img = imagecreatefromgif($source); break;
case 2: $source_img = imagecreatefromjpeg($source); break;
case 3: $source_img = imagecreatefrompng($source); break;
default:
return false;
}

// 打马赛克
for($x=$x1; $x<$x2; $x=$x+$deep){
for($y=$y1; $y<$y2; $y=$y+$deep){
$color = imagecolorat($source_img, $x+round($deep/2), $y+round($deep/2));
imagefilledrectangle($source_img, $x, $y, $x+$deep, $y+$deep, $color);
}
}

// 生成图片
switch($otype){
case 1: imagegif($source_img, $dest); break;
case 2: imagejpeg($source_img, $dest); break;
case 3: imagepng($source_img, $dest); break;
}

return is_file($dest)? true : false;

}

$source = 'source.jpg';
$dest = 'dest.jpg';

$flag = imageMosaics($source, $dest, 176, 98, 273, 197, 4);
echo '<img src="'.$source.'">';
echo '<img src="'.$dest.'">';

?> 查看全部
原理:

对图片中选定区域的每一像素,增加若干宽度及高度,生成矩型。而每一像素的矩型重叠在一起,就形成了马赛克效果。

本例使用GD库的imagecolorat获取像素颜色,使用imagefilledrectangle画矩型。
<?php  
/** 图片局部打马赛克
* @param String $source 原图
* @param Stirng $dest 生成的图片
* @param int $x1 起点横坐标
* @param int $y1 起点纵坐标
* @param int $x2 终点横坐标
* @param int $y2 终点纵坐标
* @param int $deep 深度,数字越大越模糊
* @return boolean
*/
function imageMosaics($source, $dest, $x1, $y1, $x2, $y2, $deep){

// 判断原图是否存在
if(!file_exists($source)){
return false;
}

// 获取原图信息
list($owidth, $oheight, $otype) = getimagesize($source);

// 判断区域是否超出图片
if($x1>$owidth || $x1<0 || $x2>$owidth || $x2<0 || $y1>$oheight || $y1<0 || $y2>$oheight || $y2<0){
return false;
}

switch($otype){
case 1: $source_img = imagecreatefromgif($source); break;
case 2: $source_img = imagecreatefromjpeg($source); break;
case 3: $source_img = imagecreatefrompng($source); break;
default:
return false;
}

// 打马赛克
for($x=$x1; $x<$x2; $x=$x+$deep){
for($y=$y1; $y<$y2; $y=$y+$deep){
$color = imagecolorat($source_img, $x+round($deep/2), $y+round($deep/2));
imagefilledrectangle($source_img, $x, $y, $x+$deep, $y+$deep, $color);
}
}

// 生成图片
switch($otype){
case 1: imagegif($source_img, $dest); break;
case 2: imagejpeg($source_img, $dest); break;
case 3: imagepng($source_img, $dest); break;
}

return is_file($dest)? true : false;

}

$source = 'source.jpg';
$dest = 'dest.jpg';

$flag = imageMosaics($source, $dest, 176, 98, 273, 197, 4);
echo '<img src="'.$source.'">';
echo '<img src="'.$dest.'">';

?>
 记录和分享在平时的工作学习中踩过的坑和学习总结下来的经验教训。