SpringBoot + 决策表(Decision Table)+ Excel 导入:运营人员直接配置复杂规则逻辑

今天咱们聊聊一个在业务规则管理中非常实用的话题:如何让运营人员也能轻松配置复杂的业务规则。

传统规则配置的痛点

在我们的日常开发工作中,经常会遇到这样的场景:

  • 运营人员需要调整促销规则,但每次都要找开发写代码
  • 业务规则复杂,涉及多个条件的组合判断
  • 规则变更频繁,开发和运维压力巨大
  • 业务人员不懂编程,但需要灵活配置规则

传统的代码硬编码方式不仅效率低下,还容易出错。今天我们就来聊聊如何用决策表让运营人员也能配置复杂的业务规则。

决策表的价值

相比传统的规则配置方式,决策表有以下显著优势:

  • 可视化配置:表格形式直观易懂
  • 灵活表达:支持复杂的条件组合
  • 易于维护:非技术人员也能操作
  • 快速生效:配置完成后立即生效

决策表设计思路

1. 决策表结构

决策表通常包含以下几个部分:

  • 条件列:定义规则的触发条件
  • 动作列:定义满足条件后的执行动作
  • 规则行:具体的条件-动作组合
  • 优先级:规则执行的顺序

2. Excel格式设计

excel 复制代码
| 规则ID | 规则名称 | 用户等级 | 订单金额 | 地区 | 优惠类型 | 优惠金额 | 优先级 |
|--------|----------|----------|----------|------|----------|----------|--------|
| R001   | VIP折扣   | VIP      | >1000    | *    | DISCOUNT | 0.8      | 1      |
| R002   | 新用户礼  | NEW      | >100     | *    | GIFT     | 50       | 2      |
| R003   | 北方促销  | *        | >500     | NORTH| DISCOUNT | 0.9      | 3      |

核心实现方案

1. Excel解析器

java 复制代码
@Component
public class DecisionTableParser {
    
    public List<DecisionRule> parseExcel(MultipartFile excelFile) {
        List<DecisionRule> rules = new ArrayList<>();
        
        try (InputStream inputStream = excelFile.getInputStream()) {
            Workbook workbook = WorkbookFactory.create(inputStream);
            Sheet sheet = workbook.getSheetAt(0);
            
            // 跳过标题行
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row != null) {
                    DecisionRule rule = parseRow(row);
                    if (rule != null) {
                        rules.add(rule);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Excel解析失败", e);
        }
        
        return rules;
    }
    
    private DecisionRule parseRow(Row row) {
        DecisionRule rule = new DecisionRule();
        
        // 解析各个字段
        rule.setId(getStringCellValue(row.getCell(0)));
        rule.setName(getStringCellValue(row.getCell(1)));
        
        // 解析条件
        Condition userLevelCond = new Condition("userLevel", 
            getStringCellValue(row.getCell(2)), Operator.EQUALS);
        Condition orderAmountCond = new Condition("orderAmount", 
            getStringCellValue(row.getCell(3)), Operator.GREATER_THAN);
        Condition regionCond = new Condition("region", 
            getStringCellValue(row.getCell(4)), Operator.EQUALS);
        
        rule.setConditions(Arrays.asList(userLevelCond, orderAmountCond, regionCond));
        
        // 解析动作
        Action action = new Action();
        action.setType(getStringCellValue(row.getCell(5)));
        action.setValue(getDoubleCellValue(row.getCell(6)));
        rule.setAction(action);
        
        rule.setPriority(getIntCellValue(row.getCell(7)));
        
        return rule;
    }
    
    private String getStringCellValue(Cell cell) {
        if (cell == null) return null;
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                return String.valueOf((long) cell.getNumericCellValue());
            default:
                return null;
        }
    }
}

2. 决策引擎

java 复制代码
@Component
public class DecisionEngine {
    
    private volatile List<DecisionRule> activeRules = new ArrayList<>();
    
