SpringBoot + 动态 SQL + 条件编排器:报表查询条件自由组合,业务人员也能配查询!

业务需求的痛点

相信很多同学都遇到过这样的场景:

  • 产品经理:"这个报表要支持各种条件组合查询,用户可以自定义筛选条件"
  • 业务人员:"我想要查近30天,销售额大于1000,且在华北地区,客户等级是VIP的订单"
  • 另一个业务人员:"我想要查近7天,商品类别是手机,价格在500-2000之间的商品"

这种需求听起来简单,但实现起来却很复杂。传统的做法往往是写一堆 if-else,或者为每种组合写一个查询接口,维护成本极高。

解决方案思路

我们今天要解决的,就是让业务人员能够自由组合查询条件,而不需要后端开发每次都写新的查询逻辑。

核心思路是:

  1. 动态SQL:根据用户选择的条件动态生成SQL语句
  2. 条件编排器:将查询条件抽象成可配置的组件
  3. 规则引擎:处理复杂的条件组合逻辑

技术选型

  • SpringBoot:快速搭建应用
  • MyBatis-Plus:动态SQL构建
  • Easy Rules:条件编排规则引擎
  • MySQL:数据存储

核心实现思路

1. 查询条件模型设计

首先,我们需要定义一个查询条件的数据模型:

public class QueryCondition {
    private String field;      // 字段名,如 "order_amount"
    private String operator;   // 操作符,如 ">", "<", "="
    private Object value;      // 值
    private String logic;      // 逻辑连接符,如 "AND", "OR"
}

2. 动态SQL构建器

使用MyBatis-Plus的QueryWrapper来构建动态SQL:

public class DynamicQueryBuilder {
    
    public QueryWrapper<T> buildQueryWrapper(List<QueryCondition> conditions) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        
        for (int i = 0; i < conditions.size(); i++) {
            QueryCondition condition = conditions.get(i);
            
            // 根据操作符构建查询条件
            switch (condition.getOperator()) {
                case ">":
                    wrapper.gt(condition.getField(), condition.getValue());
                    break;
                case "<":
                    wrapper.lt(condition.getField(), condition.getValue());
                    break;
                case "=":
                    wrapper.eq(condition.getField(), condition.getValue());
                    break;
                // ... 其他操作符
            }
            
            // 添加逻辑连接符
            if (i < conditions.size() - 1) {
                String nextLogic = conditions.get(i + 1).getLogic();
                if ("OR".equalsIgnoreCase(nextLogic)) {
                    wrapper.or();
                }
            }
        }
        
        return wrapper;
    }
}

3. 条件编排规则引擎

使用Easy Rules来处理复杂的条件组合逻辑:

@Rule(name = "dateRangeRule", description = "日期范围条件规则")
public class DateRangeRule {
    
    @Condition
    public boolean evaluate(@Fact("conditions") List<QueryCondition> conditions) {
        // 检查是否包含日期范围条件
        return conditions.stream()
                .anyMatch(c -> "create_date".equals(c.getField()));
    }
    
    @Action
    public void execute(@Fact("wrapper") QueryWrapper wrapper, 
                       @Fact("conditions") List<QueryCondition> conditions) {
        // 处理日期范围条件
        conditions.stream()
                .filter(c -> "create_date".equals(c.getField()))
                .forEach(c -> {
                    if (">=".equals(c.getOperator())) {
                        wrapper.ge("create_date", c.getValue());
                    } else if ("<=".equals(c.getOperator())) {
                        wrapper.le("create_date", c.getValue());
                    }
                });
    }
}

4. 查询服务实现

将上述组件整合起来:

@Service
public class ReportQueryService {
    
    private final DynamicQueryBuilder queryBuilder;
    private final RulesEngine rulesEngine;
    
    public List<ReportData> queryReport(ReportQueryRequest request) {
        // 1. 获取规则
        Rules rules = getRulesByReportType(request.getReportType());
        
        // 2. 执行规则引擎处理条件
        Facts facts = new Facts();
        facts.put("conditions", request.getConditions());
        QueryWrapper<ReportData> wrapper = new QueryWrapper<>();
        facts.put("wrapper", wrapper);
        
        rulesEngine.execute(rules, facts);
        
        // 3. 执行查询
        return reportDataMapper.selectList(wrapper);
    }
    
    private Rules getRulesByReportType(String reportType) {
        // 根据报表类型获取对应的规则
        Rules rules = new Rules();
        // 添加通用规则
        rules.register(new DateRangeRule());
        rules.register(new AmountRangeRule());
        rules.register(new TextMatchRule());
        // 根据报表类型添加特定规则
        if ("order_report".equals(reportType)) {
            rules.register(new OrderSpecificRule());
        }
        return rules;
    }
}

配置化实现

为了让业务人员能够配置查询条件,我们需要提供一个配置界面:

@RestController
@RequestMapping("/api/config")
public class ConditionConfigController {
    
    @PostMapping("/condition")
    public Result<Void> saveConditionConfig(@RequestBody ConditionConfig config) {
        // 保存条件配置到数据库
        conditionConfigService.save(config);
        return Result.success();
    }
    
    @GetMapping("/condition/{reportType}")
    public Result<List<ConditionConfig>> getConditionConfig(@PathVariable String reportType) {
        // 获取指定报表类型的条件配置
        List<ConditionConfig> configs = conditionConfigService.getByReportType(reportType);
        return Result.success(configs);
    }
}

前端交互设计

前端页面可以设计成拖拽式配置:

  1. 业务人员从条件库中拖拽需要的条件到配置区域
  2. 设置条件的参数(字段、操作符、值等)
  3. 设置条件之间的逻辑关系
  4. 保存配置

这样,业务人员就可以通过简单的配置完成复杂的查询条件组合。

优势分析

这种方案的优势显而易见:

  1. 灵活性:业务人员可以自由组合查询条件,无需开发介入
  2. 可维护性:规则化管理,便于维护和扩展
  3. 性能:通过动态SQL,只查询必要的数据
  4. 复用性:同一套规则可以应用于多个报表

注意事项

  1. 安全考虑:要对用户输入进行严格校验,防止SQL注入
  2. 性能优化:对于复杂查询,要考虑添加适当的索引
  3. 规则管理:建立规则版本管理机制,便于回滚
  4. 权限控制:不同角色的用户可配置的条件范围不同

总结

通过SpringBoot + 动态SQL + 条件编排器的组合,我们成功实现了一个灵活的报表查询系统。业务人员可以通过简单的配置完成复杂的查询条件组合,大大提高了开发效率和业务响应速度。

这种方案的核心思想是将业务逻辑规则化、配置化,既保证了系统的灵活性,又降低了维护成本。在实际项目中,可以根据具体需求进行调整和优化。

希望这篇文章对大家有所帮助!如果你有其他想法或建议,欢迎在评论区交流讨论。


服务端技术精选,专注分享后端开发实战技术,助力你的技术成长!


标题:SpringBoot + 动态 SQL + 条件编排器:报表查询条件自由组合,业务人员也能配查询!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/01/06/1767662779523.html

    0 评论
avatar