16 KiB
Ozon Seller Kit 总体架构
1. 项目定位
围绕 Ozon 跨境上品的一套自用工具链,覆盖采集 → 编辑 → 发布全链路,货源来自 Ozon 竞品页(跟卖)与 1688/淘宝/拼多多(补素材)。
四个组成部分,各自独立可用、通过明确契约衔接:
| # | 部分 | 形态 | 状态 | 职责 |
|---|---|---|---|---|
| ① | 工具台 v1 | 静态页(原生 JS + Tailwind CDN) | ✅ 在用 | 计价、上品登记、水印、俄文文案。冻结维护,不重构 |
| ② | 采集插件 | Chrome MV3 扩展 | 🔨 待开发 | Ozon/1688 商品页采集 → 落本地文件夹 |
| ③ | 发布工作台 | Web 应用 | 📋 待设计 | 导入本地文件夹 → 编辑/图片处理 → 提交发布 |
| ④ | 服务端 | FastAPI | 🔨 部分就绪 | AI 文案、图片处理、Ozon API 代理、凭证保管 |
关键边界:① 与 ③ 并存不互相替代。① 是已验证好用的轻量工具,③ 是面向"整商品发布"的新流程;③ 成熟后 ① 的计价器可能被吸收,但那是以后的事。
2. 目录结构
ozon-seller-kit/
├── server/ # ④ 服务端(FastAPI)
│ ├── main.py
│ ├── api/ # ai / image / ozon / collection
│ ├── services/
│ ├── schemas/ # Pydantic = 数据契约真源
│ ├── config/
│ └── requirements.txt
│
├── web/ # ① 工具台 v1 —— 原地冻结,不改目录名
│ ├── ozonSeller.html
│ ├── js/ css/ imgs/
│ └── README.md # 标注「v1,仅修 bug」
│
├── extension/ # ② 采集插件(WXT + TS)
│ ├── entrypoints/
│ ├── src/{profiles,collector,export,storage}/
│ └── package.json
│
├── studio/ # ③ 发布工作台
│ └── (技术栈待定,见 §8)
│
├── packages/
│ └── schema/ # 跨端共享契约(TS 类型 + JSON Schema)
│
├── reference/
│ └── 1688-extension/ # 反编译参考资料,非本项目代码
│
├── docs/ # 全部文档集中于此
│ ├── architecture.md # ← 本文
│ ├── deployment.md
│ ├── ai-copy-backend-plan.md
│ ├── contracts/ # 数据契约说明
│ ├── extension/ # 插件方案
│ └── studio/ # 发布工作台方案
│
├── start.command
└── .env / .env.example
两处与现状不同,需要迁移(§9):后端从仓库根收进 server/;seller-helper/ 拆解为 reference/ + docs/extension/。
2.1 为什么后端要收进 server/
现状后端散在仓库根(main.py、api/、services/、schemas/、config/)。四个部分并列后,根目录会同时出现 Python 包目录和三个前端项目目录,api/ 这种名字看不出属于谁。收进 server/ 后每个顶层目录一一对应一个部分。
代价:start.command 与 docs/deployment.md 里的启动路径要改,.env 加载路径 parents[1] 要跟着调。Python 内部 import 全是 from api import ... 这类顶层相对形式,只要工作目录切到 server/ 就不受影响。
这是唯一有破坏性的改动,建议在动 studio/ 之前一次做完,不要拖到中途。 如果你想零风险,也可以让后端留在根目录——架构其余部分不依赖这个决定。
2.2 为什么 web/ 不改名
改名会动 main.py 的挂载路径、start.command、部署文档,收益只是"名字更清楚"。目录名保持 web/,在里面放一个 README 标注定位即可。
路由上两代共存:studio/ 上线后占 /studio,web/ 继续在 /ozonSeller.html。谁占根路径是路由配置问题,跟目录名无关。
3. 核心数据契约:商品文件夹
这是整个架构最重要的一个决定。 四个部分之间不直接调用彼此的代码,只认一个约定:磁盘上的「商品文件夹」。
儿童保温杯_316不锈钢/
├── product.json # 商品数据(唯一真源)
├── sources.json # 采集溯源:哪个平台、哪个 URL、什么时候
└── images/
├── main/ # main-001.jpg …
├── sku/ # sku-001-blue.jpg(文件名带规格名)
├── detail/ # detail-001.jpg …
└── video/
数据流因此变成:
插件 ──写──> 商品文件夹 ──读──> 发布工作台 ──调用──> 服务端 ──> Ozon API
↑
1688 插件二次采集追加
三个好处:
- 插件和工作台完全解耦。插件不需要知道工作台存在,反之亦然。任一端重写不影响另一端。
- 中间产物可见可改。采集结果就是普通文件夹,能用 Finder 看、能手动补图、能备份、能在两台机器间拷。
- 跨平台合并天然成立。Ozon 采完,切到 1688 采同类商品,选同一个文件夹继续写入——就是往同一目录追加文件,不需要任何服务端参与。
3.1 product.json
结构对齐 Ozon ProductAPI_ImportProductsV3,但不等于它的请求体。区别在于采集阶段拿不到的字段留空,由工作台补齐:
| 字段 | 采集阶段 | 工作台补齐 |
|---|---|---|
name / description |
✅ 原文(可能是俄文) | 改写/翻译 |
images |
本地相对路径 | 上传图床后换成公网 URL |
offer_id |
❌ 空 | 必须填自己的货号(跟卖场景下不能沿用竞品的) |
description_category_id |
❌ 空 | 类目选择/推荐 |
attributes[].id |
❌ 空 | 查类目属性字典映射 |
| 尺寸重量 | 参数表里有就带上 | 校验补全 |
price / old_price |
采到竞品价,仅供参考 | 计价器算出 |
所以 product.json 有个 _meta.stage 字段标明它处在哪个阶段:collected → edited → published。
契约细节见 [docs/contracts/product-json.md](./contracts/product-json.md)。
3.2 契约真源与双语言实现
Pydantic(server/schemas/)是真源,因为服务端最终要用它做校验。由此派生:
server/schemas/product.py (Pydantic)
│
├── 导出 JSON Schema ──> packages/schema/product.schema.json
│ │
│ └── 生成 TS 类型 ──> 插件 / 工作台
└── 服务端运行时校验
不上 codegen 流水线(单人项目不值得):插件端手写一份对应的 TS interface,配一个 fixture 文件双向跑一遍校验做契约测试。schema 变更时测试会红。
4. 各部分职责与边界
① 工具台 v1(web/)
冻结。 只修 bug,不加功能、不重构、不迁技术栈。它当前的价值是「已经好用」,任何改动都是风险。
新需求一律进 studio/。计价逻辑(web/js/app.js)后续会被 studio 复用——那时候是读它、抄它的公式,不是改它。
② 采集插件(extension/)
只做四件事:识别页面、提取素材、分组、写文件夹。
不做:LLM 调用、图片处理、Ozon API、持有任何密钥。
一期只支持 Ozon(跟卖是第一优先级),1688 二期。详见 [docs/extension/plan.md](./extension/plan.md)。
③ 发布工作台(studio/)
导入本地文件夹 → 编辑 → 发布。三块能力:
- 表单编辑:product.json 各字段,类目选择,属性映射,计价(复用 v1 公式)
- 图片处理:水印、白底、图内翻译、图生图 —— 重活走服务端
- 发布:提交 Ozon 草稿,回填
product_id
④ 服务端(server/)
唯一持有密钥的地方。当前有 /api/ai/*(文案);待补 /api/image/*、/api/ozon/*。
Ozon 发布有个硬约束值得提前知道:ProductAPI_ImportProductsV3 的 images 只接受公网可访问的 URL,Ozon 服务器会主动来拉。本地文件夹里的图必须先上传到图床/对象存储才能发布。这决定了服务端必须有图床能力,也决定了「本地文件夹」方案无法绕过服务端直接发布。
5. 端到端数据流
① 浏览 Ozon 竞品页
│ 点插件按钮 → 侧边栏打开 → 人工确认页面加载完 → 点采集
▼
② 侧边栏表单化展示采集结果(可二次编辑),图片分组勾选
│ 选保存目录 → 导出
▼
③ 本地商品文件夹(product.json + images/)
│ (可选)切到 1688 采同类商品,选同一文件夹追加
▼
④ 发布工作台:选择文件夹导入
│ 编辑字段 / 加水印 / 图生图 / 定类目 / 计价
▼
⑤ 服务端:图片上传图床 → 属性字典校验 → 提交 Ozon 草稿
▼
⑥ 回填 product_id 到 product.json,stage 置 published
每一步的产物都落盘,中断了可以从任意一步接着来。
6. 技术栈
| 部分 | 技术栈 | 说明 |
|---|---|---|
| ① web | 原生 JS + Tailwind CDN | 不动 |
| ② extension | WXT + React + TS strict | WXT 比 Plasmo 活跃 |
| ③ studio | 待定(见 §8) | |
| ④ server | FastAPI + Pydantic | 已有 |
| 共享 | pnpm workspace | 只为 packages/schema 共享,不上 turborepo |
pnpm workspace 的唯一目的是让插件和 studio 共用契约类型。pnpm-workspace.yaml 三行搞定,不引入构建编排复杂度。
7. 服务端演进
当前是无状态的:只有 AI 文案代理,没有数据库。按需要逐步加,不要一次上齐:
| 阶段 | 触发条件 | 要加什么 |
|---|---|---|
| 现在 | — | 无状态,/api/ai/* |
| S1 | studio 要处理图片 | /api/image/*(水印/白底/翻译),仍无状态:收图返图 |
| S2 | 要发布到 Ozon | /api/ozon/* + 图床 + 类目字典缓存(SQLite 够用) |
| S3 | 图片处理变慢(图生图、视频) | 任务队列 + /api/job/:id 轮询 |
| S4 | 想要跨设备同步 | 商品库落库,本地文件夹降级为导入导出格式 |
S1、S2 都不需要数据库,类目字典用文件缓存即可。S4 是个大改动,只在真的有多设备需求时才做——本地文件夹方案的一个优点就是单机场景下完全不需要它。
8. 已定决策
D1 · 后端迁入 server/ ✅
代价是改 start.command、settings.py 的 .env 路径、deployment.md。Python 内部 import 不受影响(工作目录切到 server/ 即可)。这是唯一有破坏性的改动,在开工 studio 前一次做完。
D2 · studio 用 React + Vite + TS ✅
studio 的核心是"几十个字段的结构化表单 + 图片批处理",正是原生 JS 最吃力的场景。与插件同栈,图片处理组件和契约类型可两边复用。v1 的计价公式(web/js/app.js)抄过来即可,不改原文件。
D3 · 保存用 File System Access API ✅
chrome.downloads 的 filename 只能是下载目录下的相对路径,不接受绝对路径或 ..,因此无法满足"用户选择保存目录",也无法读回 sources.json 做去重。
File System Access 在 side panel(扩展页面上下文)可用,showDirectoryPicker() 拿到的 handle 存进 IndexedDB 后跨会话免重复授权,正好支撑"Ozon 采完切 1688 追加到同一文件夹"。chrome.downloads 保留为降级路径(用户拒绝授权时)。
细节见 [docs/extension/plan-revision.md](./extension/plan-revision.md) R1。
9. 迁移计划
一次性做完,中途不要停在半路:
① 后端收拢
main.py api/ services/ schemas/ config/ requirements.txt → server/
改 server/config/settings.py: parents[1] → parents[1](指向 server/,.env 仍在仓库根则用 parents[2])
改 start.command: cd server 后再 uvicorn
改 docs/deployment.md 中所有路径
② 参考资料归位
seller-helper/1688-extension/ → reference/1688-extension/
③ 文档集中
seller-helper/docs/方案设计.md → docs/extension/legacy-v1.md
seller-helper/docs/方案设计-V2.md → docs/extension/legacy-v2.md
seller-helper/docs/插件开发方案.md → docs/extension/plan.md
seller-helper/extension-plan/IMPLEMENTATION_PLAN.md → 合并进 docs/extension/plan.md
④ 上一轮错放的代码归位
seller-helper/extension-plan/profiles-ozon.ts → extension/src/profiles/ozon.ts
seller-helper/extension-plan/download-implementation.ts → extension/src/export/download.ts
(建插件项目时再放,现在先留在 docs/extension/ 作为草案附件)
⑤ 删空目录
seller-helper/
⑥ web/ 加 README 标注 v1 冻结
reference/1688-extension/ 要不要进 git:它是反编译产物,约 40 个 bundle。建议加进 .gitignore,保留在本地即可——设计结论已经写进 docs/extension/plan.md §2,原始 bundle 只在需要再次查证时才用。
10. 里程碑(已调整优先级)
插件先行:1688/淘宝 → Ozon。理由见 [docs/extension/1688-taobao-implementation.md](./extension/1688-taobao-implementation.md)。
| # | 内容 | 依赖 | 产出 | 工作量 |
|---|---|---|---|---|
| M0 | 目录迁移 + 文档集中 | — | ✅ 已完成,服务正常起 | — |
| M1 | 插件:1688 采集引擎 | M0 | Console 里能跑 scanCurrentPage() |
4h |
| M2 | 插件:淘宝 profile | M1 | 淘宝页面同样可用 | 1h |
| M3 | 插件:Side Panel + File System Access | M2 | 生成完整商品文件夹到本地 | 4h |
| M4 | 插件:sources.json 去重 | M3 | 二次采集追加不重复 | 1h |
| M5 | 契约真源:product.json Pydantic | M0 | server/schemas/product.py |
2h |
| M6 | 插件:Ozon profile 实测 | M4 | Ozon 选择器验证(需真实页面链接) | 2h |
| M7 | studio:导入文件夹 + 表单编辑 | M5 | 能改能存 | 8h |
| M8 | studio + server:图片处理 | M7 | 水印/白底可用 | 6h |
| M9 | server:Ozon 发布 | M8 | 草稿进 Ozon 后台 | 4h |
M4 结束时插件功能完整,可以采集 1688/淘宝商品到本地文件夹,跨平台追加不重复。M9 结束时全链路跑通。