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

网站设计鉴赏房子装修设计图片大全

网站设计鉴赏,房子装修设计图片大全,塑胶加工东莞网站建设技术支持,自己怎样注册企业网站在日常业务开发中,我们经常需要基于一些“规则”来决定程序的走向。比如: 客服机器人 根据用户问题领域和复杂度选择不同的模型;营销系统 根据用户画像匹配不同优惠券;风控引擎 根据请求参数、时间、分值判定是否放行。 这些规则往…

在日常业务开发中,我们经常需要基于一些“规则”来决定程序的走向。比如:

  • 客服机器人 根据用户问题领域和复杂度选择不同的模型;
  • 营销系统 根据用户画像匹配不同优惠券;
  • 风控引擎 根据请求参数、时间、分值判定是否放行。

这些规则往往以「条件 + 条件组 + 规则」的形式组织,支持 AND/OR 逻辑、字符串匹配、数值比较、日期比较、布尔运算等。
本文将手把手带大家实现一个可扩展的规则解析引擎,最终效果如下:

  • 支持 EQUALS / NOT_EQUALS / CONTAINS / IN / GREATER_THAN / LESS_THAN / REGEX 多种运算符;
  • 支持 STRING / NUMBER / DATE / BOOLEAN 多种数据类型;
  • 逻辑解析采用“AND 优先于 OR”的优先级;
  • 短路求值:在 AND 为 false 或 OR 为 true 时提前结束计算;
  • 代码可嵌入 Spring Boot,对接 REST API。

(完整项目代码附在文末,感兴趣的小伙伴可以自取hh~)


一、规则结构设计

我们将规则分为三层:

  1. 条件(Condition):原子判断,如「问题领域 == 编译原理」。
  2. 条件组(Group):由多个条件组成,内部支持 AND/OR。
  3. 规则(Rule):由多个条件组组成,组之间也支持 AND/OR。

在这里插入图片描述

前端传参示例(JSON):

{"rule": {"groups": [{"groupsOperator": "","conditions": [{ "field": "问题领域", "operator": "EQUALS", "value": "编译原理", "fieldType": "STRING", "groupOperator": "" },{ "field": "问题类别", "operator": "IN", "value": "静态分析,代码优化", "fieldType": "STRING", "groupOperator": "AND" },{ "field": "问题名称", "operator": "EQUALS", "value": "编译优化", "fieldType": "STRING", "groupOperator": "OR" }]},{"groupsOperator": "AND","conditions": [{ "field": "score", "operator": "GREATER_THAN", "value": "0.9", "fieldType": "NUMBER", "groupOperator": "" }]}]},"context": {"问题领域": "语法分析","问题类别": "LR","问题名称": "编译优化","score": 0.91}
}

对应逻辑表达式:

((问题领域 == 编译原理 AND 问题类别 ∈ {静态分析,代码优化}) OR 问题名称 == 编译优化)
AND (score > 0.9)

二、关键实现思路

1. 工具类(类型解析与容错)

  • 字符串转数字、日期、布尔值时采用 Optional,解析失败直接返回空。
  • IN 运算符支持逗号分隔。
  • REGEX 支持动态编译正则。

2. 条件求值器

负责判断单条条件是否成立:

  • STRING: equals/contains/in/regex
  • NUMBER: equals/greater/less/in
  • DATE: equals/greater/less/in
  • BOOLEAN: equals/not equals

3. 逻辑解析 —— AND 优先于 OR

这是本引擎的核心:

  • 错误做法:简单从左到右短路,会把 (A AND B) OR C 错判成 (A AND B) 不成立直接 false。

  • 正确做法先按 OR 分块,块内做 AND,再对块结果做 OR。

    • 条件层:cond[0] groupOperator cond[1] ...
    • 规则层:group[0] groupsOperator group[1] ...

算法逻辑:

