HEAD是一个指针,通常情况下它可以将它与当前分支等同(其实它是指向当前分支)
HEAD 表示当前版本,也就是最新的提交。上一个版本就是 HEAD^ ,上上一个版本就是 HEAD^^ ,往上100个版本写100个 “ ^ ” 比较容易数不过来,所以写成 HEAD~100 。HEAD~2 相当于 HEAD^^ 。
git实际上分为三个仓库,本地工作区,中间暂存区,远端仓库。
git add .
作用:把修改和添加的文件提交到暂存区,但不包括删除的文件。
git add -u
作用:把修改和删除的文件提交到暂存区,但不包括新文件
git add -a
作用:提交所有变化
撤销操作:当提交代码时不小心添加了错误文件,可以通过git reset HEAD进行撤销上次的全部提交,git reset HEAD ./../index.php 用来撤销某个文件。
git commit -m “message”
作用:简要说明提交的改动。
git diff
作用:查看当前工作区和版本库里最新版本的区别。
git pull
作用:从仓库或者本地的分支拉取并且整合代码。
git pull origin master
作用:拉取远程分支并且整合代码。
合并出现问题时可以通过git reset --merge进行回退
git push
作用:把改动提交到当前分支上
git branch
作用:查看所有本地分支
git branch -r
作用:查看所有远程分支
git branch -a
作用:查看所有分支
git checkout
作用:检出(切换)分支
git fetch
作用:当git branch -a 查看不到新的分支,git fetch更新一下分支信息后就可以看见新分支了。
git merge
作用:将其他分支合并到当前分支
git reset HEAD
作用:拉取最新一次提交到版本库的文件到暂存区,单独拉取某个文件用git reset HEAD -- index.php,然后git checkout可以拉取暂存区文件到本地仓库,同样的git checkout -- index.php可以从暂存区单独拉取一个文件到本地仓库。
git reset --hard 版本号
作用:拉取指定版本的文件到暂存区。
注意:如果想恢复到之前某个提交的版本,且那个版本之后提交的版本我们都不要了,就可以用这种方法。
恢复之前版本
git revert -n 版本号
注意:创建一个新的版本为之前提交的某个版本,可以先用git log 查看所有提交记录。
git revert HEAD~3
作用:撤销HEAD指针之前的第3个提交,并且生成一个新的提交。
注意:如果我们想撤销之前的某一版本,但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法。
局部:
git config user.name “用户名”
git config user.passsword “密码”
git config user.email “邮箱”
全局:
git config --global user.name “用户名”
git config --global user.passsword “密码”
git config --global user.email “邮箱”
查看设置:
git config --list
有时拉取代码失败可能需要当前克隆仓库的账号密码,使用以下命令:
git clone --bare http://username:password@gitlab.300.cn/package1/myProject.git
其中:
①如果username使用的是邮箱,那么@符号要用%40代替,例如:123@qq.com,要写成123%40qq.com
代码:
git add 我是修改内容.text
gitcommit --amend
解释:
【amend】袖中,会对最新一条commit进行修正,会把当前的commit和暂存区的内容合并起来后创建一个新的commit,用这个新的commit把当前commit替换掉
代码:
git add 我是忘提交的文件.text git commit -amend --no-edit
解释:
他表示提交信息不会更改,在git上仅为一次提交。
代码:
git reset --hard HEAD^ git pull
解释:
HEAD表示HEAD^往回数一个位置的commit,HEAD^表示你要恢复到哪个commit。因为你要撤销最新的一个commit(也就是当前未提交的内容),所以你要恢复到它上一次commit,也就是HEAD^。那么使用git reset --hard HEAD^就会恢复到上次提交前的样子,这时候再git pull把最后一次commit拉取下来,这样刚写完的修改就被撤销了(注意:一旦撤销到某个版本,那么这个版本之后的记录会全部消失,例如撤销到倒数第二次提交,那么最后一次提交记录就会彻底消失)。
代码:
git revert HEAD
解释:
将代码回退到上一个版本,并生成一个新的提交记录,原历史记录不变。 实际上它commit了一条与上一个版本相反的内容,相互抵消,达到撤销的效果。
connect('127.0.0.1', 6379, 30); //设置连接密码 $redis->auth('junyi'); //获取出售的数量,默认为空 $kuchun = $redis->get('kucun'); //秒杀数量 $total = 100; if ($kuchun < $total) { //监控售出数量是否变动,一旦中途变动就会打断redis事务 $redis->watch('kucun'); //开启事务 $redis->multi(); //设置售出数量+1 $redis->set("kucun", $kuchun + 1); //执行事务 $result = $redis->exec(); if ($result) { //剩余数量 $number = $total - ($kuchun + 1); //$openid 用户id $openid = $number; $redis->hset("list", "user_" . $openid, $kuchun); //获取抢购成功的用户 $data = $redis->hgetall('list'); var_dump($data); var_dump($number); } else { var_dump('手气很差哦,再试一下!'); } } else { var_dump('已经被抢光了'); } }
connect('127.0.0.1', 6379); for ($i = 1; $i rPush("goods_list", $i); } } //秒杀 function kill() { //假设这是是用户的uid $uuid = md5(uniqid('user') . time()); //创建连接redis对象 $redis = new \Redis(); //连接到服务器127.0.0.1,端口号6379,默认连接时间300,密码为空 $redis->connect('127.0.0.1', 6379); //监控列表中的值是否变动 $redis->watch("goods_list"); //开启事务 $redis->multi(); //从左边开始删除一个元素,并把删除的值赋给$goodsId if ($goodsId = $redis->lPop("goods_list")) { //秒杀成功,将幸运用户存在集合中 $redis->hSet("buy_order", $uuid, $goodsId); //执行事务 $redis->exec(); } else { //秒杀失败,将失败用户计数,默认从0开始+1 $redis->incr("fail_user_num"); } echo "SUCCESS"; }
Redis支持数据的持久化,可以将内存中的数据保存的在磁盘中,重启时可以再次加载进行使用
Redis不仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的储存。
Redis支持数据的备份,即master-slave模式的数据备份
性能极高-Redis能读的速度是110000次/s,写的速度是81000次/s。
丰富的数据类型-Redis支持二进制的案例String,Lists,Hashes,Sets以及Oracle Sets等数据类型操作。
院子-Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
丰富的特性-Redis还支持publish/subscribe,通知,key过期等等特性。
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以及追加的方式产生的,因为他们并不需要进行随机访问。
Redis的set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis的有序集合和set一样也是string类型的元素集合,且不允许重复的成员。不同的是每个元素都 会关联一个double类型的分数,redis正式通过分数来为集合中的成员进行从小到大的排序。有序集合 的成员是唯一的,但分数却可以重复。
标记事务的开始:MULTI
执行所有事务:EXEC
取消事务:DISCARD
见识一个或多个key,如果在事务执行前这个(或这些)key被其它命令所改动,那么事务将会被打断WATCH key [key……]
注意:单个redis命令的执行是原子性的,但Redis没有在事务上增加任何维持原子性的机制,所以Redis事务的执行并不是原子性的。事务可以理解为一个打包的批量执行的脚本,但批量命令并非原子性的操作,中间某条指令的失败不会导致前面已做的指令的回滚,也不会造成后续的指令不做。
RDB(redis database),可以理解为快照/内存快照,RDB持久化过程是将当前进程中的数据生成快照存储 到硬盘中。
AOF(append only file),以日志的方式记录每次写命令,服务重启的时候重新执行AOF文件中的命令 来恢复内存数据。因为解决了数据持久化实时性的问题,所以目前AOF是Redis持久化的主流方式。
RDB和AOF两种持久化方式的触发机制都分为两种:手动触发和自动触发。
手动触发:
执行save和bgsave两个命令可以手动触发RDB持久化。
save命令会阻塞当前服务器,直到RDB完成为止,如果数据量大的话会造成长时间的阻塞,线上环境一般禁止使用。
bgsave很好理解,就是background save,执行bgsave命令时Redis进程会fork一个子进程来完成RDB的过程,完成后自动结束,所以Redis主进程阻塞时间只有fork阶段的那一下。相对于save,阻塞时间很短。
自动触发:
在redis.config配置文件里可以配置自动触发:save <seconds> <changes>,这个配置的规则指的是在seconds秒内发生changes次写操作,就会自动进行一次bgsave,例如:save 900 1,指的是如果900秒内有1条Key信息发生变化就会触发一次bgsave。
还有在执行shutdown命令的时候,如果没有开启AOF持久化功能,那么会自动执行一次bgsave。
执行流程:
执行bgsave命令的时候,Redis主进程会检查是否有子进程在执行RDB/AOF持久化任务,如果有的话,直接返回。
Redis主进程会fork一个子进程来执行执行RDB操作,fork操作会对主进程造成阻塞(影响Redis的读写),fork操作完成后会发消息给主进程,从而不再阻塞主进程。
RDB子进程会根据Redis主进程的内存生成临时的快照文件,RDB完成后会使用临时快照文件替换掉原来的RDB文件。
RDB子进程完成RDB持久化后会发消息给主进程,通知RDB持久化完成。
RDB优点:
RDB文件小,非常适用于定时备份,用于灾难恢复。
Redis加载RDB文件的速度比AOF快很多,因为RDB文件中直接存储的内存数据,而AOF文件中存储的是一条条命令。
RDB缺点:
RDB无法做到实时持久化,因为fork子进程属于重量级操作,会阻塞Redis主进程。
存在老版本的Redis不兼容新版本RDB格式文件的问题。
主要因为RDB持久化不支持实时持久化,只要Redis服务宕机了,那么从上一次bgsave到宕机之间的所有数据都会丢失,所以在数据实时性要求高的情况下不适合使用RDB,所以Redis又提供了AOF持久化
其他细节性说明:
在有子进程执行RDB过程的时候,Redis主进程的读写不受影响,但是对于Redis的写操作不会同步到主进程的主内存中,而是会写到一个临时的内存区域作为一个副本,等到主进程接收到子进程完成RDB过程的消息后再将内存副本中的数据同步到主内存。
Redis默认采用LZF算法对RDB文件进行压缩,所以生成的内存文件会比内存小很多。
说明:AOF默认是关闭的,可以在redis.conf配置文件中添加下面配置开启
AOF:appendonly yes
手动触发:
执行bgrewriteaof命令直接触发AOF重写。
自动触发:
在redis.config配置文件中有两个配置项:
auto-aof-rewrite-min-size 64MB auto-aof-rewrite-min-percenrage 100
上面两个配置表示:
- 当AOF文件小于64MB的时候不进行AOF重写
- 当当前AOF文件比上次AOF重写后的文件大100%的时候进行AOF重写
可以在redis.conf配置文件中添加这两个参数来自动触发AOF重写,执行bgrewriteaof命令
执行流程:
所有的写命令都会追加到aof_buf(缓冲区)中。
可以使用不同的策略将AOF缓冲区中的命令写到AOF文件中。
随着AOF文件的越来越大,会对AOF文件进行重写。
当服务器重启的时候,会加载AOF文件并执行AOF文件中的命令用于恢复数据。
简单分析一下AOF执行流程中的一些问题:
因为Redis为了效率,使用单线程来响应命令,如果每次写命令都追加写硬盘的操作,那么Redis的响应速度还要取决于硬盘的IO效率,显然不现实,所以Redis将写命令先写到AOF缓冲区。
写道缓冲区还有一个好处是可以采用不同的策略来实现缓冲区到硬盘的同步,可以让用户自行在安全性和性能方面做出权衡。
同步策略:
在了解同步策略之前,需要先来了解两个三方法flushAppendOnlyFile、write和save:
redis的服务器进程是一个事件循环,文件事件负责处理客户端的命令请求,而时间事件负责执行serverCron函数这样的定时运行的函数。在处理文件事件执行写命令,使得命令被追加到aof_buf中,然后在处理时间事件执行serverCron函数会调用flushAppendOnlyFile函数进行文件的写入和同步
write:根据条件,将aof_buf中的缓存写入到AOF文件
save:根据条件,调用fsync或fdatasync函数将AOF文件保存到磁盘
Redis支持的三种同步策略:
AOF_FSYNC_NO:不保存(write和read命令都由主进程执行)
AOF_FSYNC_EVERYSEC:每一秒钟保存一次(write由主进程完成,save由子进程完成)
AOF_FSYNC_ALWAYS:每执行一个命令保存一次(write和read命令都由主进程执行)
AOF_FSYNC_NO:
在这种策略下,每次flushAppendOnlyFile函数被调用的时候都会执行一次write方法,但是不会执行 save方法。
只有下面三种情况下才会执行save方法:
Redis被关闭
AOF功能被关闭
系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)
这三种情况下的save操作都会引起Redis主进程阻塞,并且由于长时间没有执行save命令,所以save 命令执行的时候,阻塞时间会很长。
AOF_FSYNC_EVERYSEC:
在这种策略下,save操作原则上每隔一秒钟就会执行一次, 因为save操作是由后台子线程调用的, 所 以它不会引起服务器主进程阻塞。
其实根据Redis的状态,每当 flushAppendOnlyFile函数被调用时,write命令和save命令的执行又分 为四种不同情况:
根据以上图知道,在AOF_FSYNC_EVERYSEC策略下, 如果在情况1时发生故障停机, 那么用户最多 损失小于2秒内所产生的数据;而如果在情况2时发生故障停机,堆积了很多save命令,那么用户损 失的数据是可以超过 2 秒的。
AOF_FSYNC_ALWAYS:
在这种模式下,每次执行完一个命令后,write和save命令都会执行。
另外,因为save命令是由Redis主程序执行的,所以在save命令执行期间,主程序会被阻塞。
三种策略优缺点:
AOF_FSYNC_NO策略虽然表面上看起来提升了性能,但是会存在每次save命令执行的时候相对长时间阻塞主进程的问题。并且数据的安全性的不到保证,如果Redis服务器突然宕机,那么没有从AOF缓存中保存到硬盘中的数据都会丢失。
AOF_FSYNC_ALWAYS策略的安全性的到了最大的保障,理论上最多丢失最后一次写操作,但是由于每个写操作都会阻塞主进程,所以Redis主进程的响应速度受到了很大的影响。
AOF_FSYNC_EVERYSEC策略是比较建议的配置,也是Redis的默认配置,相对来说兼顾安全性和性能。
重写机制:
随着命令不断从AOF缓存中写入到AOF文件中,AOF文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制来压缩AOF文件。
AOF文件的压缩和RDB文件的压缩原理不一样,RDB文件的压缩是使用压缩算法将二进制的RDB文件压缩,而AOF文件的压缩主要是去除AOF文件中的无效命令,比如说:
同一个key的多次写入只保留最后一个命令
已删除、已过期的key的写命令不再保留
重写流程:
执行bgrewriteaof命令的时候,如果当前有进程正在执行AOF重写,那么直接返回;如果有进程正在执行bgsave,那么等待bgsave执行完毕再执行AOF重写。
Redis主进程会fork一个子进程执行AOF重写,开销和RDB重写一样。
AOF重写过程中,不影响Redis原有的AOF过程,包括写消息到AOF缓存以及同步AOF缓存中的数据到硬盘。
AOF重写过程中,主进程收到的写操作还会将命令写到AOF重写缓冲区,注意和AOF缓冲区区分开。
由于AOF重写过程中原AOF文件还在陆续写入数据,所以AOF重写子进程只会拿到fork子进程时的AOF文件进行重写。
子进程拿到原AOF文件中的数据写道一个临时的AOF文件中。
子进程完成AOF重写后会发消息给主进程,主进程会把AOF重写缓冲区中的数据写道AOF缓冲区,并且用新的AOF文件替换旧的AOF文件。
其他细节性说明:
Redis对AOF的重要性看得比RDB重,因为RDB的时候如果有进程正在执行AOF,那么直接返回;而AOF的时候如果有进程正在执行RDB,那么等RDb结束再执行AOF。
Redis再AOF重写的时候新建一个AOF重写缓冲区的目的是为了保证重写过程中的写命令数据不会丢失。
子进程在重写AOF文件的时候,每次写硬盘的数据量由配置决定,不能太大,否则会导致硬盘阻塞(默认32MB)。
AOF重写的整个过程有三个部分会阻塞进程:
主进程fork子进程的时候
主进程把AOF重写缓冲区中的数据写到AOF缓冲区的时候
使用新的AOF文件替换掉旧的AOF文件的时候
RDB持久化基于内存快照存储二进制文件,AOF持久化基于写命令存储文本文件。
RDB文件采用了压缩算法,比较小;AOF文件随着命令的叠加会越来越大,Redis提供了AOF重写来压缩AOF文件。
恢复RDB文件的速度比AOF文件快很多。
RDB持久化方式实时性不好,所以AOF持久化更主流。
合理的使用AOF的同步策略,理论上不会丢失大量的数据。
Redis重启的时候优先加载AOF文件,如果AOF文件不存在再去加载RDB文件。
如果AOF文件和RDB文件都不存在,那么直接启动。
不论加载AOF文件还是RDB文件,只要发生错误都会打印错误信息,并且启动失败。
//快捷查询 Db::table('think_user') ->where('name|title','like','thinkphp%') ->where('create_time&update_time','>',0) ->find(); //sql: SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' OR `title` LIKE 'thinkphp%' ) AND ( `create_time` > 0 AND `update_time` > 0 ) LIMIT 1
->whereOr([//第一维代表多个条件 [//第二维之间用or链接 //第三维之间用and连接 ['create_time', '<=', time()], ['status', '=', '1'] ],[ ['id', '>=', 10], ['update_time', '>=', time()], ] ]) //拼凑出来的结果: //where (create_time <= time() and status = 1) //or (update_time >= time() and id >= 10)
适用于多对多,并且有中间表的,例如,不同用户关联不同的兴趣标签,关联数据单独存在一个表中:
建表:
article表(字段:id,……)
label表(字段:id,name,……)
article_label表(字段:id,article_id,label_id,createtime,……)
article模型中:
//方法名随意,调用时使用此方法名 public function Labels(){ //参数1:关联模型 //参数2:中间表表名,不带表前缀 //参数3:中间表 关联 关联模型的外键 //参数4:中间表 关联 当前模型的外键 return $this->belongsToMany('LabelModel', 'article_label','label_id','article_id'); }
控制器中使用:
//查询 $data=ArticleModel::with('Labels')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条文章数据下第一个标签的名字 dump($data[0]->Labels[0]->name); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data); //删除 $data = ArticleModel::find(1);//查询文章信息 $data->Labels()->detach();//删除article_label表中当前文章的信息 //添加 $label_ids=[1,2,3,4,5,6,7,8,9];//需要绑定的label表主键id //参数1是需要绑定的多个label_id,参数2是其他需要补充的字段 $data->Labels()->attach($label_ids,['createtime'=> time()]);//保存
一般用于一条记录对应多条记录,例如某个文章的评论:
建表:
article表(字段:id,……)
comment表(字段:id,article_id,content,……)
article模型中:
//方法名字随意,调用时方法名作为参数使用 public function comments(){ //参数1:关联模型名 //参数2:关联模型外键 //参数3:当前模型表主键 return $this->hasMany('CommentModel','article_id','id'); }
控制器中使用:
$data=ArticleModel::with('comments')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条文章数据下第一条评论数据的内容 dump($data[0]->comments[0]->content); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
一般用于多级分类存在同一个表中:
建表:
classify表(字段:id,name,level,pid,……)
classify模型中:
//方法名字随意,调用时方法名作为参数使用 public function secondClassify(){ //参数1:当前模型名 //参数2:上级id(原关联模型外键) //参数3:当前模型表主键 return $this->hasMany('ClassifyModel','pid','id'); }
控制器中使用:
//查询一级分类,自动补全二级 $data=ClassifyModel::with('secondClassify')->where('level',1)->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条一级分类数据下第一个二级分类的名称 dump($data[0]->secondClassify[0]->name); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
一般用于主记录查询子记录(子表有关联记录在主表),例如一个用户信息只对应一个用户:
建表:
user表(字段:id,message_id,……)
message表(字段:id,user_id,……)
user模型中:
//方法名字随意,调用时方法名作为参数使用 public function user(){ //参数1:关联模型名 //参数2:关联模型外键 //参数3:当前模型主键 return $this->hasOne('UserModel','message_id','id'); }
控制器中使用:
$data=UserModel::with('user')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印当前用户信息对应的用户生日 dump($data[0]->user->birthday); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
一般用于主记录查询子记录(子表有一条记录属于主表),例如一个用户只有一条用户信息:
建表:
user表(字段:id,message_id,……)
message表(字段:id,user_id,……)
classify模型中:
//方法名字随意,调用时方法名作为参数使用 public function messages(){ //参数1:关联模型名 //参数2:关联模型外键 //参数3:当前模型主键 return $this->belongsTo('MessageModel','message_id','id'); }
控制器中使用:
$data=UserModel::with('messages')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条用户数据下子信息的金额 dump($data[0]->messages->money); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
//取单列:获取所有标签名,输出结构:[id=>name] LabelModel::column('name','id'); //取多列:获取所有标签名和创建时间,输出结构:[id=>[id,name,createtime]] LabelModel::where('status',1)->column('name,createtime','id');
cache()可以用于select、find、value和column方法,以及其衍生方法,使用cache方法后,在缓存有效期之内不会再次进行数据库查询操作,而是直接获取缓存中的数据,关于数据缓存的类型和设置可以参考缓存部分。
1. 简单的存储
//查询news表中id=10的新闻存储于cache中 //写true默认读取配置的中缓存时间 db('news')->cache(true)->find(10); //你也可以自定义时间,60秒表示: db('news')->cache(true,60)->find(10);
2. 指定缓存标识
//等同于使用缓存时的键,默认读取配置的中缓存时间: db('news')->cache('key')->find(15); //全局读取这条数据: $data = \think\Cahce::get('key');
3.支持设置缓存标签:
//缓存键:key,缓存时间:60秒,标签为:tagName db('news')->cache('key',60,'tagName')->find(15); //全局读取带标签的缓存: Db::name('news')->cache('news_list',60,'shx')->select(); $data = \think\Cache::tag('shx')->get('news_list');
封装性:
也称为信息隐藏,就是将一个类的使用和实现分开,只保留部分接口和方法与外部联系,或者说只公开了一些供开发人员使用的方法。于是开发人员只 需要关注这个类如何使用,而不用去关心其具体的实现过程,这样就能实现MVC分工合作,也能有效避免程序间相互依赖,实现代码模块间松藕合。
继承性:
就是子类自动继承其父级类中的属性和方法,并可以添加新的属性和方法或者对部分属性和方法进行重写。继承增加了代码的可重用性。PHP只支持单继承,也就是说一个子类只能有一个父类。
多态性:
子类继承了来自父级类中的属性和方法,并对其中部分方法进行重写。于是多个子类中虽然都具有同一个方法,但是这些子类实例化的对象调用这些相同的方法后却可以获得完全不同的结果,这种技术就是多态性。多态性增强了软件的灵活性。
易维护
采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。
质量高
在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。
效率高
在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
易扩展
由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
正则表达式是一种文本模式,包括普通字符(例如a到z之间的字母)和特殊符号(称为“元字符”)。
正则表达式使用单个字符串来描述、匹配一系列匹配某个语法规则的字符串。
正则表达式是繁琐的,但它是强大的,学会之后的应用会让你提高效率。
*:匹配的字符可以不出现,也可以出现一次或多次(大于等于0)
+:匹配的字符至少出现一次(大于等于1)
?:匹配的字符最多只可以出现一次(0或1)
{n}:代表匹配n次
{n,}:代表至少匹配n次,最多无限
{n,m}:代表至少匹配n次,最多匹配m次
^:匹配输入字符串的开始位置
$:匹配输入字符串的结束位置
\b:匹配一个单词边界,即单词与空格之间的位置(光标所在的位置)
\B:非单词边界匹配
跟在定界符//的后面:
g:匹配出所有符合要求的结果,例:/\d+/g 匹配字符串中所有数字组合
s:使正则表达式中的(.)可以匹配到换行符
i:忽略大小写配匹配,例:/[a-z]/gi 匹配所有字母忽略大小写
m:进行多行匹配,必须配合定位符^和$使用,例:
str="1233asfasfasf\nasdas13232\n54346waqadsafaf\n9999asdasda" str.match(/^\d+/gm)//匹配每行以数字开头的内容 结果: ["1233", "54346", "9999"]
字符 | 描述 |
(pattern) | 匹配 pattern 并获取这一匹配。这是一个获取匹配。 |
(?:pattern) | 匹配 pattern 但不获取匹配结果,这是一个非获取匹配,不进行存储供以后使用,但是消耗字符匹配。 |
(?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,不需要获取供以后使用。 |
(?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,不需要获取供以后使用。 |
(?<=pattern) | 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。 |
(?<!pattern) | 反向否定预查,与正向否定预查类似,只是方向相反。 |
x|y | 匹配 x 或 y。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\f | 匹配一个换页符。等价于 \x0c 和 \cL。 |
\n | 匹配一个换行符。等价于 \x0a 和 \cJ。 |
\r | 匹配一个回车符。等价于 \x0d 和 \cM。 |
\t | 匹配一个制表符。等价于 \x09 和 \cI。 |
\v | 匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
\w | 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。 |
\W | 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。 |
\cx | 匹配由 x 指明的控制字符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。 |
在使用正则表达式的时候,我们经常会使用()把某个部分括起来,称为一个子模式。
获取匹配:是指系统会在幕后将所有的子模式匹配结果保存起来,供我们查找或者替换。
非获取匹配:是指系统不会保存子模式的匹配结果,子模式的匹配更多的只是作为一种限制条件使用,如正向预查(?=)、反向预查(?<=)、负正向预查(?!)、负反向预查(?<!)
一般放在整个正则表达式的后面。
正向预查的意思是,子模式仅仅作为允许出现的条件限制,并不作为匹配结果输出。
//内容: Windows 95 and Windows 98 are the successor. Then Windows 2000 and Windows Xp appeared. Windows Vista is the Latest version of the family. //正则表达式 /Windows(?= [\d]+\b)/g //匹配结果: Windows Windows Windows //以上三个Windows分别指的是Windows 95、Windows 98、Windows 2000中的Windows。
一般放在整个正则表达式的后面。
负正向预查意思是,子模式仅仅作为不允许出现的条件限制,并不作为匹配结果输出。
//内容: Windows 95 and Windows 98 are the successor. Then Windows 2000 and Windows Xp appeared. Windows Vista is the Latest version of the family. //正则表达式 /Windows(?! [\d]+\b)/g //匹配结果: Windows Windows //以上两个Windows分别指的是Windows Xp、Windows Vista中的Windows。
一般放在整个正则表达式的前面。
反向预查与正向预查很相似,子模式仅仅作为允许出现的条件限制,不作为结果输出。
不同的是,正向预查匹配子模式前面的结果作为匹配结果,而反向预查匹配子模式后面的结果作为匹配结果。
//内容 CNY:100.2 USD:222.1 USD:301.3 HKD:122.1 CNY:114.4 //正则表达式 /(?<=CNY:)\d+\.\d/g //匹配结果: 100.2 114.4
一般放在整个正则表达式的前面。
负反向预查与反向预查很相似,子模式仅仅作为不允许出现的条件限制,不作为结果输出。
不同的是,负正向预查匹配子模式前面的结果作为匹配结果,而负反向预查匹配子模式后面的结果作为匹配结果。
//内容 CNY:100.2 USD:222.1 USD:301.3 HKD:122.1 CNY:114.4 //正则表达式 /(?<!CNY:)\b\d+\.\d/g //匹配结果: 222.1 301.3 122.1
.匹配任意除换行符“\n”外的字符;
*表示匹配前一个字符0次或无限次;
?表示前边字符的0次或1次重复
+或*后跟?表示非贪婪匹配,即尽可能少的匹配,如*?重复任意次,但尽可能少重复;
.*? 表示匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
如:a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab。不加问号的话匹配的是aabab。
有两种情况:
1. num范围在1-99,表示匹配到第几个括号的重复字符。
具体用法只有:/(.)\1/ 用来匹配两个连续的字符。其他具体用法不明确
2. num是0或者是三位数,表示8进制所对应的ASCII码字符
str="1aa2" str.match(/(.)\141/g) 结果: ["1a"]
\c匹配control+控制字符,就是说匹配由x指明的控制字符。
比如:\cj匹配control+j,代表\n。
\ca \cb \cc ... 又分别匹配 control+a, control+b,control+c....,具体他们等价于什么,看运行的程序了
百度搜索控制字符可以查看所有a-z的匹配
[0-9]+匹配多个数字,[0-9]匹配单个数字,+匹配一个或多个。
{3,15}匹配长度介于3-15字符长度,{2}匹配确定的两次,{1,}至少匹配一次
例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
()标记一个表达式的开始和结束位置。
.匹配换行符\n之外的任何单字符。
\将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义字符。
{}标记限定符表达式的开始和结束
|指明两项之间的一个选择,一般配合()使用。
[\u4e00-\u9fa5] //匹配常用中文汉字(繁体字可能匹配不到) [a-z] //匹配所有的小写字母 [A-Z] //匹配所有的大写字母 [a-zA-Z] //匹配所有的字母 [A-z] //匹配所有的字母 [0-9] //匹配所有的数字 [0-9\.\-] //匹配所有的数字,英文句号和减号 [ \f\r\t\n\v] //匹配所有的白字符(空格、换页符、回车符、水平制表符、垂直制表符) [^a-z] //除了小写字母以外的所有字符 [^\\\/\^] //除了(\)(/)(^)之外的所有字符 [^\"\'] //除了双引号(")和单引号(')之外的所有字符 ^[a-zA-Z0-9_]{1,}$ // 所有包含一个以上的字母、数字或下划线组成的字符串 ^[1-9][0-9]{0,}$ // 所有的正整数 ^\-{0,1}[0-9]{1,}$ // 所有的整数 /^[-]?(0{1}|[1-9][0-9]*)(\.[0-9]+)?$/ // 所有的浮点数 /^\w+@\w+\.\w+$/ //邮箱验证
由于章节编号在大的输入文档中会很可能超过九,所以您需要一种方式来处理两位或三位章节编号。下面的正则表达式匹配编号为任何位数的章节标题
/Chapter [1-9][0-9]*/
含义:匹配Chapter 加上一个大于零的数字,[1-9]匹配1到9任何一个数字,[0-9]匹配0-9任何一个数字,*表示前面的表达式[0-9]匹配次数大于等于0。之所以不用+,是因为编号不一定需要两位数或者更多位数,之所以不用?,是因为它会让编号限制在两位数。
下面的正则表达式匹配一个章节标题,该标题只包含两个尾随数字,并且出现在行首:
/^Chapter [1-9][0-9]{0,1}/
真正的章节标题不仅出现行的开始处,而且它还是该行中仅有的文本。它即出现在行首又出现在同一行的结尾。下面的表达式能确保指定的匹配只匹配章节而不匹配交叉引用。通过创建只匹配一行文本的开始和结尾的正则表达式,就可做到这一点。
/^Chapter [1-9][0-9]{0,1}$/
/a/匹配字母a
/7/匹配数字7
/a.c/匹配以a开头,c结尾,中间除了\n以外的换行符。(.可以匹配字符串中打印或非打印字符,除了\n)
/index\.html/匹配index.html(.前面加反斜杠代表就是.)
请注意,没有串联运算符。只须在一个字符后面键入另一个字符。(通俗讲,两个子表达式或者匹配符之间不需要连接符)
括在中括号表达式中的字符只匹配处于正则表达式中该位置的单个字符
/text [12345]/
可以匹配到text 1、text 2、text 3、text 4、text 5。
也可以写成
/text [1-5]/
但是开始值必须小于结束值
如果在中括号中使用连字符-,请用以下三种方法:
(1) [/-]反斜杠转义
(2) [-a-z]或[a-z-]将连字符放在中括号的开始或者结尾
(3) [!--]或[!-~]创建一个范围开始值小于连字符,结束值大于等于连字符
/text [^1-5]/中括号内以^开头表示匹配除了中括号内以外的任何数字和字符
替换使用 | 字符来允许在两个或多个替换选项之间进行选择。
/^Chapter|Section [1-9][0-9]{0,1}$/
上面的正则表达式要么匹配行首的单词 Chapter,要么匹配行尾的单词 Section 及跟在其后的任何数字。
/^(Chapter|Section) [1-9][0-9]{0,1}$/
在上面的正则表达式的适当位置添加括号,就可以使该正则表达式匹配 Chapter 1 或 Section 3
Js中的match()可以查找一个或多个正则表达式可以匹配的字符串,当正则为全局匹配时,返回所有匹配成功的值组成的数组。当正则为非全局匹配时,返回第一个匹配成功的相关内容。如果没有匹配到则返回null。
g可以控制是否查找多个
var str ="1;9uisjflk;jamsg56a654ag654ga"; var reg=/\d/g; console.log (str.match (reg)); 结果: ["1", "9", "5", "6", "6", "5", "4", "6", "5", "4"]
匹配连续的
var str ="1;9uisjflk; jamsg56a654ag654ga"; var reg=/\d+/g; console.log (str.match (reg)); 结果: ["1", "9", "56", "654", "654"]
Js中的test()可以验证字符串中有有没有符合要求的字符,如果字符串中有匹配的值返回 true ,否则返回 false。
var str="d2545ad5a4d54"; var patt=/\d/g; var result=patt.test(str); console.log(result); 结果: true exec() Js中的 exec()可以提取正则中符合要求的值,并且以数组的形式返回,与match()方法有相似之处(主要是全局匹配时不同,match()会一次性返回所有符合的字符,exec()是一个个返回 )。 不全局匹配的话(不加g):返回一个数组:匹配到的字符、匹配到的字符位置、匹配的整个字符串本身: var str="sad5a4d54add225sd22a"; var re=/\d+/; console.log(re.exec(str),'第一次'); console.log(re.exec(str),'第二次'); console.log(re.exec(str),'第三次'); console.log(re.exec(str),'第四次'); 结果: ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第一次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第二次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第三次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第四次"
如果全局匹配(加g):返回一个数组:匹配到的字符(每次调用指针会向后移一个,匹配到最后一个符合的字符,如果再次调用会返回null,如果还继续调用,就会从头开始匹配)、匹配到的字符位置、匹配的整个字符串本身
var str="sad5a4d54add225sd22a"; var re=/\d+/g; console.log(re.exec(str),'第一次') console.log(re.exec(str),'第二次') console.log(re.exec(str),'第三次') console.log(re.exec(str),'第四次') console.log(re.exec(str),'第五次') console.log(re.exec(str),'第六次') console.log(re.exec(str),'第七次') 结果: ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第一次" ["4", index: 5, input: "sad5a4d54add225sd22a", groups: undefined] "第二次" ["54", index: 7, input: "sad5a4d54add225sd22a", groups: undefined] "第三次" ["225", index: 12, input: "sad5a4d54add225sd22a", groups: undefined] "第四次" ["22", index: 17, input: "sad5a4d54add225sd22a", groups: undefined] "第五次" null "第六次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第七次"
search()与indexOf()方法类似,用来查找指定字符串在字符串中出现的位置。
返回的是第一个符合要求的结果在整个字符串中的位置
如果没有符合正则要求的结果,返回-1
var str="sad5a4d54add225sd22a"; var re=/\d+/g; console.log (str.search (re)); 结果: 3
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
var str="sad5a4d54add225sd22a"; var re=/\d+/g; console.log (str.replace(re, "*")) 结果: sad*a*d*add*sd*a
函数执行一个正则表达式的搜索和替换
语法:mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
$pattern:要搜索的模式,可以是字符串或一个字符串数组。(正则表达式)
$replacement:用于替换的字符串或字符串数组。
$subject:要搜索替换的目标字符或字符串数组。
$limit:可选,对于每个模式用于每个subject字符串的最大可替换次数。默认是-1(无限制)。
$count:可选,替换执行的次数
返回值:
如果subject是一个数组,preg_replace()返回一个数组,其他情况返回一个字符串。
如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。
$str = 'runo o b'; $str = preg_replace('/\s+/', '', $str); // 结果将会改变为'runoob'
使用基于数组索引的搜索替换
$string = 'The quick brown fox jumped over the lazy dog.'; $patterns = array(); $patterns[0] = '/quick/'; $patterns[1] = '/brown/'; $patterns[2] = '/fox/'; $replacements = array(); $replacements[2] = 'bear'; $replacements[1] = 'black'; $replacements[0] = 'slow'; //虽然下标是倒叙,但数组的实际顺序还是210,所以quick对应bear,brown对应black,fox对 应slow echo preg_replace($patterns, $replacements, $string);
结果:
The bear black slow jumped over the lazy dog.
使用其他参数:
$count = 0; echo preg_replace(array('/\d/', '/\s/'), '*', 'xp 4 to', -1 , $count); echo $count; //3
结果:
xp***to 3
在vendor\topthink下执行composer命令,默认安装最新发送电子邮件的插件包:
composer require phpmailer/phpmailer
安装图例:
引入方式:
use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception;
使用方法:
$mail = new PHPMailer(true);// Passing `true` enables exceptions try { //服务器配置 $mail->CharSet = "UTF-8";//设定邮件编码 $mail->SMTPDebug = 0;// 调试模式输出 $mail->isSMTP();// 使用SMTP $mail->Host = 'smtp.qq.com';// SMTP服务器 $mail->SMTPAuth = true;// 允许 SMTP 认证 $mail->Username = '*******@qq.com';// SMTP 用户名 即邮箱的用户名 $mail->Password = '**********';// SMTP 密码 部分邮箱是授权码(例如163邮箱) $mail->SMTPSecure = 'ssl';// 允许 TLS 或者ssl协议 $mail->Port = 465;// 服务器端口 25 或者465 具体要看邮箱服务器支持 $mail->setFrom('******@qq.com', 'shx');//发件人 $mail->addAddress($email, 'Joe');// 收件人 //$mail->addAddress('ellen@example.com');// 可添加多个收件人 //$mail->addReplyTo('xxxx@163.com', 'info');//回复的时候回复给哪个邮箱 建议和发件人一致 //$mail->addCC('cc@example.com');//抄送 //$mail->addBCC('bcc@example.com');//密送 //发送附件 // $mail->addAttachment('../xy.zip');// 添加附件 // $mail->addAttachment('../thumb-1.jpg', 'new.jpg');// 发送附件并且重命名 //Content $mail->isHTML(true);// 是否以HTML文档格式发送 发送后客户端可直接显示对应HTML内容 $mail->Subject = '我是邮件标题'; $mail->Body = '我是邮件内容'; $mail->AltBody = '如果邮件客户端不支持HTML则显示此内容'; $mail->send(); return '邮件发送成功'; } catch (Exception $e) { return '邮件发送失败: ' . $mail->ErrorInfo; }
本人用的是QQ邮箱的相关配置,所以这里申请方式就以QQ邮箱为例。
登入QQ邮箱。
进入设置,点击账户,向下翻,开启POP3/SMTP服务。
记录一下授权码,就是代码中的SMTP密码,无法复看,一旦忘记就只能重新申请!
SMTP Error: Could not authenticate.错误怎么解决?
网上大多数说的解决方案是:替换某个函数或者开启php.ini的某些功能。我个人曾多次出现此情况,前面的两个修改方案从未成功解决过错误,也可能我的问题和他们的报错原因不一样。本人多次入坑,强烈建议直接重新申请一次授权码就可以了,简单快捷!
这个错误我发现只要我配置好以后隔段时间如果不去使用它,可能就会产生这个错误,我的代码和配置从没动过,但是隔一段时间仍旧失效,所以我怀疑QQ邮箱这边可能有一定验证机制,长时间不使用就会出现类似于注销的情况。
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark(翻译:书签),而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST没有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。