当前位置: 首页 > news >正文

Easy Rules规则引擎实战

文章目录

  • 简介
      • pom
  • 规则
      • 抽象规则Rule
      • 基础规则BasicRule
          • 事实类Facts:map
          • 条件接口
          • 动作接口
      • 四种规则定义方式
          • 注解方式
          • RuleBuilder 链式
          • Mvel和Spel表达式
          • Yml配置
      • 常用规则类
          • DefaultRule
          • SpELRule(Spring的表达式注入)
      • 组合规则
          • UnitRuleGroup
  • 规则引擎
      • 引擎接口
      • 引擎抽象类
      • 引擎类-DefaultRulesEngine
          • 规则引擎参数(决定规则之间是否互斥|中断|跳过等)
  • 实战
      • 场景1- 恒打印
          • 规则description
          • 规则
          • 规则引擎:使用DefaultRulesEngine
          • 执行
          • fire方法执行流程
      • 场景2-yml
      • 场景3 简单if-else
      • 场景4-动态规则MVEL表达式+Json字符串
      • 场景5-QLExpress
      • 场景6- 动态规则Mvel + Json文件
      • 场景7-履约缺货处罚金额计算

简介

1、是什么

  • 基于java轻量级的规则引擎,学习成本更低、适用性更强
  • 本质一个函数,y=f(x1,x2,…,xn)
  • Easy Rules 每条规则都有一个条件(condition)和一个动作(action),简单地说,可以将其看作是一组 IF THEN 或类似SQL的when-then语句

2、作用

  • 解决业务代码和业务规则分离,实现了将业务决策从应用程序代码中分离。 接受数据输入,解释业务规则,并根据业务规则做出业务决策。

业务系统在应用过程中,常常包含着要处理"复杂、多变"的部分,这部分往往是"业务规则"或者是"数据的处理逻辑"。因此这部分的动态规则的问题,往往需要可配置,并对系统性能和热部署有一定的要求。从开发与业务的视角主要突出以下的一些问题:

  • 从开发人员视角来看

1)逻辑复杂,要使用大量if-else来实现,或者使用设计模式。但过于复杂的规则逻辑,使用设计模式也往往是存在大量并且关系复杂的类,导致代码难于维护,对新加入的同学极不友好。

2)变更时需要从头梳理逻辑,在适当的地方进行if…else…代码逻辑调整,耗费大量时间进行梳理。

3)开发周期较长,当需求发生变更时,需要研发人员安排开发周期上线,对于当下快速变化的业务,传统的开发工作方式显得捉襟见肘。

  • 从业务人员视角来看

1)业务人员期望友好的管理界面,不需要专业的开发技能就能够完成规则的管理、发布。

2)期望能够实现热部署,由业务人员配置好之后即配即用。

3)减少业务规则对开发人员的依赖。

4)降低需求变动的时间成本,快速验证发布

3、怎么做:

你可以自己构建一个简单的规则引擎。你所需要做的就是创建一组带有条件和动作的规则对象rule,将它们存储在一个集合中rules,然后遍历它们以评估(fire)条件(condition)并执行这些动作(action)。

pom

<dependency><groupId>org.jeasy</groupId><artifactId>easy-rules-core</artifactId><version>3.3.0</version>
</dependency><!--规则定义文件格式,支持json,yaml等-->
<dependency><groupId>org.jeasy</groupId><artifactId>easy-rules-support</artifactId><version>3.3.0</version>
</dependency><!--支持mvel规则语法库-->
<dependency><groupId>org.jeasy</groupId><artifactId>easy-rules-mvel</artifactId><version>3.3.0</version>
</dependency><!-- https://mvnrepository.com/artifact/org.jeasy/easy-rules-spel -->
<dependency><groupId>org.jeasy</groupId><artifactId>easy-rules-spel</artifactId><version>4.1.0</version>
</dependency>

规则

大多数业务规则可以由以下定义表示:

  • 名称name:规则命名空间中的唯一规则名称

  • 说明description:规则的简要说明

  • 优先级priority:相对于其他规则的规则优先级,较小的值表示较高的优先级

  • 事实fact:去匹配规则时的一组已知事实

  • 条件condition:为了匹配该规则,在给定某些事实的情况下应满足的一组条件

    如果规则条件为true,则规则将被触发执行。否则,规则将被忽略

  • 动作action:当条件满足时要执行的一组动作(可以添加/删除/修改事实)

    它可以用于实现各种应用程序逻辑,例如更新数据、发送消息等。

抽象规则Rule

public interface Rule extends Comparable<Rule> {String DEFAULT_NAME = "rule";String DEFAULT_DESCRIPTION = "description";int DEFAULT_PRIORITY = 2147483646;boolean evaluate(Facts var1);void execute(Facts var1) throws Exception;
}

基础规则BasicRule