// AND 优先级高于 OR : 按照 OR 切块,块内做 AND,块间做 OR
Boolean currentAnd = null;  // 当前块的累计结果
boolean anyOrTrue = false;  // 之前是否已有 AND 块为 True (用于整体 OR)for (元素 e : 序列) {if (第一个元素) {  // 第一个条件,开启 AND 块currentAnd = eval(e);continue;}if (连接符是AND) {  // AND:块内与currentAnd = currentAnd && eval(e);} else { // OR:结束上一个 AND 块,合入总结果anyOrTrue = anyOrTrue || Boolean.TRUE.equals(currentAnd);if (anyOrTrue) return true; // OR短路currentAnd = eval(e); // 开新 AND 块}
}
// 合并最后一块
anyOrTrue = anyOrTrue || Boolean.TRUE.equals(currentAnd);
return anyOrTrue;

三、代码示例

以条件组求值为例(AND > OR):

private boolean evalGroup(RuleDetailVO.GroupVO group, Map<String, Object> ctx) {List<RuleDetailVO.CondVO> conds = group.getConditions();if (conds == null || conds.isEmpty()) return false;Boolean currentAnd = null;boolean anyOrTrue = false;for (int i = 0; i < conds.size(); i++) {RuleDetailVO.CondVO c = conds.get(i);boolean curr = condEval.test(c, ctx);String join = c.getGroupOperator();boolean isOrJoin = RuleEvalUtils.isOr(join);if (i == 0) {currentAnd = curr;continue;}if (!isOrJoin) { // ANDcurrentAnd = currentAnd && curr;} else {         // ORanyOrTrue = anyOrTrue || Boolean.TRUE.equals(currentAnd);if (anyOrTrue) return true;currentAnd = curr;}}anyOrTrue = anyOrTrue || Boolean.TRUE.equals(currentAnd);return anyOrTrue;
}

规则层求值同理,只是把元素换成


四、测试用例

测试例1:

POST http://localhost:8080/api/rules/match

Content-Type:application/json

{"rule": {"groups": [{"groupsOperator": "","conditions": [{ "field": "问题领域", "operator": "EQUALS", "value": "编译原理", "fieldType": "STRING", "groupOperator": "" },{ "field": "问题类别", "operator": "IN", "value": "静态分析,代码优化", "fieldType": "STRING", "groupOperator": "AND" },{ "field": "问题名称", "operator": "EQUALS", "value": "编译优化", "fieldType": "STRING", "groupOperator": "OR" }]},{"groupsOperator": "AND","conditions": [{ "field": "score", "operator": "GREATER_THAN", "value": "0.9", "fieldType": "NUMBER", "groupOperator": "" }]}]},"context": {"问题领域": "语法分析","问题类别": "LR","问题名称": "编译优化","score": 0.91}
}

响应体:

{"hit": true
}

测试例2:

POST http://localhost:8080/api/rules/match

Content-Type:application/json

{"rule": {"groups": [{"groupsOperator": "","conditions": [{ "field": "问题领域", "operator": "EQUALS", "value": "编译原理", "fieldType": "STRING", "groupOperator": "" },{ "field": "问题类别", "operator": "IN", "value": "静态分析,代码优化", "fieldType": "STRING", "groupOperator": "AND" },{ "field": "问题名称", "operator": "EQUALS", "value": "编译优化", "fieldType": "STRING", "groupOperator": "OR" }]},{"groupsOperator": "AND","conditions": [{ "field": "score", "operator": "GREATER_THAN", "value": "0.9", "fieldType": "NUMBER", "groupOperator": "" }]}]},"context": {"问题领域": "语法分析","问题类别": "LR","问题名称": "编译优化","score": 0.91}
}

响应体:

{"hit": false
}

五、读者可进一步扩展

  1. 枚举化运算符

    • 避免拼写错误,支持自动校验。
  2. Bean Validation

    • 校验前端传参是否合法(如 fieldType 必须在 ENUM 内)。
  3. 规则预编译

    • 将规则提前编译为 Predicate<Map<String,Object>>,执行时直接调用,提升性能。
  4. 匹配路径审计

    • 记录每条条件的判断结果,用于调试与可视化。

六、总结

本文实现了一个 高可扩展的规则解析引擎,核心亮点:

  • 类型多样化:支持字符串、数字、日期、布尔;
  • 运算符丰富:equals、contains、in、regex、比较运算;
  • 逻辑正确性:严格保证 AND 优先于 OR,避免短路误判;
  • 短路优化:提升性能;
  • 可扩展性:支持与 Spring Boot 集成、缓存、审计等功能。

通过这种设计,我们可以在实际业务场景中灵活定义规则,既满足 前端可配置化,又保证 后端执行的确定性和高效性


附项目完整代码:

目录结构如下

rule-engine-springboot/
├─ pom.xml
└─ src/└─ main/├─ java/│  └─ per/│     └─ mjn/│        ├─ RuleEngineApplication.java│        ├─ common/│        │  └─ vo/│        │     └─ RuleDetailVO.java│        ├─ engine/│        │  ├─ ConditionEvaluator.java│        │  ├─ RuleEngine.java│        │  └─ RuleEvalUtils.java│        └─ web/│           ├─ MatchController.java│           └─ dto/│              ├─ MatchRequest.java│              └─ MatchResponse.java└─ resources/└─ application.yml

RuleDetailVO.java

package per.mjn.common.vo;import lombok.Data;import java.util.ArrayList;
import java.util.List;@Data
public class RuleDetailVO {private List<GroupVO> groups = new ArrayList<>();@Datapublic static class GroupVO {private String groupsOperator;private List<CondVO> conditions;}@Datapublic static class CondVO {private String field;private String operator;private String value;private String fieldType;private String groupOperator;}
}

ConditionEvaluator.java

package per.mjn.engine;import per.mjn.common.vo.RuleDetailVO;import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;public class ConditionEvaluator {public boolean test(RuleDetailVO.CondVO cond, Map<String, Object> ctx) {final String field = RuleEvalUtils.normField(cond.getField());final String op = cond.getOperator();final String fieldType = cond.getFieldType();final String expect = cond.getValue();final Object actual = ctx.get(field);if (!RuleEvalUtils.hasText(field) || !RuleEvalUtils.hasText(op)) return false;return switch (fieldType == null ? "STRING" : fieldType.toUpperCase()) {case "NUMBER" -> compareNumber(op, actual, expect);case "DATE" -> compareDate(op, actual, expect);case "BOOLEAN" -> compareBoolean(op, actual, expect);default -> compareString(op, actual, expect);};}private boolean compareString(String op, Object actual, String expect) {String a = RuleEvalUtils.asString(actual);if (a == null || expect == null) return false;return switch (op) {case "EQUALS" -> a.equals(expect);case "NOT_EQUALS" -> !a.equals(expect);case "CONTAINS" -> a.contains(expect);case "IN" -> RuleEvalUtils.parseInList(expect).contains(a);case "REGEX" -> Pattern.compile(expect).matcher(a).find();default -> false;};}private boolean compareNumber(String op, Object actual, String expect) {Optional<BigDecimal> aOpt = RuleEvalUtils.asDecimal(actual);Optional<BigDecimal> eOpt = RuleEvalUtils.asDecimal(expect);if (aOpt.isEmpty() || eOpt.isEmpty()) return false;BigDecimal a = aOpt.get();BigDecimal e = eOpt.get();int cmp = a.compareTo(e);return switch (op) {case "EQUALS" -> cmp == 0;case "NOT_EQUALS" -> cmp != 0;case "GREATER_THAN" -> cmp > 0;case "LESS_THAN" -> cmp < 0;case "IN" -> RuleEvalUtils.parseInList(expect).stream().anyMatch(s -> RuleEvalUtils.asDecimal(s).map(v -> v.compareTo(a) == 0).orElse(false));default -> false;};}private boolean compareBoolean(String op, Object actual, String expect) {Optional<Boolean> aOpt = RuleEvalUtils.asBoolean(actual);Optional<Boolean> eOpt = RuleEvalUtils.asBoolean(expect);if (aOpt.isEmpty() || eOpt.isEmpty()) return false;boolean a = aOpt.get();boolean e = eOpt.get();return switch (op) {case "EQUALS" -> a == e;case "NOT_EQUALS" -> a != e;default -> false;};}private boolean compareDate(String op, Object actual, String expect) {Optional<LocalDate> aOpt = RuleEvalUtils.asDate(actual);Optional<LocalDate> eOpt = RuleEvalUtils.asDate(expect);if (aOpt.isEmpty() || eOpt.isEmpty()) return false;LocalDate a = aOpt.get();LocalDate e = eOpt.get();int cmp = a.compareTo(e);return switch (op) {case "EQUALS" -> cmp == 0;case "NOT_EQUALS" -> cmp != 0;case "GREATER_THAN" -> cmp > 0;case "LESS_THAN" -> cmp < 0;case "IN" -> RuleEvalUtils.parseInList(expect).stream().anyMatch(s -> RuleEvalUtils.asDate(s).map(v -> v.compareTo(a) == 0).orElse(false));default -> false;};}
}

RuleEngine.java

package per.mjn.engine;import per.mjn.common.vo.RuleDetailVO;import java.util.List;
import java.util.Map;public class RuleEngine {private final ConditionEvaluator condEval = new ConditionEvaluator();public boolean matches(RuleDetailVO rule, Map<String, Object> ctx) {List<RuleDetailVO.GroupVO> groups = rule.getGroups();if (groups == null || groups.isEmpty()) return false;Boolean currentAnd = null;boolean anyOrTrue = false;for (int i = 0; i < groups.size(); i++) {RuleDetailVO.GroupVO g = groups.get(i);boolean curr = evalGroup(g, ctx);String join = g.getGroupsOperator();boolean isOrJoin = RuleEvalUtils.isOr(join);if (i == 0) {currentAnd = curr;continue;}if (!isOrJoin) {currentAnd = currentAnd && curr;} else {anyOrTrue = anyOrTrue || currentAnd;if (anyOrTrue) return true;currentAnd = curr;}}anyOrTrue = anyOrTrue || currentAnd;return anyOrTrue;}private boolean evalGroup(RuleDetailVO.GroupVO group, Map<String, Object> ctx) {List<RuleDetailVO.CondVO> conds = group.getConditions();if (conds == null || conds.isEmpty()) return false;// AND 优先级高于 OR : 按照 OR 切块,块内做 AND,块间做 ORBoolean currentAnd = null;  // 当前块的累计结果boolean anyOrTrue = false;  // 之前是否已有 AND 块为 True (用于整体 OR)for (int i = 0; i < conds.size(); i++) {RuleDetailVO.CondVO c = conds.get(i);boolean curr = condEval.test(c, ctx);String join = c.getGroupOperator();  // 当前条件与“上一条”的连接符boolean isOrJoin = RuleEvalUtils.isOr(join);if (i == 0) {  // 第一个条件,开启 AND 块currentAnd = curr;continue;}if (!isOrJoin) {  // AND 块内与currentAnd = currentAnd && curr;} else {  // OR:结束上一个 AND 块,合入总结果anyOrTrue = anyOrTrue || currentAnd;if (anyOrTrue) return true; // OR 短路currentAnd = curr;  // 开新 AND 块}}// 合并最后一个 AND 块anyOrTrue = anyOrTrue || currentAnd;return anyOrTrue;}
}

RuleEvalUtils.java

package per.mjn.engine;import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;public final class RuleEvalUtils {private RuleEvalUtils() {}public static boolean hasText(String s) {return s != null && !s.isBlank();}public static boolean isOr(String op) {return "OR".equalsIgnoreCase(op);}public static String normField(String f) {return f == null ? "" : f.trim();}public static List<String> parseInList(String v) {if (v == null) return List.of();String[] arr = v.split(",");List<String> list = new ArrayList<>(arr.length);for (String s : arr) {if (!s.isBlank()) list.add(s.trim());}return list;}public static String asString(Object o) {return (o == null) ? null : String.valueOf(o);}public static Optional<BigDecimal> asDecimal(Object o) {try {if (o == null) return Optional.empty();if (o instanceof BigDecimal bd) return Optional.of(bd);if (o instanceof Number n) return Optional.of(new BigDecimal(n.toString()));if (o instanceof String s && !s.isBlank()) return Optional.of(new BigDecimal(s.trim()));} catch (Exception ignore) {}return Optional.empty();}public static Optional<Boolean> asBoolean(Object o) {if (o == null) return Optional.empty();if (o instanceof Boolean b) return Optional.of(b);if (o instanceof String s) {String x = s.trim().toLowerCase(Locale.ROOT);if ("true".equals(x) || "1".equals(x) || "yes".equals(x)) return Optional.of(true);if ("false".equals(x) || "0".equals(x) || "no".equals(x)) return Optional.of(false);}return Optional.empty();}public static Optional<LocalDate> asDate(Object o) {if (o == null) return Optional.empty();try {if (o instanceof LocalDate d) return Optional.of(d);if (o instanceof OffsetDateTime odt) return Optional.of(odt.toLocalDate());if (o instanceof String s && !s.isBlank()) {DateTimeFormatter f = DateTimeFormatter.ISO_LOCAL_DATE;return Optional.of(LocalDate.parse(s.trim(), f));}} catch (Exception ignore) {}return Optional.empty();}
}

MatchRequest.java

package per.mjn.web.dto;import jakarta.validation.constraints.NotNull;
import lombok.Data;
import per.mjn.common.vo.RuleDetailVO;
import java.util.Map;@Data
public class MatchRequest {@NotNullprivate RuleDetailVO rule;@NotNullprivate Map<String, Object> context;
}

MatchResponse.java

package per.mjn.web.dto;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class MatchResponse {private boolean hit;
}

MatchController.java

package per.mjn.web;import jakarta.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import per.mjn.engine.RuleEngine;
import per.mjn.web.dto.MatchRequest;
import per.mjn.web.dto.MatchResponse;@RestController
@RequestMapping(path = "/api/rules", produces = MediaType.APPLICATION_JSON_VALUE)
public class MatchController {private final RuleEngine ruleEngine;public MatchController() {this.ruleEngine = new RuleEngine();}@PostMapping(path = "/match", consumes = MediaType.APPLICATION_JSON_VALUE)public MatchResponse match(@RequestBody @Valid MatchRequest req) {boolean hit = ruleEngine.matches(req.getRule(), req.getContext());return new MatchResponse(hit);}
}

RuleEngineApplication.java

package per.mjn;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class RuleEngineApplication {public static void main(String[] args) {SpringApplication.run(RuleEngineApplication.class, args);}}

application.yml

server:port: 8080
spring:jackson:serialization:WRITE_DATES_AS_TIMESTAMPS: false

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.2</version><relativePath/></parent><groupId>per.mjn</groupId><artifactId>rule-engine-springboot</artifactId><version>0.0.1-SNAPSHOT</version><name>rule-engine-springboot</name><description>rule-engine-springboot</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
http://www.dtcms.com/a/619552.html

相关文章:

  • Selenium 八大定位方法
  • 做网站业务的怎么找资源seo公司推荐推广平台
  • 【开题答辩全过程】以 基于web网络的超级商场进行销存管理系统为例,包含答辩的问题和答案
  • 深度解析KingbaseES数据库备份利器sys_dump:从参数到实战的全流程指南
  • 岳阳网站界面设计全国企业信用查询系统
  • html网站免费模板下载指数基金投资指南
  • 百度小程序seo域名如何优化
  • dnf免做卡网站哪里查询网站备案
  • 北京酷站科技有限公司北仑seo排名优化技术
  • 佛山网站建设 骏域网站建设哈尔滨网站制作建设
  • 东莞网站建设 烤活鱼seo课程简介
  • 前端模块化:ESM 与 CJS
  • 4网站建设汕头市官网
  • 可以做产品推广的网站wordpress做网站过程
  • 《Unity Shader》 6.4.2 逐像素光照
  • 北京公司网站制作衡水 网站开发
  • 东莞做网站首选企业铭缙云县城乡建设局网站
  • 41 当前用户购物车数据
  • 公司网站建设需要什么网站开发过程文档
  • wordpress调用昵称静态网站怎么做优化
  • php网站开发建设网站云优化
  • 快排做网站排名爬取漫画数据做网站
  • 黄页游戏引流推广网站昆明建设招聘信息网站
  • 网站建设投诉去哪里投诉专业做家居的网站
  • 免费祝福网页在线制作网站在线优化工具
  • 【题解】P2324 [SCOI2005] 骑士精神 [IDA*]
  • 杭州自助建站网站网站变灰代码 所有浏览器
  • 中国建设银行官网站积分抽奖网站浮窗制作
  • 网站建设四网合一app开发与网站开发的区别
  • Leetcode 55