乐尚代驾03-预估订单数据&规则引擎

本章内容

  • 掌握腾讯位置服务API接口,封装最优路线规划
  • 学习规则引擎 Drools入门
  • 理解代驾费用规则,封装代驾费用接口
  • 完成预估订单数据接口

乘客选择代驾起始点与终点,乘客下单前,先做代驾订单数据的预估,预估代驾里程、代驾时间、代驾金额及代驾的最佳驾驶线路。

预估订单数据,根据乘客选择的代驾起始点与终点,服务端需计算最佳驾驶线路与代驾预估金额。

  • 最佳驾驶线路包含:腾讯地图线路数据、距离及时间。
  • 代驾预估金额:根据预估里程和代驾时间,结合费用规则,计算代驾金额,代驾金额的计算比较复杂,涉及规则引擎,后续讲到。

预估订单数据-查找乘客当前订单

  • 如果乘客有代驾订单正在进行中,这个时候乘客不能再次下单了,只有等当前订单完成之后,才可以下单
  • 这个功能因为订单相关,后续再进行处理,目前为了后续测试,临时跳过去,假设当前乘客没有订单
    web-driver中
@Tag(name = "订单API接口管理")
@RestController
@RequestMapping("/order")
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderController {

    //TODO 后续完善,目前假设乘客当前没有订单
    @Operation(summary = "查找乘客端当前订单")
    @GuiguLogin
    @GetMapping("/searchCustomerCurrentOrder")
    public Result<CurrentOrderInfoVo> searchCustomerCurrentOrder() {
        CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();
        currentOrderInfoVo.setIsHasCurrentOrder(false);
        return Result.ok(currentOrderInfoVo);
    }
}

预估订单数据-预估驾驶线路

前端选点

前端乘客选点时,跳入选点页面,页面空白,报如下错误

使用“腾讯位置服务地图选点”插件时,我们要开通“腾讯位置服务”,并且设置授权APP ID,这样才能使用正确使用选点服务。
在这里插入图片描述

开通腾讯位置服务

腾讯位置服务服务器端API文档:https://lbs.qq.com/service/webService/webServiceGuide/webServiceOverview

代驾订单的时候,后端Java项目要预估里程和时间、计算代驾线路,需要调用腾讯位置服务来实现。

第一步 访问腾讯官网 https://lbs.qq.com/
第二步 进行注册,手机号或者微信或者其他方式
第三步 使用注册账号进行登录,找到控制台
第四步 在应用管理 – 我的应用 ,创建应用
在这里插入图片描述
第五步 在创建应用中,添加key
在这里插入图片描述
第六步 找到添加key的值
在这里插入图片描述

开发腾讯地图服务接口

  • 封装地图服务接口:代驾路线距离,线路规划,时间
  • 修改Nacos配置文件,修改腾讯位置服务key
    在这里插入图片描述
    前端修改配置
    在这里插入图片描述

在service-map中
MapController

@Tag(name = "地图API接口管理")
@RestController
@RequestMapping("/map")
@SuppressWarnings({"unchecked", "rawtypes"})
public class MapController {
    
    @Autowired
    private MapService mapService;

    @Operation(summary = "计算驾驶线路")
    @PostMapping("/calculateDrivingLine")
    public Result<DrivingLineVo> calculateDrivingLine(@RequestBody CalculateDrivingLineForm 
                                                                  calculateDrivingLineForm) {
        DrivingLineVo drivingLineVo = mapService.calculateDrivingLine(calculateDrivingLineForm);
        return Result.ok(drivingLineVo);
    }
}

MapServiceImpl