    public ActionResult evaluate(Context context) {
        // 按优先级排序规则
        List<DecisionRule> sortedRules = activeRules.stream()
            .sorted(Comparator.comparingInt(DecisionRule::getPriority))
            .collect(Collectors.toList());
        
        for (DecisionRule rule : sortedRules) {
            if (evaluateRule(rule, context)) {
                return executeAction(rule.getAction(), context);
            }
        }
        
        return ActionResult.noMatch();
    }
    
    private boolean evaluateRule(DecisionRule rule, Context context) {
        for (Condition condition : rule.getConditions()) {
            if (!evaluateCondition(condition, context)) {
                return false; // 有一个条件不满足就返回false
            }
        }
        return true;
    }
    
    private boolean evaluateCondition(Condition condition, Context context) {
        Object actualValue = context.get(condition.getField());
        Object expectedValue = condition.getValue();
        
        switch (condition.getOperator()) {
            case EQUALS:
                if ("*".equals(expectedValue.toString())) {
                    return true; // 通配符匹配
                }
                return Objects.equals(actualValue, expectedValue);
            case GREATER_THAN:
                return ((Number) actualValue).doubleValue() > Double.parseDouble(expectedValue.toString());
            case LESS_THAN:
                return ((Number) actualValue).doubleValue() < Double.parseDouble(expectedValue.toString());
            case CONTAINS:
                return actualValue.toString().contains(expectedValue.toString());
            default:
                return false;
        }
    }
    
    private ActionResult executeAction(Action action, Context context) {
        switch (action.getType()) {
            case "DISCOUNT":
                return ActionResult.discount(action.getValue());
            case "GIFT":
                return ActionResult.gift(action.getValue());
            case "COUPON":
                return ActionResult.coupon(action.getValue());
            default:
                return ActionResult.noAction();
        }
    }
    
    public void updateRules(List<DecisionRule> newRules) {
        this.activeRules = new ArrayList<>(newRules);
    }
}

3. 规则配置接口

java 复制代码
@RestController
@RequestMapping("/api/decision-table")
public class DecisionTableController {
    
    @Autowired
    private DecisionTableParser parser;
    
    @Autowired
    private DecisionEngine decisionEngine;
    
    @PostMapping("/upload")
    public ResponseEntity<String> uploadDecisionTable(@RequestParam("file") MultipartFile file) {
        try {
            // 解析Excel文件
            List<DecisionRule> rules = parser.parseExcel(file);
            
            // 验证规则
            ValidationResult validationResult = validateRules(rules);
            if (!validationResult.isValid()) {
                return ResponseEntity.badRequest()
                    .body("规则验证失败: " + validationResult.getErrorMessage());
            }
            
            // 更新决策引擎
            decisionEngine.updateRules(rules);
            
            return ResponseEntity.ok("决策表更新成功,共加载 " + rules.size() + " 条规则");
        } catch (Exception e) {
            log.error("决策表更新失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("决策表更新失败: " + e.getMessage());
        }
    }
    
    @GetMapping("/preview")
    public ResponseEntity<List<DecisionRule>> previewRules() {
        return ResponseEntity.ok(decisionEngine.getActiveRules());
    }
    
    @PostMapping("/evaluate")
    public ResponseEntity<ActionResult> evaluateRule(@RequestBody EvaluationRequest request) {
        Context context = new Context();
        context.putAll(request.getContext());
        
        ActionResult result = decisionEngine.evaluate(context);
        return ResponseEntity.ok(result);
    }
}

高级特性实现

1. 规则验证机制

java 复制代码
@Component
public class RuleValidator {
    
