MIGRATION_GUIDE_V3.md 9.26 KB

API v3.0 迁移指南

本文档帮助你从旧版 API 迁移到 v3.0。


重要变更概述

v3.0 是一个不向后兼容的版本,主要变更包括:

  1. 移除 硬编码的 price_ranges 参数
  2. 移除 aggregations 参数(ES DSL)
  3. 新增 range_filters 参数
  4. 新增 facets 参数(简化接口)
  5. 新增 标准化的分面响应格式

迁移步骤

第一步:更新过滤器参数

旧代码(v2.x)

{
  "query": "玩具",
  "filters": {
    "price_ranges": ["0-50", "50-100"]
  }
}

新代码(v3.0)

{
  "query": "玩具",
  "range_filters": {
    "price": {
      "gte": 50,
      "lte": 100
    }
  }
}

迁移对照表

旧格式 新格式
"price_ranges": ["0-50"] "price": {"lt": 50}
"price_ranges": ["50-100"] "price": {"gte": 50, "lt": 100}
"price_ranges": ["100-200"] "price": {"gte": 100, "lt": 200}
"price_ranges": ["200+"] "price": {"gte": 200}

第二步:更新聚合参数

旧代码(v2.x)

{
  "query": "玩具",
  "aggregations": {
    "category_stats": {
      "terms": {
        "field": "categoryName_keyword",
        "size": 15
      }
    },
    "brand_stats": {
      "terms": {
        "field": "brandName_keyword",
        "size": 15
      }
    }
  }
}

新代码(v3.0)- 简单模式

{
  "query": "玩具",
  "facets": ["categoryName_keyword", "brandName_keyword"]
}

新代码(v3.0)- 高级模式

{
  "query": "玩具",
  "facets": [
    {
      "field": "categoryName_keyword",
      "size": 15,
      "type": "terms"
    },
    {
      "field": "brandName_keyword",
      "size": 15,
      "type": "terms"
    }
  ]
}

第三步:更新响应解析

旧代码(v2.x)

// JavaScript
const data = await response.json();

// 解析聚合结果(ES 原始格式)
if (data.aggregations && data.aggregations.category_stats) {
    data.aggregations.category_stats.buckets.forEach(bucket => {
        console.log(bucket.key, bucket.doc_count);
    });
}
# Python
data = response.json()

# 解析聚合结果(ES 原始格式)
if 'aggregations' in data and 'category_stats' in data['aggregations']:
    for bucket in data['aggregations']['category_stats']['buckets']:
        print(bucket['key'], bucket['doc_count'])

新代码(v3.0)

// JavaScript
const data = await response.json();

// 解析分面结果(标准化格式)
if (data.facets) {
    data.facets.forEach(facet => {
        console.log(`${facet.label} (${facet.type}):`);
        facet.values.forEach(value => {
            console.log(`  ${value.label}: ${value.count}`);
        });
    });
}
# Python
data = response.json()

# 解析分面结果(标准化格式)
if 'facets' in data:
    for facet in data['facets']:
        print(f"{facet['label']} ({facet['type']}):")
        for value in facet['values']:
            print(f"  {value['label']}: {value['count']}")

完整迁移示例

示例 1:带价格过滤的搜索

旧代码

import requests

response = requests.post('http://localhost:6002/search/', json={
    "query": "玩具",
    "size": 20,
    "filters": {
        "categoryName_keyword": "玩具",
        "price_ranges": ["50-100", "100-200"]
    },
    "aggregations": {
        "brand_stats": {
            "terms": {
                "field": "brandName_keyword",
                "size": 15
            }
        }
    }
})

data = response.json()

# 解析聚合
for bucket in data['aggregations']['brand_stats']['buckets']:
    print(f"{bucket['key']}: {bucket['doc_count']}")

新代码

import requests

response = requests.post('http://localhost:6002/search/', json={
    "query": "玩具",
    "size": 20,
    "filters": {
        "categoryName_keyword": "玩具"
    },
    "range_filters": {
        "price": {
            "gte": 50,
            "lte": 200
        }
    },
    "facets": [
        {"field": "brandName_keyword", "size": 15}
    ]
})

data = response.json()

# 解析分面
for facet in data['facets']:
    if facet['field'] == 'brandName_keyword':
        for value in facet['values']:
            print(f"{value['label']}: {value['count']}")

示例 2:前端 JavaScript 迁移

旧代码

// 构建请求
const requestBody = {
    query: "玩具",
    filters: {
        price_ranges: ["50-100"]
    },
    aggregations: {
        category_stats: {
            terms: {
                field: "categoryName_keyword",
                size: 15
            }
        }
    }
};

