开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情
引言
密码是我们使用系统登陆系统不可或缺的一部分。假如你接到一个需求,实现一套校验密码的密码规则,规则如下:
- 密码长度为10-16位。
- 包含大写字母、小写字母和数字。
- 不能与最近一次密码相同。
- 不能包含敏感词。
- 不能包含连续数字,如123或345。
并且告诉你密码规则后期可能会根据用户反馈来优化,这个需求你会怎么实现呢?
规则引擎
规则引擎 由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。
规则引擎优势
- 将那些频繁且通用的业务变化抽象出来,使复杂的业务规则实现变得的简单。
- 系统代码和业务规则分离,实现规则的统一管理。
- 可动态修改规则,需求变动快速响应。
Drools简介
常见的Java开源规则引擎有很多,今天我们来介绍一下 Drools。
Drools 是一组专注于智能自动化和决策管理的项目,最引人注目的是提供基于前向链和后链推理的规则引擎,DMN决策引擎和其他项目。规则引擎是创建专家系统的基本构建块,在人工智能中,专家系统是模拟人类专家决策能力的计算机系统。Drools是KIE(知识就是一切)开源社区的一部分。
drl 语法
Drools通过编写drl文件实现规则,一个drl文件中可包含多个规则。在实现我们的密码规则需求前,先来了解一下我们的drl语法。
DRL文件基本结构:
package
import
function // Optional
query // Optional
declare // Optional
global // Optional
rule "rule name"
// Attributes
when
// Conditions
then
// Actions
end
rule "rule2 name"
...
package
package 和java的package类似,相当于命名空间。提倡package和drl文件的路径保持一致。
import
import 和java的import类似,可导入JDK中的类。
function
function 函数,可以在drl中定义函数,函数中可以编写Java代码,可提高代码的复用。
query
query 的定义可以被Java代码调用获得返回数据。
declare
declare 指定规则使用的语言类型。
global
global 可以定义一些所有规则共享的全局变量。
when 和 then
满足 when 的条件后,会执行 then 定义的代码。when相当于java中的if条件,then 相当于 满足if条件后要执行的代码逻辑。
接下来我们就通过Drools将密码规则的需求来实现一下。
Drools实现密码规则校验
首先引入Drools包(Drools 8 开始最低要求JDK11了,我们这里用JDK8做演示,选用的Drools版本是7.73.0)。
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.73.0.Final</version>
</dependency>
创建一个我们测试用的实体类。
@Data
public class PasswordRule {
/**
* 是否校验通过
*/
private Boolean verify = Boolean.TRUE;
/**
* 密码
*/
private String password;
/**
* 上一次密码
*/
private String lastPassword = "Shuaijie666";
/**
* 敏感词列表
*/
private List<String> sensitiveWordList = Arrays.asList("sensitive1", "sensitive2", "sensitive3");
/**
* 连续数字列表
*/
private List<String> continuousNumberList = Arrays.asList("123", "234", "345");
/**
* 大写字母列表
*/
private List<String> majusculeList = Arrays.asList("A", "B", "C", "D", "E", "F");
/**
* 小写字母列表
*/
private List<String> minusculeList = Arrays.asList("a", "b", "c", "d", "e", "f");
/**
* 数字列表
*/
private List<String> numberList = Arrays.asList("1", "2", "3", "4", "5", "6", "7");
}
在resources文件夹下创建 rules/password.drl 文件。
drl 文件内容如下。
package com.shuaijie.rules
dialect "java"
import com.shuaijie.model.PasswordRule
import java.util.List
// 密码长度为10-16位。
rule "rule1"
// salience 优先级,默认为0
salience 0
when
$passwordRule : PasswordRule( !(password != null && password.length() >= 10 && password.length() <= 16) )
then
$passwordRule.setVerify(Boolean.FALSE);
System.out.println("密码长度为10-16位[校验不通过]");
end
// 判断是否包含大写字母、小写字母和数字
function Boolean checkRule2(PasswordRule passwordRule){
Boolean containsMajuscule = Boolean.FALSE;
Boolean containsMinuscule = Boolean.FALSE;
Boolean containsNumber = Boolean.FALSE;
for (java.lang.String majuscule : passwordRule.getMajusculeList()) {
if(Boolean.FALSE.equals(containsMajuscule) && passwordRule.getPassword().contains(majuscule)){
containsMajuscule = Boolean.TRUE;
}
}
for (java.lang.String minuscule : passwordRule.getMinusculeList()) {
if(Boolean.FALSE.equals(containsMinuscule) && passwordRule.getPassword().contains(minuscule)){
containsMinuscule = Boolean.TRUE;
}
}
for (java.lang.String number : passwordRule.getNumberList()) {
if(Boolean.FALSE.equals(containsNumber) && passwordRule.getPassword().contains(number)){
containsNumber = Boolean.TRUE;
}
}
return containsMajuscule && containsMinuscule && containsNumber;
}
// 包含大写字母、小写字母和数字。
rule "rule2"
when
$passwordRule: PasswordRule()
// 掉用function
eval(!checkRule2($passwordRule))
then
$passwordRule.setVerify(Boolean.FALSE);
System.out.println("包含大写字母、小写字母和数字[校验不通过]");
end
// 不能与最近一次密码相同。
rule "rule3"
when
$passwordRule : PasswordRule( password == lastPassword )
then
$passwordRule.setVerify(Boolean.FALSE);
System.out.println("不能与最近一次密码相同[校验不通过]");
end
// 判断是否包含敏感词
function Boolean checkRule4(PasswordRule passwordRule){
for (java.lang.String sensitiveWord : passwordRule.getSensitiveWordList()) {
if(passwordRule.getPassword().contains(sensitiveWord)){
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
// 不能包含敏感词。
rule "rule4"
when
$passwordRule: PasswordRule()
// 掉用function
eval(checkRule4($passwordRule))
then
$passwordRule.setVerify(Boolean.FALSE);
System.out.println("不能包含敏感词[校验不通过]");
end
// 判断是否包含连续数字
function Boolean checkRule5(PasswordRule passwordRule){
for (java.lang.String continuousNumber : passwordRule.getContinuousNumberList()) {
if(passwordRule.getPassword().contains(continuousNumber)){
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
// 不能包含连续数字,如123或345。
rule "rule5"
when
$passwordRule: PasswordRule()
// 掉用function
eval(checkRule5($passwordRule))
then
$passwordRule.setVerify(Boolean.FALSE);
System.out.println("不能包含连续数字[校验不通过]");
end
准备好后,然后我们来单元测试一下。
@Test
void testDrools(){
Resource resource = ResourceFactory.newClassPathResource("rules/password.drl");
KieHelper kieHelper = new KieHelper();
kieHelper.addResource(resource);
KieBase kieBase = kieHelper.build();
KieSession kieSession = kieBase.newKieSession();
PasswordRule passwordRule = new PasswordRule();
passwordRule.setPassword("123sensitive124567");
kieSession.insert(passwordRule);
kieSession.fireAllRules();
kieSession.dispose();
System.out.println("密码规则校验结果:"+passwordRule.getVerify());
}
测试执行结果如下。
密码长度为10-16位[校验不通过]
包含大写字母、小写字母和数字[校验不通过]
不能包含敏感词[校验不通过]
不能包含连续数字[校验不通过]
密码规则校验结果:false
我们单元测试通过KieHelper简单实现,实际开发过程中可以通过构建Bean的方式来实现。
这一期密码规则的需求我们实现了并且上线了。上线一段时间后,根据用户反馈来优化(新增、修改或删除规则)我们怎么实现呢?
Drools动态化
面对多变的规则,Drools也是有应对措施的。Drools有多种动态化的方式,今天来使用的是 拼装字符串 的方式,这种方式使用方便快捷,但要对drl文件编写有一定程度了解。
来上Demo。(密码规则的需求过于繁琐,代码过长,为了方便阅读理解,Demo只使用 密码长度为10-16位 这条规则来做一个演示)
@Test
void testDynamicDrools(){
String droolsStr = "package com.shuaijie.rules\n" +
"dialect "java"\n" +
"\n" +
"import com.shuaijie.model.PasswordRule\n" +
"\n" +
"\n" +
"\n" +
"// 密码长度为10-16位。\n" +
"rule "rule1"\n" +
" // salience 优先级,默认为0\n" +
" salience 0\n" +
" when\n" +
" $passwordRule : PasswordRule( !(password != null && password.length() >= 10 && password.length() <= 16) )\n" +
" then\n" +
" $passwordRule.setVerify(Boolean.FALSE);\n" +
" System.out.println("密码长度为10-16位[校验不通过]");\n" +
"end";
KieHelper kieHelper = new KieHelper();
kieHelper.addContent(droolsStr, ResourceType.DRL);
KieBase kieBase = kieHelper.build();
KieSession kieSession = kieBase.newKieSession();
PasswordRule passwordRule = new PasswordRule();
passwordRule.setPassword("123sensitive124567");
kieSession.insert(passwordRule);
kieSession.fireAllRules();
kieSession.dispose();
System.out.println("密码规则校验结果:"+passwordRule.getVerify());
}
单元测试结果。
密码长度为10-16位[校验不通过]
密码规则校验结果:false
我们可以将规则存储到数据库中,使用时查询出来,修改的话修改数据库即可。