    public ValidationResult validateRules(List<DecisionRule> rules) {
        ValidationResult result = new ValidationResult();
        
        // 检查规则ID唯一性
        Set<String> ruleIds = new HashSet<>();
        for (DecisionRule rule : rules) {
            if (ruleIds.contains(rule.getId())) {
                result.setValid(false);
                result.setErrorMessage("规则ID重复: " + rule.getId());
                return result;
            }
            ruleIds.add(rule.getId());
        }
        
        // 检查条件表达式语法
        for (DecisionRule rule : rules) {
            for (Condition condition : rule.getConditions()) {
                if (!isValidCondition(condition)) {
                    result.setValid(false);
                    result.setErrorMessage("条件表达式错误: " + condition);
                    return result;
                }
            }
        }
        
        // 检查优先级合理性
        if (!isValidPriority(rules)) {
            result.setValid(false);
            result.setErrorMessage("优先级设置不合理,请检查");
            return result;
        }
        
        result.setValid(true);
        return result;
    }
    
    private boolean isValidCondition(Condition condition) {
        // 验证条件的有效性
        if (condition.getField() == null || condition.getValue() == null) {
            return false;
        }
        
        // 验证操作符与值的匹配性
        switch (condition.getOperator()) {
            case GREATER_THAN:
            case LESS_THAN:
                try {
                    Double.parseDouble(condition.getValue().toString());
                    return true;
                } catch (NumberFormatException e) {
                    return false;
                }
            default:
                return true;
        }
    }
}

2. 规则版本管理

java 复制代码
@Entity
@Table(name = "decision_table_version")
@Data
public class DecisionTableVersion {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String version;
    private String description;
    private String createdBy;
    private LocalDateTime createTime;
    private boolean active;
    
    @Lob
    private String tableContent; // 存储Excel内容
}

@Service
public class DecisionTableVersionService {
    
    public void saveVersion(List<DecisionRule> rules, String description, String user) {
        DecisionTableVersion version = new DecisionTableVersion();
        version.setVersion(generateVersion());
        version.setDescription(description);
        version.setCreatedBy(user);
        version.setCreateTime(LocalDateTime.now());
        version.setTableContent(convertToExcelContent(rules));
        
        versionRepository.save(version);
    }
    
    public void activateVersion(Long versionId) {
        // 停用当前版本
        versionRepository.deactivateCurrent();
        
        // 激活指定版本
        DecisionTableVersion version = versionRepository.findById(versionId).orElse(null);
        if (version != null) {
            version.setActive(true);
            versionRepository.save(version);
            
            // 加载规则到引擎
            List<DecisionRule> rules = parseExcelContent(version.getTableContent());
            decisionEngine.updateRules(rules);
        }
    }
}

3. 规则执行监控

java 复制代码
@Component
public class DecisionTableMonitor {
    
    private final MeterRegistry meterRegistry;
    
    public void recordRuleEvaluation(String ruleId, boolean matched, long executionTime) {
        Timer.Sample sample = Timer.start(meterRegistry);
        
        Tags tags = Tags.of("rule_id", ruleId, "matched", String.valueOf(matched));
        
        sample.stop(Timer.builder("decision.table.evaluation.time")
                 .tags(tags)
                 .register(meterRegistry));
    }
    
    public void recordRuleHit(String ruleId) {
        Counter.builder("decision.table.rule.hit")
               .tag("rule_id", ruleId)
               .register(meterRegistry)
               .increment();
    }
}

Excel模板优化

1. 模板验证

java 复制代码
@Component
public class ExcelTemplateValidator {
    
    public ValidationResult validateTemplate(MultipartFile file) {
        try (InputStream inputStream = file.getInputStream()) {
            Workbook workbook = WorkbookFactory.create(inputStream);
            Sheet sheet = workbook.getSheetAt(0);
            
            // 检查表头
            Row headerRow = sheet.getRow(0);
            if (headerRow == null) {
                return ValidationResult.error("缺少表头");
            }
            
            // 验证必需的列
            List<String> requiredHeaders = Arrays.asList(
                "规则ID", "规则名称", "用户等级", "订单金额", "地区", "优惠类型", "优惠金额", "优先级"
            );
            
            for (String requiredHeader : requiredHeaders) {
                boolean found = false;
                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
                    Cell cell = headerRow.getCell(i);
                    if (cell != null && requiredHeader.equals(cell.getStringCellValue())) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    return ValidationResult.error("缺少必需列: " + requiredHeader);
                }
            }
            
            return ValidationResult.success();
        } catch (Exception e) {
            return ValidationResult.error("模板验证失败: " + e.getMessage());
        }
    }
}

