06e79082
tangwang
接口优化
|
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
|
非常好的问题!让我详细解释一下搜索应用的调用链路和 OAuth 的关系。
## 调用链路说明
### 1. **OAuth 认证的作用**
OAuth **不是用于前端搜索调用**的,它主要用于:
```mermaid
graph LR
A[商家安装APP] --> B[OAuth授权]
B --> C[获取Access Token]
C --> D[后端拉取商品数据]
D --> E[建立ES索引]
E --> F[搜索服务就绪]
```
**OAuth Token 的用途:**
- ✅ 你的后端调用店匠 Admin API(拉取商品、订单、客户数据)
- ✅ 注册 Webhook(接收数据变更通知)
- ❌ **不用于**前端买家搜索时的调用
### 2. **前端搜索的实际调用链路**
当买家在店铺搜索商品时:
```
买家浏览器 → 搜索框组件(Liquid/JS) → 你的搜索API → Elasticsearch → 返回结果
```
**关键点:**
- 前端 JavaScript **直接调用**你的公开搜索 API
- 不需要 OAuth Token
- 需要传递 `store_id` 参数来标识是哪个店铺
### 3. **搜索接口的两种方案**
[2 tools called]
## 详细解答
### 📍 **方案 A:前端直接调用(推荐用于公开搜索)**
**流程:**
```javascript
// 在店铺前端页面(买家浏览器中)
const response = await fetch('https://your-domain.com/api/search/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: "蓝牙耳机",
tenant: "tenant_47167113-1", // 店铺标识
size: 24,
filters: {},
facets: ['product_type', 'vendor']
})
});
```
**你的搜索 API 需要:**
1. **允许跨域访问(CORS)**:
```python
# Python FastAPI 示例
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 或指定店匠域名白名单
allow_credentials=True,
allow_methods=["POST"],
allow_headers=["*"],
)
```
2. **根据 store_id 隔离数据**:
```python
@app.post("/api/search/products")
async def search(request: SearchRequest):
# 从 tenant 参数提取 tenant_id
tenant_id = extract_tenant_id(request.tenant)
# 使用租户专属索引
index_name = f"shoplazza_products_{tenant_id}"
# 执行搜索
results = es_client.search(index=index_name, body=query)
return results
```
3. **不需要 OAuth Token 认证**(因为是公开查询)
---
### 📍 **方案 B:通过 Java 后端中转(更安全)**
**流程:**
```
买家浏览器 → Java后端(/api/search/products?storeId=xxx) → Python搜索服务 → ES
```
**Java 后端代码:**
```java
@RestController
@RequestMapping("/api/search")
public class SearchController {
@PostMapping("/products")
public ResponseEntity<SearchResponse> search(
@RequestParam String storeId, // 从URL参数获取店铺ID
@RequestBody SearchRequest request) {
// 1. 验证店铺ID(可选:检查域名白名单)
ShopConfig shop = shopConfigMapper.selectByStoreId(storeId);
if (shop == null) {
return ResponseEntity.notFound().build();
}
// 2. 添加租户隔离参数
request.setTenant("tenant_" + shop.getTenantId());
// 3. 调用 Python 搜索服务
SearchResponse response = restTemplate.postForObject(
"http://localhost:6002/search/",
request,
SearchResponse.class
);
// 4. 记录搜索日志
searchLogService.logSearch(shop.getId(), request.getQuery(), response.getTotal());
return ResponseEntity.ok(response);
}
}
```
**前端调用(带 store_id):**
```javascript
const response = await fetch(
`https://your-domain.com/api/search/products?storeId=${config.storeId}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: "蓝牙耳机",
size: 24,
filters: {},
facets: ['product_type', 'vendor']
})
}
);
```
---
## 🔐 OAuth 认证在整个系统中的位置
```mermaid
graph TB
subgraph "1. 商家安装阶段(使用OAuth)"
A[商家安装APP] --> B[OAuth授权]
B --> C[获取Access Token]
C --> D[存储Token到数据库]
end
subgraph "2. 数据准备阶段(使用OAuth Token)"
D --> E[定时任务启动]
E --> F[使用Token调用店匠API]
F --> G[拉取商品/订单数据]
G --> H[建立ES索引]
end
subgraph "3. 买家搜索阶段(不需要OAuth)"
I[买家访问店铺] --> J[输入搜索词]
J --> K[前端JS直接调用搜索API]
K --> L[搜索ES索引]
L --> M[返回结果]
end
H -.索引建立后.-> L
```
**关键理解:**
- **OAuth Token** = 你的后端 ↔ 店匠 Admin API(拉数据用)
- **前端搜索** = 买家浏览器 ↔ 你的搜索 API(不需要 OAuth)
---
## ✅ 你需要做的事情
### 1. **搜索 API 设计**
当前你的 Python 搜索服务应该已经支持:
```python
POST http://your-domain:6002/search/
Content-Type: application/json
{
"query": "蓝牙耳机",
"tenant": "tenant_1", // 重要!用于隔离不同店铺
"size": 20,
"filters": {},
"facets": ["product_type", "vendor"]
}
```
### 2. **CORS 配置**(如果前端直接调用)
在 Python FastAPI 服务中添加:
```python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://*.myshoplaza.com", # 店匠域名
"https://your-domain.com"
],
allow_methods=["POST", "GET"],
allow_headers=["*"],
)
```
### 3. **店铺标识传递**
在前端 Liquid 模板中获取店铺域名:
```liquid
<script>
window.AI_SEARCH_CONFIG = {
storeId: "{{ shop.domain }}", // 店匠自动注入
apiEndpoint: "https://your-domain.com/api/search/products"
};
</script>
```
### 4. **安全性考虑**
- ✅ 租户隔离:根据 `tenant` 参数查询不同索引
- ✅ 域名白名单:只允许店匠域名调用
- ✅ 速率限制:防止恶意请求
- ❌ **不需要**在每次搜索时验证 OAuth Token
---
## 🎯 总结
1. **OAuth 只用于后台数据同步**,不用于前端搜索
2. **前端搜索直接调用你的公开 API**,通过 `storeId` 参数隔离数据
3. **你的搜索接口可以是无认证的**,但必须做好租户隔离
4. **推荐方案**:前端 → 你的 Java 后端 → Python 搜索服务(便于日志统计和安全控制)
有其他疑问吗?
|