目录
[一、问题背景:ES 查询正在成为系统演进的"瓶颈"](#一、问题背景:ES 查询正在成为系统演进的“瓶颈”)
[(一)查询逻辑深度绑定 Java 代码](#(一)查询逻辑深度绑定 Java 代码)
[二、目标:把 ES 查询"产品化、策略化、平台化"](#二、目标:把 ES 查询“产品化、策略化、平台化”)
[四、策略的本质:DSL 模板 + 参数模型](#四、策略的本质:DSL 模板 + 参数模型)
[(二)策略 DSL 模板设计](#(二)策略 DSL 模板设计)
[(三)DSL 设计原则](#(三)DSL 设计原则)
[1. 参数类型定义](#1. 参数类型定义)
[2. 参数定义模型](#2. 参数定义模型)
[(三)DSL 渲染引擎设计](#(三)DSL 渲染引擎设计)
[1. 支持能力](#1. 支持能力)
[2. 渲染过程](#2. 渲染过程)
[(四)DSL 条件片段 / 可选过滤(Conditional DSL)](#(四)DSL 条件片段 / 可选过滤(Conditional DSL))
[1. 设计目标(非常重要)](#1. 设计目标(非常重要))
[2. DSL 条件片段的设计方案(推荐)](#2. DSL 条件片段的设计方案(推荐))
[2.1 方案一(推荐):结构化条件块(Block DSL)](#2.1 方案一(推荐):结构化条件块(Block DSL))
[2.2 方案二:占位符级条件(不推荐长期)](#2.2 方案二:占位符级条件(不推荐长期))
[2.3 方案三:引入表达式引擎(高级)](#2.3 方案三:引入表达式引擎(高级))
[3. 条件 DSL 的执行模型](#3. 条件 DSL 的执行模型)
[4. 条件表达式设计(刻意简化)](#4. 条件表达式设计(刻意简化))
[4.1 支持的表达式范围](#4.1 支持的表达式范围)
[4.2 表达式执行方式](#4.2 表达式执行方式)
[5.Java 实现设计](#5.Java 实现设计)
[5.1 条件处理入口](#5.1 条件处理入口)
[5.2 识别 if 节点](#5.2 识别 if 节点)
[5.3 if 节点执行逻辑](#5.3 if 节点执行逻辑)
[5.4 条件表达式执行器(简化版)](#5.4 条件表达式执行器(简化版))
[6. 与原执行流程的整合](#6. 与原执行流程的整合)
[六、ES 执行层:彻底屏蔽 Java ES 复杂度](#六、ES 执行层:彻底屏蔽 Java ES 复杂度)
[(二)领域模型(Domain Model)](#(二)领域模型(Domain Model))
[1. Strategy(策略主实体)](#1. Strategy(策略主实体))
[2. StrategyVersion(可执行版本)](#2. StrategyVersion(可执行版本))
[3. StrategyParam(参数定义)](#3. StrategyParam(参数定义))
[(三)Repository 层(数据访问)](#(三)Repository 层(数据访问))
[(四)参数处理引擎(Parameter Engine)](#(四)参数处理引擎(Parameter Engine))
[1. 参数处理入口](#1. 参数处理入口)
[2. 默认实现](#2. 默认实现)
[(五)DSL 渲染引擎(核心)](#(五)DSL 渲染引擎(核心))
[1. DSL 渲染接口](#1. DSL 渲染接口)
[2. 基于占位符的实现](#2. 基于占位符的实现)
[(六)ES 执行适配层](#(六)ES 执行适配层)
[1. 统一执行接口](#1. 统一执行接口)
[2. 实现思路(伪实现)](#2. 实现思路(伪实现))
[1. StrategyExecuteService](#1. StrategyExecuteService)
[2. 默认实现(核心流程)](#2. 默认实现(核心流程))
[(三)策略调试执行(Draft / Preview Execution)](#(三)策略调试执行(Draft / Preview Execution))
[1. 设计目标](#1. 设计目标)
[2. 整体架构设计](#2. 整体架构设计)
[3. 表结构扩展(支持 Draft)](#3. 表结构扩展(支持 Draft))
[3.1 增加 version_status](#3.1 增加 version_status)
[3.2 Draft 的定义](#3.2 Draft 的定义)
[4. 调试执行 API 设计](#4. 调试执行 API 设计)
[4.1 请求模型](#4.1 请求模型)
[4.2 返回模型(关键)](#4.2 返回模型(关键))
[5. 调试执行核心流程](#5. 调试执行核心流程)
[5.1 调试执行服务接口](#5.1 调试执行服务接口)
[5.2 DebugOptions](#5.2 DebugOptions)
[5.3 核心实现](#5.3 核心实现)
[6. 平台交互体验(非常重要)](#6. 平台交互体验(非常重要))
[7. 安全与边界控制](#7. 安全与边界控制)
[(四)平台侧如何支持条件 DSL?](#(四)平台侧如何支持条件 DSL?)
[八、策略化 ES 查询平台参考"可直接落库"的完整表结构设计](#八、策略化 ES 查询平台参考“可直接落库”的完整表结构设计)
[1. 表职责](#1. 表职责)
[2. 表结构设计](#2. 表结构设计)
[2.1 为什么 strategy_id 要单独存在?](#2.1 为什么 strategy_id 要单独存在?)
[2.2 biz_domain 的价值](#2.2 biz_domain 的价值)
[1. 表职责](#1. 表职责)
[2. 表结构设计](#2. 表结构设计)
[2.1 dsl_template(核心)](#2.1 dsl_template(核心))
[2.1 result_mapping(高级)](#2.1 result_mapping(高级))
[1. 表职责](#1. 表职责)
[2. 表结构设计](#2. 表结构设计)
[1. 表职责](#1. 表职责)
[2. 表结构](#2. 表结构)
[1. 策略缓存](#1. 策略缓存)
[2. 查询结果缓存(可选)](#2. 查询结果缓存(可选))
干货分享,感谢您的阅读!
在很多系统中,Elasticsearch 早已不只是"搜索引擎",而是承载推荐、风控、运营分析等核心业务的关键查询基础设施 。
但随着业务复杂度上升,ES 查询逐渐演变为代码耦合重、变更成本高、风险难以控制的系统瓶颈。
我们将从真实工程问题出发,分享一套将 ES 查询策略化、平台化、可治理 的完整设计思路,探讨如何把查询能力从代码中解放出来,构建一套可长期演进的查询策略平台。
一、问题背景:ES 查询正在成为系统演进的"瓶颈"
在大多数中大型业务系统中,Elasticsearch 已经不再只是一个"搜索引擎",而是:
-
推荐系统的召回层
-
风控系统的规则筛选层
-
广告系统的人群圈选引擎
-
内容平台的多维过滤与排序引擎
但与此同时,ES 查询的使用方式却高度工程化、碎片化现状问题:
(一)查询逻辑深度绑定 Java 代码
-
每一个查询场景 = 一个 Java 方法
-
DSL 写在代码里
-
参数拼接、条件判断、排序逻辑全靠 if/else
(二)需求变更成本极高
-
改一个过滤条件 → 改代码 → 发版
-
运营 / 算法 / 产品完全无法自助
(三)查询逻辑不可见、不可验证
-
DSL 实际长什么样,只有开发知道
-
是否真的"召回正确数据",只能靠线上日志猜
(四)无法复用、无法治理
-
类似的查询逻辑大量重复
-
无统一命名、无版本、无权限、无监控
ES 的能力很强,但使用方式极其原始。
二、目标:把 ES 查询"产品化、策略化、平台化"
我们希望构建的是一套:
以「策略 ID」为入口的 Elasticsearch 查询平台
核心目标
| 目标 | 说明 |
|---|---|
| 业务零开发 | 业务方只传 strategyId + 参数 |
| 策略可配置 | 查询 DSL 在平台配置 |
| 实时验证 | 策略配置后可立即测试 |
| 版本可控 | 策略可发布 / 回滚 / 灰度 |
| 执行统一 | 屏蔽 Java ES 开发复杂度 |
| 可观测 | 查询效果、性能、命中率可监控 |
一句话总结:"ES 查询 = 一条可配置、可验证、可运营的策略"
三、总体架构设计
(一)架构总览

(二)策略化ES查询系统架构和流程图

(三)核心模块拆解
| 模块 | 职责 |
|---|---|
| 策略管理平台 | 策略创建、调试、发布 |
| 策略执行服务 | 对外统一策略调用入口 |
| DSL 渲染引擎 | 参数替换、条件计算 |
| ES 适配层 | 屏蔽 Java ES API |
| 策略缓存 | 提高性能、支持热更新 |
| 验证与调试 | 实时验证策略效果 |
四、策略的本质:DSL 模板 + 参数模型
(一)什么是"策略"
在这个体系中,一条策略等于
一份可参数化的 ES DSL 模板 + 一份参数定义 + 一份执行约束
(二)策略 DSL 模板设计
示例策略 DSL
Groovy
{
"query": {
"bool": {
"must": [
{ "term": { "user_id": "${userId}" } }
],
"filter": [
{ "range": { "score": { "gte": "${minScore:0}" } } }
]
}
},
"sort": [
{ "create_time": "desc" }
],
"size": "${size:10}"
}
(三)DSL 设计原则
-
100% 原生 ES DSL
-
仅增加轻量模板能力
-
不发明新查询语言
(四)参数系统设计
1. 参数类型定义
| 参数类型 | 示例 |
|---|---|
| string | userId |
| number | size |
| boolean | enableFilter |
| list | categoryList |
| enum | sortType |
2. 参数定义模型
Scala
{
"name": "size",
"type": "number",
"required": false,
"default": 10,
"desc": "返回条数"
}
五、策略执行全流程
(一)调用方式
html
POST /strategy/execute
{
"strategyId": "user_recommend_v1",
"params": {
"userId": "123",
"size": 5
}
}
(二)执行流程拆解
(三)DSL 渲染引擎设计
1. 支持能力
-
${param}强制参数 -
${param:default}默认值 -
条件片段(进阶)
2. 渲染过程
-
解析 DSL JSON
-
查找占位符
-
参数校验
-
替换并生成最终 DSL
(四)DSL 条件片段 / 可选过滤(Conditional DSL)
现实业务中,ES 查询几乎从来不是"固定结构"。
典型场景
-
- 参数存在才加 filter
-
- 开关打开才启用某个 must
-
- 列表为空时跳过 terms 查询
-
- 不同用户走不同召回逻辑
如果没有条件 DSL,结果只能是:
java
if (param != null) {
bool.must(...)
}
❌ 这正是我们要消灭的东西。
1. 设计目标(非常重要)
我们要的 不是:
-
❌ 一个完整脚本语言
-
❌ 在 DSL 里写 Java / Groovy / JS
我们要的是:结构化、可控、可验证的"条件片段"
2. DSL 条件片段的设计方案(推荐)
三种方案,并明确:👉 第一种是强烈推荐、可长期演进的,仅个人建议。
2.1 方案一(推荐):结构化条件块(Block DSL)
DSL 示例
php
{
"query": {
"bool": {
"must": [
{
"$if": {
"condition": "userId != null",
"then": { "term": { "user_id": "${userId}" } }
}
},
{
"$if": {
"condition": "enableScoreFilter == true",
"then": {
"range": {
"score": { "gte": "${minScore:0}" }
}
}
}
}
]
}
},
"size": "${size:10}"
}
设计优势
| 维度 | 优势 |
|---|---|
| 可读性 | 非技术人员也能理解 |
| 安全性 | 不执行任意代码 |
| 可验证 | condition 可单独校验 |
| 可扩展 | 支持 else / 嵌套 |
2.2 方案二:占位符级条件(不推荐长期)
java
"must": [
${userId ? {"term":{"user_id":userId}} : ""}
]
❌ 不结构化
❌ 难以校验
❌ 易注入
2.3 方案三:引入表达式引擎(高级)
-
MVEL / SpEL / QLExpress
-
适合 后期高级策略
不是第一版该做的事情,可长期演进
3. 条件 DSL 的执行模型
- 执行顺序(非常关键)
- 解析 JSON 结构
- 识别 $if 节点
- 计算 condition 表达式
- 命中 then → 展开
- 未命中 → 移除
- 最终生成合法 ES DSL
⚠️ 注意:条件判断发生在"参数替换之前"还是之后,是一个设计点
推荐顺序:参数解析 → 条件判断 → DSL 展开 → 占位符替换
4. 条件表达式设计(刻意简化)
4.1 支持的表达式范围
perl
== != > < >= <=
&& ||
null / true / false
示例
bash
userId != null
size > 10
enableScoreFilter == true
categoryList != null && categoryList.size > 0
4.2 表达式执行方式
自己解析,不引入脚本引擎
-
安全
-
可控
-
易调试
5.Java 实现设计
5.1 条件处理入口
java
public interface ConditionalDslProcessor {
JsonNode process(JsonNode dslRoot, Map<String, Object> params);
}
5.2 识别 $if 节点
java
public class DefaultConditionalDslProcessor
implements ConditionalDslProcessor {
@Override
public JsonNode process(JsonNode root, Map<String, Object> params) {
return walk(root, params);
}
private JsonNode walk(JsonNode node, Map<String, Object> params) {
if (node.isObject() && node.has("$if")) {
return processIfNode(node.get("$if"), params);
}
if (node.isArray()) {
ArrayNode newArray = JsonNodeFactory.instance.arrayNode();
for (JsonNode child : node) {
JsonNode processed = walk(child, params);
if (processed != null) {
newArray.add(processed);
}
}
return newArray;
}
if (node.isObject()) {
ObjectNode newObj = JsonNodeFactory.instance.objectNode();
node.fields().forEachRemaining(e ->
newObj.set(e.getKey(), walk(e.getValue(), params))
);
return newObj;
}
return node;
}
}
5.3 $if 节点执行逻辑
java
private JsonNode processIfNode(JsonNode ifNode,
Map<String, Object> params) {
String condition = ifNode.get("condition").asText();
boolean match = ConditionEvaluator.evaluate(condition, params);
if (match) {
return walk(ifNode.get("then"), params);
}
return NullNode.getInstance(); // 被移除
}
5.4 条件表达式执行器(简化版)
java
public class ConditionEvaluator {
public static boolean evaluate(String expr, Map<String, Object> params) {
// 示例:userId != null
if (expr.contains("!=")) {
String key = expr.split("!=")[0].trim();
return params.get(key) != null;
}
if (expr.contains("== true")) {
String key = expr.replace("== true", "").trim();
return Boolean.TRUE.equals(params.get(key));
}
throw new UnsupportedOperationException("Unsupported expr: " + expr);
}
}
在实现第一版**完全够用,**后期可以无缝替换为表达式引擎
6. 与原执行流程的整合
原流程:参数解析 → DSL 渲染 → ES 执行
新流程(升级):参数解析 → 条件 DSL 处理 → 占位符替换 → ES 执行
集成示例
java
JsonNode dslJson = objectMapper.readTree(version.getDslTemplate());
JsonNode conditionalProcessed =
conditionalDslProcessor.process(dslJson, resolvedParams);
String finalDsl =
dslRenderEngine.render(conditionalProcessed.toString(), resolvedParams);
你现在的系统已经具备:
| 能力 | 状态 |
|---|---|
| 参数化查询 | ✅ |
| 条件化查询 | ✅ |
| 无代码策略变更 | ✅ |
| 策略自治 | ✅ |
这已经不是"封装 ES",而是:一个轻量级、可控的查询规则引擎
六、ES 执行层:彻底屏蔽 Java ES 复杂度
(一)策略执行服务的整体分层设计
javascript
strategy-executor
├── controller # 对外API
├── service # 策略执行主流程
├── domain # 策略领域模型
├── repository # DB访问
├── engine
│ ├── dsl # DSL渲染引擎
│ ├── param # 参数校验与填充
│ └── es # ES执行适配
├── cache # 策略缓存
└── model # 请求 / 返回模型
(二)领域模型(Domain Model)
Domain 层是整个体系"最重要的稳定锚点"
1. Strategy(策略主实体)
java
@Data
public class Strategy {
private Long id;
private String strategyId;
private String name;
private String bizDomain;
private String owner;
private boolean enabled;
}
2. StrategyVersion(可执行版本)
java
@Data
public class StrategyVersion {
private Long id;
private String strategyId;
private String version;
private boolean active;
private String esCluster;
private String esIndex;
private String dslTemplate; // 原始DSL
private String resultMapping; // JSON
private int timeoutMs;
private int maxSize;
}
3. StrategyParam(参数定义)
java
@Data
public class StrategyParam {
private Long id;
private Long strategyVersionId;
private String name;
private ParamType type;
private boolean required;
private String defaultValue;
private String validateRule;
}
public enum ParamType {
STRING, NUMBER, BOOLEAN, LIST
}
(三)Repository 层(数据访问)
java
public interface StrategyRepository {
Strategy findByStrategyId(String strategyId);
}
public interface StrategyVersionRepository {
StrategyVersion findActiveVersion(String strategyId);
}
public interface StrategyParamRepository {
List<StrategyParam> findByVersionId(Long versionId);
}
⚠️ 注意:
执行路径永远只查"active version",灰度由上层控制。
(四)参数处理引擎(Parameter Engine)
1. 参数处理入口
java
public interface ParamResolver {
Map<String, Object> resolve(
List<StrategyParam> paramDefs,
Map<String, Object> inputParams
);
}
2. 默认实现
java
public class DefaultParamResolver implements ParamResolver {
@Override
public Map<String, Object> resolve(
List<StrategyParam> defs,
Map<String, Object> input) {
Map<String, Object> resolved = new HashMap<>();
for (StrategyParam def : defs) {
Object value = input.get(def.getName());
if (value == null) {
if (def.isRequired() && def.getDefaultValue() == null) {
throw new IllegalArgumentException(
"Missing required param: " + def.getName()
);
}
value = def.getDefaultValue();
}
validateType(def, value);
resolved.put(def.getName(), value);
}
return resolved;
}
private void validateType(StrategyParam def, Object value) {
// 简化示例,可扩展正则 / 表达式
if (value == null) return;
switch (def.getType()) {
case NUMBER:
if (!(value instanceof Number)) {
throw new IllegalArgumentException(def.getName() + " must be number");
}
break;
case BOOLEAN:
if (!(value instanceof Boolean)) {
throw new IllegalArgumentException(def.getName() + " must be boolean");
}
break;
}
}
}
(五)DSL 渲染引擎(核心)
1. DSL 渲染接口
java
public interface DslRenderEngine {
String render(String dslTemplate, Map<String, Object> params);
}
2. 基于占位符的实现
java
public class PlaceholderDslRenderEngine implements DslRenderEngine {
private static final Pattern PATTERN = Pattern.compile("\\$\\{(.+?)\\}");
@Override
public String render(String template, Map<String, Object> params) {
Matcher matcher = PATTERN.matcher(template);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String key = matcher.group(1);
Object value = params.get(key);
if (value == null) {
throw new IllegalArgumentException("Missing param: " + key);
}
matcher.appendReplacement(sb, formatValue(value));
}
matcher.appendTail(sb);
return sb.toString();
}
private String formatValue(Object value) {
if (value instanceof String) {
return "\"" + value + "\"";
}
return value.toString();
}
}
优点:
-
简单
-
可控
-
不引入复杂脚本引擎
(六)ES 执行适配层
1. 统一执行接口
java
public interface EsExecutor {
SearchResult execute(
String cluster,
String index,
String dsl,
int timeoutMs
);
}
2. 实现思路(伪实现)
java
public class RestHighLevelEsExecutor implements EsExecutor {
@Override
public SearchResult execute(
String cluster,
String index,
String dsl,
int timeoutMs) {
// 1. 构建 SearchRequest
// 2. 设置 index / source / timeout
// 3. 执行并返回结果
return new SearchResult();
}
}
(七)策略执行主流程
1. StrategyExecuteService
java
public interface StrategyExecuteService {
StrategyResult execute(String strategyId, Map<String, Object> params);
}
2. 默认实现(核心流程)
java
public class DefaultStrategyExecuteService implements StrategyExecuteService {
private StrategyVersionRepository versionRepo;
private StrategyParamRepository paramRepo;
private ParamResolver paramResolver;
private DslRenderEngine dslRenderEngine;
private EsExecutor esExecutor;
@Override
public StrategyResult execute(String strategyId, Map<String, Object> params) {
// 1. 获取生效版本
StrategyVersion version = versionRepo.findActiveVersion(strategyId);
if (version == null) {
throw new IllegalStateException("No active version for strategy " + strategyId);
}
// 2. 获取参数定义
List<StrategyParam> paramDefs =
paramRepo.findByVersionId(version.getId());
// 3. 参数解析
Map<String, Object> resolvedParams =
paramResolver.resolve(paramDefs, params);
// 4. DSL 渲染
String finalDsl =
dslRenderEngine.render(version.getDslTemplate(), resolvedParams);
// 5. 执行 ES
SearchResult esResult =
esExecutor.execute(
version.getEsCluster(),
version.getEsIndex(),
finalDsl,
version.getTimeoutMs()
);
// 6. 结果映射
return StrategyResult.from(esResult, version.getResultMapping());
}
}
到这一步,已经完成了:
✅ 策略 = 可配置实体
✅ 查询 = 数据驱动
✅ 执行 = 统一入口
✅ Java 代码 不再关心具体 ES DSL
这已经是一个 完整可运行的策略执行内核。
七、策略管理平台设计(重头戏)
(二)平台核心能力
| 能力 | 说明 |
|---|---|
| 策略创建 | DSL 编辑 |
| 参数配置 | 参数定义 |
| 实时调试 | 在线测试 |
| 发布管理 | 版本 / 灰度 |
| 权限控制 | 团队隔离 |
| 监控分析 | 调用效果 |
(二)策略实时验证(非常关键)
验证流程
-
输入测试参数
-
生成最终 DSL
-
实际请求 ES
-
展示结果 & 命中数
-
高亮 DSL 与参数映射
这是"让策略可信"的核心能力
(三)策略调试执行(Draft / Preview Execution)
现实问题
-
DSL 写对了 ≠ 召回对了
-
参数填对了 ≠ 命中预期数据
-
ES 查询逻辑复杂,靠"脑补"不可靠
没有调试能力的后果
| 角色 | 痛点 |
|---|---|
| 策略配置者 | 不敢发布 |
| 开发 | 被迫陪跑 |
| 线上 | 发布即事故 |
1. 设计目标
调试执行必须满足:
| 维度 | 要求 |
|---|---|
| 不影响线上 | 不走 active version |
| 即时反馈 | 秒级返回 |
| 结果可解释 | 展示 DSL、条件命中 |
| 安全 | 不能误写数据 |
| 可对比 | 新旧策略对比 |
2. 整体架构设计

调试执行与正式执行的核心区别
| 维度 | 正式执行 | 调试执行 |
|---|---|---|
| 策略版本 | active | 指定 version / draft |
| 是否发布 | 必须 | ❌ 不需要 |
| 缓存 | 可用 | ❌ 禁用 |
| 限制 | 正常 | 强限制 |
| 记录 | 日志 | 调试日志 |
3. 表结构扩展(支持 Draft)
3.1 增加 version_status
sql
ALTER TABLE strategy_version
ADD COLUMN version_status VARCHAR(20)
DEFAULT 'DRAFT'
COMMENT 'DRAFT / PUBLISHED / OFFLINE';
3.2 Draft 的定义
| 状态 | 说明 |
|---|---|
| DRAFT | 编辑态,仅用于调试 |
| PUBLISHED | 可被发布 |
| OFFLINE | 下线 |
4. 调试执行 API 设计
4.1 请求模型
java
POST /strategy/debug/execute
{
"strategyId": "user_recommend",
"version": "v2",
"params": {
"userId": "123",
"enableScoreFilter": true
},
"options": {
"limit": 20,
"explain": true
}
}
4.2 返回模型(关键)
java
{
"finalDsl": "{ ... }",
"conditionResult": [
{
"condition": "enableScoreFilter == true",
"matched": true
}
],
"hits": [ ... ],
"total": 120
}
finalDsl 是灵魂
没有它,调试毫无意义
5. 调试执行核心流程
- 加载指定版本(可为 DRAFT)
- 参数校验(同正式)
- 条件 DSL 执行(记录命中情况)
- DSL 渲染
- 强制只读 + limit
- ES 执行
- 结果解释与返回
5.1 调试执行服务接口
java
public interface StrategyDebugService {
StrategyDebugResult debug(
String strategyId,
String version,
Map<String, Object> params,
DebugOptions options
);
}
5.2 DebugOptions
java
@Data
public class DebugOptions {
private int limit = 20;
private boolean explain = true;
}
5.3 核心实现
java
public class DefaultStrategyDebugService
implements StrategyDebugService {
private StrategyVersionRepository versionRepo;
private StrategyParamRepository paramRepo;
private ConditionalDslProcessor conditionalProcessor;
private DslRenderEngine dslRenderEngine;
private EsExecutor esExecutor;
@Override
public StrategyDebugResult debug(
String strategyId,
String version,
Map<String, Object> params,
DebugOptions options) {
StrategyVersion sv =
versionRepo.findByStrategyIdAndVersion(strategyId, version);
if (sv == null) {
throw new IllegalArgumentException("Version not found");
}
// 1. 参数解析
Map<String, Object> resolved =
resolveParams(sv.getId(), params);
// 2. 条件 DSL 处理(带记录)
ConditionTrace trace = new ConditionTrace();
JsonNode processedDsl =
conditionalProcessor.processWithTrace(
sv.getDslTemplate(), resolved, trace
);
// 3. DSL 渲染
String finalDsl =
dslRenderEngine.render(processedDsl.toString(), resolved);
// 4. 强制限制
finalDsl = enforceLimit(finalDsl, options.getLimit());
// 5. ES 执行
SearchResult esResult =
esExecutor.execute(
sv.getEsCluster(),
sv.getEsIndex(),
finalDsl,
sv.getTimeoutMs()
);
// 6. 构造返回
return StrategyDebugResult.of(
finalDsl,
trace,
esResult
);
}
}
6. 平台交互体验(非常重要)
UI 推荐展示顺序
-
输入参数区
-
条件命中列表(✔ / ✖)
-
最终 DSL(可复制)
-
命中条数
-
Top N 结果
7. 安全与边界控制
调试执行 必须强约束:
| 风险 | 防护 |
|---|---|
| 大查询 | 强制 size |
| 写操作 | 禁止 |
| scroll | 禁止 |
| update/delete | 拦截 |
(四)平台侧如何支持条件 DSL?
编辑器能力
-
$if自动补全 -
condition 校验
-
then 结构校验
调试能力
-
显示命中 / 未命中条件
-
高亮被裁剪的 DSL 片段
你现在的策略平台已经具备:
✅ 可配置
✅ 可条件化
✅ 可验证
✅ 可发布
这四点齐了,才配叫"平台"。
八、策略化 ES 查询平台参考"可直接落库"的完整表结构设计
(一)整体数据模型总览
我们先看全局 ER 关系(逻辑层):

(二)策略主表(strategy)
1. 表职责
策略的"产品级"定义
-
策略唯一标识(strategy_id)
-
策略归属(业务线 / 团队)
-
生命周期管理(启用 / 禁用 / 删除)
-
权限与治理的锚点
2. 表结构设计
sql
CREATE TABLE strategy (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
strategy_id VARCHAR(100) NOT NULL COMMENT '策略唯一标识(对外使用)',
strategy_name VARCHAR(200) NOT NULL COMMENT '策略名称',
strategy_desc TEXT COMMENT '策略描述',
biz_domain VARCHAR(100) NOT NULL COMMENT '业务域(推荐/搜索/风控等)',
owner VARCHAR(100) NOT NULL COMMENT '负责人',
team VARCHAR(100) COMMENT '所属团队',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-启用 0-禁用',
is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_strategy_id (strategy_id)
) COMMENT='策略主表';
2.1 为什么 strategy_id 要单独存在?
-
id:数据库内部 -
strategy_id:业务和接口唯一标识 -
支持跨环境、跨系统引用
2.2 biz_domain 的价值
-
查询治理
-
权限隔离
-
监控聚合
(三)策略版本表(strategy_version)
1. 表职责
策略真正"会被执行"的实体
-
DSL 内容
-
索引配置
-
查询约束
-
灰度、发布、回滚的核心
2. 表结构设计
sql
CREATE TABLE strategy_version (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
strategy_id VARCHAR(100) NOT NULL COMMENT '策略ID',
version VARCHAR(50) NOT NULL COMMENT '版本号,如 v1、v2',
is_active TINYINT NOT NULL DEFAULT 0 COMMENT '是否当前生效版本',
es_cluster VARCHAR(100) NOT NULL COMMENT 'ES集群标识',
es_index VARCHAR(200) NOT NULL COMMENT '索引或索引模式',
dsl_template JSON NOT NULL COMMENT 'ES DSL 模板',
result_mapping JSON COMMENT '结果映射配置',
timeout_ms INT DEFAULT 2000 COMMENT '查询超时',
max_size INT DEFAULT 1000 COMMENT '最大size限制',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-可用 0-禁用',
creator VARCHAR(100) NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_strategy_version (strategy_id, version)
) COMMENT='策略版本表';
关键字段深度解释
2.1 dsl_template(核心)
-
JSON 类型
-
存储原始模板,含
${param}占位符 -
不做任何预渲染
2.1 result_mapping(高级)
php
{
"fields": ["id", "title", "score"],
"rename": {
"create_time": "ctime"
}
}
(四)策略参数表(strategy_param)
1. 表职责
参数的"强约束来源"
-
参数类型
-
是否必填
-
默认值
-
校验规则
2. 表结构设计
sql
CREATE TABLE strategy_param (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
strategy_version_id BIGINT NOT NULL COMMENT '策略版本ID',
param_name VARCHAR(100) NOT NULL COMMENT '参数名',
param_type VARCHAR(50) NOT NULL COMMENT '类型:string/number/list/boolean',
required TINYINT NOT NULL DEFAULT 0 COMMENT '是否必填',
default_value VARCHAR(500) COMMENT '默认值',
validate_rule VARCHAR(500) COMMENT '校验规则(正则或表达式)',
param_desc VARCHAR(200) COMMENT '参数说明',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_version_param (strategy_version_id, param_name)
) COMMENT='策略参数表';
参数校验逻辑来源
| 校验项 | 来源 |
|---|---|
| 是否存在 | required |
| 类型校验 | param_type |
| 范围校验 | validate_rule |
| 默认值 | default_value |
(五)策略发布表(strategy_publish)
1. 表职责
记录策略发布、切换、灰度历史
2. 表结构
sql
CREATE TABLE strategy_publish (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
strategy_id VARCHAR(100) NOT NULL,
from_version VARCHAR(50),
to_version VARCHAR(50) NOT NULL,
publish_type VARCHAR(50) NOT NULL COMMENT 'full / gray',
gray_rule JSON COMMENT '灰度规则',
publisher VARCHAR(100) NOT NULL,
publish_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) COMMENT='策略发布记录表';
(六)策略权限表(strategy_permission)
sql
CREATE TABLE strategy_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
strategy_id VARCHAR(100) NOT NULL,
subject_type VARCHAR(50) NOT NULL COMMENT 'user / role / team',
subject_id VARCHAR(100) NOT NULL,
permission VARCHAR(50) NOT NULL COMMENT 'read / edit / publish',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) COMMENT='策略权限表';
(七)为什么要"拆表这么细"?
| 表 | 解决的问题 |
|---|---|
| strategy | 生命周期 & 治理 |
| strategy_version | 演进 / 灰度 / 回滚 |
| strategy_param | 稳定性 & 安全 |
| strategy_publish | 可审计 |
| strategy_permission | 可协作 |
这套表能支撑什么?
✅ 可配置策略
✅ 在线调试
✅ 安全参数校验
✅ 多版本并行
✅ 灰度发布
✅ 策略治理
(八)缓存、性能与稳定性设计
1. 策略缓存
-
Redis / Caffeine
-
key = strategyId + version
-
支持发布后热更新
2. 查询结果缓存(可选)
-
高频、幂等策略
-
TTL + 参数 hash
九、总结:你真正做成的是什么?
回到最初的问题------这套设计,并不是一个 ES 的二次封装工具。
你真正构建的是一套 面向复杂业务系统的「查询策略基础设施」。
它完成了几件非常关键、但常被低估的事情:
-
把 查询逻辑从代码中剥离,变成可配置、可演进的策略资产
-
把 一次性 DSL 实现,升级为有版本、有发布、有回滚的长期能力
-
把 风险后置的线上试错,前移为可验证、可解释的调试执行
-
把 个人经验驱动的查询设计,沉淀为组织级、平台级的能力体系
最终,你得到的不是"更好写 ES"的方式,而是:
一套可治理、可验证、可运营、可持续演进的查询能力平台。
当查询具备了 配置、条件、验证、发布 这四个基本能力时,它才真正从"工程实现细节",跃迁为"系统级基础设施"。