@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class MapServiceImpl implements MapService {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${tencent.map.key}")
    private String key;

    //计算驾驶线路
    @Override
    public DrivingLineVo calculateDrivingLine(CalculateDrivingLineForm calculateDrivingLineForm) {
        //请求腾讯提供接口,按照接口要求传递相关参数,返回需要结果
        //使用HttpClient,目前Spring封装调用工具使用RestTemplate
        //定义调用腾讯地址
        String url = "https://apis.map.qq.com/ws/direction/v1/driving/?from={from}&to={to}&key={key}";

        //封装传递参数
        Map<String,String> map = new HashMap();
        //开始位置
        // 经纬度:比如 北纬40 东京116
        map.put("from",calculateDrivingLineForm.getStartPointLatitude()+","+calculateDrivingLineForm.getStartPointLongitude());
        //结束位置
        map.put("to",calculateDrivingLineForm.getEndPointLatitude()+","+calculateDrivingLineForm.getEndPointLongitude());
        //key
        map.put("key",key);

        //使用RestTemplate调用 GET
        JSONObject result = restTemplate.getForObject(url, JSONObject.class, map);
        //处理返回结果
        //判断调用是否成功
        int status = result.getIntValue("status");
        if(status != 0) {//失败
            throw new GuiguException(ResultCodeEnum.MAP_FAIL);
        }

        //获取返回路线信息
        JSONObject route =
                result.getJSONObject("result").getJSONArray("routes").getJSONObject(0);

        //创建vo对象
        DrivingLineVo drivingLineVo = new DrivingLineVo();
        //预估时间
        drivingLineVo.setDuration(route.getBigDecimal("duration"));
        //距离  6.583 == 6.58 / 6.59
        drivingLineVo.setDistance(route.getBigDecimal("distance")
                 .divide(new BigDecimal(1000))
                .setScale(2, RoundingMode.HALF_UP));
        //路线
        drivingLineVo.setPolyline(route.getJSONArray("polyline"));

        return drivingLineVo;
    }
}

配置类

@Configuration
public class MapConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

远程调用定义

@FeignClient(value = "service-map")
public interface MapFeignClient {

    /**
     * 计算驾驶线路
     * @param calculateDrivingLineForm
     * @return
     */
    @PostMapping("/map/calculateDrivingLine")
    Result<DrivingLineVo> calculateDrivingLine(@RequestBody CalculateDrivingLineForm calculateDrivingLineForm);
}

预估订单数据-预估订单金额

乘客选择了出发和结束的地点,计算预估金额是多少,根据约定规则进行金额计算。

费用计算

代驾费用=里程费 + 等候费 + 远途费

  • 里程费
    里程费=基础里程费+超出起步里程费
    在这里插入图片描述
  • 等待费
    在这里插入图片描述
  • 远途费
    在这里插入图片描述
  • 规则虽然是上面这样的,但是实际中,规则可能进行随时调整,比如油价上涨收取燃油附加费,比如大雪天气,费用增加等等

  • 对于不经常变化的业务,我们通常是硬编码到程序中。但是经常变化的业务,我们就得把业务流程从代码中剥离出来,我们怎么从程序中剥离出去?这里就需要用到规则引擎了。

引入规则引擎Drools

在service-rule中引入依赖

<dependencies>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-decisiontables</artifactId>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-mvel</artifactId>
    </dependency>
</dependencies>

创建Drools配置类

@Configuration
public class DroolsConfig {
    // 制定规则文件的路径
    private static final String RULES_CUSTOMER_RULES_DRL = "rules/FeeRule.drl";

    @Bean
    public KieContainer kieContainer() {
        KieServices kieServices = KieServices.Factory.get();

        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();

        KieModule kieModule = kb.getKieModule();
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
        return kieContainer;
    }
}

规则文件在resource里面已经有了。
service-rules
FeeRuleController

@RestController
@RequestMapping("/rules/fee")
@SuppressWarnings({"unchecked", "rawtypes"})
public class FeeRuleController {

    @Autowired
    private FeeRuleService feeRuleService;

    @Operation(summary = "计算订单费用")
    @PostMapping("/calculateOrderFee")
    public Result<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm calculateOrderFeeForm) {
        FeeRuleResponseVo feeRuleResponseVo = feeRuleService.calculateOrderFee(calculateOrderFeeForm);
        return Result.ok(feeRuleResponseVo);
    }

}

FeeRuleServiceImpl