package org.jeasy.rules.core;import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;public class BasicRule implements Rule {protected String name;//名称protected String description;//说明protected int priority;//优先/*** 此方法封装规则的条件(conditions)** @param facts :事实* @return 如果提供的事实适用于该规,则条件返回true*/public boolean evaluate(Facts facts) {return false;}/*** 此方法封装规则的操作(actions)* evaluate方法值为TRUE才能触发execute方法(在满足规则条件时应执行的操作)。* @throws 如果在执行过程中发生错误将抛出Exception*/public void execute(Facts facts) throws Exception {}}
事实类Facts:map
package org.jeasy.rules.api;import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;public class Facts implements Iterable<Map.Entry<String, Object>> {private Map<String, Object> facts = new HashMap();public Object put(String name, Object fact) {Objects.requireNonNull(name);return this.facts.put(name, fact);}public Object remove(String name) {Objects.requireNonNull(name);return this.facts.remove(name);}public <T> T get(String name) {Objects.requireNonNull(name);return this.facts.get(name);}
}
  • 扩展:可以将普通对象(包含属性|方法)facts.put()。也可以将rpc对象put进来
@Resource
private MqService mqService;facts.put("mqService", mqService)这样condition方法就可以使用mqService的方法
.when("mqService.doRuleConditions(conditionList,tmpParam)")@Override
public boolean evaluate(@Facts("mqService") MqService mqService) {mqService.doRuleConditions();
}
条件接口
public interface Condition {Condition FALSE = new Condition() {public boolean evaluate(Facts facts) {return false;}};Condition TRUE = new Condition() {public boolean evaluate(Facts facts) {return true;}};boolean evaluate(Facts var1);
}
动作接口
public interface Action {void execute(Facts var1) throws Exception;
}

四种规则定义方式

注解方式

1、eg1(也可以实现Rule接口)

package com.mjp.easyrules.drmo1;import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.core.BasicRule;public class MyRule extends BasicRule {@Overridepublic boolean evaluate(Facts facts) {return false;}@Overridepublic void execute(Facts facts) throws Exception {}
}

2、纯注解

@Rule(name = "weather rule", description = "if it rains then take an umbrella", priority = 777)
public class WeatherRule {@Conditionpublic boolean itRains(@Fact("rain") boolean rain, @Fact("other") String other) {return rain;}@Action(order = 1)public void takeAnUmbrella() {System.out.println("It rains, take an umbrella!");}@Action(order = 2)public void then2(Facts facts) throws Exception {//my actions2}
}
RuleBuilder 链式

1、eg1

Rule rule = new RuleBuilder().name("myRule").description("myRuleDescription").priority(2).when(facts -> true).then(facts -> {// do Action1}).then(facts -> {// do Action2}).build();

2、eg2

Rule weatherRule = new RuleBuilder().name("weather rule").description("if it rains then take an umbrella").when(facts -> facts.get("rain").equals(true)).then(facts -> System.out.println("It rains, take an umbrella!")).build();
Mvel和Spel表达式

1、eg1:Mvel表达式

Mvel表达式

Rule weatherRule = new MVELRule().name("weather rule").description("if it rains then take an umbrella").when("rain == true").then("System.out.println(\"It rains, take an umbrella!\");");
  • when:对象属性

when方法参数,只能是facts对象中key对象,内部定义的属性或方法

.when("person.age > 18")
.when("person.isHappy() == true") 
也可以是类的静态方法
Demo.add(1,2)//这样when会执行去调用类方法add

2、补充:

  • when、then内容,可以从自定义的json文件中读取并解析成Mvel表达式形式。解析过程可参考场景6

3、eg2:Spel表达式同理

Spel表达式

Yml配置

eg1: weather-rule.yml

name: "weather rule"
description: "if it rains then take an umbrella"
condition: "true"
actions:- "System.out.println(\"It rains, take an umbrella!\");"
  • condition:对象方法
condition: "person.isAdult() == false"
condition: "person.getResult(person.getQualifications())"

eg2:一个yml文件创建多个规则

---
name: adult rule
description: when age is greater than 18, then mark as adult
priority: 1
condition: "person.age > 18"
actions:- "person.setAdult(true);"---
name: weather rule
description: when it rains, then take an umbrella
priority: 2
condition: "rain == true"
actions:- "System.out.println("It rains, take an umbrella!");"
  • 读取多条规则
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());Rule weatherRule = ruleFactory.createRule(new FileReader("D:\\CodeBetter\\src\\main\\resources\\weather-rule.yml"));
// 多条规则
Rules rules = ruleFactory.createRules(new FileReader("rules.yml"));

常用规则类

DefaultRule
package org.jeasy.rules.core;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Facts;class DefaultRule extends BasicRule {private Condition condition;//一个规则,一个条件。private List<Action> actions;//一个规则,当满足条件时,可以有多个动作public boolean evaluate(Facts facts) {return this.condition.evaluate(facts);}public void execute(Facts facts) throws Exception {Iterator var2 = this.actions.iterator();while(var2.hasNext()) {Action action = (Action)var2.next();action.execute(facts);}}
}
SpELRule(Spring的表达式注入)

表达式注入

package org.jeasy.rules.spel;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.core.BasicRule;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ParserContext;public class SpELRule extends BasicRule {private Condition condition;//条件private final List<Action> actions;//动作private final ParserContext parserContext;//文本解析private BeanResolver beanResolver;public boolean evaluate(Facts facts) {return this.condition.evaluate(facts);}public void execute(Facts facts) throws Exception {Iterator var2 = this.actions.iterator();while(var2.hasNext()) {Action action = (Action)var2.next();action.execute(facts);}}public SpELRule when(String condition) {this.condition = new SpELCondition(condition, this.parserContext, this.beanResolver);return this;}public SpELRule then(String action) {this.actions.add(new SpELAction(action, this.parserContext, this.beanResolver));return this;}
}
  • when方法-SpELCondition
public class SpELCondition implements Condition {private final ExpressionParser parser;private final Expression compiledExpression;private BeanResolver beanResolver;public SpELCondition(String expression, ParserContext parserContext, BeanResolver beanResolver) {this.parser = new SpelExpressionParser();this.beanResolver = beanResolver;this.compiledExpression = this.parser.parseExpression(expression, parserContext);}public boolean evaluate(Facts facts) {StandardEvaluationContext context = new StandardEvaluationContext();context.setRootObject(facts.asMap());context.setVariables(facts.asMap());if (this.beanResolver != null) {context.setBeanResolver(this.beanResolver);}return (Boolean)this.compiledExpression.getValue(context, Boolean.class);}
}
  • then方法- SpELAction
public class SpELAction implements Action {private static final Logger LOGGER = LoggerFactory.getLogger(SpELAction.class);private final ExpressionParser parser;private final String expression;private final Expression compiledExpression;private BeanResolver beanResolver;public SpELAction(String expression, ParserContext parserContext, BeanResolver beanResolver) {this.parser = new SpelExpressionParser();this.expression = expression;this.beanResolver = beanResolver;this.compiledExpression = this.parser.parseExpression(expression, parserContext);}public void execute(Facts facts) {try {StandardEvaluationContext context = new StandardEvaluationContext();context.setRootObject(facts.asMap());context.setVariables(facts.asMap());if (this.beanResolver != null) {context.setBeanResolver(this.beanResolver);}this.compiledExpression.getValue(context);} catch (Exception var3) {LOGGER.error("Unable to evaluate expression: '" + this.expression + "' on facts: " + facts, var3);throw var3;}}
}

组合规则

抽象CompositeRule类由一组规则组成。这是一个典型地组合设计模式的实现。可以以不同方式触发组合规则。

三种CompositeRule具体子类:

UnitRuleGroup : 要么应用所有规则,要么不应用任何规则(AND逻辑)
ActivationRuleGroup : 它触发第一个适用规则,并忽略组中的其他规则(XOR逻辑)
ConditionalRuleGroup : 如果具有最高优先级的规则计算结果为true,则触发其余规则

UnitRuleGroup
  • 规则1
        Rule weatherRule1 = new MVELRule().name("weather rule1").description("if it rains then move").when("isRain == true").then("System.out.println(\"rule1: It rains, move!\");");
  • 规则2
@Rule(name = "weather rule2", description = "if it rains then take an umbrella",priority = 777)
public class WeatherRule {@Conditionpublic boolean itRains(@Fact("isRain") boolean isRain) {return isRain;}@Action(order = 1)public void takeAnUmbrella() {System.out.println("rule2: action1-It rains, take an umbrella!");}@Action(order = 2)public void then(Facts facts) throws Exception {System.out.println("rule2: action2-边打伞,边听歌");}
}
  • 执行
// 1.创建事实factFacts facts = new Facts();facts.put("isRain", true);// 2.创建规则1(Mvel形式)Rule weatherRule1 = new MVELRule().name("weather rule1").description("if it rains then move").when("isRain == true").then("System.out.println(\"rule1: It rains, move!\");");// 2.创建规则1(注解形式)WeatherRule weatherRule2 = new WeatherRule();// 3.创建组合规则UnitRuleGroup myUnitRuleGroup = new UnitRuleGroup("myUnitRuleGroup", "unit of weatherRule1 and weatherRule2");myUnitRuleGroup.addRule(weatherRule1);myUnitRuleGroup.addRule(weatherRule2);// 4.创建规则引擎DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();// 5.定义RulesRules rules = new Rules();rules.register(myUnitRuleGroup);// 7.执行defaultRulesEngine.fire(rules, facts);
  • 输出
rule2: action1-It rains, take an umbrella!
rule2: action2-边打伞,边听歌
rule1: It rains, move!

先输出规则2(优先级为777),规则1(优先级为默认值:2147483646),值越小,优先级越高

规则引擎

引擎接口

public interface RulesEngine {RulesEngineParameters getParameters();List<RuleListener> getRuleListeners();List<RulesEngineListener> getRulesEngineListeners();void fire(Rules var1, Facts var2);Map<Rule, Boolean> check(Rules var1, Facts var2);
}

引擎抽象类

AbstractRuleEngine作用就是抽出多个引擎类共有的,不需要再各自额外重复去实现

引擎类-DefaultRulesEngine

public final class DefaultRulesEngine extends AbstractRuleEngine {private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRulesEngine.class);public void fire(Rules rules, Facts facts) {this.triggerListenersBeforeRules(rules, facts);this.doFire(rules, facts);this.triggerListenersAfterRules(rules, facts);}void doFire(Rules rules, Facts facts) {Iterator var3 = rules.iterator();while(var3.hasNext()) {Rule rule = (Rule)var3.next();String name = rule.getName();int priority = rule.getPriority();if (rule.evaluate(facts)) {this.triggerListenersAfterEvaluate(rule, facts, true);try {this.triggerListenersBeforeExecute(rule, facts);rule.execute(facts);this.triggerListenersOnSuccess(rule, facts);if (this.parameters.isSkipOnFirstAppliedRule()) {LOGGER.debug("Next rules will be skipped since parameter skipOnFirstAppliedRule is set");break;}} catch (Exception var8) {this.triggerListenersOnFailure(rule, var8, facts);if (this.parameters.isSkipOnFirstFailedRule()) {LOGGER.debug("Next rules will be skipped since parameter skipOnFirstFailedRule is set");break;}}} else {this.triggerListenersAfterEvaluate(rule, facts, false);if (this.parameters.isSkipOnFirstNonTriggeredRule()) {LOGGER.debug("Next rules will be skipped since parameter skipOnFirstNonTriggeredRule is set");break;}}}}public Map<Rule, Boolean> check(Rules rules, Facts facts) {this.triggerListenersBeforeRules(rules, facts);Map<Rule, Boolean> result = this.doCheck(rules, facts);this.triggerListenersAfterRules(rules, facts);return result;}private Map<Rule, Boolean> doCheck(Rules rules, Facts facts) {LOGGER.debug("Checking rules");Map<Rule, Boolean> result = new HashMap();Iterator var4 = rules.iterator();while(var4.hasNext()) {Rule rule = (Rule)var4.next();if (this.shouldBeEvaluated(rule, facts)) {result.put(rule, rule.evaluate(facts));}}return result;}
}
规则引擎参数(决定规则之间是否互斥|中断|跳过等)

RulesEngineParameters

skipOnFirstAppliedRule:为true告诉引擎规则,被触发时跳过后面的规则。使用场景是:各个规则之间互斥a || b || c || d,如果a为true,则b、c、d不执行当发现一个满足条件的规则并执行了相关操作后,便不再继续判断其他规则skipOnFirstFailedRule:失败时跳过后面的规则。a && b && c && d,如果a为false,则b、c、d不执行skipOnFirstNonTriggeredRule:一个规则不会被触发跳过后面的规则。如果满足当前的规则,则执行相应的操作,直到遇到不满足条件的规则为止,并且也不会对其他规则进行判断了
rulePriorityThreshold:如果优先级超过定义的阈值,则跳过下一个规则。版本3.3已经不支持更改,默认MaxInt

实战

场景1- 恒打印

规则description

默认打印Hello World

规则
public class MyRule extends BasicRule {@Overridepublic String getName() {return "my rule";}@Overridepublic String getDescription() {return "my rule description";}@Overridepublic boolean evaluate(Facts facts) {return true;}@Overridepublic void execute(Facts facts) throws Exception {System.out.println("我是一个打印Hello World的规则");}
}
规则引擎:使用DefaultRulesEngine
执行
public static void main(String[] args) {// 1.创建事实factFacts facts = new Facts();// 2.创建条件condition(这里MyRule中默认为true,就不自定义Condition实现类了)// 3.创建动作action(这里MyRule中默认为:打印HelloWorld,就不自定义Action实现类了)// 4.创建规则MyRule myRule = new MyRule();// 5.定义RulesRules rules = new Rules();rules.register(myRule);// 6.创建规则引擎DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();// 7.执行defaultRulesEngine.fire(rules, facts);}

输出

DefaultRulesEngineListener - Rules evaluation started
DefaultRuleListener - Rule 'my rule' triggered我是一个打印Hello World的规则DefaultRuleListener - Rule 'my rule' performed successfully
fire方法执行流程
第一步:List<RulesEngineListener> rulesEngineListeners;执行 void beforeEvaluate(Rule var1, Facts var2)第二步:MyRuleboolean evaluate(Facts facts)第三步:List<RuleListener> ruleListeners;执行void afterEvaluate(Rule var1, Facts var2, boolean evaluateResult)第四步:List<RuleListener> ruleListeners;执行void beforeExecute(Rule var1, Facts var2);第五步:MyRulevoid execute(Facts facts)第六步:List<RuleListener> ruleListeners;执行void onSuccess(Rule var1, Facts var2);

场景2-yml

1、规则description

if it rains then take an umbrella

2、定义规则

weather-rule.yml

name: "weather rule"
description: "if it rains then take an umbrella"
condition: "isRain == true"
actions:- "System.out.println(\"It rains, take an umbrella!\");"

3、自定义规则引擎:使用默认的

4、执行

// 1.创建事实factFacts facts = new Facts();facts.put("isRain", true);// 2.创建条件condition(weatherRule中:isRain == true,就不自定义Condition实现类了)// 3.创建动作action(weatherRule中默认为:It rains, take an umbrella!,就不自定义Action实现类了)// 4.创建规则(Yml形式)MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());Rule weatherRule = ruleFactory.createRule(new FileReader("D:\\CodeBetter\\src\\main\\resources\\weather-rule.yml"));// 5.创建规则引擎DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();// 6.定义RulesRules rules = new Rules();rules.register(weatherRule);// 7.执行defaultRulesEngine.fire(rules, facts);

场景3 简单if-else

1、功能描述

从1数到100,并且:

  • 需求1:如果数字是5的倍数,则打印"i :是5的倍数"
  • 需求2:如果数字是7的倍数,请打印"i :是7的倍数"
  • 需求3:如果数字是5和7的倍数,请打印"i :是5的倍数、7的倍数"
  • 需求4:否则打印数字本身

2、常规实现方法

public class FizzBuzz {public static void main(String[] args) {for(int i = 1; i <= 100; i++) {if (((i % 5) == 0) && ((i % 7) == 0))System.out.print("fizzbuzz");else if ((i % 5) == 0) System.out.print("fizz");else if ((i % 7) == 0) System.out.print("buzz");else System.out.print(i);}}
}

3、使用Easy Rules规则引擎实现

将每个需求编写一条规则:

  • rule1
@Rule(name = "Mod5Rule", description = "mod5", priority = 1)
public class Mod5Rule {@Conditionpublic boolean canMod5(@Fact("number") Integer number) {return number % 5 == 0;}@Actionpublic void action(Facts facts) {Integer i = facts.get("number");System.out.print(i + " :是5的倍数");}
}
  • rule2
@Rule(name = "Mod7Rule", description = "mod7", priority = 2)
public class Mod7Rule {@Conditionpublic boolean canMod7(@Fact("number") Integer number) {return number % 7 == 0;}@Actionpublic void action(Facts facts) {Integer i = facts.get("number");System.out.print(i + " :是7的倍数");}
}
  • rule3
public class Mod5And7Rule extends UnitRuleGroup {public Mod5And7Rule(Object... rules) {for (Object rule : rules) {addRule(rule);}}@Overridepublic int getPriority() {return 0;}
}
  • rule4
@Rule(name = "OtherRule", description = "不是5和7的倍数", priority = 3)
public class OtherRule {@Conditionpublic boolean canNotMod5Or7(@Fact("number") Integer number) {return number % 5 != 0 || number % 7 != 0;}@Actionpublic void action(@Fact("number") Integer number) {System.out.print(number);}
}
  • 执行
// 创建规则引擎RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);// 创建规则Rules rules = new Rules();rules.register(new Mod5Rule());rules.register(new Mod7Rule());rules.register(new Mod5And7Rule(new Mod5Rule(), new Mod7Rule()));rules.register(new OtherRule());// 触发规则Facts facts = new Facts();for (int i = 1; i <= 100; i++) {facts.put("number", i);fizzBuzzEngine.fire(rules, facts);}
  • 这里规则引擎参数:skipOnFirstAppliedRule(true):告诉引擎,被触发时跳过后面的规则。

    即当i = 5时,满足规则1(mod5),执行完action1,就不会再去匹配其他rule2、3、4规则了。使用场景是:各个规则之间互斥

场景4-动态规则MVEL表达式+Json字符串

如图1

在这里插入图片描述

输入facts:NORMAL_NUMBER和ERROR_NUMBER两个值,使用Mvel表达式解析facts是否满足上述json中定义的condition

  • 操作符枚举类(加减乘除,> 、<)
package com.mjp.easyrules.csdn;public enum OperationEnum {GREATER_THAN("GREATER_THAN", "%s > %s", "大于"),GREATER_THAN_EQUAL("GREATER_THAN_EQUAL", "%s >= %s", "大于等于"),LESS_THAN("LESS_THAN", "%s < %s", "小于"),LESS_THAN_EQUAL("LESS_THAN_EQUAL", "%s <= %s", "小于等于"),EQUAL("EQUAL", "%s == %s", "等于"),UNEQUAL("UNEQUAL", "%s != %s", "不等于"),BETWEEN("BETWEEN", "%s >= %s && %s <= %s", "介于之间"),OUT_OF_RANGE("OUT_OF_RANGE", "%s >= %s || %s >= %s", "超出范围"),CONTAINS("CONTAINS", "%s.contains(\"%s\")", "包含"),STARTSWITH("STARTSWITH", "%s.startsWith(\"%s\")", "前缀"),ENDSWITH("ENDSWITH", "%s.endsWith(\"%s\")", "后缀"),;public static OperationEnum getOperationByOperator(String operator) {OperationEnum[] list = OperationEnum.values();for (OperationEnum item : list) {String compareOperator = item.getOperator();if (compareOperator.equals(operator)) {return item;}}return null;}private final String operator;private final String expression;private final String remark;OperationEnum(String operator, String expression, String remark) {this.operator = operator;this.expression = expression;this.remark = remark;}public String getOperator() {return operator;}public String getExpression() {return expression;}public String getRemark() {return remark;}
}
  • 逻辑运算符(&&、||、and、or)
public class EasyRulesConstants {// 事实别名public static final String FACT_ALIAS = "fact";// 结果别名public static final String RESULT_ALIAS = "result";// and关系public static final String RELATION_AND = "and";// or关系public static final String RELATION_OR = "or";// 匹配成功信息public static final String MATCH_SUCCESS_MESSAGE = "匹配成功";public static final String FIELD_TYPE = "type";public static final String FIELD_OPERATOR = "operator";public static final String FIELD_NAME = "metricName";public static final String FIELD_VALUE = "value";public static final String FIELD_CHILDREN = "children";public static final String EXPRESSION_TYPE = "EXPRESSION";public static final String RELATION_TYPE = "RELATION";public static final String LEFT_BRACKETS = "(";public static final String RIGHT_BRACKETS = ")";public static final String SYMBOL_SPACE = " ";public static final String SYMBOL_EMPTY = "";public static final String LOGICAL_AND = "&&";public static final String LOGICAL_OR = "||";
}
  • 工具类(负责解析自定义json字符串中的condition和action内容,赋值给Mvel规则when、then)
package com.mjp.easyrules.csdn;@Slf4j
public class EasyRulesUtil {/*** 执行规则匹配* @param fact 事实json* @param ruleModel 规则模型*/public static RuleResult match(JSONObject fact, RuleModel ruleModel){// 结果RuleResult result = new RuleResult();result.setRuleId(ruleModel.getRuleId());// 规则实例Facts facts = new Facts();facts.put(FACT_ALIAS, fact);facts.put(RESULT_ALIAS, result);// 规则内容Rule mvelrule = new MVELRule().name(ruleModel.getRuleName()).description(ruleModel.getDescription()).when(ruleModel.getWhenExpression()).then(ruleModel.getThenExpression());// 规则集合Rules rules = new Rules();// 将规则添加到集合rules.register(mvelrule);// 创建规则执行引擎,并执行规则RulesEngine rulesEngine = new DefaultRulesEngine();rulesEngine.fire(rules, facts);return result;}/*** 构建mvel条件表达式* @param json 节点json,例如:* {*     "type": "EXPRESSION",*     "operator": "LESS_THAN",*     "metricName": "NORMAL_NUMBER",*     "value": "11",*     "children": []* }*/public static String buildWhenExpression(JSONObject json) {StringBuilder mvelExpressionSB = new StringBuilder();String type = json.getString(FIELD_TYPE);String operator = json.getString(FIELD_OPERATOR);switch (type) {case EXPRESSION_TYPE:String fieldName = json.getString(FIELD_NAME);String fieldValue = json.getString(FIELD_VALUE);mvelExpressionSB.append(buildOperatorExpress(operator, fieldName, fieldValue));break;case RELATION_TYPE:JSONArray children = json.getJSONArray(FIELD_CHILDREN);if (children.size() == 0) {return SYMBOL_EMPTY;}operator = convertRelationExpress(operator);StringBuilder sb = new StringBuilder();for (int i = 0; i < children.size(); i++) {JSONObject child = children.getJSONObject(i);// 递归构建单个规则条件String childExpression = buildWhenExpression(child);if (!childExpression.isEmpty()) {if (sb.length() > 0) {sb.append(SYMBOL_SPACE).append(operator).append(SYMBOL_SPACE);}sb.append(LEFT_BRACKETS).append(childExpression).append(RIGHT_BRACKETS);}}mvelExpressionSB.append(sb);break;default:break;}return mvelExpressionSB.toString();}/*** 构建mvel表达式* @param operator 操作符* @param fieldName 字段名称* @param value 字段值*/private static String buildOperatorExpress(String operator, String fieldName, Object value) {OperationEnum operationEnum = OperationEnum.getOperationByOperator(operator);if (ObjectUtils.isNotEmpty(operationEnum)) {String expression = operationEnum.getExpression();return String.format(expression, buildValueExpress(fieldName), value);}return SYMBOL_EMPTY;}/*** 构建mvel取值表达式* @param fieldName 字段名称*/private static String buildValueExpress(String fieldName) {return String.format("%s.get(\"%s\")", FACT_ALIAS, fieldName);}/*** 转换条件连接符* @param relation 条件连接符*/private static String convertRelationExpress(String relation) {if (StringUtils.isEmpty(relation)){return SYMBOL_EMPTY;} else if(relation.equalsIgnoreCase(RELATION_AND)){return LOGICAL_AND;} else if(relation.equalsIgnoreCase(RELATION_OR)){return LOGICAL_OR;}return relation;}/*** 构建mvel结果表达式*/public static String buildThenExpression() {StringBuilder sb = new StringBuilder();sb.append(RESULT_ALIAS).append(".setValue(\"").append(MATCH_SUCCESS_MESSAGE).append("\");");log.info("thenExpression: {}", sb);return sb.toString();}@Datapublic static class RuleModel {private String ruleId;String ruleName;String description;String whenExpression;String thenExpression;}@Datapublic static class RuleResult {// 规则主键private String ruleId;// 是否匹配, 默认falseboolean isMatch = false;// 匹配信息,默认为匹配失败String message = "匹配失败";/*** 匹配成功后设置成功信息*/public void setValue(String message){this.message = message;this.isMatch = true;}}
}
  • 测试
		// 1. 新增规则EasyRulesUtil.RuleModel ruleModel = new EasyRulesUtil.RuleModel();ruleModel.setRuleId("1");ruleModel.setRuleName("rule1");ruleModel.setDescription("测试规则");// 2. 设置规则条件String ruleJson = "{\n" +"    \"validateCondition\": {\n" +"        \"type\": \"RELATION\",\n" +"        \"operator\": \"OR\",\n" +"        \"children\": [\n" +"            {\n" +"                \"type\": \"EXPRESSION\",\n" +"                \"operator\": \"LESS_THAN\",\n" +"                \"metricName\": \"NORMAL_NUMBER\",\n" +"                \"value\": \"11\",\n" +"                \"children\": []\n" +"            },\n" +"            {\n" +"                \"type\": \"EXPRESSION\",\n" +"                \"operator\": \"LESS_THAN_EQUAL\",\n" +"                \"metricName\": \"ERROR_NUMBER\",\n" +"                \"value\": \"11\",\n" +"                \"children\": []\n" +"            },\n" +"            {\n" +"                \"type\": \"RELATION\",\n" +"                \"children\": [\n" +"                    {\n" +"                        \"type\": \"EXPRESSION\",\n" +"                        \"metricName\": \"NORMAL_NUMBER\",\n" +"                        \"operator\": \"GREATER_THAN\",\n" +"                        \"value\": 10,\n" +"                        \"children\": []\n" +"                    },\n" +"                    {\n" +"                        \"type\": \"EXPRESSION\",\n" +"                        \"metricName\": \"ERROR_NUMBER\",\n" +"                        \"operator\": \"GREATER_THAN\",\n" +"                        \"value\": 100,\n" +"                        \"children\": []\n" +"                    },\n" +"                    {\n" +"                        \"type\": \"RELATION\",\n" +"                        \"children\": [\n" +"                            {\n" +"                                \"type\": \"EXPRESSION\",\n" +"                                \"metricName\": \"NORMAL_NUMBER\",\n" +"                                \"operator\": \"EQUAL\",\n" +"                                \"value\": 1,\n" +"                                \"children\": []\n" +"                            },\n" +"                            {\n" +"                                \"type\": \"EXPRESSION\",\n" +"                                \"metricName\": \"ERROR_NUMBER\",\n" +"                                \"operator\": \"EQUAL\",\n" +"                                \"value\": 1,\n" +"                                \"children \": []\n" +"                            }\n" +"                        ],\n" +"                        \"operator\": \"OR\"\n" +"                    }\n" +"                ],\n" +"                \"operator\": \"OR\"\n" +"            }\n" +"        ]\n" +"    }\n" +"}";JSONObject conditionJson = JSON.parseObject(ruleJson);// 3. 设置factString whenExpression = EasyRulesUtil.buildWhenExpression(conditionJson.getJSONObject("validateCondition"));ruleModel.setWhenExpression(whenExpression);// 4. 设置结果表达式ruleModel.setThenExpression(EasyRulesUtil.buildThenExpression());// 5. 设置匹配条件JSONObject json = new JSONObject();json.put("NORMAL_NUMBER", 12);json.put("ERROR_NUMBER", 12);json.put("省=陕西;市=西安;", 100);// 6. 调用规则匹配EasyRulesUtil.RuleResult result = EasyRulesUtil.match(json, ruleModel);System.out.println(result);

结果分析:显然结果匹配成功。原因如图2

在这里插入图片描述

facts:NORMAL_NUMBER = 10、ERROR_NUMBER = 10

condition:如图1

显然NORMAL_NUMBER = 10,满足第一个条件 < 11,直接返回true。

如果我们设置fact:NORMAL_NUMBER = 12,则NORMAL_NUMBER 不满足第一个条件。

但是fact中ERROR_NUMBER = 10 <= 11满足第二个条件,直接返回True

场景5-QLExpress

1、使用阿里的QLExpress

        <dependency><groupId>com.alibaba</groupId><artifactId>QLExpress</artifactId><version>3.3.2</version></dependency>
ExpressRunner runner = new ExpressRunner();DefaultContext<String, MetaRuleResult> context = new DefaultContext<>();context.put("o", MetaRuleResult.builder().skuId(1L).result(true).metaRule("o").failureReason("").build());context.put("l", MetaRuleResult.builder().skuId(1L).result(false).metaRule("l").failureReason("锁库存不可更改").build());context.put("s", MetaRuleResult.builder().skuId(1L).result(true).metaRule("s").failureReason("").build());context.put("w", MetaRuleResult.builder().skuId(1L).result(true).metaRule("w").failureReason("售罄预警").build());context.put("lo", MetaRuleResult.builder().skuId(1L).result(true).metaRule("lo").failureReason("").build());context.put("llo", MetaRuleResult.builder().skuId(1L).result(false).metaRule("llo").failureReason("锁库且修改值小于等于OR值可以更改").build());Object result;DefaultContext<String, Object> computeContext = new DefaultContext<>();for (Map.Entry<String, MetaRuleResult> entry : context.entrySet()) {computeContext.put(entry.getKey(), entry.getValue().getResult());}String ruleExpress = "o&&l&&s&&w&&lo&&llo";result = runner.execute(ruleExpress, computeContext, null, true, false);Boolean bResult = (Boolean) result;System.out.println(bResult);//false
  • MetaRuleResult
@Data
@Builder
public class MetaRuleResult {private Long skuId;private Boolean result;private String metaRule;private String failureReason;
}

2、使用EasyRules实现上述功能

  • 规则1
@Rule(name = "oRule", description = "修改值>=OR可修改")
public class ORule {@Conditionpublic boolean when(@Fact("oRule") MetaRuleResult oRule) {return oRule.getResult();}@Actionpublic void then(Facts facts) {System.out.println("修改值>=OR可修改");}
}

这里的规则是原子规则

  • 规则2
@Rule(name = "lRule", description = "锁库不可更改")
public class LRule {@Conditionpublic boolean when(@Fact("lRule") MetaRuleResult lRule) {return lRule.getResult();}@Actionpublic void then(Facts facts) {System.out.println("没锁库可更改");}
}
  • 自定义组合规则
public class MyUnitRuleGroup extends CompositeRule {public MyUnitRuleGroup() {}public MyUnitRuleGroup(String name) {super(name);}public MyUnitRuleGroup(String name, String description) {super(name, description);}public MyUnitRuleGroup(String name, String description, int priority) {super(name, description, priority);}public boolean evaluate(Facts facts) {if (!this.rules.isEmpty()) {Iterator var2 = this.rules.iterator();Rule rule;do {if (!var2.hasNext()) {return true;}rule = (Rule)var2.next();} while(rule.evaluate(facts));// 将失败的facts记录失败的原因String ruleName = rule.getName();MetaRuleResult metaRuleResult = facts.get(ruleName);facts.put("执行失败" + ruleName, metaRuleResult);return false;} else {return false;}}public void execute(Facts facts) throws Exception {Iterator var2 = this.rules.iterator();while(var2.hasNext()) {Rule rule = (Rule)var2.next();rule.execute(facts);}}
}

作用:这里的规则是组合规则,是原子规则的组合形式,可扩展

这里的自定义规则组合,是快速失败机制:即l&&o中如果lRule的condiotion为false,则直接失败,使用facts记录一个失败原因。也可以自定义将每个rule-condition为false的原因都记录下来

  • 自定义condition-after-listeren
public class MyRuleListener implements RuleListener {@Overridepublic boolean beforeEvaluate(Rule rule, Facts facts) {return true;}@Overridepublic void afterEvaluate(Rule rule, Facts facts, boolean b) {String ruleName = rule.getName();if (b) {// 只有l&&o为true,才会走到这个逻辑,否则走下面逻辑facts.put(ruleName, MetaRuleResult.builder().skuId(1L).result(true).metaRule(ruleName).failureReason("").build());} else {// l&&o有一个不满足,则总体失败,将各个失败的原因都记录下来StringBuilder sb = new StringBuilder();for (Map.Entry<String, Object> fact : facts) {String key = fact.getKey();if (key.contains("失败")) {MetaRuleResult result = (MetaRuleResult)fact.getValue();// 这里result中有中间参数比如lockStatus,则原因就可以写:lockStatus=true,已锁库,不允许修改sb.append(result.getFailureReason()+ "且");}}facts.put(ruleName, MetaRuleResult.builder().skuId(1L).result(false).metaRule(ruleName).failureReason(sb.toString()).build());}}@Overridepublic void beforeExecute(Rule rule, Facts facts) {}@Overridepublic void onSuccess(Rule rule, Facts facts) {}@Overridepublic void onFailure(Rule rule, Facts facts, Exception e) {}
}

作用:组合规则,执行结果。成功|失败,已经失败原因

  • 执行
		// 1.factFacts facts = new Facts();facts.put("oRule",          MetaRuleResult.builder().skuId(1L).result(true).metaRule("o").failureReason("").build());facts.put("lRule",      MetaRuleResult.builder().skuId(1L).result(false).metaRule("l").failureReason("").build());// 2.ruleORule oRule = new ORule();LRule lRule = new LRule();String oAndLRuleName = "o&&l";MyUnitRuleGroup oAndL = new MyUnitRuleGroup(oAndLRuleName, ">=OR且未锁过库规则校验");oAndL.addRule(oRule);oAndL.addRule(lRule);// 3.rulesRules rules = new Rules();rules.register(oAndL);// 4.引擎DefaultRulesEngine engine = new DefaultRulesEngine();engine.registerRuleListener(new MyRuleListener());engine.fire(rules,facts);MetaRuleResult result = facts.get(oAndLRuleName);if (!result.getResult()) {System.out.println(oAndLRuleName + result.getFailureReason());}
  • 扩展

1)db获取规则表达式:

先根据网店+品类+角色+修改类型,查询db获取组合规则,比如l&&o

2)工厂模式解析组合规则

然后根据l&&o,解析出规则为l和o,组合成l&&o

3)facts获取数据

自定义策略模式,key为枚举类型,value为对应的rpc查询接口

facts.put("o",          MetaRuleResult.builder().skuId(1L).result(queryData("o")).metaRule("o").failureReason("").build());

这里的queryData方法,根据规则类型o,获取对应的Rpc接口-ORGateway,然后查询or值,然后比较结果

4)组合规则中,判断每个原子规则是否执行通过,失败则记录对应执行失败原因

5)在condition-after中自定义listeren,如果组合规则condition为false,则记录组合规则整体的执行失败以及失败原因

6)如果组合规则整体执行失败,则本次结果为false

场景6- 动态规则Mvel + Json文件

1、背景

动态规则就是由于业务场景的变化,之前的规则已经不适用现在的业务场景,需要更改相对应的规则。

例如:之前是满300减30,现在是满200-30

  • 正常情况下我们需要改规则类里面的比较参数代码,然后打包上线。
  • 如果使用动态规则,修改一下配置中的规则json文件即可,线上代码会读取最新的规则配置文件,无需上线

2、前提说明

1)规则类中的condtion方法,可以入参传入Facts参数,然后使用facts.get()方法获取内容 ,但是规则文件(eg:json)的condtion中无法传入Facts参数,也就无法使用此参数

2) 自定义RuleListener监听会作用到所有执行的规则,如何仅处理我们指定的规则

