正则表达式实现密码检查

起因

起因是一个朋友问怎么实现一个密码检查功能:

  1. 密码只能由大写字母,小写字母,数字构成;
  2. 密码不能以数字开头;
  3. 密码中至少出现大写字母,小写字母和数字这三种字符类型中的两种;
  4. 密码长度8-100位

然后他贴了写的代码:

$value = 'A1234567890a';
$rule = '/^[A-Z][A-Za-z]{7,100}|^[A-Z][A-Z0-9]{7,100}|^[a-z][A-Za-z]{7,100}|^[a-z][0-9a-z]{7,100}$/';
var_dump(preg_match($rule,(string)$value));    

一看这变量名以$开头,大概是php的,但我不怎么懂PHP,只看得懂中间的正则,猜最后一行是输出匹配结果。

这个正则显然是不满足上面说的条件的,|的优先级比$低,所以前面的表达式没有控制密码长度的作用。我依稀记得曾经学正则也看过相关的题目,这种复杂的正则检查是要用零宽断言。

然而我觉得零宽断言这东西写起来复杂,别人看更觉得复杂的东西,团队里面显然不是所有人都会去弄懂的。写这种复杂正则,以后需求变更,修改起来简直是噩梦,所以没怎么认真看,只知道有这么个东西,于是我就认真翻了一下相关的文章。

链接如下:
https://deerchao.net/tutorials/regex/regex.htm

这个是我觉得写得比较好的,内容很充实的一个文章了。

解决问题

对于这个需求,我们先完成第124点,这三个比较简单,正则写出来如下

var regex = /^[A-Za-z][A-Za-z0-9]{7,99}$/

这个问题难写的就是第3点,如果不用零宽断言,是要用很多个|去做判断,非常长。

第三点是

密码中至少出现大写字母,小写字母和数字这三种字符类型中的两种;

换个说法就是

不能全是大写字母,不能全是小写字母,不能全是数字。

或者说

存在一个非大写字母,存在一个非小写字母,存在一个非数字。

第一种

对于第一种说法,可以用

var regex = /^(?![A-Z]*$)/; // 不能全是大写字母
var regex2 = /^(?![a-z]*$)/; // 不能全是小写字母
var regex3 = /^(?![0-9]*$)/; // 不能全是数字

这里的*可以换成+,只会在空字符串的时候有差异,对于我们上面的需求是没有差异的,因为限定了8-100的长度。

与前面的正则组合起来就是

var regex = /^(?![A-Z]*$)(?![a-z]*$)(?![0-9]*$)[A-Za-z][A-Za-z0-9]{7,99}$/;

第二种

对于第二种说法,可以用

var regex = /^(?=.*[^A-Z])/ // 存在一个非大写字母
var regex2 = /^(?=.*[^a-z])/ // 存在一个非小写字母
var regex3 = /^(?=.*[^0-9])/ // 存在一个非数字

这里*不能替换成+,为什么可以自己思考一下,我会给一反例证明替换有问题。

组合起来就是

var regex = /^(?=.*[^A-Z])(?=.*[^a-z])(?=.*[^0-9])[A-Za-z][A-Za-z0-9]{7,99}$/;

如果将所有的*替换成+,那么对于Aaaaaaaa,正则匹配会失败,根据需求应该是成功。

第三种

什么?居然还有第三种?是的,当然还有第三种,和第四种。

刚刚我们写的断言,按照链接给的名字是零宽度正预测先行断言(?=exp)零宽度负预测先行断言(?!exp),当然名字其实不重要的,翻译过来的名字有很多种,不同的文章也不同,也不知道哪个是比较官方的。

所以我们可以用另外两种断言实现,分辨是零宽度正回顾后发断言(?<=exp)零宽度负回顾后发断言(?<!exp)

对于第三种和第四种,它是从后面检查前面的断言,这种断言据说js是不支持的,但是chrome的引擎似乎是支持的,也不清楚是怎么回事。

(?<!exp)形式写第一种说法

var regex = /(?<!^[A-Z]*)$/; // 不能全是大写字母
var regex2 = /(?<!^[a-z]*)$/; // 不能全是小写字母
var regex3 = /(?<!^[0-9]*)$/; // 不能全是数字

同样的*可以替换成+,组合起来就是

var regex = /^[A-Za-z][A-Za-z0-9]{7,99}(?<!^[A-Z]*)(?<!^[a-z]*)(?<!^[0-9]*)$/

第四种

不废话了直接贴代码

var regex = /(?<=[^A-Z].*)$/ // 存在一个非大写字母
var regex2 = /(?<=[^a-z].*)$/ // 存在一个非小写字母
var regex3 = /(?<=[^0-9].*)$/ // 存在一个非数字

组合后如下,同样的不能把*替换成+,反例是aaaaaaaA

var regex = /^[A-Za-z][A-Za-z0-9]{7,99}(?<=[^A-Z].*)(?<=[^a-z].*)(?<=[^0-9].*)$/;

第五种、第六种……

组合有很多种,因为条件都说的很清楚了,四种断言是可以组合的,所以其实远不止以上说的四种情况,重要的是要掌握四种断言的本质。

结尾

本文只是一个引子,并不是说怎么学正则,学习正则可以参考上面给的链接,这里再给一次https://deerchao.net/tutorials/regex/regex.htm

警告:不要写过于复杂的正则,多人协作中,代码的可读性远比性能和炫技重要。

posted @ 2019-01-19 13:44  weilence  阅读(2593)  评论(0编辑  收藏  举报