@Autowired
    private KieContainer kieContainer;

    //计算订单费用
    @Override
    public FeeRuleResponseVo calculateOrderFee(FeeRuleRequestForm calculateOrderFeeForm) {

        //封装输入对象
        FeeRuleRequest feeRuleRequest = new FeeRuleRequest();
        feeRuleRequest.setDistance(calculateOrderFeeForm.getDistance());
        Date startTime = calculateOrderFeeForm.getStartTime();
        feeRuleRequest.setStartTime(new DateTime(startTime).toString("HH:mm:ss"));
        feeRuleRequest.setWaitMinute(calculateOrderFeeForm.getWaitMinute());

        //Drools使用
        KieSession kieSession = kieContainer.newKieSession();

        //封装返回对象
        FeeRuleResponse feeRuleResponse = new FeeRuleResponse();
        kieSession.setGlobal("feeRuleResponse",feeRuleResponse);

        kieSession.insert(feeRuleRequest);
        kieSession.fireAllRules();
        kieSession.dispose();

        //封装数据到FeeRuleResponseVo返回
        FeeRuleResponseVo feeRuleResponseVo = new FeeRuleResponseVo();
        // feeRuleResponse -- feeRuleResponseVo
        BeanUtils.copyProperties(feeRuleResponse,feeRuleResponseVo);
        return feeRuleResponseVo;
    }

远程调用接口FeeRuleFeignClient

/**
     * 计算订单费用
     * @param calculateOrderFeeForm
     * @return
     */
    @PostMapping("/rules/fee/calculateOrderFee")
    Result<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm calculateOrderFeeForm);

web-customer中
OrderController

	@Autowired
    private OrderService orderService;

    @Operation(summary = "预估订单数据")
    @GuiGuLogin
    @PostMapping("/expectOrder")
    public Result<ExpectOrderVo> expectOrder(@RequestBody ExpectOrderForm expectOrderForm) {
        return Result.ok(orderService.expectOrder(expectOrderForm));
    }

实现类

	@Autowired
    private MapFeignClient mapFeignClient;
    @Autowired
    private FeeRuleFeignClient feeRuleFeignClient;
    
    //预估订单数据
    @Override
    public ExpectOrderVo expectOrder(ExpectOrderForm expectOrderForm) {
        //获取驾驶线路
        CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
        BeanUtils.copyProperties(expectOrderForm,calculateDrivingLineForm);
        Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
        DrivingLineVo drivingLineVo = drivingLineVoResult.getData();

        //获取订单费用
        FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
        calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
        calculateOrderFeeForm.setStartTime(new Date());
        calculateOrderFeeForm.setWaitMinute(0);
        Result<FeeRuleResponseVo> feeRuleResponseVoResult = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm);
        FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoResult.getData();

        //封装ExpectOrderVo
        ExpectOrderVo expectOrderVo = new ExpectOrderVo();
        expectOrderVo.setDrivingLineVo(drivingLineVo);
        expectOrderVo.setFeeRuleResponseVo(feeRuleResponseVo);
        return expectOrderVo;
    }

测试的时候在service-map中添加依赖

<!--mongodb-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

启动项目的时候显示Web server failed to start. Port 8504 was already in use.
在这里插入图片描述
查看端口占用后发现8504端口没有被占用,暂时先去改配置文件中的端口号,可以正常启动。
提示此key每日调用量以达到上线,需要去腾讯位置服务官网,给周边推荐(explore)和关键词输入提示两个接口分配额度。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

规则引擎

对于不经常变化的业务,我们通常是硬编码到程序中。但是经常变化的业务,我们就得把业务流程从代码中剥离出来,我们怎么从程序中剥离出去?这里就需要用到规则引擎了。

规则引擎可以做到把算法剥离出程序,你可以保存到TXT文件或者数据库表里面,用的时候再加载回程序。虽然加载回来的算法是字符串,但是规则引擎有办法运行这些字符串。例如遇到雨雪天气,代驾费用就得上调一些。如果是业务淡季,代驾费用可以下调一点。既然代驾费的算法经常要变动,我们肯定不能把算法写死到程序里面。我们要把算法从程序中抽离,保存到MySQL里面。将来我们要改动计费算法,直接添加一个新纪录就行了,原有记录不需要删改,程序默认使用最新的计费方式。

什么是规则引擎

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