@Override
public void afterEvaluate(Rule rule, Facts facts, boolean evaluateResult) {if(evaluateResult && Objects.equals(rule.getName(), "我们指定的规则名称")) {}
}

3、场景

  • 输入一个人的信息,信息中包含了这个人的学历等级,作为规则事实

    • 方式一:condition中制定(推荐,可以动态配置0和11)
    • @Condition修饰方法入参( @Fact(“person”) Person person )
    [{"name": "newEducationAdd","description": "修改学历添加列表","condition": "person.getQualifications() >= 0 && person.getQualifications()<=11","priority": 3,"actions": ["System.out.println(\"新规则执行了\")"]}
    ]
    
    • 方式二:将condition条件判断,自定在fact-key中对象的属性和方法中(不推荐)
[{"name": "newEducationAdd","description": "修改学历添加列表","condition": "person.getResult(person.getQualifications())","priority": 3,"actions": ["System.out.println(\"新规则执行了\")"]
}]
@Data
public class Person {// 姓名private String name;// 年龄private int age;// 描述private String dec;// 学历等级private int qualifications;private List<Education> educationList;public boolean getResult(int level){return AddEducation.getResult(level);}
}@UtilityClass
public class AddEducation {public static boolean getResult(int level){return level >= 0 && level <= 11;}
}
  • 如果学历等级符合规则,则去查询学历证书情况(集合存储)

  • 查出完学历证书后,在检测学历证书与他的学历等级是否匹配,匹配规则为:

    • 学历证书数量与学历等级相同
    • 最大学历证书的学历等级与学历等级一致
  • 匹配通过则学历真实,信息中会添加真实学历匹配结果

  • 未匹配通过则学历造假嫌疑,信息中会添加造假学历信息