2. 智能提示

java 复制代码
@RestController
@RequestMapping("/api/decision-table/template")
public class TemplateController {
    
    @GetMapping("/download")
    public ResponseEntity<Resource> downloadTemplate() {
        try {
            // 生成带有下拉选项的Excel模板
            Workbook workbook = new XSSFWorkbook();
            Sheet sheet = workbook.createSheet("决策表模板");
            
            // 创建样式
            CellStyle headerStyle = createHeaderStyle(workbook);
            CellStyle dropdownStyle = createDropdownStyle(workbook);
            
            // 创建表头
            Row headerRow = sheet.createRow(0);
            String[] headers = {"规则ID", "规则名称", "用户等级", "订单金额", "地区", "优惠类型", "优惠金额", "优先级"};
            for (int i = 0; i < headers.length; i++) {
                Cell cell = headerRow.createCell(i);
                cell.setCellValue(headers[i]);
                cell.setCellStyle(headerStyle);
            }
            
            // 设置下拉列表
            setDataValidation(sheet, 2, 2, 1000, new String[]{"VIP", "普通", "NEW", "*"});
            setDataValidation(sheet, 4, 4, 1000, new String[]{"NORTH", "SOUTH", "EAST", "WEST", "*"});
            setDataValidation(sheet, 5, 5, 1000, new String[]{"DISCOUNT", "GIFT", "COUPON"});
            
            // 将Workbook转换为字节数组
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            workbook.write(outputStream);
            workbook.close();
            
            byte[] bytes = outputStream.toByteArray();
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            Resource resource = new InputStreamResource(byteArrayInputStream);
            
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=decision_table_template.xlsx")
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .body(resource);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

最佳实践建议

  1. 模板标准化:制定标准的Excel模板格式
  2. 权限控制:限制规则配置的操作权限
  3. 测试验证:配置前进行规则测试
  4. 版本管理:保存历史版本便于回滚
  5. 监控告警:监控规则执行情况

通过决策表+Excel的方式,我们可以让运营人员也能轻松配置复杂的业务规则,大大提升业务响应速度。


以上就是本期分享的内容,希望对你有所帮助。更多技术干货,请关注服务端技术精选,我们下期再见!

相关推荐
Cache技术分享2 小时前
324. Java Stream API - 实现 Collector 接口:自定义你的流式收集器
前端·后端
三水不滴2 小时前
SpringBoot + Redis 滑动窗口计数:打造高可靠接口防刷体系
spring boot·redis·后端
若水不如远方2 小时前
分布式一致性原理(四):工程化共识 —— Raft 算法
分布式·后端·算法
老迟聊架构2 小时前
深入理解低延迟与高吞吐:从架构哲学到技术抉择
后端·架构
hrhcode2 小时前
【Netty】一.Netty架构设计与Reactor线程模型深度解析
java·spring boot·后端·spring·netty
三水不滴2 小时前
千万级数据批处理实战:SpringBoot + 分片 + 分布式并行处理方案
spring boot·分布式·后端
会算数的⑨2 小时前
Spring AI Alibaba 学习(三):Graph Workflow 深度解析(下篇)
java·人工智能·分布式·后端·学习·spring·saa
用户7344028193422 小时前
java 乐观锁的达成和注意细节
后端
哈库纳3 小时前
dbVisitor 利用 queryForPairs 让键值查询一步到位
java·后端·架构