需要注意的是规则引擎并不是一个具体的技术框架,而是指的一类系统,即业务规则管理系统。目前市面上具体的规则引擎产品有:drools、VisualRules、iLog等。

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

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

规则引擎的优势

使用规则引擎的优势如下:

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

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

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

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

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

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

规则引擎应用场景

对于一些存在比较复杂的业务规则并且业务规则会频繁变动的系统比较适合使用规则引擎,如下:

1、风险控制系统----风险贷款、风险评估

2、反欺诈项目----银行贷款、征信验证

3、决策平台系统----财务计算

4、促销平台系统----满减、打折、加价购

Drools介绍

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

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

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

Drools入门案例

1.创建springboot项目

groupId:com.atguigu.drools
artifactId:drools_demo

2.引入依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>17</java.version>
        <drools.version>8.41.0.Final</drools.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-decisiontables</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-mvel</artifactId>
            <version>${drools.version}</version>
        </dependency>
    </dependencies>

3.添加Drools配置类

package com.atguigu.drools.config;

import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 规则引擎配置类
 */
@Configuration
public class DroolsConfig {

    private static final KieServices kieServices = KieServices.Factory.get();
    //制定规则文件的路径
    private static final String RULES_CUSTOMER_RULES_DRL = "rules/order.drl";

    @Bean
    public KieContainer kieContainer() {
        //获得Kie容器对象
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
        kieBuilder.buildAll();

        KieModule kieModule = kieBuilder.getKieModule();
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());

        return kieContainer;
    }

}

说明:

  • 定义了一个 KieContainerSpring BeanKieContainer用于通过加载应用程序的/resources文件夹下的规则文件来构建规则引擎。
  • 创建KieFileSystem实例并配置规则引擎并从应用程序的资源目录加载规则的 DRL 文件。
  • 使用KieBuilder实例来构建 drools 模块。我们可以使用KieSerive单例实例来创建 KieBuilder 实例。
  • 最后,使用 KieService 创建一个 KieContainer 并将其配置为 spring bean

4.创建实体类Order

package com.atguigu.drools.model;

public class Order {

    private double amout;
    private double score;

    public double getAmout() {
        return amout;
    }