上线

2、前提说明

1)规则类中的condtion方法,可以入参传入Facts参数,然后使用facts.get()方法获取内容 ,但是规则文件(eg:json)的condtion中无法传入Facts参数,也就无法使用此参数

2) 自定义RuleListener监听会作用到所有执行的规则,如何仅处理我们指定的规则

@Override
public void afterEvaluate(Rule rule, Facts facts, boolean evaluateResult) {if(evaluateResult && Objects.equals(rule.getName(), "我们指定的规则名称")) {}
}

3、场景

  • 输入一个人的信息,信息中包含了这个人的学历等级,作为规则事实

    • 方式一:condition中制定(推荐,可以动态配置0和11)
    • @Condition修饰方法入参( @Fact(“person”) Person person )
    [{"name": "newEducationAdd","description": "修改学历添加列表","condition": "person.getQualifications() >= 0 && person.getQualifications()<=11","priority": 3,"actions": ["System.out.println(\"新规则执行了\")"]}
    ]
    
    • 方式二:将condition条件判断,自定在fact-key中对象的属性和方法中(不推荐)
[{"name": "newEducationAdd","description": "修改学历添加列表","condition": "person.getResult(person.getQualifications())","priority": 3,"actions": ["System.out.println(\"新规则执行了\")"]
}]
@Data
public class Person {// 姓名private String name;// 年龄private int age;// 描述private String dec;// 学历等级private int qualifications;private List<Education> educationList;public boolean getResult(int level){return AddEducation.getResult(level);}
}@UtilityClass
public class AddEducation {public static boolean getResult(int level){return level >= 0 && level <= 11;}
}
  • 如果学历等级符合规则,则去查询学历证书情况(集合存储)

  • 查出完学历证书后,在检测学历证书与他的学历等级是否匹配,匹配规则为:

    • 学历证书数量与学历等级相同
    • 最大学历证书的学历等级与学历等级一致
  • 匹配通过则学历真实,信息中会添加真实学历匹配结果

  • 未匹配通过则学历造假嫌疑,信息中会添加造假学历信息

