需求 按照登录用户的会员等级 和签到周期 根据一定的计算规则送积分。由于之前都是通过if else去做的控制。规则变更的时候可能需要重新调整代码甚至发布服务。

由于不想再每次规则变更后需要调整代码,于是最近在确认方案, 于是最好找到了规则引擎。

什么是规则引擎

规则引擎,全称为业务规则管理系统,英文名为BRMS(即Business Rule Management System)。规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模块编写业务决策(业务规则),由用户或开发者在需要时进行配置、管理。

需要注意的是规则引擎并不是一个具体的技术框架,而是指的一类系统,即业务规则管理系统。目前市面上具体的规则引擎产品有:drools(最活跃的开源规则引擎)、VisualRules(旗正规则引擎)国内商业规则引擎品牌、iLog JRules(商用BRMS)等。

规则引擎实现了将业务决策从应用程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是一个输入输出平台

系统中引入规则引擎后,业务规则不再以程序代码的形式驻留在系统中,取而代之的是处理规则的规则引擎,业务规则存储在规则库中,完全独立于程序。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交业务规则等。业务规则被加载到规则引擎中供应用系统调用

使用规则引擎好处:

1、业务规则与系统代码分离,实现业务规则的集中管理

2、在不重启服务的情况下可随时对业务规则进行扩展和维护

3、可以动态修改业务规则,从而快速响应需求变更

4、规则引擎是相对独立的,只关心业务规则,使得业务分析人员也可以参与编辑、维护系统的业务规则

5、减少了硬编码业务规则的成本和风险

6、使用规则引擎提供的规则编辑工具,使复杂的业务规则实现变得的简单

Drools介绍

drools是一款由JBoss组织提供的基于Java语言开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或特定的存储介质中(例如存放在数据库中),使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效。

drools官网地址:https://drools.org/

文档地址: https://docs.jboss.org/drools/release/6.5.0.Final/drools-docs/html_single/index.html (就是打开太慢)

介绍信息:https://docs.jboss.org/drools/release/7.58.0.Final/drools-docs/html_single/index.html#decision-engine-con_decision-engine

drools源码下载地址:https://github.com/kiegroup/drools

以上都是介绍drools 中的信息。

在项目中的使用,由于我目前项目都是基于springboot开发,所以提供的代码示例都是基于springboot版本的。

规则引擎api 调用流程:

1.引入依赖

<dependency><groupId>org.drools</groupId><artifactId>drools-core</artifactId><version>7.10.0.Final</version></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-compiler</artifactId><version>7.10.0.Final</version></dependency><dependency><groupId>org.kie</groupId><artifactId>kie-api</artifactId><version>7.10.0.Final</version></dependency><dependency><groupId>org.kie</groupId><artifactId>kie-spring</artifactId><exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></exclusion></exclusions><version>7.10.0.Final</version></dependency>

2.在resources目录下 新建 rules 文件夹 存放规则文件的(建议idea里面装dools插件)

新建一个 resources/rules/bookrule.drl 文件