    public void setAmout(double amout) {
        this.amout = amout;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

5.order.drl

创建规则文件resources/rules/order.drl

//订单积分规则
package com.order
import com.atguigu.drools.model.Order

//规则一:100元以下 不加分
rule "order_rule_1"
    when
        $order:Order(amout < 100)
    then
        $order.setScore(0);
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

//规则二:100元 - 500元 加100分
rule "order_rule_2"
    when
        $order:Order(amout >= 100 && amout < 500)
    then
         $order.setScore(100);
         System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end

//规则三:500元 - 1000元 加500分
rule "order_rule_3"
    when
        $order:Order(amout >= 500 && amout < 1000)
    then
         $order.setScore(500);
         System.out.println("成功匹配到规则三:500元 - 1000元 加500分");
end

//规则四:1000元以上 加1000分
rule "order_rule_4"
    when
        $order:Order(amout >= 1000)
    then
         $order.setScore(1000);
         System.out.println("成功匹配到规则四:1000元以上 加1000分");
end

6.编写测试类

package com.atguigu.drools;

import org.junit.jupiter.api.Test;
import com.atguigu.drools.model.Order;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DroolsDemosApplicationTests {

    @Autowired
    private KieContainer kieContainer;

    @Test
    public void test(){
        //从Kie容器对象中获取会话对象
        KieSession session = kieContainer.newKieSession();

        //Fact对象,事实对象
        Order order = new Order();
        order.setAmout(1300);

        //将Order对象插入到工作内存中
        session.insert(order);

        //激活规则,由Drools框架自动进行规则匹配,如果规则匹配成功,则执行当前规则
        session.fireAllRules();
        //关闭会话
        session.dispose();

        System.out.println("订单金额:" + order.getAmout() +
                ",添加积分:" + order.getScore());
    }

}

通过上面的入门案例我们可以发现,使用drools规则引擎主要工作就是编写规则文件,在规则文件中定义跟业务相关的业务规则。规则定义好后就需要调用drools提供的API将数据提供给规则引擎进行规则模式匹配,规则引擎会执行匹配成功的规则并将计算的结果返回给我们。

可能大家会有疑问,就是我们虽然没有在代码中编写规则的判断逻辑,但是我们还是在规则文件中编写了业务规则,这跟在代码中编写规则有什么本质的区别呢?

我们前面其实已经提到,使用规则引擎时业务规则可以做到动态管理。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交业务规则等。这样就可以做到在不重启服务的情况下调整业务规则。

Drools基础语法

1.规则文件构成

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

drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。

一套完整的规则文件内容构成如下:

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

Drools支持的规则文件,除了drl形式,还有Excel文件类型的。

2.规则体语法结构

规则体是规则文件内容中的重要组成部分,是进行业务规则判断、处理业务结果的部分。
规则体语法结构如下:

rule "ruleName"
    attributes
    when
        LHS 
    then
        RHS
end

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

3.注释

在drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。
单行注释用"//“进行标记,多行注释以”/“开始,以”/"结束。如下示例:

//规则rule1的注释,这是一个单行注释
rule "rule1"
    when
    then
        System.out.println("rule1触发");
end

/*
规则rule2的注释,
这是一个多行注释
*/
rule "rule2"
    when
    then
        System.out.println("rule2触发");
end

4.Pattern模式匹配

前面我们已经知道了Drools中的匹配器可以将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。
pattern的语法结构为:绑定变量名:Object(Field约束)
其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的0个或多个表达式。

例如我们的入门案例中:

//规则二:100元 - 500元 加100分
rule "order_rule_2"
    when
        $order:Order(amout >= 100 && amout < 500)
    then
         $order.setScore(100);
         System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end

通过上面的例子我们可以知道,匹配的条件为:
1、工作内存中必须存在Order这种类型的Fact对象-----类型约束
2、Fact对象的amout属性值必须大于等于100------属性约束
3、Fact对象的amout属性值必须小于500------属性约束
以上条件必须同时满足当前规则才有可能被激活。

5.比较操作符

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

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

前6个比较操作符和Java中的完全相同。

6.Drools内置方法

规则文件的RHS部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,**操作完成后规则引擎会重新进行相关规则的匹配,**原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。

6.1.update方法

update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配。 (要避免死循环)

参数:

//Fact对象,事实对象
Order order = new Order();
order.setAmout(30);

规则:

//规则一:100元以下 不加分
rule "order_rule_1"
    when
        $order:Order(amout < 100)
    then
        $order.setAmout(150);
	    update($order) //update方法用于更新Fact对象,会导致相关规则重新匹配
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

//规则二:100元 - 500元 加100分
rule "order_rule_2"
    when
        $order:Order(amout >= 100 && amout < 500)
    then
         $order.setScore(100);
         System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end

在更新数据时需要注意防止发生死循环。

6.2.insert方法

insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配。

//规则一:100元以下 不加分
rule "order_rule_1"
    when
        $order:Order(amout < 100)
    then
        Order order = new Order();
        order.setAmout(130);
        insert(order);      //insert方法的作用是向工作内存中插入Fact对象,会导致相关规则重新匹配
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

//规则二:100元 - 500元 加100分
rule "order_rule_2"
    when
        $order:Order(amout >= 100 && amout < 500)
    then
         $order.setScore(100);
         System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
6.3.retract方法

retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配。

//规则一:100元以下 不加分
rule "order_rule_1"
    when
        $order:Order(amout < 100)
    then
        retract($order)      //retract方法的作用是删除工作内存中的Fact对象,会导致相关规则重新匹配
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

7.规则属性 attributes

前面我们已经知道了规则体的构成如下:

rule "ruleName"
    attributes
    when
        LHS
    then
        RHS
end

本章节就是针对规则体的attributes属性部分进行讲解。Drools中提供的属性如下表(部分属性):

属性名说明
salience指定规则执行优先级
dialect指定规则使用的语言类型,取值为java和mvel
enabled指定规则是否启用
date-effective指定规则生效时间
date-expires指定规则失效时间
activation-group激活分组,具有相同分组名称的规则只能有一个规则触发
agenda-group议程分组,只有获取焦点的组中的规则才有可能触发
timer定时器,指定规则触发的时间
auto-focus自动获取焦点,一般结合agenda-group一起使用
no-loop防止死循环

重点说一下我们项目需要使用的属性

7.1.salience属性

salience属性用于指定规则的执行优先级,取值类型为Integer数值越大越优先执行。每个规则都有一个默认的执行顺序,如果不设置salience属性,规则体的执行顺序为由上到下。

可以通过创建规则文件salience.drl来测试salience属性,内容如下:

package com.order

rule "rule_1"
    when
        eval(true)
    then
        System.out.println("规则rule_1触发");
end
    
rule "rule_2"
    when
        eval(true)
    then
        System.out.println("规则rule_2触发");
end

rule "rule_3"
    when
        eval(true)
    then
        System.out.println("规则rule_3触发");
end

通过控制台可以看到,由于以上三个规则没有设置salience属性,所以执行的顺序是按照规则文件中规则的顺序由上到下执行的。接下来我们修改一下文件内容:

package com.order

rule "rule_1"
    salience 9
    when
        eval(true)
    then
        System.out.println("规则rule_1触发");
end

rule "rule_2"
    salience 10
    when
        eval(true)
    then
        System.out.println("规则rule_2触发");
end

rule "rule_3"
    salience 8
    when
        eval(true)
    then
        System.out.println("规则rule_3触发");
end

通过控制台可以看到,规则文件执行的顺序是按照我们设置的salience值由大到小顺序执行的。
建议在编写规则时使用salience属性明确指定执行优先级。

7.2.no-loop属性

no-loop属性用于防止死循环,当规则通过update之类的函数修改了Fact对象时,可能使当前规则再次被激活从而导致死循环。取值类型为Boolean,默认值为false,测试步骤如下:

编写规则文件/resources/rules/activationgroup.drl

//订单积分规则
package com.order
import com.atguigu.drools.model.Order

//规则一:100元以下 不加分
rule "order_rule_1"
    no-loop true         //防止陷入死循环
    when
        $order:Order(amout < 100)
    then
        $order.setScore(0);
        update($order)
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

通过控制台可以看到,由于我们没有设置no-loop属性的值,所以发生了死循环。接下来设置no-loop的值为true再次测试则不会发生死循环。

8.Drools高级语法

前面章节我们已经知道了一套完整的规则文件内容构成如下:

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

global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。

语法结构为:global 对象类型 对象名称

在使用global定义的全局变量时有两点需要注意:

1、如果对象类型为包装类型时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。可以理解为它是当前规则代码中的global副本,规则内部修改不会影响全局的使用。

2、如果对象类型为集合类型或JavaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效。

订单Order:

package com.atguigu.drools.model;

public class Order {

    private double amout;

    public double getAmout() {
        return amout;
    }

    public void setAmout(double amout) {
        this.amout = amout;
    }

}

积分Integral:

package com.atguigu.drools.model;

public class Integral {

    private double score;

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

规则文件:

//订单积分规则
package com.order
import com.atguigu.drools.model.Order

global com.atguigu.drools.model.Integral integral;

//规则一:100元以下 不加分
rule "order_rule_1"
    no-loop true         //防止陷入死循环
    when
        $order:Order(amout < 100)
    then
        integral.setScore(10);
        update($order)
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

测试:

@Test
public void test1(){
    //从Kie容器对象中获取会话对象
    KieSession session = kieContainer.newKieSession();

    //Fact对象,事实对象
    Order order = new Order();
    order.setAmout(30);

    //全局变量
    Integral integral = new Integral();
    session.setGlobal("integral", integral);

    //将Order对象插入到工作内存中
    session.insert(order);

    //激活规则,由Drools框架自动进行规则匹配,如果规则匹配成功,则执行当前规则
    session.fireAllRules();
    //关闭会话
    session.dispose();

    System.out.println("订单金额:" + order.getAmout());
    System.out.println("添加积分:" + integral.getScore());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值