场景7-履约缺货处罚金额计算

相关文章:

Easy Rules规则引擎实战

文章目录 简介pom 规则抽象规则Rule基础规则BasicRule事实类Facts&#xff1a;map条件接口动作接口 四种规则定义方式注解方式RuleBuilder 链式Mvel和Spel表达式Yml配置 常用规则类DefaultRuleSpELRule&#xff08;Spring的表达式注入&#xff09; 组合规则UnitRuleGroup 规则引…...

听GPT 讲Rust源代码--library/alloc(2)

File: rust/library/alloc/src/vec/mod.rs 在Rust源代码中&#xff0c;rust/library/alloc/src/vec/mod.rs这个文件是Rust标准库中的Vec类型的实现文件。Vec是一个动态大小的数组类型&#xff0c;在内存中以连续的方式存储其元素。 具体来说&#xff0c;mod.rs文件中定义了以下…...

OSG读取和添加节点学习

之前加载了一个模型&#xff0c;代码是&#xff0c; osg::Group* root new osg::Group(); osg::Node* node new osg::Node(); node osgDB::readNodeFile("tree.osg"); root->addChild(node); root是指向osg::Group的指针&#xff1b; node是 osg:…...

计算机网络技术--念念

选择题&#xff1a; 1.只要遵循GNU通用公共许可证,任何人和机构都可以自由修改和再发布的操作系统是&#xff08;Linux &#xff09; 2.在计算机网络的各种功能中,最基本的、为其他功能提供实现基础的是&#xff08;实现数据通信 &#xff09; 3.计算机网络具有分布式处理功能,…...

