43f1139f
tangwang
refactor: ES查询结构重...
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
# 统一约定重构总结
**重构日期**: 2025-11-12
**核心原则**: **统一约定,不做兼容,保持简单**
---
## 问题
前端筛选日期范围(listing time)没有生效,ES 查询中没有对应的过滤项。
### 根本原因
**数据类型不一致**:
- API 层使用 `Dict[str, RangeFilter]`(Pydantic 模型)
- ES Query Builder 期望普通字典
- 没有做转换,导致过滤失效
### 错误方案(违反简洁原则)
```python
# ❌ 支持多种格式(兼容代码)
if hasattr(range_spec, 'model_dump'):
range_dict = range_spec.model_dump() # Pydantic 模型
else:
range_dict = range_spec # 普通字典
```
**问题**:
- 代码复杂
- 多种数据流
- 难以维护
---
## 正确方案:统一约定
### 核心思想
**整个系统只使用一种数据格式:Pydantic 模型**
### 数据流
```
API Request (JSON)
↓
Pydantic 验证 → Dict[str, RangeFilter]
↓
Searcher(透传)
↓
ES Query Builder → range_filter.model_dump()
↓
ES Query (字典)
↓
Elasticsearch
```
### 类型定义
```python
# API 层 (models.py)
range_filters: Optional[Dict[str, RangeFilter]] = None
# Searcher 层 (searcher.py)
range_filters: Optional[Dict[str, Any]] = None # 透传
# ES Query Builder 层 (es_query_builder.py)
range_filters: Optional[Dict[str, 'RangeFilter']] = None # 明确类型
```
---
## 实现代码
### `/home/tw/SearchEngine/search/es_query_builder.py`
```python
def _build_filters(
self,
filters: Optional[Dict[str, Any]] = None,
range_filters: Optional[Dict[str, 'RangeFilter']] = None
) -> List[Dict[str, Any]]:
"""
构建过滤子句。
Args:
filters: 精确匹配过滤器字典
range_filters: 范围过滤器(Dict[str, RangeFilter],RangeFilter 是 Pydantic 模型)
"""
filter_clauses = []
# 1. 处理精确匹配过滤
if filters:
for field, value in filters.items():
if isinstance(value, list):
filter_clauses.append({"terms": {field: value}})
else:
filter_clauses.append({"term": {field: value}})
# 2. 处理范围过滤(RangeFilter Pydantic 模型)
if range_filters:
for field, range_filter in range_filters.items():
# 统一约定:range_filter 就是 RangeFilter 模型
range_dict = range_filter.model_dump(exclude_none=True)
if range_dict:
filter_clauses.append({
"range": {field: range_dict}
})
return filter_clauses
```
### 关键点
1. **不检查类型**:不用 `isinstance` 或 `hasattr` 检查
2. **直接调用**:直接调用 `range_filter.model_dump()`
3. **类型注解**:明确标注 `Dict[str, 'RangeFilter']`
---
## 统一约定的好处
### 1. **代码简洁**
- 不需要类型检查
- 不需要兼容逻辑
- 单一数据流
### 2. **类型安全**
- 编译时类型明确
- IDE 类型提示完整
- 运行时自动验证
### 3. **易于维护**
- 数据流清晰
- 修改影响范围小
- 新人容易理解
### 4. **高性能**
- 没有运行时类型检查
- 没有条件分支
- Pydantic 验证高效
---
## 测试结果
### ✅ 数值范围过滤
```json
{
"query": "玩具",
"range_filters": {
"price": {"gte": 50, "lte": 200}
}
}
```
**结果**: ✓ 50 hits,ES filter 正确生成
### ✅ 日期时间范围过滤
```json
{
"query": "玩具",
"range_filters": {
"create_time": {"gte": "2024-01-01T00:00:00Z"}
}
}
```
**结果**: ✓ 67 hits,ES filter 正确生成
### ✅ 混合过滤
```json
{
"query": "玩具",
"filters": {"categoryName_keyword": "桌面休闲玩具"},
"range_filters": {"price": {"gte": 10}}
}
```
**结果**: ✓ 50 hits,多个 filter 正确生成
---
## 对比:错误 vs 正确
| 方面 | 支持多种(错误) | 统一约定(正确) |
|------|----------------|----------------|
| **代码行数** | 更多(if/else) | 更少(单一逻辑) |
| **类型检查** | 运行时多次检查 | 编译时明确 |
| **数据流** | 多条路径 | 单一路径 |
| **可维护性** | 复杂 | 简单 |
| **错误处理** | 隐式容错 | 明确失败 |
| **性能** | 较慢(检查) | 较快(直接) |
---
## 架构原则
### 🎯 核心原则
1. **统一约定** - 全系统使用同一种数据格式
2. **不做兼容** - 不支持多种格式,明确失败
3. **保持简单** - 单一数据流,清晰的类型
4. **早期验证** - 在 API 层验证,内部直接使用
### 📐 设计决策
**何时使用 Pydantic 模型?**
- ✅ API 边界:请求/响应
- ✅ 内部传递:跨模块传递复杂数据
- ✅ 配置定义:类型安全的配置
**何时使用字典?**
- ✅ ES DSL:最终的查询字典
- ✅ 简单键值:单层简单数据
- ❌ 不用于跨模块传递复杂结构
### 🚫 反模式
**避免这些做法:**
```python
# ❌ 兼容多种格式
if isinstance(x, dict):
...
elif hasattr(x, 'model_dump'):
...
# ❌ 运行时类型转换
if is_pydantic_model(x):
x = x.model_dump()
# ❌ 可选的多种输入
def process(data: Union[Dict, Model]):
...
```
**正确做法:**
```python
# ✓ 明确单一类型
def process(data: Model):
dict_data = data.model_dump()
...
# ✓ 在边界转换
# API → Pydantic → 内部处理 → Pydantic → Response
```
---
## 系统一致性
### 统一的数据流模式
```
1. Facets 配置
API: List[Union[str, FacetConfig]]
→ Searcher (透传)
→ ES Query Builder (只接受 str 或 FacetConfig)
✓ 统一约定
2. Range Filters
API: Dict[str, RangeFilter]
→ Searcher (透传)
→ ES Query Builder (只接受 RangeFilter)
✓ 统一约定
3. 响应 Facets
ES Response (字典)
→ Searcher: 构建 List[FacetResult]
→ API: 返回 List[FacetResult]
✓ 统一约定
```
所有模块都遵循相同的原则:**统一约定,不做兼容**
---
## 实施指南
### 添加新功能时
1. **定义 Pydantic 模型** - 在 `api/models.py` 定义
2. **API 层验证** - FastAPI 自动验证
3. **内部直接使用** - 不做类型检查和转换
4. **明确类型注解** - 让 IDE 和 mypy 检查
### 重构现有代码时
1. **识别兼容代码** - 查找 `isinstance`, `hasattr` 等
2. **统一为一种格式** - 选择 Pydantic 或字典
3. **移除条件分支** - 直接使用统一格式
4. **更新类型注解** - 明确标注类型
---
## 总结
### ✅ 已完成
- ✅ 修复日期范围过滤
- ✅ 统一 range_filters 为 Pydantic 模型
- ✅ 移除所有兼容代码
- ✅ 保持代码简洁
### 🎯 核心价值
**"统一约定,不做兼容,保持简单"**
这不仅仅是代码风格,而是架构原则:
- 降低认知负担
- 减少 bug 产生
- 提高代码质量
- 加速开发效率
### 📚 参考
- `BEST_PRACTICES_REFACTORING.md` - 最佳实践文档
- `FACETS_FIX_SUMMARY.md` - Facets 修复总结
- 本文档 - 统一约定原则
---
**版本**: v3.2
**状态**: ✅ 完成并通过测试
**原则**: 统一约定 > 兼容多种 > 代码简洁至上
|