// 发送请求
const response = await fetch('/search/', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(requestBody)
});

const data = await response.json();

// 显示聚合结果
const buckets = data.aggregations.category_stats.buckets;
buckets.forEach(bucket => {
    console.log(`${bucket.key}: ${bucket.doc_count}`);
});

新代码

// 构建请求
const requestBody = {
    query: "玩具",
    range_filters: {
        price: {
            gte: 50,
            lte: 100
        }
    },
    facets: [
        {field: "categoryName_keyword", size: 15}
    ]
};

// 发送请求
const response = await fetch('/search/', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(requestBody)
});

const data = await response.json();

// 显示分面结果
data.facets.forEach(facet => {
    console.log(`${facet.label}:`);
    facet.values.forEach(value => {
        console.log(`  ${value.label}: ${value.count}`);
    });
});

字段映射对照

请求字段

v2.x v3.0 说明
filters.price_ranges range_filters.price 价格范围过滤
aggregations facets 分面配置
- range_filters.* 任意数值字段范围过滤

响应字段

v2.x v3.0 说明
aggregations facets 分面结果
aggregations.*.buckets[].key facets[].values[].value 分面值
aggregations.*.buckets[].doc_count facets[].values[].count 文档数量
- facets[].values[].label 显示标签
- facets[].values[].selected 是否选中
- facets[].label 分面名称
- facets[].type 分面类型

常见问题

Q1: 我的代码使用了 price_ranges,如何迁移?

A: 使用 range_filters 替代:

# 旧
filters = {"price_ranges": ["50-100"]}

# 新
range_filters = {"price": {"gte": 50, "lte": 100}}

Q2: 我使用了 ES 聚合语法,如何迁移?

A: 使用简化的 facets 配置:

# 旧
aggregations = {
    "my_agg": {
        "terms": {
            "field": "categoryName_keyword",
            "size": 15
        }
    }
}

# 新(简单模式)
facets = ["categoryName_keyword"]

# 新(高级模式)
facets = [
    {"field": "categoryName_keyword", "size": 15}
]

Q3: 响应中的 aggregations 字段去哪了?

A: 已改名为 facets,并且格式标准化了:

# 旧
for bucket in data['aggregations']['category_stats']['buckets']:
    print(bucket['key'], bucket['doc_count'])

# 新
for facet in data['facets']:
    for value in facet['values']:
        print(value['value'], value['count'])

Q4: 如何实现多个不连续的价格范围?

A: v3.0 不支持单字段多个不连续范围。请使用布尔查询或分多次查询:

# 方案1:分两次查询
result1 = search(query="玩具", range_filters={"price": {"lt": 50}})
result2 = search(query="玩具", range_filters={"price": {"gte": 200}})

# 方案2:使用范围分面统计(不过滤,只统计)
facets = [{
    "field": "price",
    "type": "range",
    "ranges": [
        {"key": "低价", "to": 50},
        {"key": "高价", "from": 200}
    ]
}]

Q5: 我需要继续使用旧接口,怎么办?

A: v3.0 不提供向后兼容。建议:

  1. 尽快迁移到新接口
  2. 如果暂时无法迁移,请继续使用 v2.x 版本
  3. 参考本文档快速完成迁移(通常只需要 1-2 小时)

迁移检查清单

完成以下检查项,确保迁移完整:

后端代码

  • [ ] 移除所有 price_ranges 参数的使用
  • [ ] 将范围过滤改为 range_filters
  • [ ] 将 aggregations 改为 facets
  • [ ] 更新响应解析,使用 facets 而不是 aggregations

前端代码

  • [ ] 更新搜索请求体,使用新参数
  • [ ] 更新状态管理,添加 rangeFilters
  • [ ] 重写分面结果显示逻辑
  • [ ] 移除所有 ES DSL 聚合代码

测试

  • [ ] 测试简单搜索功能
  • [ ] 测试范围过滤功能
  • [ ] 测试分面搜索功能
  • [ ] 测试组合查询功能
  • [ ] 测试排序功能

文档

  • [ ] 更新 API 调用文档
  • [ ] 更新代码注释
  • [ ] 通知团队成员新的 API 变更

获取帮助

  • API 文档: API_DOCUMENTATION.md
  • 使用示例: API_EXAMPLES.md
  • 测试脚本: test_new_api.py
  • 变更日志: CHANGES.md

迁移难度: ⭐⭐ (简单到中等)
预计时间: 1-2 小时
优势: 更灵活、更通用、更易用的 API


版本: 3.0
日期: 2024-11-12