C#_var

文章目录 一、前言二、隐式类型的局部变量2.1 var和匿名类型2.2 批注 三、总结 一、前言 C#中有一个 var 类型&#xff0c;不管什么类型的变量&#xff0c;都可以用它接收&#xff0c;实属懒人最爱了。 我没有了解过它的底层&#xff0c;甚至没看过它的说明文档&#xff0c;也…...

Linux---进程控制

一、进程创建 fork函数 在Linux中fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程&#xff0c;原进程为父进程 fork函数的功能&#xff1a; 分配新的内存和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统的进程列表中fork返…...

Java注解学习,一文掌握@Autowired 和 @Resource 注解区别

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…...

系列一、如何正确的获取Spring Cloud Alibaba Spring Cloud Spring Boot之间的版本对应关系

一、正确的获取Spring Cloud Alibaba & Spring Cloud & Spring Boot之间的版本对应关系 1.1、概述 Java发展日新月异&#xff0c;Spring Cloud Alibaba 、 Spring Cloud 、 Spring Boot在GitHub上的迭代也是异常的频繁&#xff0c;这也说明其社区很活跃&#xff0c;通…...

数据预处理:标准化和归一化

标准化和归一化简介 1、数据预处理概述2、数据标准化3、数据归一化4、标准化和归一化怎么选1、数据预处理概述 在选择了合适模型的前提下,机器学习可谓是“训练台上3分钟,数据数量和质量台下10年功”。数据的收集与准备是机器学习中的重要一步,是构建一个好的预测模型大厦的…...

