52 KiB
Atlas Seller Kit 交接说明
更新:2026-09-10。此文是新 AI 对话、架构讨论与开发工作的首读入口。开始开发前,必须先阅读并遵守
development-collaboration-contract.md。
1. 项目定位
这是一个全新的云端产品,不是 ozon-seller-kit 的本地版重构。目标用户可拥有多个门店,每个门店只能绑定一个平台;平台自身可支持多个市场、国家、语言和币种。因此语言、币种、类目和发布规则不能写死为 Ozon 或中文/俄文。
当前已完成 pnpm monorepo 初始化:包含 Web、NestJS API、BullMQ Worker、WXT 浏览器扩展、共享 API 契约、数据库包、本地基础设施 Compose 与依赖锁文件。API 已实现健康/readiness、集中配置与安全基础、认证、个人工作空间上下文、平台目录/上下文、门店、水印/素材、采集箱与扩展采集入库;Worker 已具备 Ozon 凭据验证、采集记录清理和按需来源素材转存消费链路,仓库同步尚未接入。Web 已具备完整前端框架(布局、路由、主题)、登录/注册页、页头平台选择器、店铺管理、水印管理、真实采集箱页面和采集数据测试页;浏览器扩展已提供毛子 ERP 风格的网页登录态同步、悬浮窗及 Ozon/1688/淘宝/天猫首版页面状态解析。不要从旧项目复制 SQLite 模型、无鉴权 API、旧 /trial/:id 或商品编辑页组件。可选择性复用已经验证过的纯计价公式、采集扩展思路和 AI 任务经验,但必须先适配多租户和云端边界。
当前运行时基线:Node 24.20.0、pnpm 11.25.0、PostgreSQL 18.6、Redis 7.4.11、MinIO RELEASE.2025-09-07T16-13-09Z;Web 默认端口为 8100,API 默认端口为 8200,扩展 dev server 默认端口为 8110(仅本地开发用),Worker 无 HTTP 端口。完整目录、依赖边界与版本表见 architecture/project-structure.md 和 architecture/technology-and-deployment.md。
0. 交接结论(先看这里)
当前已经完成的是“采集 → 采集箱 → 商品编辑草稿”的主要数据闭环,以及 M3.0–M3.4 商品领域基础、编辑器核心、AI 来源素材字段级合并、动态属性和缓存同步基础。数据库迁移、工作空间隔离、同商品归属外键、幂等键和 product/profile 双乐观锁已经落地。
当前没有完成的是“可直接用于发布”的闭环:候选素材选择/物化、定价确认、AI 任务与候选结果、Ozon 首次发布、在线商品同步仍未开发。真实站点回归也尚未由本项目完成;用户已确认此前提出的首次回归项基本满足采集箱和商品编辑需求,但仍建议在最终发布前保留一轮样本验收。
当前最高优先级是完成真实样本回归,然后进入候选素材选择/物化这一条发布前纵向能力。类目树改造已落地:Worker 使用显式指定的 Ozon 缓存源预热共享树,编辑器直接读取 Cascader 树,并会在空类目时按 Ozon 来源层级回填;详见 1.28。
接手前请先阅读 git status --short;当前 M3 商品编辑和 M3.4/M3.5 缓存改动均已提交。不要用 git reset --hard 或批量清理未跟踪文件。共享 contracts 类型需先构建,再执行 Web/API/Worker 检查。
1.1 开发进度(2026-09-02)
今日完成三项工作,全部通过 typecheck、生产构建,并经浏览器或启动实测:
Web 前端框架(apps/web)
- 参照
ozon-seller-kit/studio搭建:紫色主题(colorPrimary #8b5cf6、圆角 8)、index.cssCSS 变量、可折叠侧边栏 + 吸顶页头 + 移动端 Drawer 的MainLayout、@路径别名、StrictMode入口。 - 主题显式
fontSize: 14;输入类控件默认高度 32→34(controlHeight)、小尺寸 24→26(controlHeightSM);body 字号同步 14px。 - 依赖使用最新稳定版:React 19.2.8、antd 6.6.2,新增
@ant-design/icons6.3.4;沿用仓库精确版本号约定。 - 左侧菜单按
requirements/account-store-watermark.md的导航约定:在线商品(默认首页)、采集箱、店铺管理、水印管理、定价工具,均为占位页;另有菜单外的商品 AI 编辑页/products/:id。 - 基础设施:
services/api.ts(axios 封装 + 统一错误提取)、config/env.ts(VITE_变量)、统一占位组件components/PagePlaceholder。 - Web tsconfig 覆盖 base 的
module: Node16为ESNext + bundler(base 面向 NestJS;Node16 会要求相对导入带扩展名);TypeScript 7 已移除baseUrl,paths直接相对 tsconfig 解析为"@/*": ["./src/*"]。
注册与登录页面(契约:api/auth.md)
/login、/register两页共用AuthLayout(品牌视觉与主布局一致)。注册字段:显示名称(选填 ≤80)、邮箱、密码、确认密码;登录失败统一 401 文案,注册重复邮箱 409 落到邮箱字段提示。- 会话基础:access token 仅内存持有(
services/authToken.ts),不进入任何持久化存储;api.ts拦截器自动附加 Bearer 头,401 时用 refresh cookie 换新 token 并重放一次(并发去重),刷新失败整页跳转/login。 - 实测发现并修复:antd v6 中空值会同时触发
required与type: 'email'两条错误,已改为非空才校验格式的emailFormatRule(utils/validators.ts)。 - 浏览器实测通过:表单校验、页面互链、API 未启动时的错误提示路径。密码复杂度按契约不在前端硬编码,仅提示以服务端校验为准。
- 留待 API 认证模块就绪后:接入真实
/api/auth/*调用、主布局路由守卫、刷新页面后的会话恢复;auth 类型届时迁移至@atlas/contracts。
Worker 与扩展骨架修复
- Worker:
ioredis是 BullMQ 的可选 peer 依赖,pnpm 严格隔离下必须由应用显式声明,已新增ioredis 6.0.0;main.ts改为显式构造 IORedis 实例并设置maxRetriesPerRequest: null(原生 ESM 运行必需,也是后续 BullMQ Worker 的硬性要求)。tsxdev 与node dist/main.js两种运行方式均验证可连接本地 Redis。 - 扩展:WXT dev server 从默认的 3000–3010 漂移改为固定
8110(EXTENSION_PORT可覆盖,strictPort开启);.output/构建产物已加入.gitignore并移出 git 跟踪。端口约定同步更新至交接、部署与项目结构三份文档及.env.example。
1.2 开发进度(2026-09-03 下午):店铺管理与平台上下文前端
Web 接入平台上下文与门店管理 API(契约:api/stores-and-platforms.md)
- 页头新增平台选择器:
services/platform.tsx的PlatformProvider加载GET /api/platforms目录与GET /api/platform-context(X-Atlas-Platform-Idheader),默认取目录第一家(Ozon 优先);选择结果仅在 localStorage 保存平台 id(非敏感 UI 偏好)。页头组件layouts/PlatformSelector.tsx,tooltip 展示当前平台默认店铺。 - 店铺管理页
pages/shops/ShopsPage.tsx覆盖已实现的门店接口:游标分页列表(“加载更多”)、创建(202 + operation,提示“验证中”)、编辑(If-Match乐观锁,409 时刷新列表并提示重新编辑)、设为默认、手动重新验证凭据、仓库同步(凭据未验证时展示 422 脱敏错误)、软删除(DEFAULT_STORE_REPLACEMENT_REQUIRED转为可操作提示)。API Key 只存在于提交瞬间的内存表单,不写入任何存储。凭据验证与仓库同步的实际执行仍依赖未实现的 Outbox Dispatcher + Worker,店铺会停留在“验证中”。 - 基础设施:
services/api.ts新增getList(保留{ data, meta }分页信封)与apiErrorCode(业务错误码提取);services/stores.ts封装门店接口并按契约生成Idempotency-Key。 @atlas/contracts补充只读响应类型Platform、PlatformContext、CursorPaginationMeta、StoreOperationResponse(前端 AI 按冻结契约补类型,无行为变更,后端请复核)。- 修复 API 阻断性 bug(跨领域改动,原因:阻断本功能联调;影响
apps/api/src/stores/stores.controller.ts):tsx/esbuild 不生成design:paramtypes装饰器元数据,控制器裸类型构造参数在 dev 运行时注入undefined,GET /api/platforms返回 500。已改为与auth.controller.ts一致的显式@Inject(...);服务层原本就使用显式 token,不受影响。 - 浏览器实测(全新迁移的本地 PostgreSQL 18 + Redis 7):注册 → 平台选择器 → 创建/编辑/设默认/删除保护/同步 422 错误路径 → 刷新后会话恢复与列表持久化,全部通过;contracts/api/web typecheck 与 web production build 通过。
1.3 开发进度(2026-09-03 晚):店铺表单与界面收口
- 店铺创建与编辑表单补齐空白字符拦截;API Key 输入上限与冻结契约的 8192 字符保持一致,不在前端自行收紧。
- 设置页补充取消操作;全局主题目前使用
borderRadius: 10、默认控件高度 34,小尺寸控件高度 26,输入类控件单独使用 16px 字号与 40px 高度。按钮保留 Ant Design 默认字号、高度与内边距。 - 页头面包屑改用 12px
RightOutlined分隔符,并完成条目、分隔符与侧栏折叠按钮的垂直对齐;页头水平内边距调整为0 20px 0 10px。店铺与设置页的视觉调整不改变接口或领域行为。 - Ozon 平台现支持
CNY与RUB;新建 Ozon 店铺时默认预选CNY。默认币种由平台能力default_currency_code提供,服务端仍以平台支持币种表校验提交值。
1.4 开发进度(2026-09-04):凭据验证 Worker
- Worker 轮询 Transactional Outbox,按工作空间隔离认领事件并投递 BullMQ;任务只携带 store、operation 与 secret ID,绝不携带 API Key。
store_credential_verification任务仅在 Worker 内读取当前未撤销密钥、短暂解密并调用 OzonPOST /v1/description-category/tree({ "language": "ZH_HANS" })进行低风险验证。该请求已用真实店铺验证可用;仓库接口留待仓库同步模块按其实际请求契约接入。401 表示认证失败;403 单独提示账户/权限不匹配;其他确定性 4xx 记录脱敏 HTTP 状态并标为验证请求错误,不误报为凭据无效。临时网络、限流和 Ozon 服务端错误最多指数退避重试 5 次。- 验证成功回写
active / valid;密钥被替换或门店删除时,旧任务取消且不得覆盖新状态。前端在验证中只显示一个标签,避免与“未验证”同时出现。
1.5 开发进度(2026-09-04):水印模板 API
- 已实现字体目录、按工作空间隔离的水印模板列表/详情/创建/编辑/软删除,以及店铺推荐水印绑定与解绑。文字/图片模板均创建不可变渲染版本;仅改名不新增版本,渲染参数或类型变化会新增版本。
- 删除模板会解除所有当前店铺绑定,但不删除历史版本或图片资产。模板与店铺写操作均要求
Idempotency-Key;编辑要求If-Match。 - 图片模板只可引用当前工作空间内、已完成安全校验的
media_asset;素材直传、图片校验和短时预览 URL 的实现见 1.6,前端仍不能自行拼接对象存储地址。
1.6 开发进度(2026-09-04):水印图片素材直传
- 已实现仅供
watermark_image使用的两阶段素材接口:创建 10 分钟有效的 MinIO/S3 预签名 PUT 上传意图、完成上传后验证对象存在、大小、SHA-256、PNG/JPEG/WebP 魔数与 1–8192 px 尺寸,并创建或复用工作空间内的media_asset。 - 已实现素材短时预览 URL(10 分钟);普通 API 返回永不包含 bucket、object key 或长期 URL。图片模板只接受当前工作空间内、类型为 PNG/JPEG/WebP 的已验证素材。
- 本地环境默认使用
MINIO_ROOT_USER/MINIO_ROOT_PASSWORD连接http://localhost:9000;生产必须显式配置最小权限的OBJECT_STORAGE_ACCESS_KEY/OBJECT_STORAGE_SECRET_KEY。
1.7 开发进度(2026-09-04):上传意图清理 Worker
- Worker 启动时立即执行、之后每 5 分钟扫描一次所有工作空间:将超过有效期仍为
pending的上传意图置为expired。 - 对
failed/expired意图,Worker 删除其 MinIO/S3 临时对象;对象不存在也视为已清理。仅对象存储删除成功才写入object_cleaned_at,删除暂时失败会保留记录并在下次扫描重试。 - 新增迁移
007_media_upload_cleanup.sql,该字段与清理索引必须在部署 Worker 前执行。
1.8 开发进度(2026-09-04):M2.1 采集箱基础闭环
- 已新增
008_collection_box_foundation.sql与009_fix_collection_limit_function.sql,并在本地 PostgreSQL 实际执行。迁移建立工作空间隔离的采集记录、商品草稿、默认 SKU 与来源素材转存任务;采集箱默认上限为 1000 条,数据库触发器在并发创建/恢复时锁定工作空间并强制限制。 - 平台能力现有固定键:
can_be_source、can_be_target、can_be_header_context。Ozon 是首期目标和页头平台;1688、淘宝、天猫仅作为采集来源,API 平台目录不会把它们暴露给 ERP 页头选择器。 - API 已实现手动创建采集草稿、按页头平台列出采集箱、备注的
If-Match更新、批量删除、清空和失败素材重试入口。采集记录始终不绑定目标平台或店铺;本阶段列表的上架摘要固定为“等待首次上架”,不冒充 Ozon 前台可见性。 - 批量删除和清空会软删除 Atlas 采集草稿、创建
operation与 Outbox 事件;Worker 取消尚未执行的素材任务并完成清理操作。它们绝不删除或下架平台商品。 - Web 的采集箱已替换占位页,支持容量显示、标题/来源筛选、手动创建、批量删除、清空确认、空/加载/错误状态。商品编辑入口明确保留给下一任务,未伪造可发布能力。
apps/api/test/collection.e2e.test.ts覆盖手动创建、幂等重放、列表读取、乐观锁冲突和软删除;该测试、本次受影响的 API/Worker/Web TypeScript 检查及 Web 生产构建均已通过。任务清单见m2.1-collection-box-foundation.md。
1.9 开发进度(2026-09-05):M2.2 / M2.3 扩展采集首个闭环
- 已实现毛子 ERP 风格的 Atlas Web 登录态同步:扩展打开 Atlas Web 标签,轮询读取本站 localStorage 中的短期 Web access token,保存到 extension storage 并用 Bearer token 检查会话/采集。扩展不读取 refresh Cookie、来源站 Cookie 或来源站 localStorage。
- 已实现
POST /api/extension/materials:从扩展身份推导工作空间,创建或复用未绑定目标平台/门店的采集记录、SPU、SKU 草稿和来源素材任务。采集载荷严格限制为来源证据、规范化字段和素材 URL,不能携带店铺、Cookie、API Key 或目标平台。 - Worker 已消费素材转存 outbox 事件,执行 HTTPS 公网地址校验、大小/图片格式限制、哈希去重与 MinIO/Kodo 转存;单个素材失败不回滚采集箱商品。
- 浏览器扩展已实现 Ozon 公开商品页的小型 Shadow DOM 悬浮窗及未登录/已登录状态。Ozon 首版解析器以当前公开 SKU 和公开变体状态为证据,不生成笛卡尔积假 SKU。Ozon Seller 四接口本地观察、Ozon 变体详情逐页补全及 1688/淘宝/天猫解析仍未开始。
- 详情、边界和本地配置见
m2.2-m2.3-extension-collection.md。
1.10 开发进度(2026-09-06):M2.4 来源解析器与采集测试页
- 扩展浮窗现覆盖 1688 商品详情、淘宝商品详情和天猫商品详情。1688 仅遍历
skuMapOriginal中实际存在的组合;淘宝/天猫仅在同时取得skuBase.skus[].skuId + propPath时创建多 SKU,并以skuCore.sku2info补充参考价格。规格轴本身从不展开为笛卡尔积。 - 缺少真实组合证据时,扩展只保存规格轴,服务端创建一个默认 SKU 并标记为待确认;颜色等轴值图片仍作为候选素材,绝不伪造来源 SKU 图片绑定。
- “编辑上架”暂时不进入未完成的商品编辑模块,改为打开
/collection-test/{collectionItemId}。该只读测试页展示实际保存的规范化载荷、原始证据、SKU 草稿和素材转存任务,用于验证每个平台的采集结果。GET /api/collection-items/{id}已提供该工作空间隔离的检查读模型。 - 已通过 API/Worker TypeScript 检查、Web 与扩展生产构建;
apps/api/test/extension.e2e.test.ts已覆盖扩展会话、采集幂等复用和测试页所用检查读模型。尚未在真实来源商品页执行样本回归,接手者应按第 6 节先完成该验证。
1.11 开发进度(2026-09-07):扩展网页登录态同步与稳定 ID
- 扩展登录采用毛子 ERP 模式:点击后打开并保留 Atlas Web 标签;Web 登录或恢复会话后写入本站 localStorage,扩展每 2 秒读取并验证该短期 Web Bearer token。没有有效网页会话时,用户在同一标签登录后即可完成插件登录。
- Atlas Web access token 写入 localStorage 是维护者批准的窄范围例外;扩展不读取 refresh Cookie、来源站 Cookie 或来源站 localStorage。Token 失效后插件清除缓存并重新要求网页登录。
- 不再需要
EXTENSION_COLLECTOR_REDIRECT_URI或identity权限;方案 A 的网页确认 + PKCE 配对设计单独记录在extension-login-alternatives.md,当前不作为插件默认路径。 apps/extension/wxt.config.ts的 manifest 带有仅公开的 RSAkey,Chrome 为解压加载和自托管包保持稳定扩展 ID;私钥未写入仓库。wxt build已确认最终manifest.json含该 key。- 已通过 contracts、API(含测试 TS)、Web、扩展的类型/生产构建,以及
apps/api/test/extension.e2e.test.ts:Web Bearer 会话检查 → 工作空间解析 → 采集幂等上报。 - 同一工作空间内的同一来源商品再次采集时,系统保留
collection_item/productID,但用最新页面快照覆盖来源标题、价格、证据、SKU 与候选素材。迁移013_collection_source_snapshot_refresh.sql为记录和素材任务增加快照版本;旧转存任务取消,Worker 仅处理当前版本,不能将旧页面内容回写到新快照。 - Ozon 公开商品页解析器现从 JSON-LD
BreadcrumbList提取来源类目(可见面包屑为回退),保存当前页面语言的类目路径及对应面包屑证据;解析器版本为ozon-public-1.1.0。
1.12 开发进度(2026-09-07):Ozon 公开多 SKU 采集与归并
- 解析器改为优先读取 Ozon 已渲染的
state-webAspects-*.data-state.aspects[].variants[],不再用脚本文本正则猜测 SKU。保存的三 SKU 页面快照packages/reference/ozon1.html已确认能直接得到5387004839、5387005639、5387005221。 - 增加只限 Ozon MAIN world 的页面 JSON bridge:只接受数字 SKU 和固定的
modal/aspectsNew/product/<sku>路径,白名单返回webAspects、webProductHeading、webPrice、webGallery状态;不暴露 Cookie、请求头或任意请求能力。扩展以并发 3、最多 100 SKU 队列补全实际变体详情;个别失败保留已发现变体和证据。 - Ozon 公开页面没有稳定 SPU/组 ID;当前 URL SKU 仅保存为入口证据。API 对 Ozon 用本次
source_sku集合与已有product_variants.source_sku的交集复用并覆盖同一采集快照,允许用户从同组任一 SKU 页面重复采集。多个既有记录命中时返回SOURCE_VARIANT_GROUP_AMBIGUOUS,不静默合并。
1.13 开发进度(2026-09-07):Ozon 商品特征、描述与素材预览
/collection-test/{collectionItemId}的“素材转存任务”现在直接展示转存成功素材的缩略图,并显示关联来源 SKU。页面为每个succeeded + asset_id的任务按 4 路并发申请素材短时预览 URL;URL 只保存在浏览器内存,不能写回采集记录、产品或对象存储路径。没有 SKU 直接关联的规格轴图片会明确显示为“商品级”。已在现有三 SKU 采集记录7172fa38-0e4d-485e-9d53-1bd92aced79a实测确认 37 张素材可显示。- Ozon 解析器升级为
ozon-public-2.2.0。本地页面优先读取webShortCharacteristics、webDetailedCharacteristics、webCharacteristics的characteristics[];当前入口 SKU 的/product/<sku>/补全响应会再读取完整webCharacteristics,去重后写入common.attributes。这沿用旧插件已验证的数据形状,不通过页面文本或全页 DOM 猜测参数。 - 固定的 Ozon 详情页段仍只读取
webDescription/webRichContent。其中webDescription.richAnnotationJson被转换为纯文本写入common.description,原 JSON 同时保留在raw_value供检查;富文本中的图片只走既有详情素材白名单,不会混入描述文本。 - MAIN world bridge 的返回白名单已经增加上述三个 Characteristics widget,仍然只接受固定 Ozon 同源路径和数字 SKU,不暴露 Cookie、请求头或任意请求能力。
- 兼容性提醒: 已有采集记录不会自动重新解析历史
normalized_payload。例如上面的现有记录仍标记为ozon-public-2.1.0,只能验证新素材预览;需重新在来源商品页执行“编辑上架”,系统会覆盖同一采集快照,届时在规范化载荷查看新版属性与描述。 - 已通过扩展
wxt prepare + tsc + build、Webvite build、tsx --test test/*.test.mts(5 项,包括 Ozon 页面快照中“类型/原产国/材料”等 5 个特征和富文本描述样本)以及浏览器测试页实测。
1.14 开发进度(2026-09-08):重采集素材任务外键修复
- 修复重复“编辑上架”覆盖来源快照时
collection_asset_transfers.workspace_id为空导致的数据库非空约束错误。根因不在新任务插入:旧的复合外键(workspace_id, source_variant_id) ... ON DELETE SET NULL在删除旧 SKU 时,会按 PostgreSQL 规则同时清空两个引用列,误将不可为空的workspace_id设为NULL。 - 覆盖流程现在先将历史素材任务的可空
source_variant_id显式置空,再删除旧 SKU。迁移014_collection_asset_variant_fk.sql同时把外键改为ON DELETE SET NULL (source_variant_id),从 schema 层杜绝同类删除操作再次触发该错误。已在本地数据库实际执行并核验最终约束定义。 - 该错误发生在同一数据库事务内,因此报错的旧请求会完整回滚,不会留下半成品变体、转存任务或 Outbox 事件。扩展可在 API 开发进程热重载后直接再次点击“编辑上架”。
- 已通过 API TypeScript 检查、
apps/api/test/extension.e2e.test.ts(首次采集 → 同组 SKU 重采集 → 快照覆盖)和新增的转存任务workspace_id断言。
1.15 已确认设计(2026-09-08):采集图片按需物化
- 新规则取代“采集即转存”: 采集阶段仅保存候选来源 URL、图片角色、SKU/规格关联、排序与证据;测试页直接加载来源 URL,以最快速度核对且不产生对象存储和下载费用。
media_asset只代表 Atlas 已验证、可稳定编辑与发布的正式素材。 - 用户选图进入编辑时建议转存;加水印可在单次任务中临时下载来源图、仅存水印结果(来源失效后不可重试);AI 改图等需要图片字节,通常先转存或在受控任务中临时下载;AI 成功输出和正式发布图片必须是 Atlas 控制的
media_asset。不得把来源 URL 直接提交到目标平台,也不得为下载图片读取来源 Cookie 或绕过防盗链。 - 首期不做自动删除已选择但未发布素材。该能力涉及引用、编辑草稿、处理中任务、恢复和误删风险,当前不值得引入。以后如需控成本,只能先考虑清理从未被任何商品、派生任务或发布快照引用的素材,并单独立项。
- 完整决策、场景表、AI/水印边界与按需物化要求见
media-storage-strategy.md。后续采集、编辑、AI、水印和发布任务开始前必须先读此文;按需物化已通过pending_selection候选状态和POST /api/collection-items/{id}/asset-materializations落地,正式商品编辑页尚未接入选图动作。
1.16 开发进度(2026-09-08):采集图片按需转存与来源预览
- 已新增迁移
015_collection_asset_pending_selection.sql、016_collection_asset_pending_selection_default.sql:为素材处理枚举增加pending_selection,并将新候选的默认状态改为该值。枚举变更和使用它的默认值/索引刻意拆为两个迁移事务,避免 PostgreSQL 新枚举值在同一事务中立即使用的限制。 POST /api/extension/materials现在只保存来源图片候选,绝不自动创建collection.assets.materialize.requestedOutbox 事件或下载图片;快照覆盖会取消旧快照中pending_selection、queued、running的候选/任务。返回的materialization新增pending_selection计数。- 已新增受
Idempotency-Key保护的POST /api/collection-items/{id}/asset-materializations:只能选当前快照的pending_selection候选,转为queued后创建 operation 并投递现有来源素材 Worker。商品编辑、AI、水印和发布后续均复用此入口;采集测试页不会调用它。 /collection-test/{collectionItemId}现在直接显示候选的来源 URL;已转存素材仍通过短时受控 URL 预览。页面明确区分“候选,未转存”“等待转存”“转存中”“已转存”等状态,方便核对而无需等待或消耗存储。
1.17 开发进度(2026-09-08):采集候选视频(首期)
- 已新增并在本地开发数据库执行
017_collection_video_candidates.sql:collection_asset_transfers新增media_type(image/video)和可选poster_url。共享采集契约的素材项新增mediaType(默认为image)及posterUrl;检查读模型和测试页可区分媒体类型。 - Ozon 仅从固定
webGallery/webVideo状态读取视频;1688 仅从旧采集器验证过的gallery.fields.video|videos状态读取;淘宝、天猫仅从旧采集器验证过的item.videos状态读取。三者都不扫描页面video标签,因此不会将评价、推荐或广告视频误采为商品视频。 - 首期候选仅接受 HTTPS 的直接 MP4/WebM URL,并保留可用封面 URL。
blob:、HLS (m3u8)、DRM 与未知格式不采集;Worker 按需转存时以 MP4/WebM 文件魔数复核,图片仍限制 20MB,视频限制 200MB。暂不实现视频转码、封面抽帧、时长/尺寸解析、视频水印或发布。 /collection-test/{collectionItemId}对未转存视频显示preload=none原生播放器,对已转存视频使用 Atlas 短时预览 URL;与图片一样,采集本身不下载也不产生存储成本。完整规则见media-storage-strategy.md。
1.18 开发进度(2026-09-08):旧采集器结构化路径迁移
- Ozon 解析器已对齐旧采集器的结构化合并方案:
webDescription/webRichContent富文本优先,schema.org Product.description为兜底;首屏webShortCharacteristics、默认商品页和详情页返回的webCharacteristics/webDetailedCharacteristics都会合并去重。ozon-public-2.3.0是对应版本;旧快照必须重新采集。 - 1688、淘宝、天猫已加入受限 MAIN-world 商品状态桥:1688 仅投影公开商品的画廊、SKU、价格、参数等固定字段;淘宝/天猫仅投影
__ICE_APP_CONTEXT__中的公开商品结果。桥不提供任意 window 读取、Cookie、请求头或通用 fetch。新采集器将这些结构化数据、JSON-LD、受限详情 DOM 与既有图片 profile 映射为 Atlas 新CollectedProductPayload。 /collection-test/{collectionItemId}现单独展示商品描述、类目路径、全部特征表、规格轴、SKU 草稿、素材候选,并保留规范化载荷和原始证据,供解析核对。扩展解析器测试覆盖 JSON-LD 描述兜底、完整特征合并和图片白名单。
1.19 开发进度(2026-09-09):M3.0 商品编辑领域基础
- 新增迁移
018_product_editor_foundation.sql:采集期最小products/product_variants扩展为可编辑的产品聚合,并建立平台发布配置、SKU 平台 listing、平台属性及未映射属性草稿表。所有表启用并强制工作空间 RLS。 product_variant_platform_listings同时保存product_id,以 profile→product 和 variant→product 两条复合外键强制同商品归属;数据库会拒绝同一工作空间中“商品 A profile + 商品 B SKU”的错误组合。属性和未映射属性也受 profile/variant 与 product 的复合外键约束。- 已实现
POST /api/collection-items/{id}/editor-contexts:只有当前页头目标平台存在已验证默认门店时,才原子取得或创建产品的该平台 profile,并为现有 SKU 建立初始not_listedlisting。GET /api/products/{id}/editor返回产品、profile、SKU listing、属性和待处理未映射属性的只读快照。 products.version已迁移为统一命名的revision;新增共享版本冲突辅助。M3.1 的完整草稿写入仍必须以product.revision + profile.revision条件更新为边界,并覆盖并发保存。- API 集成测试覆盖默认店铺前置、上下文幂等复用、读模型、跨商品外键拒绝和未映射属性读回。正式编辑 Web、草稿保存、类目切换、素材选择、定价与发布尚未开始。
1.20 开发进度(2026-09-09):M3.1 商品草稿保存与正式编辑入口
- 已实现
PUT /api/products/{id}/editor-draft:产品基础资料、当前平台标题/描述/标签/履约方式,以及完整 SKU 集合和未上架 listing 的offer_id在同一事务保存。新 SKU 以client_key回传稳定 ID;省略现有 SKU 会软删除 Atlas 草稿,绝不触发平台下架。 - 请求必须携带
If-Match: "{product_revision}:{profile_revision}"与Idempotency-Key。服务端先后执行两个带 revision 条件的更新;任一步冲突或 SKU/货号校验失败都会回滚整笔保存。店铺内货号重复会返回可展示的422,重复请求键安全重放原响应。 - Web 的采集箱“编辑上架”和手动创建现会创建编辑上下文后进入
/products/{id}/editor。正式页可编辑产品基础资料、当前平台文案及 SKU/货号,遇到版本冲突会重新加载最新快照。 - 此批刻意不展示未具备平台 schema 或发布校验的数据:类目/属性切换与未映射属性恢复、规格继承、候选媒体物化/选图、定价确认、AI 和首次 Ozon 导入仍待后续纵向任务。
1.21 开发进度(2026-09-10):复采与编辑草稿 P0 修复
- 重复采集不再删除
product_variants。未进入编辑器前,SKU 仍可作为最新来源快照投影更新;一旦任一product_platform_profile存在,复采只更新来源证据/规范化载荷/候选素材,并按稳定source_variant_key复用既有 SKU ID,不覆盖 Atlas 草稿字段或已建平台 listing。 - 复采新发现的来源 SKU 会为该商品全部现有平台 profile 建立独立
not_listedlisting;来源不再返回的 SKU 不会被自动删除或恢复。这样既保留用户编辑和发布审计,也不会以级联删除绕开 SKU 与 profile 的同商品归属约束。 - 扩展集成测试已覆盖“进入编辑、保存人工 SKU 字段与 offer 后再次采集”的场景,并确认原映射保留、新 SKU 可编辑。完整 API 集成测试通过。
1.22 开发进度(2026-09-10):M3.2 商品编辑核心
- 编辑快照与
PUT /api/products/{id}/editor-draft已覆盖父商品实物/包装规格、SKU 覆盖规格、有效继承规格、规格轴值、每个 SKU 当前平台状态、以及该平台全部活动且凭据有效的目标店铺候选。 - 切换目标店铺仍通过同一原子草稿保存:仅
not_listed/failed等未导入 listing 改为新店铺;importedSKU 永远保留其首次导入的店铺映射。规格确认状态为confirmed时服务端要求相应四项尺寸/重量完整,数值必须大于零。 - Web 编辑页新增父/子规格编辑、规格轴值、复制 SKU、已导入 SKU 删除影响提示、目标店铺选择、上架状态摘要,以及路由离页与浏览器关闭前的未保存修改确认。类目/动态属性、素材选择、定价、AI 与首次发布仍未开始。
- 商品编辑集成测试覆盖规格保存/继承、复制 SKU、店铺切换与已导入 SKU 映射保留;Web production build 通过。
1.23 开发进度(2026-09-10):M3.3 编辑器数据闭环
- 新增迁移
019_product_editor_ai_source_material.sql,在商品聚合中持久化结构化ai_source_material。未保存时由当前规范化采集载荷初始化;保存后作为用户整理稿保留,后续复采不覆盖。原始normalized_payload/raw_evidence不被编辑器修改。 GET /api/products/{id}/editor直接返回collection_item_id、source_material和服务端组装的ai_input,Web 不再扫描采集箱前 100 条记录寻找来源。ai_input同时包含父商品默认实物/包装规格,以及每个 SKU 合并继承后的有效规格和规格轴。PUT /api/products/{id}/editor-draft将 AI 来源素材与商品、平台文案、SKU 和规格原子保存,并继续使用 product/profile 双 revision 与幂等键。右侧来源标题和描述的修改现可持久化;未来 AI 动作必须先保存草稿,再从服务端读模型创建任务。- 商品编辑集成测试覆盖来源载荷初始化、用户素材保存、原始证据不变、复采不覆盖整理稿,以及父/SKU 有效规格进入 AI 输入。
1.24 开发进度(2026-09-10):M3.3.1 来源素材字段级合并
- 新增迁移
020_product_ai_source_overrides.sql:将 M3.3 的整份ai_source_material草稿演进为ai_source_material_overrides与ai_source_material_base_revision。服务端以最新规范化采集数据为底稿,只叠加用户明确编辑过的顶层字段。 - 编辑快照新增
source_material_state:同时返回最新来源、覆盖字段、复采后发生分歧的字段与基线版本。右侧“采集的商品信息”展示合并后的有效值;未编辑字段随复采自动更新,已编辑字段保留,并支持逐项恢复或全部采用最新。 - 保存请求新增
ai_source_overridden_fields。来源版本必须与服务端当前版本一致;保存过程中发生复采返回409 SOURCE_SNAPSHOT_CHANGED,防止旧页面覆盖新证据。原始normalized_payload/raw_evidence仍保持只读。 - AI 输入继续由服务端组装:来源部分读取字段级合并后的有效素材;规格部分读取用户在左侧保存的父商品规格、SKU 覆盖值及其继承后的有效值。
1.25 开发进度(2026-09-10):M3.4 类目与动态属性
- 旧迁移
021_product_category_attribute_schema.sql建立了平台级 schema 缓存;最新设计由022_store_catalog_cache.sql另建门店级类目、schema 与字典缓存,发布草稿只引用门店级数据。 - 新增编辑器接口:类目候选、类目属性 schema 与属性字典值。类目 schema/字典接口支持传入未保存的
category_id预览,因此用户选择新类目后不必先保存一次才能填写字段。 - 编辑器草稿可原子保存
store_product_category_id、类目来源与商品/SKU 属性;服务端派生platform_type_id,复核类型、值数量、字典值与 SKU 归属。类目变更或 schema 下线的旧属性会转入product_unmapped_attributes,不会默默丢失或进入新的发布 payload。 - Web 编辑页已提供类目选择、商品/SKU 维度动态字段、字典选择、未映射属性告警;上架店铺/主题标签保持标准 40px 控件高度,父商品包装重量与三项包装尺寸宽度分别为 150px / 120px。
1.26 开发进度(2026-09-10):M3.5 Ozon 类目与属性缓存同步
- 已实现类目树、类目属性 schema、字典值缓存的数据表、Worker 同步逻辑、Transactional Outbox 和读 API;缓存属于门店级发布元数据,不属于某个商品草稿。浏览器、operation 响应与 outbox 事件都不含 API Key。
- 当前旧实现仍把“同步 Ozon 类目”暴露为商品编辑页操作;这已被最新产品决策否定,不能作为完成状态使用。属性 schema 仍是按类目读取/按需同步的现状,后续需单独确认是否也改为启动预热。
- 字典不会全量拉取:字段先读取已有缓存;用户输入至少两个字符且本地无匹配时,现有实现可显式发起 Ozon
/attribute/values/search,Worker 仅 upsert 返回的命中项。同步失败应保留旧缓存和可读错误状态。
1.27 历史状态(2026-09-10):平台级预热方案已废弃
- 原交接要求的“Worker 启动时以指定门店预热一份平台共享类目树”已被产品决策替代:类目、属性和字典缓存按门店隔离,不能再恢复平台级共享缓存或环境变量指定的共享源。
- 保留的原则是:浏览器、日志、operation 和 outbox 事件均不携带 API Key;编辑器只读缓存,不提供类目树手动同步入口。
1.28 开发进度(2026-09-10):门店类目同步与来源回填
- Ozon 门店凭据验证成功后,在同一事务中创建门店类目同步 operation 与 outbox 事件;Worker 使用该门店
Client-Id + Api-Key请求中文类目树,并写入store_product_categories。失败只标记类目同步 operation,不会把已验证的凭据改为无效。 - 已删除编辑器的手动类目树同步接口和客户端调用。属性 schema 与字典值仍按当前类目和关键词按需同步,使用原有 Outbox/Worker 链路。
- Cascader 显示完整缓存树路径,但表单与草稿 API 始终保存末级类目 UUID,修复了路径数组不能直接提交到
editor-draft的问题。 - 对 Ozon 来源商品,进入或读取编辑器时会将
common.categories的规范化层级路径与活动缓存节点精确匹配;仅当 profile 未保存类目时写入匹配项并标记source_capture。已有手工选择或此前保存的类目永远不被复采覆盖。API 集成测试覆盖回填和保护规则。 - 已新增 API 集成测试覆盖来源回填和保护规则;仍需在有效 Ozon 门店、真实 Ozon 样本与浏览器页面上执行最终回归。本地 Docker 基础设施未启动时,该集成测试无法实际执行。
1.29 当前完成情况与下一步(2026-09-10)
已完成:
- Cascader 使用完整路径显示、末级 UUID 保存;初始回显、手动切换和服务端门店归属校验均已接入。切换目标门店会清空旧门店类目及动态属性,再加载新门店缓存。
- 门店验证成功后在同一事务创建
store_category_tree_syncoperation/outbox;Worker 用该门店Client-Id + Api-Key和ZH_HANS拉取树。BullMQ 任务使用 5 次指数退避重试;同步失败写 operation,不使已验证门店变为无效。 - 编辑器旧手动类目树同步接口及客户端调用已删除;来源面包屑自动匹配只在空类目时写入
source_capture,不会覆盖手选或已保存选择。 - Contracts、API、Worker、Web TypeScript 检查和 Web 生产构建已通过;
git diff --check已通过。
未完成 / 下一步:
- 为
store_category_tree_sync增加门店级去重:已有有效缓存跳过;同一门店的并发验证/重复事件只允许一次实际 Ozon 请求。 - 新增 Worker 集成测试,覆盖空缓存首次同步、缓存跳过、并发互斥、Ozon 失败后的指数重试和 API Key 不进入队列/日志/数据库。
- 启动本地 PostgreSQL 与 Redis,执行
022_store_catalog_cache.sql,运行 API 集成测试并检查 RLS、外键和历史 profile 兼容性。 - 使用真实已验证 Ozon 门店和样本,在
http://localhost:8100回归:类目树回显、切换店铺、保存 UUID、来源回填、不覆盖手选类目以及属性/字典按需同步。
当前阻塞:本机 PostgreSQL 与 Redis 未运行,apps/api/test/product-editor.e2e.test.ts 连接报 ECONNREFUSED,因此 API 集成测试和迁移尚未实际验证。
2. 已确认产品决策
| 主题 | 决策 |
|---|---|
| 商品模型 | 每个商品是一个 SPU,且始终至少有一条 SKU;单规格商品使用默认 SKU,多规格商品使用多条 SKU。 |
| 平台可售单元 | 一个 SKU 对应一个独立平台 offer;Ozon 首个平台中对应 /v3/product/import.items[] 的一项和独立 offer_id。 |
| 通用属性 | 标题、品牌、型号、主题标签、简介、条形码、采买地址、货源备注及未来商品级属性;类目和平台属性属于独立的平台发布配置。首期不做富内容。 |
| SKU 属性 | SKU 名称、图片、货号、采购价、售价、划线价、包装重量和尺寸、商品重量和尺寸、商品颜色、颜色名称等。首期不支持视频。 |
| 型号/货号 | 型号(model)是父商品通用字段;offer_id 是当前平台下的每个 SKU 字段。采集时两者可为空;首次上架前必须由用户填写,系统不从标题、规格或来源 SKU 自动生成。被成功导入的 offer_id 作为历史事实锁定。 |
| 规格 | 实物规格与包装物流规格严格分离;尺寸图只读取实物规格,计价/物流/发布只读取包装规格。 |
| 继承 | 可继承字段的 null 表示该字段没有覆盖父商品默认值,不表示 SKU 不存在、名称为空或商品为单规格。 |
| 尺寸图 | 必须逐 SKU 生成,任务保存 SKU ID 和实物规格快照。 |
| 定价 | SKU 上点“定价”打开弹窗,以有效包装数据计算;用户确认后回填 SKU 销售价和划线价。 |
| 编辑体验 | 新商品 AI 编辑页取代旧商品编辑页;旧试算页不再追加多 SKU 功能。 |
| 采集插件 | 采集 Ozon、1688、淘宝、天猫详情页;未登录只显示登录入口,已登录显示“编辑上架 / 采集商品 / 进入 ERP”。采集箱为工作空间共用,采集记录不关联目标平台或门店;用户进入 ERP 编辑时,按页头当前平台(首期默认 Ozon)取得该平台发布配置并预选默认店铺。A/B 平台的上架状态独立。详情见 requirements/collector-browser-extension.md。 |
| 采集 SKU | 只有来源给出稳定 SKU/组合证据时创建多个 SKU;不能把规格轴做笛卡尔积。没有专属 SKU 图时,真实 SKU 继承父商品默认图而不复制资产。 |
| 媒体 | 采集主图/详情图是商品候选池;来源明确给出 SKU、或可靠的规格轴值(如颜色)与图片关系时才自动绑定。其余素材由编辑页人工选择。 |
| 发布与在线商品 | 商品采集、编辑、定价、AI 媒体和首次导入至 Ozon 后台是核心闭环。正式发布使用官方 Seller API,按一 SKU 一 import item/offer 执行并轮询结果;首期不传库存或仓库,用户在 Ozon 后台设置库存。已导入 SKU 不可再次上架,未上架 SKU 仍可首次上架;SPU 在当前平台汇总为未上架/已部分上架/已上架。在线商品由异步 Ozon API 同步,按 Ozon 商品 SKU 展示,亦包含非 Atlas 发布商品。 |
3. Ozon 已核对结论
仓内旧项目的 docs/ozon-seller-api/04-product-import.md 记录:Ozon /v3/product/import 每个 item 都有独立 offer_id、价格、包装尺寸/重量和图片,尺寸/重量不能为零;单次最多 100 项。03-category-attributes.md 记录类目属性的 is_aspect=true 表示变体属性。
因此 Ozon 适配器应以“一 SKU 一 import item/offer”为最小稳定单位。现有资料没有可强制把多个 item 合并为一个 Ozon 前台变体组的 API 契约,不能依赖自动归并;这是未来 Ozon 联调阶段要在真实店铺或沙盒验证的能力。
4. 云端架构底线
- 使用 PostgreSQL;所有业务行必须受
workspace_id或可安全追溯至工作空间的外键约束。 - 首期注册后静默创建一个个人工作空间,并只建立一个
owner成员;不展示工作空间、成员、角色或店铺级权限管理。后续有团队协作需求时再扩展。 - 门店只能绑定一个平台,不能跨平台使用;市场/国家、默认语言、支持语言和支持币种属于平台能力配置,不属于门店。门店保留已确认的默认计价币种与加密凭据;上述信息均不得写进商品核心表。
- 本地素材使用 MinIO;共享测试和生产素材使用七牛 Kodo,不使用应用服务器本地目录作为持久存储。
- AI、生图、导出、采集转存和发布使用 Redis + BullMQ 队列/Worker,任务可重试、可恢复且携带工作空间与 SKU 上下文。关键可重试写操作使用持久化幂等记录;异步任务与业务事实在同一 PostgreSQL 事务写入 Transactional Outbox,再由 Dispatcher 投递队列。Redis/BullMQ 不作为业务事实唯一来源。
- 用户界面语言、采集源语言、商品编辑语言和发布目标语言相互独立;文案及属性值需要支持多语言版本。
- 一期实现插件中显式开启的 Ozon Seller 网页本地观察:在用户浏览器的已登录 Seller 标签页调用四个已记录内部接口,并仅上传净化结果;不得提取完整、保存或上传 Cookie,不得阻塞标准采集、不得替代官方 Seller API 发布或把未验证经营数据视为本店事实。加密保存 Cookie 仅作为未来二级实验能力预留。
建议的核心层级:
User → Workspace → Membership / Role
→ Store → Platform connection / default currency
→ Product (SPU) → ProductVariant (SKU)
→ attributes / media / pricing snapshots / tasks
5. 已冻结的第一阶段 API 契约
第一阶段 HTTP API 的接口范围已讨论完成,当前状态为设计冻结、部分业务接口已实现。认证、平台目录/上下文、创建/查询/编辑/默认/删除门店、异步操作读取及仓库摘要读取已有实现与集成测试;门店凭据验证、仓库同步的实际平台调用仍待 Worker 消费 Outbox 后完成。后续编码以 docs/api/ 为唯一 HTTP 契约来源;旧 product/api.md 仅作历史背景,不能作为实现依据。
- 覆盖账户认证、平台/门店、水印与素材、定价、浏览器采集/采集箱、商品编辑/首次发布、在线商品读取与同步。
- 所有接口遵循统一鉴权、工作空间隔离、页头平台上下文、金额字符串、错误格式、
Idempotency-Key、If-Match和异步operation约定。 - 响应信封是强制契约: 成功单资源或同步动作返回
{ "data": T },列表返回{ "data": T[], "meta": PaginationMeta },失败返回{ "error": ApiError },204 No Content不返回响应体。项目不采用完整 JSON:API;任何调用方不得自行增加嵌套data或以data: null表示错误。完整规则见api/conventions.md。 - 关键异步动作必须在同一事务写入业务记录、
operations(如需用户可见进度)和outbox_events;Dispatcher 至少一次投递 BullMQ,Worker 必须幂等。 - DDL 已通过本地隔离 PostgreSQL 执行验证;迁移执行器、认证/工作空间基础、幂等与 Outbox 写入闭环、Outbox Dispatcher、Ozon 凭据验证 Worker、上传意图清理 Worker、采集记录清理 Worker 与来源素材转存 Worker 均已实现。仓库同步、发布和在线商品同步 Worker 仍待实现。
仍需在对应模块开始编码前完成的 P1/P2 决策和数据模型补丁,集中记录在 reviews/implementation-readiness-open-items.md。
6. 下一步建议
下一步建议按以下顺序推进:
注意:下面的历史清单仍保留用于追溯;当前执行优先级以
1.27 当前进行中和本段补充为准。先完成类目启动同步、树形选择与 Ozon 来源类目自动回填,再进入真实采集回归和后续发布能力。
当前执行顺序:
- 配置专用 Ozon 类目缓存源,在真实 Ozon 样本和 8100 页面完成类目预热、树形选择及来源回填回归。
- 完成候选媒体选择与按需物化,并把已选素材纳入 SKU 编辑草稿。
- 完成定价、AI 任务与候选结果、首次发布和在线商品同步的剩余纵向闭环。
历史计划(仅供追溯)
- 在真实 Ozon 多 SKU 页面加载已构建扩展,确认 MAIN world bridge 未被页面策略阻断、三 SKU 都写入采集测试页、SKU 级画廊关系正确,并在新版规范化载荷确认完整特征与描述;再以任一兄弟 SKU 页面重采集,确认服务端覆盖同一记录。
- 用允许保存的单 SKU、单规格轴和颜色×尺码页面样本,在 1688、淘宝、天猫逐项验证采集结果;页面状态未暴露组合数据时补站点适配器,但绝不以规格轴猜测 SKU。之后独立实施 Seller 网页四接口本地观察实验,默认关闭、仅保存净化结果且不影响标准采集。
- 将当前
/collection-test/{collectionItemId}只读检查面替换为正式商品编辑入口:按页头平台创建发布配置、预选同平台默认门店,并接入 SKU/媒体/类目编辑。 - 进入商品编辑/在线同步前,关闭评审中的 P1:SKU 与平台发布配置同商品归属、未映射属性持久化、统一乐观锁 Repository 规则;平台能力 Schema 已在 M2.1 按固定键落地。在线商品归档策略和 Client ID 编辑边界仍待确认。
- 账户认证待开发:忘记密码。 登录页提供入口;请求接口始终返回同一结果以防邮箱枚举,使用短时有效的一次性不透明邮件令牌(数据库仅存哈希),确认重置后撤销该用户全部 session。实施前先补充认证 API 契约、邮件投递配置和迁移;交付 API、Web、邮件链接与限流/令牌失效回归测试。详情见
requirements/account-store-watermark.md§2.3。
账户资料的已确认补充:users.avatar_asset_id 只保存素材引用,绝不保存公开 URL;users.revision 用于 PATCH /api/auth/me 的 If-Match。资料更新已实现幂等与版本冲突检测;修改密码已实现旧密码校验、按 IP/用户限流、撤销旧会话并轮换新会话。头像上传必须复用素材模块的预签名直传与校验流程,不引入 base64 直传;素材模块尚未实现时头像只能为 null。