Files
cmshoppe/docs/04-architecture.md
T
chengmaandClaude Opus 4.8 479d02a2b8 docs: 初始化 cmshopee 文档、设计与项目骨架
- docs/ 完整 harness coding 文档集(愿景/需求/技术栈/架构/编码规则/任务/api/routes/current-state)
- 5 Tab 流水线设计 + UI 效果图 SVG(docs/ui/)
- cdp.py CDP 底座;prototypes/ 已验证原型脚本(待 editor.py 移植后清理)
- AGENTS.md/CLAUDE.md 入口、progress.md 执行流水、.gitignore(排除凭证/DB/图片)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:30:37 +08:00

322 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构设计
> 本文讲“怎么把技术栈搭起来”:模块职责、存储模型、流水线、CDP 已验证事实、开发顺序。
> 具体用了哪些库 / 平台,见 [技术栈](03-tech-stack.md)。
## 一、系统结构
Windows 本地桌面自动化工具,无后端服务,5 Tab GUI 驱动一条流水线。
```text
运营(人)
|
v
GUI(Tkinter ttk.Notebook,5 Tab)
① 导入采集 ② AI生成 ③ 更新shopee ④ 账号管理 ⑤ 设置
|
v
核心模块(Python)
├── appconfig 读应用配置 config.json(Chrome 路径、目录根、AI 配置、端口范围…)
├── db SQLite 读写:账号、任务、各阶段结果(cmshopee.db)
├── excel openpyxl 导入输入列 / 回写输出列到原 Excel
├── config 账号 ↔ user-data-dir 绑定、slug、目录创建
├── chrome 按账号拼启动参数、启动/探测 Chrome、生成快捷方式
├── cdp CDP 客户端(连接、找/开 tab、执行 JS、拖拽)
├── editor 登录检测 / 采集旧标题旧封面 / 改标题 / 换封面 / 点更新
└── ai 文本生成(提示词+旧标题→新标题)/ 图像生成(提示词+旧封面→新封面)
|
v
Google Chrome(每账号独立 --user-data-dir + --remote-debugging-port) + AI 服务(外部)
|
v
Shopee 卖家中心页面 / 本地图片目录
```
真实组件:
- GUI 入口:`gui.py`(待建,Tkinter + `ttk.Notebook`,5 Tab)。
- 核心模块:`appconfig.py`、`db.py`、`excel.py`、`config.py`、`chrome.py`、`editor.py`、`ai.py`(待建);CDP 底座 `cdp.py`(已有)。
- 已验证脚本(重构进模块):`prototypes/demo.py`、`prototypes/set_title.py`、`prototypes/set_cover.py`、`prototypes/get_title.py`、`prototypes/cookies.py`、`prototypes/inspect_images.py`、`prototypes/grab.py`。
- 外部依赖:本机 Google Chrome;Shopee;AI 服务(文本+图像,服务商待定);`openpyxl`。
## 二、流水线(核心)
每个任务(Excel 行)依次走过 5 个阶段,状态字段 `stage` 贯穿全程:
```text
imported → collected → generated → applied
(导入 Excel) (采集旧数据) (AI 生成新数据) (改 Shopee 并提交)
① Tab ① Tab ② Tab ③ Tab
```
- **imported**:openpyxl 解析输入列入库。
- **collected**:只读打开商品页,读旧标题、下载旧封面到本地,写 `old_title/old_cover_path`,回写 Excel 旧字段。
- **generated**:AI 用提示词+旧标题生成新标题;用提示词+旧封面生成新封面(image-to-image),新图存本地。**无人工确认环节**,生成完即可被 ③ 执行(新图留档本地 + 回写 Excel 供事后追溯)。
- **applied**:打开编辑页换标题+封面,**总是点「更新」提交线上**(无开关),回写结果。
任意阶段失败 → `stage` 不前进、记 `error`、`result/skipped`,不影响其他任务。
## 三、职责划分
**GUI(5 Tab)**:见 [routes.md](routes.md)。只做交互与预览,不写业务逻辑;耗时操作走后台线程。**首次未配账号/未登录时,① ③ 执行按钮禁用并提示去 ④。**
**核心模块**
- `appconfig`:读写 `config.json`(Chrome 路径、`chrome_user_data_dir` 根、图片目录、AI 配置、端口、DB 路径、默认是否提交)。
- `db`:SQLite 读写账号、任务、各阶段结果;建表/迁移。
- `excel`:openpyxl 读输入列、把输出列回写原 Excel(处理文件锁)。
- `config`:账号 ↔ user-data-dir 绑定;slug;目录创建。
- `chrome`:拼接启动命令、启动、探测端口、(可选)生成快捷方式。
- `cdp`:连接调试端口、找/开 tab、执行 JS、拖拽、注入文件。
- `editor`:登录检测、**采集**(读旧标题、下载旧封面)、改标题、换封面、点更新。
- `ai`:`gen_title(prompt, old_title)`、`gen_cover(prompt, old_cover_path)`(外部 AI;服务商待定)。
**存储(同一事实只存一处)**
- 应用配置(模型选择、生成参数、目录、Chrome 路径)→ `config.json`。
- AI 模型清单(url/模型/密钥/类型/连接超时)→ `config/ai_models.json`(含密钥,必须 gitignore)。
- 业务数据(账号、任务、各阶段结果)→ SQLite `cmshopee.db`。
- 图片(采集的旧封面、AI 生成的新封面)→ 本地图片目录(路径记在 DB)。
- 提示词 → 标题提示词存单文件 `title_prompt.txt`;封面提示词存多模板 `prompts/cover/<名称>.txt`。
- 登录态 → 各账号 `chrome_user_data_dir/<slug>/`。
## 四、多账号隔离方案(决策)
采用**每账号独立 user-data-dir**(非 Chrome profile)。`--remote-debugging-port` 绑定在 user-data-dir/进程上,profile 方案无法每账号独立 CDP、串号风险高。启动主路径用程序 `subprocess` 直启(`--remote-debugging-port` + `--remote-allow-origins=*` + `--user-data-dir`);可选生成 `.lnk` 快捷方式(PowerShell `WScript.Shell`,参数写在「目标」字段)。
## 五、数据模型
### 5.1 应用配置 `config.json`
```json
{
"chrome_path": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"user_data_root": "chrome_user_data_dir",
"image_dir": "images",
"db_path": "cmshopee.db",
"default_debug_port": 9222,
"debug_port_range": [9222, 9260],
"cdp_ready_timeout": 60,
"ai": {
"default_text_model": "GPT-5.5 文本",
"default_image_model": "Nano Banana 2",
"title_concurrency": 4,
"image_concurrency": 4,
"retry": 2,
"jpg_quality": 90,
"resolution": "1k",
"resolution_timeouts": { "512": 180, "1k": 240, "2k": 360, "4k": 600 }
}
}
```
`ai` 段只放**选择 + 全局生成参数**:
- `default_text_model` / `default_image_model`:引用 `ai_models.json` 里的模型名(标题用文本模型、封面用图像模型)。
- `resolution`:当前分辨率,下拉 `512 / 1k / 2k / 4k`。
- `resolution_timeouts`:分辨率 → **等待大模型返回超时(秒)** 的映射;用户选分辨率即自动套用,不单独填。
- 模型本身的定义(url/key/类型/连接超时…)在 `config/ai_models.json`,见 5.1b。
- 密钥不在 `config.json`:每个模型的 `api_key` 存于 `config/ai_models.json`,加密、打码、gitignore。
### 5.1b AI 模型清单 `config/ai_models.json`
模型定义清单("有哪些模型"),与 `config.json` 的 `ai` 段("选了哪个 + 全局参数")职责分开。
```json
{
"models": [
{
"name": "Nano Banana 2", // 服务商/模型名,唯一,作下拉显示与引用键
"category": "image", // text | image —— 决定它出现在“标题/图片大模型”哪个下拉
"enabled": true,
"url": "https://api.vectorengine.ai/v1/chat/completions",
"model": "gemini-3.1-flash-image-preview", // 模型 ID
"api_key": "***", // 密钥:加密存、UI 打码、不入日志、不进版本库
"api_type": "auto", // chat | images_edits | auto —— 决定请求构造方式
"connect_timeout_seconds": 30, // 连接该服务超时(每模型,默认 30)
"timeout_seconds": 0, // 返回超时:0/留空 = 运行时按 resolution_timeouts 取值
"extra_body": {}
}
]
}
```
关键事实:
- **`category`(文本/图像)是必需的**:角色下拉据此过滤(标题下拉只列 text、图片下拉只列 image),防止错配。
- 约束:**至少各有一个 text 与一个 image 模型**;下拉默认最少一项、删到剩一项时禁用「删除」。
- `connect_timeout_seconds`(连接超时)属于**模型**;返回超时由分辨率映射决定(不在模型上单设)。
- `api_type` 反映不同 API 形状(`chat`/`images_edits`/`auto`),请求构造按它分支。
- `name` 唯一;`api_key` 加密存、打码显示。
### 5.2 SQLite `cmshopee.db`
```sql
-- 账号(④ 账号管理)
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
account_name TEXT NOT NULL, -- Shopee 登录账号名(展示/参考)
alias TEXT UNIQUE NOT NULL, -- 别名,Excel 用它匹配
region_host TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL, -- user-data-dir 子目录名 [a-z0-9_]
user_data_dir TEXT NOT NULL,
debug_port INTEGER NOT NULL,
password_enc TEXT, -- 加密存,仅参考,不自动登录
note TEXT,
created_at TEXT NOT NULL,
last_login_at TEXT
);
-- 任务 + 各阶段结果(贯穿流水线)
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
batch_id TEXT NOT NULL,
source_file TEXT,
-- 输入列(Excel)
account_name TEXT,
alias TEXT NOT NULL,
item_id TEXT NOT NULL,
-- 采集输出(程序写,改前快照)
old_title TEXT,
old_cover_path TEXT, -- 旧封面本地图片路径
-- AI 输出
new_title TEXT,
new_cover_path TEXT, -- 新封面本地图片路径
-- 应用
committed INTEGER DEFAULT 0, -- 是否成功点「更新」提交
stage TEXT DEFAULT 'imported', -- imported/collected/generated/applied/failed/skipped
error TEXT,
updated_at TEXT
);
```
关键事实:
- `alias` 是账号↔任务**唯一关联键**;找不到账号 → stage=skipped,error=别名未匹配,最后弹窗汇总。
- `account_name` 仅展示/参考;匹配以 `alias` 为准。
- `old_title/old_cover_path`:程序**采集阶段抓取**的快照(输出)。
- `new_title/new_cover_path`:**AI 生成**结果(输出),图片落本地,**无人工确认**,生成即可应用。
- `committed`:③ 执行时**总是点「更新」提交**;该字段记录提交是否成功。
- 各字段**每阶段处理完立即写回 SQLite**(实时落库);阶段结束后批量回写 Excel。
### 5.3 Excel 模板
| 列 | 含义 | 输入/输出 |
| --- | --- | --- |
| 账号名 | Shopee 账号名(展示) | 输入 |
| 别名 | 关联 accounts.alias(匹配键) | 输入 |
| 商品id | item_id | 输入 |
| 旧标题 | 采集到的旧标题 | 输出,回写 |
| 旧封面图片路径 | 采集到的旧封面本地路径 | 输出,回写 |
| 新标题 | AI 生成的新标题 | 输出,回写 |
| 新封面图片路径 | AI 生成的新封面本地路径 | 输出,回写 |
| 更新状态 | 处理结果(成功/失败/跳过+原因) | 输出,回写 |
- 输入列(账号名/别名/商品id)运营填;其余为程序各阶段输出,**回写到原 Excel**(被锁→提示重试/另存副本)。
- 别名以此列为匹配权威,不解析文件名。
### 5.4 本地图片目录
```text
images/<slug>/<item_id>_old.<ext> # 采集下载的旧封面
images/<slug>/<item_id>_new.<ext> # AI 生成的新封面
```
路径记入 DB;上传新封面用本地 `<item_id>_new` 文件(Windows 绝对路径传 setFileInputFiles)。
## 六、关键流程细节
### 6.1 采集(① Tab,只读)
- 用账号 Chrome 打开商品页,等就绪,读旧标题(标题输入框 value)。
- 旧封面:取第一张 itembox 的 `img.src`(CDN 链接),下载到 `images/<slug>/<item_id>_old`。
- 写 `old_title/old_cover_path`、stage=collected;批量回写 Excel 旧字段。
### 6.2 AI 生成(② Tab)
单个「开始生成」按钮,**两段式、各自并发**(标题快、图片慢,分开并发更高效):
1. **并发生成标题**:线程池大小 = `title_concurrency`,用 `default_text_model` 调 `gen_title(标题提示词, old_title)` → new_title。
2. **接着并发生成图片**:线程池大小 = `image_concurrency`,用 `default_image_model` 调 `gen_cover(封面提示词, old_cover_path, resolution, jpg_quality)` → 新图存 `images/<slug>/<item_id>_new.jpg`。
- 连接超时取该模型 `connect_timeout_seconds`;**返回超时取 `resolution_timeouts[resolution]`**(512→180/1k→240/2k→360/4k→600)。
- 失败重试:每次调用失败按 `retry` 次重试,仍失败则记 error(不阻塞其余)。
- 每条/每张完成**立即 `set_generated` 写库**(实时落库,停止或崩溃不丢已生成的)。
- **「停止」**:取消未开始的任务,正在跑的少量完成或中断;停止后可再次「开始生成」对剩余继续。
- 进度:`标题 x/n · 封面 x/n · 失败 n`。
- 生成后 stage=generated;**无人工确认**。双击任务弹窗查看新旧封面(纯查看),可选对某行 `重生成`;新标题直接用 AI 输出(不可编辑)。
- 并发数、重试、分辨率、jpg 质量、模型/Key 均来自 ⑤ 设置(`config.json` 的 `ai` 段)。
提示词管理:
- **标题提示词**:单个文本,「保存」写入 `title_prompt.txt`;软件启动时加载该文件回显到输入框(缺失则空)。
- **封面提示词**:多模板。下拉选模板(读 `prompts/cover/*.txt`),图标工具栏 新建/保存/另存为/重命名/删除;重名校验、删除二次确认、删空给默认。
- **变量**:封面提示词支持占位符 `{旧标题}`、`{新标题}`、`{商品id}`、`{店铺}`,生成前用该任务真实值替换(`render_prompt`)。「插入标题」= 在光标处插入 `{新标题}`;「预览」= 用某条任务的值替换变量后展示,确认实际发送给 AI 的内容。
### 6.3 应用更新(③ Tab)
- 对**已生成(generated)任务**:`open_product` → `change_title(new_title)`(如有)→ `replace_cover(new_cover_path)`(如有)→ **总是 `click_update` 提交**。
- 串行、单条失败继续;每条立即写 SQLite;全部完成回写 Excel(新字段+状态)+ 弹窗汇总。
### 6.4 登录检测
- 打开卖家中心,重定向到登录页或缺会话 Cookie(`SPC_ST`/`SPC_U`)→ 未登录;不自动登录,提示人工登录。
## 七、CDP 已验证事实(务必遵守)
| 难点 | 已验证结论 |
| --- | --- |
| Chrome 启动参数 | 全关后带 `--remote-debugging-port=<port> --remote-allow-origins=* --user-data-dir=<dir>`;缺 allow-origins 则 WebSocket 403 |
| 代理干扰 | 清除 `*_proxy`(requests `trust_env=False`),否则连本地 CDP 超时 |
| WebSocket Origin | `websocket-client` `suppress_origin=True` |
| SPA 就绪 | 不用 load 事件;轮询“标题输入框 + 图片 itembox + 上传输入框”三者都在 |
| 标题输入框 | XPath `//input[@class='eds-input__input' and string-length(@modelvalue)>24]` |
| 写标题 | 原生 setter + 派发 `input`/`change`;`value`==`modelvalue`==新值 |
| 读旧封面 | 第一张 itembox 的 `img.src`(`susercontent` CDN),下载到本地 |
| 上传输入框 | `.shopee-image-manager__upload input[type=file]`;`DOM.setFileInputFiles` 传 Windows 路径 |
| 上传成功 | 张数 +1 且新图 src 为 `susercontent` |
| 封面=第一位 | `Input.dispatchMouseEvent` 拖到第一位,落点 `第一张.left - 0.30*宽` |
| 满 9 张 | 上限 9;换封面先删第一张(`.shopee-image-manager__icon--delete`,确认框待实测)|
| 更新按钮 | `button.eds-button` 中 `<span>更新</span>`;③ 执行即点提交;禁用态(校验未过)记为失败 |
| 登录检测 | 重定向到登录页或缺 `SPC_ST` → 未登录 |
高风险动作(删满 9 张封面、点更新提交、AI 图直接上线)先在测试商品验证。注意:本设计**无人工确认、无提交开关**,③ 一旦执行即把 AI 标题/封面提交线上,新图本地留档+回写 Excel 是唯一事后追溯手段。
## 八、推荐开发顺序
1. **地基**:`editor.py`(含采集)、`appconfig.py`+`config.json`、`db.py`、`.gitignore`。
2. **账号与启动**(④):`config` 建目录、`chrome` 启动器/快捷方式、登录保活与检测、账号 CRUD。
3. **导入采集**(①):`excel` 导入、采集旧标题/旧封面、回写。
4. **AI 生成**(②):`ai` 模块、提示词、对照预览与确认。
5. **更新 shopee**(③):对已生成任务换标题+封面、直接提交、回写。
6. **设置**(⑤)+ 首次未配账号引导保护。
## 九、项目结构建议
```text
cmshopee/
├── docs/
├── config.json # 应用配置(模型选择/生成参数/路径,gitignore)
├── config/ai_models.json # AI 模型清单(含密钥,必须 gitignore)
├── cmshopee.db # SQLite(账号/任务/结果,gitignore)
├── chrome_user_data_dir/ # 各账号 Chrome 配置(含登录态,gitignore)
├── images/ # 旧封面/新封面本地图片(建议 gitignore)
├── title_prompt.txt # 标题提示词(单文件,启动回显)
├── prompts/cover/<名称>.txt # 封面提示词模板(多个)
├── appconfig.py / db.py / excel.py / config.py / chrome.py / editor.py / ai.py / prompts.py / gui.py # 待建
├── cdp.py # CDP 底座(正式模块,已有)
└── prototypes/ # 已验证原型/探查脚本(demo/set_*/get_title/cookies/inspect_images/grab/1.py)
# 逻辑待并入 editor.py 后清理;见 prototypes/README.md
```
> `config.json`、`config/ai_models.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` 含密钥/凭证/业务数据,必须 gitignore。
## 十、架构纪律
- CDP 交互事实变化同步第七节。
- 存储边界:应用设置→config.json,账号/任务/结果→SQLite,图片→本地目录并记路径于 DB,登录态→user-data-dir;同一事实只存一处。
- 别名是账号↔任务唯一关联键。
- 密码与 AI Key 加密存、不外传、不写日志;不自动登录。
- AI 生成内容直接用于 ③ 更新(无人工确认);新图本地留档 + 回写 Excel 以备追溯。
- 高风险模块先单独验证,再接入流水线。