Node.js+Express 路由配置,实现接口分类管理

首先创建一个路由目录及文件 routes/user.js代码 const express require(express); const router express.Router(); // 使用express提供的router对象 const db require(../dbserver/mysql);router.get(/api/user, (req, res) > {const sqlStr SELECT * FROM sys_user;…...

HTML-基础知识-基本结构,注释,文档说明,字符编码(一)

1.超文本标记语言不分大小写。 2.超文本标签属性名和属性值不区分大小写。 3.超文本标签属性值重复&#xff0c;听取第一个。 4.html结构 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"vi…...

《系统架构设计师教程(第2版)》第3章-信息系统基础知识-05-专家系统(ES)

文章目录 1. 先了解人工智能2.1 人工智能的特点2.2 人工智能的主要分支2. ES概述2.1 概述2.2 和一般系统的区别1)第一遍说了5点(理解为主)2)第二遍说的3点(主要记这个)3. ES的特点4. ES的组成4.1 知识库4.2 综合数据库4.3 推理机4.4 知识获取模块4.5 解释程序4.6 人一机接…...

OSCHINA Gitee 联合呈现,《2023 中国开源开发者报告》正式发布,总结分非常帮,可以免费看的报告!

《2023 中国开源开发者报告》 详细地址&#xff1a; https://talk.gitee.com/report/china-open-source-2023-annual-report.pdf 不需要收费下载&#xff01;&#xff01; 其中大模型的部分总结的非常棒 gietee 也支持 AI 模型托管了 如何在 Gitee 上托管 AI 模型 https://…...

