【代码示例】springboot使用drools实现动态规划
文章目錄
- 實體類
- service
- Drools配置
- 規則文件
- controller
- 改進:實現動態規劃
要求:不同的貨物重量有不同的金額,需要根據重量計算金額,且修改規則后動態寫入到數據庫中
實體類
AddressRule
/*** 封裝計算訂單價格所需的參數*/ public class AddressRule {/*** 貨品總重量*/private double totalWeight;/*** 距離*/private double distance;/*** 續重價格*/private double continuedFee;/*** 首重*/private double firstWeight;/*** 首重價格*/private double firstFee;public double getFirstFee() {return firstFee;}public void setFirstFee(double firstFee) {this.firstFee = firstFee;}public double getFirstWeight() {return firstWeight;}public void setFirstWeight(double firstWeight) {this.firstWeight = firstWeight;}public double getTotalWeight() {return totalWeight;}public void setTotalWeight(double totalWeight) {this.totalWeight = totalWeight;}public double getDistance() {return distance;}public void setDistance(double distance) {this.distance = distance;}public double getContinuedFee() {return continuedFee;}public void setContinuedFee(double continuedFee) {this.continuedFee = continuedFee;} }AddressCheckResult
/*** 封裝訂單價格計算后的結果*/ public class AddressCheckResult {private boolean postCodeResult = false; // true:通過校驗;false:未通過校驗private String result;public String getResult() {return result;}public void setResult(String result) {this.result = result;}public boolean isPostCodeResult() {return postCodeResult;}public void setPostCodeResult(boolean postCodeResult) {this.postCodeResult = postCodeResult;} }service
public interface DroolsRulesService {/*** 根據條件計算訂單價格* @param addressRule* @return*/String calcFee(AddressRule addressRule); } public class DroolsRulesServiceImpl implements DroolsRulesService {/*** 根據條件計算訂單價格* @param addressRule* @return*/@Overridepublic String calcFee(AddressRule addressRule) {//貨物總重量BigDecimal totalWeight = new BigDecimal(addressRule.getTotalWeight());//首重BigDecimal firstWeight = new BigDecimal(addressRule.getFirstWeight());//首重價格BigDecimal firstFee = new BigDecimal(addressRule.getFirstFee());//續重價格BigDecimal continuedFee = new BigDecimal((addressRule.getContinuedFee()));//超過首重部分重量=總重量-首重BigDecimal lost = totalWeight.subtract(firstWeight);//3.5//只保留整數部分lost = lost.setScale(0,BigDecimal.ROUND_DOWN);return continuedFee.multiply(lost).add(firstFee).toString();} //這個main函數是用來測試的public static void main(String[] args) {AddressRule addressRule = new AddressRule();addressRule.setTotalWeight(10.3);addressRule.setFirstWeight(1);addressRule.setFirstFee(20);addressRule.setContinuedFee(6);String s = new DroolsRulesServiceImpl().calcFee(addressRule);System.out.println(s);} }Drools配置
@Configuration public class DroolsAutoConfiguration {private static final String RULES_PATH = "rules/";@Bean@ConditionalOnMissingBean(KieFileSystem.class)public KieFileSystem kieFileSystem() throws IOException {KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();for (Resource file : getRuleFiles()) {kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));}return kieFileSystem;}private Resource[] getRuleFiles() throws IOException {ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");}@Bean@ConditionalOnMissingBean(KieContainer.class)public KieContainer kieContainer() throws IOException {final KieRepository kieRepository = getKieServices().getRepository();kieRepository.addKieModule(new KieModule() {@Overridepublic ReleaseId getReleaseId() {return kieRepository.getDefaultReleaseId();}});KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());kieBuilder.buildAll();KieContainer kieContainer=getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());return kieContainer;}private KieServices getKieServices() {return KieServices.Factory.get();}@Bean@ConditionalOnMissingBean(KieBase.class)public KieBase kieBase() throws IOException {return kieContainer().getKieBase();}@Bean@ConditionalOnMissingBean(KieSession.class)public KieSession kieSession() throws IOException {return kieContainer().newKieSession();}@Bean@ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)public KModuleBeanFactoryPostProcessor kiePostProcessor() {return new KModuleBeanFactoryPostProcessor();} }規則文件
在rule包下寫規則文件:
package rules;import com.itheima.pinda.entity.fact.AddressRule import com.itheima.pinda.entity.fact.AddressCheckResult import com.itheima.pinda.service.DroolsRulesService import com.itheima.pinda.service.impl.DroolsRulesServiceImpldialect "java"rule "1千克以內20元"activation-group "mygroup"salience 10whenaddressRule : AddressRule(totalWeight != null && totalWeight <= 1.00)checkResult : AddressCheckResult()thencheckResult.setPostCodeResult(true);checkResult.setResult("20");System.out.println("1千克以內20元"); endrule "1千克以上,訂單距離在200公里以下的,首重1千克,首重價格20元,續重每1千克資費為6元"activation-group "mygroup"salience 9whenaddressRule : AddressRule(totalWeight != null && totalWeight > 1.00 && distance <= 200.00)checkResult : AddressCheckResult()thenaddressRule.setFirstFee(20.00);addressRule.setFirstWeight(1.00);addressRule.setContinuedFee(6.00);DroolsRulesService droolsRulesService = new DroolsRulesServiceImpl();String orderAmount = droolsRulesService.calcFee(addressRule);checkResult.setPostCodeResult(true);checkResult.setResult(orderAmount);System.out.println("1千克以上,訂單距離在200公里以下的,首重1千克,首重價格20元,續重每1千克資費為6元"); endrule "1千克以上,訂單距離在200~500公里的,首重1千克,首重價格20元,續重每1千克資費為9元"activation-group "mygroup"salience 8whenaddressRule : AddressRule(totalWeight != null && totalWeight > 1.00 && distance <= 500.00)checkResult : AddressCheckResult()thenaddressRule.setFirstFee(20.00);addressRule.setFirstWeight(1.00);addressRule.setContinuedFee(9.00);DroolsRulesService droolsRulesService = new DroolsRulesServiceImpl();String orderAmount = droolsRulesService.calcFee(addressRule);checkResult.setPostCodeResult(true);checkResult.setResult(orderAmount);System.out.println("1千克以上,訂單距離在200~500公里的,首重1千克,首重價格20元,續重每1千克資費為9元"); endrule "1千克以上,訂單距離在500公里以上的,首重1千克,首重價格20元,續重每1千克資費為15元"activation-group "mygroup"salience 7whenaddressRule : AddressRule(totalWeight != null && totalWeight > 1.00 && distance > 500.00)checkResult : AddressCheckResult()thenaddressRule.setFirstFee(20.00);addressRule.setFirstWeight(1.00);addressRule.setContinuedFee(15.00);DroolsRulesService droolsRulesService = new DroolsRulesServiceImpl();String orderAmount = droolsRulesService.calcFee(addressRule);checkResult.setPostCodeResult(true);checkResult.setResult(orderAmount);System.out.println("1千克以上,訂單距離在500公里以上的,首重1千克,首重價格20元,續重每1千克資費為15元"); endcontroller
/*** 新增訂單** @param orderDTO 訂單信息* @return 訂單信息*/@PostMapping("")public OrderDTO save(@RequestBody OrderDTO orderDTO, HttpServletResponse res) {log.info("保存訂單信息:{}", JSON.toJSONString(orderDTO));Order order = new Order();order.setEstimatedArrivalTime(LocalDateTime.now().plus(2, ChronoUnit.DAYS));Map map = orderService.calculateAmount(orderDTO);log.info("實時計算運費:{}", map);orderDTO = (OrderDTO) map.get("orderDto");BeanUtils.copyProperties(orderDTO, order);if ("send error msg".equals(orderDTO.getSenderAddress()) || "receive error msg".equals(orderDTO.getReceiverAddress())) {return orderDTO;}order.setAmount(new BigDecimal(map.getOrDefault("amount", "23").toString()));orderService.saveOrder(order);log.info("訂單信息入庫:{}", order);OrderDTO result = new OrderDTO();BeanUtils.copyProperties(order, result);return result;}orderService.calculateAmount方法:
/*** 計算訂單價格* @param orderDTO* @return*/@Overridepublic Map calculateAmount(OrderDTO orderDTO) {//計算訂單距離orderDTO = this.getDistance(orderDTO);if("sender error msg".equals(orderDTO.getSenderAddress()) || "receiver error msg".equals(orderDTO.getReceiverAddress())){//地址解析失敗,直接返回Map map = new HashMap();map.put("amount","0");map.put("errorMsg","無法計算訂單距離和訂單價格,請輸入真實地址");map.put("orderDto",orderDTO);return map;}KieSession session = KieContainer.newKieSession();//ReloadDroolsRulesService.kieContainer.newKieSession();//設置Fact對象AddressRule addressRule = new AddressRule();addressRule.setTotalWeight(orderDTO.getOrderCargoDto().getTotalWeight().doubleValue());addressRule.setDistance(orderDTO.getDistance().doubleValue());//將對象加入到工作內存session.insert(addressRule);AddressCheckResult addressCheckResult = new AddressCheckResult();session.insert(addressCheckResult);int i = session.fireAllRules();System.out.println("觸發了" + i + "條規則");session.destroy();if(addressCheckResult.isPostCodeResult()){System.out.println("規則匹配成功,訂單價格為:" + addressCheckResult.getResult());orderDTO.setAmount(new BigDecimal(addressCheckResult.getResult()));Map map = new HashMap();map.put("orderDto",orderDTO);map.put("amount",addressCheckResult.getResult());return map;}return null;}/*** 調用百度地圖服務接口,根據寄件人地址和收件人地址計算訂單距離* @param orderDTO* @return*/public OrderDTO getDistance(OrderDTO orderDTO){//調用百度地圖服務接口獲取寄件人地址對應的坐標經緯度String begin = BaiduMapUtils.getCoordinate(orderDTO.getSenderAddress());if(begin == null){orderDTO.setSenderAddress("sender error msg");return orderDTO;}//調用百度地圖服務接口獲取收件人地址對應的坐標經緯度String end = BaiduMapUtils.getCoordinate(orderDTO.getReceiverAddress());if(end == null){orderDTO.setReceiverAddress("receiver error msg");return orderDTO;}Double distance = BaiduMapUtils.getDistance(begin, end);DecimalFormat decimalFormat = new DecimalFormat("#.##");String distanceStr = decimalFormat.format(distance/1000);orderDTO.setDistance(new BigDecimal(distanceStr));return orderDTO;}改進:實現動態規劃
思路:
1、將規則文件的內容存儲在數據庫中
2、Drools相關對象(例如KieContainer對象)的創建都基于數據庫中存儲的規則來創建
3、提供HTTP訪問接口,當規則發生變化時調用此接口重新加載數據庫中的規則,重新創建KieContainer等對象
第一步:在pd_oms數據庫中創建數據表rule,用于存儲規則內容
第二步:創建Rule實體類,和上面的rule表對應
package com.itheima.pinda.entity; ? import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.experimental.Accessors; import java.io.Serializable; ? @Data @ToString @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("rule") @ApiModel public class Rule implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.INPUT)private Long id;private String ruleKey;private String content;private String version;private String lastModifyTime;private String createTime; }第三步:創建RuleMapper接口
package com.itheima.pinda.mapper; ? import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.itheima.pinda.entity.Order; import com.itheima.pinda.entity.Rule; import org.apache.ibatis.annotations.Mapper; ? /*** 規則*/ @Mapper public interface RuleMapper extends BaseMapper<Rule> { }第四步:創建ReloadDroolsRulesService,用于重新加載數據庫中的規則
package com.itheima.pinda.service.impl; ? import com.itheima.pinda.entity.Address; import com.itheima.pinda.entity.Rule; import com.itheima.pinda.mapper.RuleMapper; import org.kie.api.KieServices; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.KieRepository; import org.kie.api.builder.Message; import org.kie.api.runtime.KieContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; ? /***重新加載規則*/ @Service public class ReloadDroolsRulesService {public static KieContainer kieContainer; ?@Autowiredprivate RuleMapper ruleMapper; ?public void reload() {KieContainer kieContainer = loadContainerFromString(loadRules());this.kieContainer = kieContainer;} ?private List<Rule> loadRules() {List<Rule> rules = ruleMapper.selectList(null);return rules;} ?public KieContainer loadContainerFromString(List<Rule> rules) {long startTime = System.currentTimeMillis();KieServices ks = KieServices.Factory.get();KieRepository kr = ks.getRepository();KieFileSystem kfs = ks.newKieFileSystem(); ?for (Rule rule : rules) {String drl = rule.getContent();kfs.write("src/main/resources/" + drl.hashCode() + ".drl", drl);} ?KieBuilder kb = ks.newKieBuilder(kfs); ?kb.buildAll();if (kb.getResults().hasMessages(Message.Level.ERROR)) {throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());}long endTime = System.currentTimeMillis();System.out.println("Time to build rules : " + (endTime - startTime) + " ms");startTime = System.currentTimeMillis();KieContainer kContainer = ks.newKieContainer(kr.getDefaultReleaseId());endTime = System.currentTimeMillis();System.out.println("Time to load container: " + (endTime - startTime) + " ms");return kContainer;} }第五步:創建CommandLineRunnerImpl,在項目啟動時加載數據庫中最新規則
package com.itheima.pinda.service.impl; ? import com.itheima.pinda.service.ReloadDroolsRulesService; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import javax.annotation.Resource; ? /*** 項目啟動時加載最新規則**/ @Component @Slf4j public class CommandLineRunnerImpl implements CommandLineRunner {@Resourceprivate ReloadDroolsRulesService rules; ?@Overridepublic void run(String... args) throws Exception {rules.reload();} }第六步:創建RulesReloadController
package com.itheima.pinda.controller; ? import com.itheima.pinda.service.ReloadDroolsRulesService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import java.io.IOException; ? @RequestMapping("/rules") @Controller public class RulesReloadController {@Resourceprivate ReloadDroolsRulesService rules; ?/*** 從數據庫加載最新規則** @return* @throws IOException*/@ResponseBody@RequestMapping("/reload")public String reload() throws IOException {rules.reload();return "ok";} }第七步:注釋掉DroolsAutoConfiguration上面的configuration注解
第八步:修改OrderServiceImpl的calculateAmount方法,修改KieSession的獲取方式
KieSession session = KieContainer.newKieSession();
改為
KieSession session = ReloadDroolsRulesService.kieContainer.newKieSession();
總結
以上是生活随笔為你收集整理的【代码示例】springboot使用drools实现动态规划的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 离散数学练习题
- 下一篇: snowflake分布式自增长id的ja