docs: 更新文档
This commit is contained in:
@@ -431,9 +431,11 @@ if missing:
|
||||
|
||||
## 7. 缓存策略
|
||||
|
||||
Ozon 只定义查询参数和分页规则,不规定 Atlas 的 TTL。2026-09-18 的[架构决策](../../architecture/platform-product-reference-catalog.md)将属性定义和字典候选值设为平台级参考数据:同一平台所有门店共用,本阶段不维护门店差异层;此方案尚未实现。当前代码仍以门店、类目和类型为作用域按需同步,用户从 Atlas 本地缓存读取。
|
||||
Ozon 只定义查询参数和分页规则,不规定 Atlas 的 TTL。Atlas 已按 2026-09-18 的[架构决策](../../architecture/platform-product-reference-catalog.md)将属性定义和字典候选值迁移为平台级参考数据:同一平台所有门店共用,本阶段不维护门店差异层。商品编辑器、草稿校验和发布预检都从 Atlas 平台缓存读取,Ozon 请求统一由 Worker 使用平台参考源凭据执行。
|
||||
|
||||
目标同步方式:对活跃 `description_category_id + type_id` 的属性定义定期后台复核;字典候选值至少以类目、类型、属性、字典 ID 和语言限定作用域。`category_dependent=false` 可以作为进一步去重的依据,但在跨账号/跨类目验证前不能只以 `dictionary_id` 合并。小字典可完整分页并标记 `complete`;大字典先保存常用页与搜索命中,并明确标记 `partial`。搜索结果不是全集,不能据此清理未命中的旧值。定时和管理员手动同步必须共用异步任务链路,成功后原子切换版本,失败保留上次成功值。
|
||||
类型属性和字典默认 14 天内 fresh,14~21 天(含 21 天)stale 时旧缓存可用并低优先级刷新,严格超过 21 天或从未同步时必须高优先级刷新成功后才能作为当前 schema、候选值或发布依据。系统不周期性扫描属性和字典;刷新由管理员批次/单字典操作、普通用户访问或发布预检触发。
|
||||
|
||||
字典候选值至少以平台、类目类型、Atlas 属性 UUID、外部值 ID 和语言限定作用域。`category_dependent=false` 可以作为进一步去重的依据,但在跨账号/跨类目验证前不能只以 `dictionary_id` 合并。小字典可完整分页并标记 `complete`;大字典保存已同步页与搜索命中并明确标记 `partial`。搜索结果不是全集,不能据此清理未命中的旧值;partial 字典的规范化关键词有独立缓存状态。成功后原子更新缓存,失败保留上次成功值。
|
||||
|
||||
---
|
||||
|
||||
@@ -510,106 +512,13 @@ Ozon 只定义查询参数和分页规则,不规定 Atlas 的 TTL。2026-09-18
|
||||
|
||||
---
|
||||
|
||||
## 10. V2 项目集成
|
||||
## 10. Atlas 当前集成
|
||||
|
||||
### API 层(已实现)
|
||||
|
||||
```python
|
||||
# server/api/categories.py
|
||||
|
||||
@router.get("/categories/{category_id}/attributes")
|
||||
async def get_attributes(
|
||||
category_id: int,
|
||||
type_id: int = Query(...),
|
||||
shop_id: str = Query(...),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
shop = await get_shop(db, shop_id)
|
||||
attrs = await ozon_client.get_category_attributes(
|
||||
shop.client_id_dec,
|
||||
shop.api_key_dec,
|
||||
category_id,
|
||||
type_id
|
||||
)
|
||||
return {"result": attrs}
|
||||
|
||||
@router.get("/categories/attribute/{attribute_id}/values")
|
||||
async def get_attribute_values(
|
||||
attribute_id: int,
|
||||
category_id: int = Query(...),
|
||||
type_id: int = Query(...),
|
||||
q: str = Query(None), # 搜索关键词
|
||||
shop_id: str = Query(...),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
shop = await get_shop(db, shop_id)
|
||||
|
||||
if q and len(q) >= 2:
|
||||
# 搜索接口
|
||||
values = await ozon_client.search_attribute_values(
|
||||
shop.client_id_dec, shop.api_key_dec,
|
||||
attribute_id, category_id, type_id, q
|
||||
)
|
||||
else:
|
||||
# 全量拉取(分页)
|
||||
values = await ozon_client.get_attribute_values(
|
||||
shop.client_id_dec, shop.api_key_dec,
|
||||
attribute_id, category_id, type_id
|
||||
)
|
||||
|
||||
return {"result": values}
|
||||
```
|
||||
|
||||
### 前端(待实现)
|
||||
|
||||
```tsx
|
||||
// studio/src/pages/product/components/AttributeMapper.tsx
|
||||
|
||||
import { Form, Select, Input, Tag } from 'antd';
|
||||
|
||||
export function AttributeMapper({ categoryId, typeId, rawParams, onChange }) {
|
||||
const [attributes, setAttributes] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/categories/${categoryId}/attributes?type_id=${typeId}`)
|
||||
.then(r => r.json())
|
||||
.then(data => setAttributes(data.result));
|
||||
}, [categoryId, typeId]);
|
||||
|
||||
// 自动匹配
|
||||
const autoMatch = () => {
|
||||
const matched = rawParams.map(p => {
|
||||
const attr = attributes.find(a =>
|
||||
normalize(a.name) === normalize(p.key)
|
||||
);
|
||||
return attr ? { ...p, attrId: attr.id, attr } : p;
|
||||
});
|
||||
return matched;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{autoMatch().map((item, i) => (
|
||||
<Form.Item
|
||||
key={i}
|
||||
label={item.key}
|
||||
required={item.attr?.is_required}
|
||||
>
|
||||
{item.attr?.dictionary_id > 0 ? (
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="选择字典值"
|
||||
onSearch={(q) => fetchValues(item.attr.id, q)}
|
||||
/>
|
||||
) : (
|
||||
<Input defaultValue={item.value} />
|
||||
)}
|
||||
</Form.Item>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
- 商品编辑器通过 `GET /api/product-platform-profiles/{profile_id}/attribute-schema` 读取当前平台类型属性,响应包含 Atlas `attribute_id` 和统一 `cache` 元数据。
|
||||
- 字典通过 `GET /api/product-platform-profiles/{profile_id}/attribute-dictionaries/{attribute_id}` 读取;`attribute_id` 是平台属性 UUID,不以门店或单独 `dictionary_id` 定位。
|
||||
- fresh/stale 立即返回;expired/missing 创建或复用高优先级任务并最多等待 5 秒,仍未完成时返回 `202`。浏览器不会拿到平台凭据或未落库的 Ozon 临时结果。
|
||||
- 发布前使用 `POST /api/product-platform-profiles/{profile_id}/publish-preflights`,只刷新当前类型 schema 和所选 SKU 实际使用的字典,再执行必填、scope、字典有效性及其他首次上架校验。
|
||||
- 管理后台支持三种类型属性批次、单字典同步、缓存策略弹窗和最近任务观察;类目树固定每 7 天同步,属性与字典不做周期性全量扫描。
|
||||
|
||||
---
|
||||
|
||||
@@ -617,5 +526,5 @@ export function AttributeMapper({ categoryId, typeId, rawParams, onChange }) {
|
||||
|
||||
- [02-category-tree.md](./02-category-tree.md) —— 获取类目树
|
||||
- [04-product-import.md](./04-product-import.md) —— 发布商品(使用属性)
|
||||
- [docs/v2/database.md](../v2/database.md) §2.7 —— 属性缓存表结构
|
||||
- [docs/v2/ozon-publish.md](../v2/ozon-publish.md) §3 —— 属性映射策略
|
||||
- [平台目录缓存刷新方案](../../architecture/platform-catalog-cache-refresh-plan.md) —— 属性和字典缓存状态机
|
||||
- [商品编辑与发布 API](../../api/product-editor-and-publishing.md) —— 当前 Atlas 端点与发布预检
|
||||
|
||||
Reference in New Issue
Block a user