Commit b0ad8e894c2d3bd149ae8cdfd2ff02becfaf56c6
1 parent
ddc4abd1
文档完善
Showing
1 changed file
with
28 additions
and
272 deletions
Show diff stats
docs/搜索API对接指南.md
| ... | ... | @@ -35,31 +35,36 @@ curl -X POST "http://localhost:6002/search/" \ |
| 35 | 35 | }' |
| 36 | 36 | ``` |
| 37 | 37 | |
| 38 | -### Python示例 | |
| 38 | +### curl示例:带过滤与分页 | |
| 39 | 39 | |
| 40 | -```python | |
| 41 | -import requests | |
| 42 | - | |
| 43 | -url = "http://localhost:6002/search/" | |
| 44 | -response = requests.post(url, json={"query": "芭比娃娃"}) | |
| 45 | -data = response.json() | |
| 46 | -print(f"找到 {data['total']} 个结果") | |
| 40 | +```bash | |
| 41 | +curl -X POST "http://localhost:6002/search/" \ | |
| 42 | + -H "Content-Type: application/json" \ | |
| 43 | + -d '{ | |
| 44 | + "tenant_id": "demo-tenant", | |
| 45 | + "query": "芭比娃娃 AND 配件", | |
| 46 | + "size": 5, | |
| 47 | + "from": 10, | |
| 48 | + "filters": { | |
| 49 | + "vendor_keyword": ["乐高", "孩之宝"] | |
| 50 | + }, | |
| 51 | + "sort_by": "min_price", | |
| 52 | + "sort_order": "asc" | |
| 53 | + }' | |
| 47 | 54 | ``` |
| 48 | 55 | |
| 49 | -### JavaScript示例 | |
| 56 | +### curl示例:开启调试与分面 | |
| 50 | 57 | |
| 51 | -```javascript | |
| 52 | -const response = await fetch('http://localhost:6002/search/', { | |
| 53 | - method: 'POST', | |
| 54 | - headers: { | |
| 55 | - 'Content-Type': 'application/json' | |
| 56 | - }, | |
| 57 | - body: JSON.stringify({ | |
| 58 | - query: '芭比娃娃' | |
| 59 | - }) | |
| 60 | -}); | |
| 61 | -const data = await response.json(); | |
| 62 | -console.log(`找到 ${data.total} 个结果`); | |
| 58 | +```bash | |
| 59 | +curl -X POST "http://localhost:6002/search/" \ | |
| 60 | + -H "Content-Type: application/json" \ | |
| 61 | + -d '{ | |
| 62 | + "tenant_id": "demo-tenant", | |
| 63 | + "query": "芭比娃娃", | |
| 64 | + "facets": ["category_keyword", "vendor_keyword"], | |
| 65 | + "min_score": 0.2, | |
| 66 | + "debug": true | |
| 67 | + }' | |
| 63 | 68 | ``` |
| 64 | 69 | |
| 65 | 70 | --- |
| ... | ... | @@ -89,6 +94,7 @@ console.log(`找到 ${data.total} 个结果`); |
| 89 | 94 | |
| 90 | 95 | ```json |
| 91 | 96 | { |
| 97 | + "tenant_id": "string (required)", | |
| 92 | 98 | "query": "string (required)", |
| 93 | 99 | "size": 10, |
| 94 | 100 | "from": 0, |
| ... | ... | @@ -108,6 +114,7 @@ console.log(`找到 ${data.total} 个结果`); |
| 108 | 114 | |
| 109 | 115 | | 参数 | 类型 | 必填 | 默认值 | 说明 | |
| 110 | 116 | |------|------|------|--------|------| |
| 117 | +| `tenant_id` | string | ✅ | - | 租户ID,用于隔离不同站点或客户的数据 | | |
| 111 | 118 | | `query` | string | ✅ | - | 搜索查询字符串,支持布尔表达式(AND, OR, RANK, ANDNOT) | |
| 112 | 119 | | `size` | integer | ❌ | 10 | 返回结果数量(1-100) | |
| 113 | 120 | | `from` | integer | ❌ | 0 | 分页偏移量(用于分页) | |
| ... | ... | @@ -539,226 +546,6 @@ curl -X POST "http://localhost:6002/search/image" \ |
| 539 | 546 | |
| 540 | 547 | --- |
| 541 | 548 | |
| 542 | -## 错误处理 | |
| 543 | - | |
| 544 | -### 错误响应格式 | |
| 545 | - | |
| 546 | -```json | |
| 547 | -{ | |
| 548 | - "error": "错误信息", | |
| 549 | - "detail": "详细错误信息(可选)" | |
| 550 | -} | |
| 551 | -``` | |
| 552 | - | |
| 553 | -### 常见错误码 | |
| 554 | - | |
| 555 | -| HTTP状态码 | 说明 | 处理建议 | | |
| 556 | -|-----------|------|---------| | |
| 557 | -| 200 | 成功 | - | | |
| 558 | -| 400 | 请求参数错误 | 检查请求参数格式和必填字段 | | |
| 559 | -| 404 | 接口不存在 | 检查接口路径 | | |
| 560 | -| 500 | 服务器内部错误 | 联系技术支持 | | |
| 561 | - | |
| 562 | -### 错误处理示例 | |
| 563 | - | |
| 564 | -**Python**: | |
| 565 | -```python | |
| 566 | -import requests | |
| 567 | - | |
| 568 | -try: | |
| 569 | - response = requests.post(url, json=payload, timeout=10) | |
| 570 | - response.raise_for_status() | |
| 571 | - data = response.json() | |
| 572 | -except requests.exceptions.HTTPError as e: | |
| 573 | - print(f"HTTP错误: {e}") | |
| 574 | - if response.status_code == 400: | |
| 575 | - error_data = response.json() | |
| 576 | - print(f"错误详情: {error_data.get('detail')}") | |
| 577 | -except requests.exceptions.RequestException as e: | |
| 578 | - print(f"请求异常: {e}") | |
| 579 | -``` | |
| 580 | - | |
| 581 | -**JavaScript**: | |
| 582 | -```javascript | |
| 583 | -try { | |
| 584 | - const response = await fetch(url, { | |
| 585 | - method: 'POST', | |
| 586 | - headers: { 'Content-Type': 'application/json' }, | |
| 587 | - body: JSON.stringify(payload) | |
| 588 | - }); | |
| 589 | - | |
| 590 | - if (!response.ok) { | |
| 591 | - const error = await response.json(); | |
| 592 | - throw new Error(error.error || `HTTP ${response.status}`); | |
| 593 | - } | |
| 594 | - | |
| 595 | - const data = await response.json(); | |
| 596 | -} catch (error) { | |
| 597 | - console.error('搜索失败:', error.message); | |
| 598 | -} | |
| 599 | -``` | |
| 600 | - | |
| 601 | ---- | |
| 602 | - | |
| 603 | -### 5. 代码示例 | |
| 604 | - | |
| 605 | -**完整的搜索函数(Python)**: | |
| 606 | - | |
| 607 | -```python | |
| 608 | -import requests | |
| 609 | -from typing import Dict, Any, Optional, List | |
| 610 | - | |
| 611 | -class SearchClient: | |
| 612 | - def __init__(self, base_url: str = "http://localhost:6002"): | |
| 613 | - self.base_url = base_url | |
| 614 | - self.timeout = 10 | |
| 615 | - | |
| 616 | - def search( | |
| 617 | - self, | |
| 618 | - query: str, | |
| 619 | - size: int = 20, | |
| 620 | - from_: int = 0, | |
| 621 | - filters: Optional[Dict] = None, | |
| 622 | - range_filters: Optional[Dict] = None, | |
| 623 | - facets: Optional[List] = None, | |
| 624 | - sort_by: Optional[str] = None, | |
| 625 | - sort_order: str = "desc" | |
| 626 | - ) -> Dict[str, Any]: | |
| 627 | - """ | |
| 628 | - 执行搜索查询 | |
| 629 | - | |
| 630 | - Args: | |
| 631 | - query: 搜索查询字符串 | |
| 632 | - size: 返回结果数量 | |
| 633 | - from_: 分页偏移量 | |
| 634 | - filters: 精确匹配过滤器 | |
| 635 | - range_filters: 范围过滤器 | |
| 636 | - facets: 分面配置 | |
| 637 | - sort_by: 排序字段 | |
| 638 | - sort_order: 排序方向 | |
| 639 | - | |
| 640 | - Returns: | |
| 641 | - 搜索结果字典 | |
| 642 | - """ | |
| 643 | - url = f"{self.base_url}/search/" | |
| 644 | - payload = { | |
| 645 | - "query": query, | |
| 646 | - "size": size, | |
| 647 | - "from": from_, | |
| 648 | - } | |
| 649 | - | |
| 650 | - if filters: | |
| 651 | - payload["filters"] = filters | |
| 652 | - if range_filters: | |
| 653 | - payload["range_filters"] = range_filters | |
| 654 | - if facets: | |
| 655 | - payload["facets"] = facets | |
| 656 | - if sort_by: | |
| 657 | - payload["sort_by"] = sort_by | |
| 658 | - payload["sort_order"] = sort_order | |
| 659 | - | |
| 660 | - try: | |
| 661 | - response = requests.post( | |
| 662 | - url, | |
| 663 | - json=payload, | |
| 664 | - timeout=self.timeout | |
| 665 | - ) | |
| 666 | - response.raise_for_status() | |
| 667 | - return response.json() | |
| 668 | - except requests.exceptions.RequestException as e: | |
| 669 | - raise Exception(f"搜索请求失败: {e}") | |
| 670 | - | |
| 671 | -# 使用示例 | |
| 672 | -client = SearchClient() | |
| 673 | -result = client.search( | |
| 674 | - query="玩具", | |
| 675 | - size=20, | |
| 676 | - filters={"category_keyword": "益智玩具"}, | |
| 677 | - range_filters={"min_price": {"gte": 50, "lte": 200}}, | |
| 678 | - facets=["category_keyword", "vendor_keyword"], | |
| 679 | - sort_by="min_price", | |
| 680 | - sort_order="asc" | |
| 681 | -) | |
| 682 | - | |
| 683 | -print(f"找到 {result['total']} 个结果") | |
| 684 | -for product in result['results']: | |
| 685 | - print(f"{product['title']} - ¥{product['price']}") | |
| 686 | -``` | |
| 687 | - | |
| 688 | -**完整的搜索函数(JavaScript)**: | |
| 689 | - | |
| 690 | -```javascript | |
| 691 | -class SearchClient { | |
| 692 | - constructor(baseUrl = 'http://localhost:6002') { | |
| 693 | - this.baseUrl = baseUrl; | |
| 694 | - this.timeout = 10000; | |
| 695 | - } | |
| 696 | - | |
| 697 | - async search({ | |
| 698 | - query, | |
| 699 | - size = 20, | |
| 700 | - from = 0, | |
| 701 | - filters = null, | |
| 702 | - rangeFilters = null, | |
| 703 | - facets = null, | |
| 704 | - sortBy = null, | |
| 705 | - sortOrder = 'desc' | |
| 706 | - }) { | |
| 707 | - const url = `${this.baseUrl}/search/`; | |
| 708 | - const payload = { | |
| 709 | - query, | |
| 710 | - size, | |
| 711 | - from, | |
| 712 | - }; | |
| 713 | - | |
| 714 | - if (filters) payload.filters = filters; | |
| 715 | - if (rangeFilters) payload.range_filters = rangeFilters; | |
| 716 | - if (facets) payload.facets = facets; | |
| 717 | - if (sortBy) { | |
| 718 | - payload.sort_by = sortBy; | |
| 719 | - payload.sort_order = sortOrder; | |
| 720 | - } | |
| 721 | - | |
| 722 | - try { | |
| 723 | - const response = await fetch(url, { | |
| 724 | - method: 'POST', | |
| 725 | - headers: { | |
| 726 | - 'Content-Type': 'application/json' | |
| 727 | - }, | |
| 728 | - body: JSON.stringify(payload), | |
| 729 | - signal: AbortSignal.timeout(this.timeout) | |
| 730 | - }); | |
| 731 | - | |
| 732 | - if (!response.ok) { | |
| 733 | - const error = await response.json(); | |
| 734 | - throw new Error(error.error || `HTTP ${response.status}`); | |
| 735 | - } | |
| 736 | - | |
| 737 | - return await response.json(); | |
| 738 | - } catch (error) { | |
| 739 | - throw new Error(`搜索请求失败: ${error.message}`); | |
| 740 | - } | |
| 741 | - } | |
| 742 | -} | |
| 743 | - | |
| 744 | -// 使用示例 | |
| 745 | -const client = new SearchClient(); | |
| 746 | -const result = await client.search({ | |
| 747 | - query: '玩具', | |
| 748 | - size: 20, | |
| 749 | - filters: { category_keyword: '益智玩具' }, | |
| 750 | - rangeFilters: { min_price: { gte: 50, lte: 200 } }, | |
| 751 | - facets: ['category_keyword', 'vendor_keyword'], | |
| 752 | - sortBy: 'min_price', | |
| 753 | - sortOrder: 'asc' | |
| 754 | -}); | |
| 755 | - | |
| 756 | -console.log(`找到 ${result.total} 个结果`); | |
| 757 | -result.results.forEach(product => { | |
| 758 | - console.log(`${product.title} - ¥${product.price}`); | |
| 759 | -}); | |
| 760 | -``` | |
| 761 | - | |
| 762 | 549 | --- |
| 763 | 550 | |
| 764 | 551 | ## 其他接口 |
| ... | ... | @@ -933,37 +720,6 @@ curl "http://localhost:6002/search/12345" |
| 933 | 720 | |
| 934 | 721 | --- |
| 935 | 722 | |
| 936 | -## 常见问题(FAQ) | |
| 937 | - | |
| 938 | -**Q1: 如何判断一个字段应该用哪种过滤器?** | |
| 939 | -`filters` 针对 keyword/布尔/整数字段做精确匹配;`range_filters` 针对数值或日期字段做区间查询。 | |
| 940 | - | |
| 941 | -**Q2: 可以同时使用多个过滤器吗?** | |
| 942 | -可以,所有过滤条件为 AND 关系。例如: | |
| 943 | - | |
| 944 | -```json | |
| 945 | -{ | |
| 946 | - "filters": { | |
| 947 | - "category_keyword": "玩具", | |
| 948 | - "vendor_keyword": "乐高" | |
| 949 | - }, | |
| 950 | - "range_filters": { | |
| 951 | - "min_price": {"gte": 50, "lte": 200} | |
| 952 | - } | |
| 953 | -} | |
| 954 | -``` | |
| 955 | - | |
| 956 | -**Q4: 分面结果里的 `selected` 字段含义是什么?** | |
| 957 | -指示该分面值是否已在当前过滤条件中,前端可据此高亮。 | |
| 958 | - | |
| 959 | -**Q5: 如何自定义排序?** | |
| 960 | -设置 `sort_by` 和 `sort_order`,常用字段包括 `min_price`, `max_price`, `title`, `create_time`, `update_time`, `relevance_score`。 | |
| 961 | - | |
| 962 | -**Q6: 如何启用调试模式?** | |
| 963 | -添加 `debug: true`,即可在响应中看到 `debug_info`(ES DSL、阶段耗时、打分细节)。 | |
| 964 | - | |
| 965 | ---- | |
| 966 | - | |
| 967 | 723 | ## 附录 |
| 968 | 724 | |
| 969 | 725 | ### 常用字段列表 | ... | ... |