代码随想Day55 | 392.判断子序列、115.不同的子序列

392.判断子序列 第一种思路是双指针&#xff0c;详细代码如下&#xff1a; class Solution { public:bool isSubsequence(string s, string t) {//双指针if(s.empty()&&t.empty()) return true;int i0,j0;while(i<t.size()){if(s[j]t[i]) j;if(js.size()) return t…...

电缆厂 3D 可视化管控系统 | 图扑数字孪生

图扑软件(Hightopo)专注于 Web 的 2D&3D 可视化&#xff0c;自主研发 2D&3D 图形渲染引擎、数据孪生应用开发平台和开发工具&#xff0c;广泛应用于 2D&3D 可视化、工业组态与数字孪生领域&#xff0c;图扑软件为工业物联网、楼宇、场馆、园区、数据中心、工厂、电…...

C语言之scanf浅析

前言&#xff1a; 当有了变量&#xff0c;我们需要给变量输入值就可以使用scanf函数&#xff0c;如果需要将变量的值输出在屏幕上的时候可以使用printf函数&#xff0c;如&#xff1a; #include <stdio.h> int main() {int score 0;printf("请输⼊成绩:");sc…...

Java商城 免 费 搭 建:鸿鹄云商实现多种商业模式,VR全景到SAAS,应有尽有

鸿鹄云商 b2b2c产品概述 【b2b2c平台】&#xff0c;以传统电商行业为基石&#xff0c;鸿鹄云商支持“商家入驻平台自营”多运营模式&#xff0c;积极打造“全新市场&#xff0c;全新 模式”企业级b2b2c电商平台&#xff0c;致力干助力各行/互联网创业腾飞并获取更多的收益。从消…...

Cypress安装与使用教程(3)—— 软测大玩家

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…...

Dryad数据库学习

从一篇science论文中看到数据存储在了这个平台&#xff0c;这里分享一下&#xff1a;datadryad.org 亲测无需注册&#xff0c;可以直接下载&#xff0c;从一个数据测试看&#xff0c;数据存储在亚马逊云&#xff0c;下载速度还可以&#xff0c;6M/s的样子。 Dryad 是一个开放的…...

TypeScript 的基础语法

书接上上文&#xff1a;关于vue3的知识点 和 上文 &#xff1a;TypeScript的安装与报错 我们来接着看TypeScript 的基础语法 TypeScript 语法 1. 类型注解 类型注解是 变量后面约定类型的语法&#xff0c;用来约定类型&#xff0c;明确提示 // 约定变量 age 的类型为 numbe…...

FA模板制作

1、链接克隆模板的制作 &#xff08;1&#xff09;安装一个全新的Windows 10&#xff0c;挂载并安装tools&#xff0c;关闭防火墙 &#xff08;2&#xff09;挂载FusionAccess_WindowsDestop_Install_6.5.1.iso后启用本地Administrator本地超管&#xff0c;切换为本地超管&am…...

国科大2023.12.28图像处理0854最后一节划重点

国科大图像处理2023速通期末——汇总2017-2019 图像处理 王伟强 作业 课件 资料 第1、2章不考 第3章 空间域图像增强 3.2 基本灰度变换(考过填空) 3.2.1 图像反转 3.2.2 对数变换 3.2.3 幂次变换 3.3 直方图处理 3.3.1 直方图均衡化&#xff08;大题计算&#xff09; …...

51单片机中TCON, IE, PCON等寄存器的剖析

在单片机中&#xff0c;如何快速通过名字记忆IQ寄存器中每一个控制位的作用呢&#xff1f; IE&#xff08;interrupt enable&#xff09;寄存器中&#xff0c;都是中断的使能位置。 其中的EA&#xff08;enable all&#xff09;是总使能位&#xff0c;ES(enable serial)是串口…...

2023.12.28 Python高级-正则表达式

目录 re正则表达式,一种专门用来匹配目标字符串的规则 re.match(),从头匹配一个,无则none re.search(), 不从头匹配返回一个,无则none re.findall(), 不从头匹配,用list返回所有 re分组 re匹配修饰符 re贪婪非贪婪 re切割和替换 re正则表达式,一种专门用来匹配目标字符串…...

编程笔记 html5cssjs 014 网页布局框架

编程笔记 html5&css&js 014 网页布局框架 一、Bootstrap简介二、使用Bootstrap布局 网页布局不只用HTML&#xff0c;还要用CSS和JAVASCRIPT等技术完成,这里暂时简单了解一下Bootstrap。 一、Bootstrap简介 这是一个开源的前端框架&#xff0c;由Twitter的前端工程师Ma…...

抖店和商品橱窗有什么区别?新手应该选哪个?

我是电商珠珠 临近年底了&#xff0c;有的人已经开始为下一年筹谋&#xff0c;有的去抖音做账号做直播带货&#xff0c;不会直播带货的就想尝试做下抖店&#xff0c;来为以后的经济打基础。 刚想要接触却对这类有些迷糊&#xff0c;发现商品橱窗和抖店都可以卖货&#xff0c;…...

在Adobe Acrobat上如何做PDF文档签名

Adobe Acrobat如何做PDF文档签名&#xff1f;PDF文档签名是指对PDF文档进行基于证书的数字签名&#xff0c;类似于传统的手写签名&#xff0c;可标识签名文档的人员。与手写签名不同&#xff0c;数字签名难以伪造&#xff0c;因为其包含签名者唯一的加密信息。为PDF文档进行基于…...

Leetcode 988. Smallest String Starting From Leaf (二叉树遍历好题)

Smallest String Starting From Leaf Medium 1.6K 227 Companies You are given the root of a binary tree where each node has a value in the range [0, 25] representing the letters ‘a’ to ‘z’. Return the lexicographically smallest string that starts at a le…...

redis 三主六从高可用docker(不固定ip)

redis集群(cluster)笔记 redis 三主三从高可用集群docker swarm redis 三主六从高可用docker(不固定ip) 此博客解决&#xff0c;redis加入集群后&#xff0c;是用于停掉后重启&#xff0c;将nodes.conf中的旧的Ip替换为新的IP&#xff0c;从而达到不会因为IP变化导致集群无法…...

12.26

key_it.c #include"key_it.h" void led_init() {// 设置GPIOE/GPIOF时钟使能RCC->MP_AHB4ENSETR | (0x3 << 4);// 设置PE10/PE8/PF10为输出模式GPIOE->MODER & (~(0x3 << 20));GPIOE->MODER | (0x1 << 20);GPIOE->MODER & (~…...