BEST_PRACTICES_REFACTORING.md 6.29 KB

最佳实践重构总结

重构日期: 2025-11-12
重构人员: AI Assistant
重构原则: 代码简洁、类型安全、单一职责


重构目标

  1. 移除兼容代码:不再支持多种数据格式,统一使用最佳实践
  2. 修复前端422错误:支持日期时间字符串的 range filter
  3. 保持代码简洁:单一数据流,清晰的类型定义

修改文件

1. /home/tw/SearchEngine/api/models.py

修改:RangeFilter 模型

之前:只支持数值 (float)

gte: Optional[float] = Field(None, description="大于等于 (>=)")

之后:支持数值和日期时间字符串

gte: Optional[Union[float, str]] = Field(None, description="大于等于 (>=)。数值或ISO日期时间字符串")

理由

  • 价格字段需要数值过滤:{"gte": 50, "lte": 200}
  • 时间字段需要字符串过滤:{"gte": "2023-01-01T00:00:00Z"}
  • 使用 Union[float, str] 同时支持两种类型

2. /home/tw/SearchEngine/search/es_query_builder.py

修改:build_facets 方法

之前:兼容字典和 Pydantic 模型

else:
    if isinstance(config, dict):
        field = config['field']
        facet_type = config.get('type', 'terms')
        ...
    else:
        # Pydantic模型
        field = config.field
        facet_type = config.type
        ...

之后:只支持标准格式(str 或 FacetConfig)

# 简单模式:只有字段名(字符串)
if isinstance(config, str):
    field = config
    ...

# 高级模式:FacetConfig 对象
else:
    field = config.field
    facet_type = config.type
    ...

理由

  • API 层已经定义标准格式:List[Union[str, FacetConfig]]
  • 移除字典支持,保持单一数据流
  • 代码更简洁,意图更清晰

3. /home/tw/SearchEngine/search/searcher.py

修改:_standardize_facets 方法

之前:兼容字典和 Pydantic 模型

else:
    field = config.get('field') if isinstance(config, dict) else config.field
    facet_type = config.get('type', 'terms') if isinstance(config, dict) else getattr(config, 'type', 'terms')

之后:只支持标准格式

else:
    # FacetConfig 对象
    field = config.field
    facet_type = config.type

理由

  • 与 build_facets 保持一致
  • 移除冗余的类型检查
  • 代码更清晰易读

数据流设计

标准数据流(最佳实践)

API Request
    ↓
Pydantic 验证 (SearchRequest)
    ↓ facets: List[Union[str, FacetConfig]]
Searcher.search()
    ↓
QueryBuilder.build_facets()
    ↓ 只接受 str 或 FacetConfig
ES Query (aggs)
    ↓
ES Response (aggregations)
    ↓
Searcher._standardize_facets()
    ↓ 只处理 str 或 FacetConfig
List[FacetResult] (Pydantic 模型)
    ↓
SearchResponse
    ↓
API Response (JSON)

类型定义

# API 层
facets: Optional[List[Union[str, FacetConfig]]] = None

# Query Builder 层
def build_facets(facet_configs: Optional[List[Union[str, 'FacetConfig']]])

# Searcher 层
def _standardize_facets(...) -> Optional[List[FacetResult]]

# Response 层
facets: Optional[List[FacetResult]] = None

测试结果

✅ 所有测试通过

测试场景 状态 说明
字符串 facets ["categoryName_keyword"]
FacetConfig 对象 {"field": "price", "type": "range", ...}
数值 range filter {"gte": 50, "lte": 200}
日期时间 range filter {"gte": "2023-01-01T00:00:00Z"}
混合使用 filters + range_filters + facets

✅ 前端 422 错误已修复

问题:前端筛选 listing time 时返回 422 Unprocessable Entity

原因RangeFilter 只接受 float,不接受日期时间字符串

解决:修改 RangeFilter 支持 Union[float, str]

验证

curl -X POST /search/ \
  -d '{"query": "玩具", "range_filters": {"create_time": {"gte": "2023-01-01T00:00:00Z"}}}'
# ✓ 200 OK

代码质量

✅ 无 Linter 错误

No linter errors found.

✅ 类型安全

  • 所有类型明确定义
  • Pydantic 自动验证
  • IDE 类型提示完整

✅ 代码简洁

  • 移除所有兼容代码
  • 单一数据格式
  • 清晰的控制流

最佳实践原则

1. 单一职责

每个方法只处理一种标准格式,不兼容多种输入

2. 类型明确

使用 Pydantic 模型而不是字典,类型在编译时就确定

3. 数据流清晰

API → Pydantic → 业务逻辑 → Pydantic → Response

4. 早期验证

在 API 层就验证数据,不在内部做多重兼容检查

5. 代码可维护

  • 删除冗余代码
  • 保持一致性
  • 易于理解和修改

对比总结

方面 重构前 重构后
数据格式 字典 + Pydantic(兼容) 只有 Pydantic
类型检查 运行时多重检查 编译时类型明确
代码行数 更多(兼容代码) 更少(单一逻辑)
可维护性 复杂(多种路径) 简单(单一路径)
错误处理 隐式容错 明确验证
RangeFilter 只支持数值 支持数值+字符串
前端兼容 422 错误 完全兼容

后续建议

1. 代码审查

  • [x] 移除所有字典兼容代码
  • [x] 统一使用 Pydantic 模型
  • [x] 修复前端 422 错误

2. 测试覆盖

  • [x] 字符串 facets
  • [x] FacetConfig 对象
  • [x] 数值 range filter
  • [x] 日期时间 range filter
  • [x] 混合场景

3. 文档更新

  • [x] 最佳实践文档
  • [x] 数据流设计
  • [ ] API 文档更新

4. 性能优化

  • [ ] 添加请求缓存
  • [ ] 优化 Pydantic 验证
  • [ ] 监控性能指标

结论

本次重构成功实现了以下目标:

代码简洁:移除所有兼容代码,保持单一数据流
类型安全:统一使用 Pydantic 模型,编译时类型检查
功能完整:修复前端 422 错误,支持所有筛选场景
可维护性:代码清晰,易于理解和修改

核心原则不做兼容多种方式的代码,定义一种最佳实践,所有模块都适配这种新方式


版本: v3.1
状态: ✅ 完成并通过测试
下次更新: 根据业务需求扩展