// 规则引擎代码中注释 只能 使用 // 或者 /* */ 这两种 ,不支持#这种//图书优惠规则
package book.discount
import com.example.edwin.po.Order//规则一:所购图书总价在100元以下的没有优惠
rule "book_discount_1"   // 这个 book_discount_1 名称要唯一 // salience 10   优先级 数字越大优先级越高when$order:Order(originalPrice < 100)then$order.setRealPrice($order.getOriginalPrice());System.out.println("成功匹配到规则一:所购图书总价在100元以下的没有优惠");
end//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"when$order:Order(originalPrice < 200 && originalPrice >= 100)then$order.setRealPrice($order.getOriginalPrice() - 20);System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end//规则三:所购图书总价在200到300元的优惠50元
rule "book_discount_3"when$order:Order(originalPrice <= 300 && originalPrice >= 200)then$order.setRealPrice($order.getOriginalPrice() - 50);System.out.println("成功匹配到规则三:所购图书总价在200到300元的优惠50元");
end//规则四:所购图书总价在300元以上的优惠100元
rule "book_discount_4"when$order:Order(originalPrice >= 300)then$order.setRealPrice($order.getOriginalPrice() - 100);System.out.println("成功匹配到规则四:所购图书总价在300元以上的优惠100元");
end
@Data
public class Order implements Serializable {//订单原始价格,即优惠前价格private Double originalPrice;//订单真实价格,即优惠后价格private Double realPrice;
}@Testvoid contextLoads() {KieServices kieServices = KieServices.Factory.get();KieContainer container = kieServices.getKieClasspathContainer();//会话对象,用于和规则引擎交互KieSession kieSession = container.newKieSession();//构造订单对象,设置原始价格,由规则引擎根据优惠规则计算优惠后的价格Order order = new Order();order.setOriginalPrice(190d);//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配kieSession.insert(order);//激活规则引擎,如果规则匹配成功则执行规则int allRules = kieSession.fireAllRules();//关闭会话kieSession.dispose();System.out.println("allRules : " + allRules);System.out.println("order: " +order);}通过上面的demo可以发现,使用drools规则引擎主要工作就是编写规则文件,
在规则文件中定义跟业务相关的业务规则,例如本案例定义的就是优惠规则。
规则定义好后就需要调用drools提供的API将数据提供给规则引擎进行规则模式匹配,
引擎会执行匹配成功的规则并将计算的结果返回给我们。可能大家会有疑问,就是我们虽然没有在代码中编写规则的判断逻辑,但是我们
还是在规则文件中编写了业务规则,这跟在代码中编写规则有什么本质的区别呢?我们前面其实已经提到,使用规则引擎时业务规则可以做到动态管理。业务人员
可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交
业务规则等。这样就可以做到在不重启服务的情况下调整业务规则

规则引擎构成

drools规则引擎由以下三部分构成:

  • Working Memory(工作内存)
  • Rule Base(规则库)
  • Inference Engine(推理引擎)

其中Inference Engine(推理引擎)又包括:

  • Pattern Matcher(匹配器)
  • Agenda(议程)
  • Execution Engine(执行引擎)

如下图所示:

3.3.2 相关概念说明

Working Memory:工作内存,drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配,所以我们开发的应用程序只需要将我们的数据插入到Working Memory中即可,例如本案例中我们调用kieSession.insert(order)就是将order对象插入到了工作内存中。

Fact:事实,是指在drools 规则应用当中,将一个普通的JavaBean插入到Working Memory后的对象就是Fact对象,例如本案例中的Order对象就属于Fact对象。Fact对象是我们的应用和规则引擎进行数据交互的桥梁或通道。

Rule Base:规则库,我们在规则文件中定义的规则都会被加载到规则库中。

Pattern Matcher:匹配器,将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,匹配成功的规则将被激活并放入Agenda中。

Agenda:议程,用于存放通过匹配器进行模式匹配后被激活的规则。

Execution Engine:执行引擎,执行Agenda中被激活的规则。

3.3.3 规则引擎执行过程

3.3.4 KIE介绍

我们在操作Drools时经常使用的API以及它们之间的关系如下图:

通过上面的核心API可以发现,大部分类名都是以Kie开头。Kie全称为Knowledge Is Everything,即"知识就是一切"的缩写,是Jboss一系列项目的总称。如下图所示,Kie的主要模块有OptaPlanner、Drools、UberFire、jBPM。

规则文件构成

在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl

drl就是Drools Rule Language。在规则文件中编写具体的规则内容
一套完整的规则文件的内容如:

关键字 描述
package 包名只限于逻辑上的管理,同一个包名下的查询或者是函数可以直接调用
import 用于导入类或者静态方法
global 全局变量
function 自定义函数
query 查询
rule end 规则

语法:

rule "ruleName"attributeswhenLHSthenRHS
endrule:关键字,表示规则开始,参数为规则的唯一名称。attributes:规则属性,是rule与when之间的参数,为可选项。when:关键字,后面跟规则的条件部分。LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。then:关键字,后面跟规则的结果部分。RHS(Right Hand Side):是规则的后果或行动部分的通用名称。end:关键字,表示一个规则结束

在新建一个 userRule.drl

package user
import com.example.edwin.po.UserInfo
import com.example.edwin.util.Resp
// 全局应用需要在 kieSession 中设置 global 属性 如果不设置 UserService 是获取不到的
// 除非 UserService 是个公共配置类,调用的方法是静态方法
global com.example.edwin.service.UserService userServicerule "rule_user_1"
//   no-loop    防止死循环  可以根据具体的规则 搭配使用
//    salience 10   //指定规则执行优先级
//    dialect java   //指定规则使用的语言类型,取值为java和mvel
//    enabled false  //指定规则是否启用
//    activation-group  “my-test-group” //激活分组,具有相同分组名称的规则只能有一个规则触发when$user:UserInfo(id matches "1" && name != "")thenuserService.insertInfo($user);$user.setId("15");
//      update($user);  将内存中的数据 更新  会出发规则 重新选择 建议搭配  no-loop使用,或者在写的时候一定注意 规则条件 避免死循环
//      insert($user);  将内存中的数据 插入  会出发规则 重新选择
//      retract($user)  将内存中的数据 清除 会出发规则 重新选择System.out.println("匹配到1:"+ $user);
endrule "rule_user_2"when$user:UserInfo(id =='10' && name contains 'yu')// 返回值设置$resp:Resp()then// 先插入数据库userService.insertInfo($user);$resp.setCode(200);$resp.setData($user);$resp.setMsg("操作成功");System.out.println("匹配到2:"+ $user);
endrule "rule_user_3"when$user:UserInfo(id matches "15" && name != "")thenuserService.insertInfo($user);System.out.println("匹配到3:"+ $user);
end

其他代码类:

@Data
public class UserInfo implements Serializable {private String id;private String name;
}@Service
public class UserService {public void insertInfo(UserInfo userInfo){System.out.println("insert success!"+ userInfo.toString());
//        test();}private void test(){throw new RuntimeException("error !");}
}@Data
public class Resp implements Serializable {private Integer code;private Object data;private String msg;
}@Testvoid UserTest() {KieServices kieServices = KieServices.Factory.get();KieContainer container = kieServices.getKieClasspathContainer();//会话对象,用于和规则引擎交互KieSession kieSession = container.newKieSession();//构造订单对象,设置原始价格,由规则引擎根据优惠规则计算优惠后的价格UserInfo user = new UserInfo();user.setId("1");user.setName("yulang你好");//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配kieSession.insert(user);kieSession.setGlobal("userService",userService);Resp resp = new Resp();kieSession.insert(resp);//激活规则引擎,如果规则匹配成功则执行规则int allRules = kieSession.fireAllRules();//关闭会话kieSession.dispose();System.out.println("allRules : " + allRules);System.out.println("order: " +user);System.out.println("resp: " +resp);}

Drools提供的比较操作符,如下表:

符号 说明
> 大于
< 小于
>= 大于等于
<= 小于等于
== 等于
!= 不等于
contains 检查一个Fact对象的某个属性值是否包含一个指定的对象值
not contains 检查一个Fact对象的某个属性值是否不包含一个指定的对象值
memberOf 判断一个Fact对象的某个属性是否在一个或多个集合中
not memberOf 判断一个Fact对象的某个属性是否不在一个或多个集合中
matches 判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配
not matches 判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配

一般我自己常用前面8种 ,

只要按照规则来编写规则文件,其实套路都是一只的,只需要自己按照要求编写自己的规则类。

我在项目中采用的是动态加载规则类的方式,只需要在页面上调整下规则,新的规则自动发布,不需要重启服务等。

具体实现思路如下:

1、将规则文件的内容存储在数据库中

2、Drools相关对象(例如KieContainer对象)的创建都基于数据库中存储的规则来创建

3、提供HTTP访问接口,当规则发生变化时调用此接口重新加载数据库中的规则,重新创建KieContainer等对象

实现步骤及代码:

1.数据库创建一张规则表:
CREATE TABLE `rule`  (`id` bigint(20) NOT NULL AUTO_INCREMENT,`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`create_time` TIMESTAMP  NOT NULL DEFAULT CURRENT_TIMESTAMP,`last_modify_time` TIMESTAMP not NULL DEFAULT CURRENT_TIMESTAMP,`rule_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`version` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `UK_9yepjak9olg92holwkr8p3l0f`(`rule_key`) USING BTREE,UNIQUE INDEX `UK_ilmbp99kyt6gy10224pc9bl6n`(`version`) USING BTREE,UNIQUE INDEX `UK_ei48upwykmhx9r5p7p4ndxvgn`(`last_modify_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;2. 将规则数据插入表中:
INSERT INTO `hosp`.`rule` (`id`, `content`, `create_time`, `last_modify_time`, `rule_key`, `version`) VALUES (1, 'package user.resp\r\nimport com.example.edwin.po.UserInfo\r\nimport com.example.edwin.util.Resp\r\nglobal com.example.edwin.service.UserService userService\r\n\r\nrule \"resp_user_1\"\r\n    when\r\n       $user:UserInfo(id matches \"1\" && name != \"\")\r\n    then\r\n      userService.insertInfo($user);\r\n      $user.setId(\"15\");\r\n      System.out.println(\"匹配到1:\"+ $user);\r\nend\r\n\r\nrule \"resp_user_2\"\r\n   when\r\n       $user:UserInfo(id ==\'10\' && name contains \'yu\')\r\n       $resp:Resp()\r\n   then\r\n      userService.insertInfo($user);\r\n      $resp.setCode(200);\r\n      $resp.setData($user);\r\n      $resp.setMsg(\"操作成功\");\r\n       System.out.println(\"匹配到2:\"+ $user);\r\nend\r\n\r\nrule \"resp_user_3\"\r\n    when\r\n       $user:UserInfo(id matches \"15\" && name != \"\")\r\n    then\r\n      userService.insertInfo($user);\r\n      System.out.println(\"匹配到3:\"+ $user);\r\nend\r\n', now(), now(), 'score', '1');
INSERT INTO `hosp`.`rule` (`id`, `content`, `create_time`, `last_modify_time`, `rule_key`, `version`) VALUES (2, '\r\n//图书优惠规则\r\npackage book.discount\r\nimport com.example.edwin.po.Order\r\n\r\n//规则一:所购图书总价在100元以下的没有优惠\r\nrule \"book_discount_1\"\r\n  // salience 10   优先级 数字越大优先级越高\r\n    when\r\n        $order:Order(originalPrice < 100)\r\n    then\r\n        $order.setRealPrice($order.getOriginalPrice());\r\n        System.out.println(\"成功匹配到规则一:所购图书总价在100元以下的没有优惠\");\r\nend\r\n\r\n//规则二:所购图书总价在100到200元的优惠20元\r\nrule \"book_discount_2\"\r\n    when\r\n        $order:Order(originalPrice < 200 && originalPrice >= 100)\r\n    then\r\n//        System.out.println(\"$op:\"+$op);\r\n        $order.setRealPrice($order.getOriginalPrice() - 20);\r\n        System.out.println(\"成功匹配到规则二:所购图书总价在100到200元的优惠20元\");\r\nend\r\n\r\n//规则三:所购图书总价在200到300元的优惠50元\r\nrule \"book_discount_3\"\r\n    when\r\n        $order:Order(originalPrice <= 300 && originalPrice >= 200)\r\n    then\r\n        $order.setRealPrice($order.getOriginalPrice() - 50);\r\n        System.out.println(\"成功匹配到规则三:所购图书总价在200到300元的优惠50元\");\r\nend\r\n\r\n//规则四:所购图书总价在300元以上的优惠100元\r\nrule \"book_discount_4\"\r\n    when\r\n        $order:Order(originalPrice >= 300)\r\n    then\r\n        $order.setRealPrice($order.getOriginalPrice() - 100);\r\n        System.out.println(\"成功匹配到规则四:所购图书总价在300元以上的优惠100元\");\r\nend', now(), now(), 'book_money', '2');3. 实体类@Entity
@Data
@Table(name = "rule")
public class Rule implements Serializable {private static final long serialVersionUID = 1L;@Id@GeneratedValue(strategy= GenerationType.IDENTITY)//主键生成策略@Column(name="id")//数据库字段名private Long id;@Column(name="rule_key")private String ruleKey;@Column(name="content")private String content;@Column(name="version")private String version;@Column(name="last_modify_time")private String lastModifyTime;@Column(name="create_time")private String createTime;
}4. dao层
@Repository
public interface RuleRepository extends JpaRepository<Rule,Long>{
}5. 动态加载规则, 我这里是默认启动加载全部的规则。也可以在规则变更的时候重新加载@Autowiredprivate RuleRepository ruleRepository;@Autowiredpublic void list(){List<Rule> all = ruleRepository.findAll();KieServices kieServicesFactory = KieServices.Factory.get();KieRepository repository = kieServicesFactory.getRepository();KieFileSystem kieFileSystem = kieServicesFactory.newKieFileSystem();for(Rule rule : all){String drl = rule.getContent();System.out.println(drl);kieFileSystem.write("src/main/resources/" + drl.hashCode() + ".drl", drl);}KieBuilder kieBuilder = kieServicesFactory.newKieBuilder(kieFileSystem);kieBuilder.buildAll();if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) {throw new RuntimeException("Build Errors:\n" + kieBuilder.getResults().toString());}KieContainer kContainer = kieServicesFactory.newKieContainer(repository.getDefaultReleaseId());this.kieContainer = kContainer;}6. 业务调用规则@GetMapping("/test")public String test() {KieSession kieSession = kieContainer.newKieSession();//构造订单对象,设置原始价格,由规则引擎根据优惠规则计算优惠后的价格UserInfo user = new UserInfo();user.setId("10");user.setName("yulang你好");//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配kieSession.insert(user);kieSession.setGlobal("userService",userService);Resp resp = new Resp();kieSession.insert(resp);//激活规则引擎,如果规则匹配成功则执行规则int allRules = kieSession.fireAllRules();System.out.println("触发了" + allRules + "条规则");//关闭会话kieSession.dispose();return "success" + resp.toString();}

所有的demo代码地址项目:链接:https://pan.baidu.com/s/1k4H60031EhCtG-2L-k2VFQ 
提取码:rfv2

drools 里面还有很多功能细节,感兴趣的朋友可以看下官网研究下。另外Kie好像还有个在线编辑的工具 WorkBench,可以根据自己情况进行学习。

drools规则引擎的在项目中的使用手记相关推荐

  1. 大数据风控项目实战 Drools规则引擎

    可以借鉴的干货 1,统一存储服务,包含:多种存储库连接封装和服务封装 在统一存储服务 2.获取配置的环境 类:EnvVariable 一.风控项目介绍 对一个复杂支付系统提供统一.全面.高效的风险控制 ...

  2. 详解:Drools规则引擎探究

    引入 ▐ 问题引入 天猫奢品业务方为了吸引更多的新客,和提高会员的活跃度,做了一期活动,通过购买天猫奢品频道内的任意商品就赠送特殊积分,积分可以直接兑换限量的奢品商品.假如业务方给的规则如下: 主刃同 ...

  3. 使用 Drools 规则引擎实现业务逻辑,可调试drl文件

    http://www.srcsky.com/tech/arts/389.html 代码下载http://download.csdn.net/detail/zhy011525/2462313 使用 Dr ...

  4. drools规则引擎因为内存泄露导致的内存溢出

    进入这个问题之前,先了解一下drools: 在很多行业应用中比如银行.保险领域,业务规则往往非常复杂,并且规则处于不断更新变化中,而现有很多系统做法基本上都是将业务规则绑定在程序代码中. 主要存在的问 ...

  5. SpringBoot2 整合 Drools规则引擎,实现高效的业务规则

    本文源码:GitHub·点这里 || GitEE·点这里 一.Drools引擎简介 1.基础简介 Drools是一个基于java的规则引擎,开源的,可以将复杂多变的规则从硬编码中解放出来,以规则脚本的 ...

  6. SpringBoot整合Drools规则引擎动态生成业务规则

    最近的项目中,使用的是flowable工作流来处理业务流程,但是在业务规则的配置中,是在代码中直接固定写死的,领导说这样不好,需要规则可以动态变化,可以通过页面去动态配置改变,所以就花了几天时间去研究 ...

  7. drools规则引擎 java_Drools规则引擎的使用总结

    前一段时间在开发了一个做文本分析的项目.在项目技术选型的过程中,尝试使用了Drools规则引擎.让它来作为项目中有关模式分析和关键词匹配的任务.但后来,因为某种原因,还是撇开了Drools.现将这个过 ...

  8. 使用 Drools 规则引擎实现业务逻辑

    要求施加在当今软件产品上的大多数复杂性是行为和功能方面的,从而导致组件实现具有复杂的业务逻辑.实现 J2EE 或 J2SE 应用程序中业务逻辑最常见的方法是编写 Java 代码来实现需求文档的规则和逻 ...

  9. drools规则引擎的基本使用和原理介绍

    理论基石 借用:<drools规则引擎技术指南>来说, drools是: 开源项目,规则引擎技术,规则语法形成的规则文件,可以存在数据库等,通过drools包提供的接口,调用生成对应的结果 ...

最新文章

  1. MFC界面库BCGControlBar v25.3新版亮点:Gauge Controls
  2. 3G时代需要“移动云计算专业”
  3. python 之模块引入
  4. .NET 5.0正式发布,有什么功能特性(翻译)
  5. Orchard Core一分钟搭建ASP.NET Core CMS
  6. indesign如何画弧线_彩铅画入门教程,如何给独角兽设计一款好发型
  7. 使用faker生成测试数据
  8. 服务器怎么控制忽略样式_使用JavaScript来编写你的CSS样式代码——JSS
  9. Source Xref 与 JavaDocs 学习理解
  10. The Second Assignment
  11. Spring.Net的AOP的通知
  12. 基于visual Studio2013解决C语言竞赛题之1030计算函数
  13. c 生成html的div,createElement动态创建HTML对象脚本代码
  14. 经济数学线性代数第三版课后习题答案
  15. docker安装详细教程
  16. 安森美的全局快门图像传感器解决机器视觉的成像需求
  17. vs2017 开关“/NOENTRY”只与 DLL 兼容;链接时不使用“/NOENTRY”
  18. 关于U盘中毒,文件全变成快捷方式
  19. 基于RedHat6.5的Greenplum环境配置
  20. Mac M1 踩坑之Tensorflow安装 Processed finished with exit code 132

热门文章

  1. 食堂饭卡刷卡原理及吐槽
  2. AI圣经-深度学习-读书笔记(一)-引言
  3. 使用docker容器运行java程序
  4. 电子计算机房用电负荷标准,8.1 供配电_数据中心设计规范 GB50174-2017_消防规范网_119消防考试网...
  5. 数图复习---第二章
  6. python输入数字比大小_Python练习实例47 | 比较任意两个数字的大小
  7. 从4D到成像 | 4D毫米波雷达技术的发展
  8. 计算机应用基础任务化教程135,在PowerPoint 2010中SmartArt图形的制作及技巧
  9. 福昕阅读器 for Mac官方中文版
  10. 项目案例 | 白云机场三期扩建工程设计BIM集成应用