diff --git a/AGENTS.md b/AGENTS.md index 4e97638..f3d5f06 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,7 @@ cmshopee 是一个 **Windows 本地桌面自动化工具**:管理多个 Shopee ## 安全红线 -- 绝不把真实账号、密码、Cookie、token 写进代码、文档或日志;密码加密存、不自动登录。 +- 绝不把真实账号、密码、Cookie、token 写进代码、文档或日志;密码/API Key 可在本地配置或 DB 明文保存,但文件必须 gitignore,且不自动登录。 - `config.json`、`cmshopee.db`、`chrome_user_data_dir/`(含密码/登录态)绝不提交版本库。 - 「更新」提交线上、删除满 9 张封面等不可逆动作,必须有显式确认;③ 点击「开始更新」后必须弹窗确认,用户确认后才批量更新当前筛选结果。 - 不绕过 Shopee 的验证码、风控、限流或权限校验。 @@ -41,6 +41,7 @@ cmshopee 是一个 **Windows 本地桌面自动化工具**:管理多个 Shopee ```bash python -m py_compile *.py # 语法检查 +python -m unittest discover -s tests # 纯逻辑单元测试(tests 存在后) python prototypes/demo.py # 单账号闭环(不提交) ``` diff --git a/CLAUDE.md b/CLAUDE.md index 1f0b72a..3fbe4a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,4 +10,4 @@ Claude Code 处理本仓库任务时: 2. 再按 `AGENTS.md` 进入 [`docs/00-ai-start-here.md`](docs/00-ai-start-here.md) 和相关文档(重点 `docs/04-architecture.md` 第七节 CDP 已验证事实)。 3. 不在本文重复维护任务流程、编码规则或文档清单,避免和 `AGENTS.md` 漂移。 -安全红线(详见 `AGENTS.md`):不写真实凭证、密码加密存不自动登录;不提交 `config.json`、`cmshopee.db`、`chrome_user_data_dir/`;不可逆操作需显式确认,③ 开始更新前需弹窗确认;不绕过 Shopee 风控。 +安全红线(详见 `AGENTS.md`):不写真实凭证到代码/文档/日志;密码/API Key 可本地明文保存但不自动登录;不提交 `config.json`、`cmshopee.db`、`chrome_user_data_dir/`;不可逆操作需显式确认,③ 开始更新前需弹窗确认;不绕过 Shopee 风控。 diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index 724265a..70a0d74 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -11,7 +11,7 @@ cmshopee 是一个 Windows 本地桌面自动化工具(PySide6,5 Tab), 目标闭环:④ 配账号并登录 → ① 导入 Excel(按“别名”列关联账号)、采集旧标题/旧封面并回写 → ② 用提示词 AI 生成新标题/新封面(不设逐条确认阶段)→ ③ 对已生成任务点击「开始更新」,弹窗确认后批量改标题+换封面并点「更新」提交 → 结果实时存 SQLite、批量回写原 Excel。 -存储:应用设置 `config.json` + AI 模型清单 `config/ai_models.json`(含加密 AI Key)+ 业务数据 SQLite `cmshopee.db` + Excel 用 openpyxl + 图片存本地 `images/`。AI 服务商待定。 +存储:应用设置 `config.json` + AI 模型清单 `config/ai_models.json`(含本地明文 AI Key,必须 gitignore)+ 业务数据 SQLite `cmshopee.db` + Excel 用 openpyxl + 图片存本地 `images/`。AI 服务商待定。 ## 必读顺序 @@ -39,7 +39,7 @@ cmshopee 是一个 Windows 本地桌面自动化工具(PySide6,5 Tab), 3. Phase 2:导入 Excel、采集旧标题/旧封面并回写。 4. Phase 3:AI 生成新标题/新封面。 5. Phase 4:③ 批量确认后更新 Shopee 并回写结果。 -6. Phase 5:设置、加密密钥方案、收尾。 +6. Phase 5:设置、敏感信息提示与日志脱敏、收尾。 ## 领取任务规则 @@ -116,6 +116,7 @@ cmshopee 是一个 Windows 本地桌面自动化工具(PySide6,5 Tab), ```bash python -m py_compile *.py # 语法检查 +python -m unittest discover -s tests # 纯逻辑单元测试(tests 存在后) python prototypes/demo.py # 单账号闭环验证(分步,不提交) ``` @@ -123,4 +124,5 @@ python prototypes/demo.py # 单账号闭环验证(分步,不 - 改 CDP / editor 逻辑后:在测试商品(ITEM_ID 51100639510)上跑 `prototypes/demo.py` 实测。 - 改账号配置后:验证 SQLite 账号读写与 user-data-dir 创建;改应用设置后验证 `config.json` 读写。 +- 改 DB / Excel / prompts / appconfig 纯逻辑后:补或更新 `tests/` 下的 unittest。 - 如果命令当前不可运行(如 GUI 未建),在回复里如实说明。 diff --git a/docs/02-requirements.md b/docs/02-requirements.md index de7c437..4ab6a6f 100644 --- a/docs/02-requirements.md +++ b/docs/02-requirements.md @@ -38,7 +38,7 @@ | 功能 | 用户能做什么 | 优先级 | | --- | --- | --- | -| 账号管理(④) | 增删改账号:账号名/别名/地区/数据目录/端口/密码(加密仅参考)/备注;登录状态;启动登录、检测登录 | P0 | +| 账号管理(④) | 增删改账号:账号名/别名/地区/数据目录/端口/密码(本地明文仅参考,UI 打码)/备注;登录状态;启动登录、检测登录 | P0 | | 导入(①) | 导入多个 Excel,按模板解析输入列(账号名/别名/商品id)为任务列表 | P0 | | 导入校验汇总(①) | 导入后展示文件数、解析行数(原始数据量)、有效/无效行、匹配账号行数(按账号细分)、未匹配行数;跑采集前先纠错 | P0 | | 采集旧数据(①) | 程序只读打开商品页,抓取旧标题、下载旧封面到本地,回写 Excel 旧字段 | P0 | @@ -91,7 +91,7 @@ | 执行方式 | 多账号串行、逐任务执行,单条失败继续 | | 旧标题/旧封面 | 程序在「采集」阶段改前抓取的快照(输出列),运营不填 | | 新标题/新封面 | AI 生成(输出列),直接用于 ③;不设逐条确认阶段,本地留档+回写 Excel 供追溯 | -| AI 服务 | 文本+图像生成,服务商/模型待定;Key 加密存于配置;见 [技术栈](03-tech-stack.md) | +| AI 服务 | 文本+图像生成,服务商/模型待定;Key 本地明文存于 `config/ai_models.json`,UI 打码、不入日志;见 [技术栈](03-tech-stack.md) | | 本地图片 | 旧封面下载、新封面生成存本地图片目录,路径记 DB | | 结果落库时机 | 各阶段处理完立即写 SQLite;该 Excel 全部完成后批量回写原文件 | | 原文件被占用 | 回写时若原 Excel 被锁定,提示关闭重试或另存副本(SQLite 为事实来源) | @@ -100,11 +100,11 @@ ## 七、待确认 / 风险点 - **账号 / 凭证风险**:登录态存在各账号 user-data-dir,等同账号凭证;目录不得提交版本库、不得外传。 -- **密码存储风险**:Tab2 的密码加密存于 SQLite,仅供人工参考,**绝不自动登录/自动填**;`cmshopee.db`、`config.json` 必须 gitignore;加密密钥不得与密文同存(见 [任务 T-403](06-tasks.md))。 +- **密码存储风险**:Tab④ 的密码本地明文存于 SQLite,仅供人工参考,**绝不自动登录/自动填**;`cmshopee.db`、`config.json` 必须 gitignore;UI 打码、日志脱敏(见 [任务 T-503](06-tasks.md))。 - **别名匹配风险**:别名以 Excel 列为准;不匹配的行略过并最后弹窗,执行起始在日志逐条打印「文件→匹配账号」留痕,防误改。 - **AI 主图风险(高,已知并接受)**:本设计不设逐条人工审核和常驻提交开关;AI 生成的标题/封面经 ③ 批量确认后会提交线上。主图若失真/夸大/侵权可能违反 Shopee 规则甚至下架。缓解:开始更新前弹窗确认筛选范围和任务数量,新图本地留档 + 回写 Excel 供事后追溯;强烈建议先在测试商品验证再批量。 - **AI 成本与依赖**:接入文本+图像 AI = 新外部依赖 + API 费用 + Key 管理;服务商/模型/合规待 [技术栈](03-tech-stack.md) 确认。 -- **AI Key 安全**:Key 加密存于 `config/ai_models.json`,不写日志、不提交版本库。 +- **AI Key 安全**:Key 本地明文存于 `config/ai_models.json`,UI 打码,不写日志,不提交版本库。 - **第三方平台风险**:Shopee 页面结构、class 名、接口随时可能变;限流、风控、封号风险存在,禁止高频批量。 - **自动化边界风险**:③ 确认后会自动改标题、上传图片、拖拽并点「更新」提交线上;点「开始更新」并确认前需自行确保筛选范围、任务来源与 AI 产出可接受。 - **合规风险**:仅在自有/授权账号上操作;遵守 Shopee 卖家条款;不绕过任何平台限制。 diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index fa49b1a..1488363 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -22,7 +22,7 @@ | AI 图像生成 | 选 `default_image_model`(category=image,image-to-image) | 选型在配置 | 提示词+旧封面→新封面;分辨率 512/1k/2k/4k,返回超时随分辨率 | | 并发 | 标准库 `concurrent.futures.ThreadPoolExecutor` | 已定 | 标题/图片分别按并发数并行;可停止、可重试 | | 图片处理 | `requests`(下载)+ `Pillow`(按分辨率/jpg质量存盘) | 部分待定 | 下载旧封面;新封面按 resolution 生成、jpg_quality 存盘 | -| 测试 | `python -m py_compile` + 手动 CDP 验证 | 待完善 | 纯 UI/浏览器交互难以单测;关键逻辑(配置读写、参数拼装)可加 `pytest` | +| 测试 | `python -m py_compile` + `unittest` + 手动 CDP/AI 验证 | 已定(分层) | 配置/DB/Excel/prompts 用单测;CDP/Shopee 与真实 AI 属集成验证或 mock | ## 二、决策记录与演进 @@ -44,6 +44,7 @@ | 安装依赖 | `pip install websocket-client requests openpyxl pillow PySide6` | | 检查 PySide6 | `python -c "import PySide6; print(PySide6.__version__)"` | | 语法检查 | `python -m py_compile *.py` | +| 单元测试 | `python -m unittest discover -s tests` | | 跑单账号演示 | `python prototypes/demo.py`(分步)/ `set AUTO=1 && python prototypes/demo.py`(自动) | | 提交更新(真改线上) | `set UPDATE=1 && python prototypes/demo.py` | | 读取某账号 Cookie(调试) | `python prototypes/cookies.py` | @@ -67,3 +68,13 @@ set AUTO=1 && python prototypes/demo.py - GUI 框架固定为 PySide6,不允许混入 Tkinter/PyQt/Web 形成两套 UI。 - 不引入第二套浏览器自动化方案(不要 cdp.py 之外再混入 selenium/playwright)。 - 不确定的技术选型先更新本文,再进入代码。 + +## 五、测试分层 + +| 层级 | 覆盖对象 | 验证方式 | +| --- | --- | --- | +| 语法 | 所有 `.py` | `python -m py_compile *.py`,后续可扩展 `compileall` | +| 单元 | `appconfig/db/excel/prompts/config/chrome` 的纯逻辑 | `python -m unittest discover -s tests`,使用临时目录/临时 SQLite/样例 Excel | +| GUI 轻测 | PySide6 主窗口可创建、Tab 数量、worker signal 基本行为 | 可用 unittest 构造 `QApplication`,不连真实 Shopee | +| 集成 | CDP/editor 操作 Shopee 测试商品 | 手动跑 `prototypes/demo.py` 或后续专用集成脚本 | +| 外部 AI | 模型连接、文本/图像生成 | 默认 mock;真实调用只在手动验证时跑,避免成本和限流 | diff --git a/docs/04-architecture.md b/docs/04-architecture.md index 9eca8f8..06a31e5 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -41,7 +41,7 @@ Shopee 卖家中心页面 / 本地图片目录 ## 二、流水线(核心) -每个任务(Excel 行)依次走过 5 个阶段,状态字段 `stage` 贯穿全程: +每个任务(Excel 行)依次走过 4 个业务阶段,状态字段 `stage` 贯穿全程: ```text imported → collected → generated → applied @@ -74,7 +74,7 @@ imported → collected → generated → applied **存储(同一事实只存一处)** - 应用配置(模型选择、生成参数、目录、Chrome 路径)→ `config.json`。 -- AI 模型清单(url/模型/密钥/类型/连接超时)→ `config/ai_models.json`(含密钥,必须 gitignore)。 +- AI 模型清单(url/模型/密钥/类型/连接超时)→ `config/ai_models.json`(API Key 本地明文保存,必须 gitignore,UI 打码显示)。 - 业务数据(账号、任务、各阶段结果)→ SQLite `cmshopee.db`。 - 图片(采集的旧封面、AI 生成的新封面)→ 本地图片目录(路径记在 DB)。 - 提示词 → 标题提示词存单文件 `title_prompt.txt`;封面提示词存多模板 `prompts/cover/<名称>.txt`。 @@ -116,7 +116,7 @@ imported → collected → generated → applied - `resolution`:当前分辨率,下拉 `512 / 1k / 2k / 4k`。 - `resolution_timeouts`:分辨率 → **等待大模型返回超时(秒)** 的映射;用户选分辨率即自动套用,不单独填。 - 模型本身的定义(url/key/类型/连接超时…)在 `config/ai_models.json`,见 5.1b。 -- 密钥不在 `config.json`:每个模型的 `api_key` 存于 `config/ai_models.json`,加密、打码、gitignore。 +- 密钥不在 `config.json`:每个模型的 `api_key` 存于 `config/ai_models.json`,本地明文保存、UI 打码、gitignore、不入日志。 ### 5.1b AI 模型清单 `config/ai_models.json` @@ -131,7 +131,7 @@ imported → collected → generated → applied "enabled": true, "url": "https://api.vectorengine.ai/v1/chat/completions", "model": "gemini-3.1-flash-image-preview", // 模型 ID - "api_key": "***", // 密钥:加密存、UI 打码、不入日志、不进版本库 + "api_key": "***", // 密钥:本地明文存、UI 打码、不入日志、不进版本库 "api_type": "auto", // chat | images_edits | auto —— 决定请求构造方式 "connect_timeout_seconds": 30, // 连接该服务超时(每模型,默认 30) "timeout_seconds": 0, // 返回超时:0/留空 = 运行时按 resolution_timeouts 取值 @@ -147,11 +147,21 @@ imported → collected → generated → applied - 约束:**至少各有一个 text 与一个 image 模型**;下拉默认最少一项、删到剩一项时禁用「删除」。 - `connect_timeout_seconds`(连接超时)属于**模型**;返回超时由分辨率映射决定(不在模型上单设)。 - `api_type` 反映不同 API 形状(`chat`/`images_edits`/`auto`),请求构造按它分支。 -- `name` 唯一;`api_key` 加密存、打码显示。 +- `name` 唯一;`api_key` 本地明文保存、打码显示。 ### 5.2 SQLite `cmshopee.db` ```sql +-- 批次(一次导入动作) +CREATE TABLE batches ( + id TEXT PRIMARY KEY, -- batch_id,如 20260626_153000_xxxx + source_files_json TEXT NOT NULL, -- 导入文件绝对路径列表(JSON) + status TEXT NOT NULL DEFAULT 'active', -- active/done/partial/failed + note TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + -- 账号(④ 账号管理) CREATE TABLE accounts ( id INTEGER PRIMARY KEY, @@ -161,44 +171,75 @@ CREATE TABLE accounts ( 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, -- 加密存,仅参考,不自动登录 + password TEXT, -- 本地明文,仅参考,不自动登录;UI 打码,gitignore note TEXT, created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, last_login_at TEXT ); -- 任务 + 各阶段结果(贯穿流水线) CREATE TABLE tasks ( - id INTEGER PRIMARY KEY, - batch_id TEXT NOT NULL, - source_file TEXT, + id INTEGER PRIMARY KEY, + batch_id TEXT NOT NULL REFERENCES batches(id), + -- Excel 回写定位 + source_file TEXT NOT NULL, -- 展示用原路径 + source_file_abs TEXT NOT NULL, -- 绝对路径,作为回写分组依据 + source_sheet TEXT NOT NULL, -- sheet 名 + source_row INTEGER NOT NULL, -- Excel 行号(1-based) + row_key TEXT NOT NULL UNIQUE, -- batch/file/sheet/row 组成,防重复导入 -- 输入列(Excel) - account_name TEXT, - alias TEXT NOT NULL, - item_id TEXT NOT NULL, + account_name TEXT, + alias TEXT NOT NULL, + item_id TEXT NOT NULL, -- 采集输出(程序写,改前快照) - old_title TEXT, - old_cover_path TEXT, -- 旧封面本地图片路径 + old_title TEXT, + old_cover_path TEXT, -- 旧封面本地图片路径 -- AI 输出 - new_title TEXT, - new_cover_path TEXT, -- 新封面本地图片路径 + 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 + committed INTEGER NOT NULL DEFAULT 0, -- 是否成功点「更新」提交 + stage TEXT NOT NULL DEFAULT 'imported', -- imported/collected/generated/applied + status TEXT NOT NULL DEFAULT 'pending', -- pending/running/success/failed/skipped/cancelled + last_error TEXT, + collect_attempts INTEGER NOT NULL DEFAULT 0, + generate_attempts INTEGER NOT NULL DEFAULT 0, + apply_attempts INTEGER NOT NULL DEFAULT 0, + imported_at TEXT NOT NULL, + collected_at TEXT, + generated_at TEXT, + applied_at TEXT, + updated_at TEXT NOT NULL, + UNIQUE(batch_id, source_file_abs, source_sheet, source_row) ); + +CREATE INDEX idx_tasks_batch_stage_status ON tasks(batch_id, stage, status); +CREATE INDEX idx_tasks_alias ON tasks(alias); +CREATE INDEX idx_tasks_item ON tasks(item_id); ``` 关键事实: - `alias` 是账号↔任务**唯一关联键**;找不到账号 → stage=skipped,error=别名未匹配,最后弹窗汇总。 - `account_name` 仅展示/参考;匹配以 `alias` 为准。 +- `source_file_abs/source_sheet/source_row` 是回写 Excel 的权威定位;即使商品 ID 重复,也按原行回写。 +- `row_key` 防止同一批次内重复导入同一行。 - `old_title/old_cover_path`:程序**采集阶段抓取**的快照(输出)。 - `new_title/new_cover_path`:**AI 生成**结果(输出),图片落本地;不设逐条确认阶段,生成即可进入 ③。 +- `stage` 表示已完成到哪个业务阶段;`status` 表示当前处理结果。失败时 `stage` 保持在最后成功阶段,`status=failed`,错误写 `last_error`。 - `committed`:③ 用户确认批量更新弹窗后逐条点「更新」提交;该字段记录提交是否成功。 - 各字段**每阶段处理完立即写回 SQLite**(实时落库);阶段结束后批量回写 Excel。 +### 5.2b SQLite 并发与连接规则 + +- `db.connect()` 统一设置:`PRAGMA foreign_keys=ON`、`PRAGMA journal_mode=WAL`、`PRAGMA busy_timeout=5000`、`PRAGMA synchronous=NORMAL`。 +- SQLite connection 不跨线程共享;每个 PySide6 worker / 后台线程按需创建自己的 connection。 +- 写操作使用短事务:单条任务完成即提交,避免长时间占用写锁。 +- 写库遇到 locked/busy:按短间隔重试,超过 busy timeout 后返回明确错误并通过 signal 通知 GUI。 +- GUI 不直接缓存“事实状态”;写库成功后由 worker 发 `row_updated`,GUI 再刷新对应行。 +- Excel 回写以 SQLite 为事实来源;原 Excel 被锁时不回滚 SQLite,只提示关闭后重试或另存副本。 + ### 5.3 Excel 模板 | 列 | 含义 | 输入/输出 | @@ -328,7 +369,7 @@ cmshopee/ - CDP 交互事实变化同步第七节。 - 存储边界:应用设置→config.json,账号/任务/结果→SQLite,图片→本地目录并记路径于 DB,登录态→user-data-dir;同一事实只存一处。 - 别名是账号↔任务唯一关联键。 -- 密码与 AI Key 加密存、不外传、不写日志;不自动登录。 +- 密码与 AI Key 本地明文保存、UI 打码、不外传、不写日志;不自动登录。 - AI 生成内容直接进入 ③ 更新候选;③ 批量确认后提交,新图本地留档 + 回写 Excel 以备追溯。 - 高风险模块先单独验证,再接入流水线。 - GUI 只通过 signal/slot 接收 worker 进度;禁止后台线程直接操作 Qt widget 或共享 SQLite connection。 diff --git a/docs/05-coding-rules.md b/docs/05-coding-rules.md index 4e18ff3..45abd62 100644 --- a/docs/05-coding-rules.md +++ b/docs/05-coding-rules.md @@ -52,7 +52,10 @@ 完成前至少检查: - [ ] `python -m py_compile` 通过。 +- [ ] 纯逻辑改动有对应 `unittest`,至少覆盖正常路径和一个失败路径。 - [ ] 涉及 CDP 的改动,在测试商品(ITEM_ID 51100639510)上实跑验证。 +- [ ] 涉及 DB 的改动,覆盖 schema 初始化、重复初始化、短事务写入、失败状态写入。 +- [ ] 涉及 Excel 的改动,覆盖 source_file_abs/source_sheet/source_row 回写定位。 - [ ] 对得上需求验收标准(如标题 `value`+`modelvalue` 双等于、封面在第一位)。 - [ ] 没有夹带无关改动。 - [ ] 涉及选择器/流程/配置 schema 变化时,文档已同步。 @@ -60,13 +63,14 @@ ```bash python -m py_compile *.py +python -m unittest discover -s tests python prototypes/demo.py # 单账号闭环验证(不提交) ``` ## 7. 绝不 - 绝不把真实账号、密码、Cookie、token 写进代码、文档或日志。 -- 绝不明文存密码;DB 里密码必须加密,且加密密钥不与密文同存。 +- 绝不把密码/API Key 写进代码、文档、日志或导出文件;本地配置/DB 可明文保存,必须 gitignore,UI 打码。 - 绝不把 `config.json`、`cmshopee.db`、`chrome_user_data_dir/` 提交版本库。 - 绝不自动登录 / 自动填账号密码;登录由人工完成,程序只检测登录态。 - 绝不在缺少 ③ 批量确认弹窗确认的情况下点击「更新」提交线上。 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index b26454a..3c2e9c8 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -23,8 +23,10 @@ | --- | --- | --- | --- | --- | | T-001 | `editor.py`:改标题/换封面/点更新/登录检测/**采集(读旧标题+旧封面下载)**/apply_task,复用 `cdp.py` | - | 函数可调用,在测试商品跑通;与 `prototypes/demo.py` 行为一致 | TODO | | T-002 | `appconfig.py` + `config.json`(含 image_dir、ai 选择/参数段、端口等默认值;不含 AI Key) | - | 读写正常;不存在则写默认;AI Key 留给 `config/ai_models.json`/T-501 | TODO | -| T-003 | `db.py` + SQLite 建表(accounts/tasks,含各阶段字段) | - | `init_db` 幂等;账号/任务/各 set_* 可用;schema 同架构 5.2 | TODO | +| T-003 | `db.py` + SQLite 建表(batches/accounts/tasks,含 Excel 行定位、状态、时间戳、重试字段) | - | `init_db` 幂等;`connect` 设置 WAL/busy_timeout/foreign_keys;账号/批次/任务/各 set_* 可用;schema 同架构 5.2 | TODO | | T-004 | `.gitignore`:排除 `config.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` | T-002, T-003 | 凭证/业务/图片不被提交 | TODO | +| T-005 | AI 模型清单后端:`config/ai_models.json` 读写 + category 过滤 + 测试连接 | T-002 | 本地明文 api_key;UI/API 打码显示;日志脱敏;至少 text/image 各一个;`get_model` 返回调用所需字段 | TODO | +| T-006 | 单元测试基座:`tests/` + appconfig/db/excel/prompts 最小测试 | T-002, T-003 | `python -m unittest discover -s tests` 可跑;不依赖真实 Shopee/AI;临时文件在测试目录清理 | TODO | ## Phase 1 · 账号管理(④) @@ -35,14 +37,14 @@ | T-103 | 首次登录保活 + 登录检测 `is_logged_in` | T-102, T-001 | 关闭再启动免重登;登录/未登录判断准确 | TODO | | T-104 | PySide6 五 Tab 主窗口骨架(`QMainWindow` + `QTabWidget`,5 Tab 空壳) | T-002 | 五个 Tab 按顺序可切换;启动不阻塞;基础状态栏可用 | TODO | | T-104b | PySide6 worker 基类与线程启动工具(`BaseWorker` + `QThread` 包装) | T-104 | signals: progress/log/row_updated/failed/finished/cancelled;取消标记可用;worker 不直接操作 QWidget | TODO | -| T-105 | Tab④ 账号增删改(账号名/别名/端口/密码加密)+ 启动登录 + 检测登录 | T-104, T-101, T-103 | 增删改入库、建目录;密码加密默认打码;状态列刷新 | TODO | +| T-105 | Tab④ 账号增删改(账号名/别名/端口/密码本地明文仅参考)+ 启动登录 + 检测登录 | T-104, T-101, T-103 | 增删改入库、建目录;密码字段 UI 打码;状态列刷新;不自动登录/填密码 | TODO | | T-106 | 可选:为账号生成桌面快捷方式 | T-102 | `.lnk` 目标含该账号参数;双击进对应账号 | TODO | ## Phase 2 · 导入采集(①) | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | -| T-201 | `excel.py` 导入:解析多文件输入列入库 | T-003 | 按模板解析账号名/别名/商品id;脏数据容错;写 tasks(batch_id) | TODO | +| T-201 | `excel.py` 导入:解析多文件输入列入库 | T-003 | 按模板解析账号名/别名/商品id;记录 source_file_abs/source_sheet/source_row/row_key;脏数据容错;写 batches/tasks | TODO | | T-202 | Tab① 任务列表 + 导入按钮 + 别名匹配标记 | T-201, T-105 | `QTableView` 显示账号/别名/商品id/阶段;未匹配标“略过” | TODO | | T-202b | Tab① 导入汇总栏 | T-202 | 导入后显示 文件数/解析行数/有效/无效/匹配(按账号)/未匹配;未匹配可点击筛出 | TODO | | T-203 | 采集旧标题+旧封面(只读),下载图片,立即写库 | T-202, T-001, T-104b | 通过 worker 执行;逐条 set_collected;旧封面下载到 `images//`;未登录/未匹配略过记原因 | TODO | @@ -53,7 +55,7 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | -| T-301 | 确定 AI 服务商/模型并接入 `ai.py`(`gen_title`/`gen_cover`,带重试/分辨率/jpg质量) | T-002 | 可调;Key 加密读取;`gen_cover` 支持 resolution+jpg_quality;失败按 retry 重试;错误明确 | TODO | +| T-301 | 确定 AI 服务商/模型并接入 `ai.py`(`gen_title`/`gen_cover`,带重试/分辨率/jpg质量) | T-005 | 从 `config/ai_models.json` 读取模型与本地明文 Key;`gen_cover` 支持 resolution+jpg_quality;失败按 retry 重试;错误明确;日志脱敏 | TODO | | T-302 | Tab② 左右布局:左提示词(标题/封面),右按批次/店铺/状态筛选 + 任务列表 | T-301, T-203 | 左 ~1/4 提示词多行;右筛选+列表(店铺/商品id/旧标题/新标题/状态) | TODO | | T-302p | `prompts.py` + Tab② 提示词管理 | T-302 | 标题保存/启动回显 title_prompt.txt;封面多模板(下拉+新建/保存/另存为/重命名/删除,存 prompts/cover/);插入 `{新标题}`;预览变量替换;render_prompt 接入生成 | TODO | | T-303 | Tab② 开始生成(单按钮)+ 停止 + 进度:**先并发标题再并发图片** | T-302, T-104b | `generate_batch` 先 title_concurrency 并发标题、再 image_concurrency 并发图片;worker/signal 回传进度;每条 set_generated 立即写库;停止可取消未开始项;进度 标题/封面/失败 计数;双击弹窗看新旧封面 | TODO | @@ -70,15 +72,15 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | -| T-501 | Tab⑤ 设置 · AI 模型管理(master-detail):模型下拉 + 新增/删除 + 详情(服务商名/类别/api_type/模型ID/网址/密钥打码/连接超时) + 测试连接 | T-002 | 读写 `config/ai_models.json`;name 唯一;类别 text/image;至少各一个;密钥打码加密;测试连接可用 | TODO | +| T-501 | Tab⑤ 设置 · AI 模型管理 UI(master-detail):模型下拉 + 新增/删除 + 详情(服务商名/类别/api_type/模型ID/网址/密钥打码/连接超时) + 测试连接 | T-005, T-104 | 复用 T-005 后端;name 唯一;类别 text/image;至少各一个;密钥本地明文保存、UI 打码;测试连接可用 | TODO | | T-501b | Tab⑤ 设置 · 角色与生成参数:标题大模型/图片大模型下拉、分辨率(512/1k/2k/4k)、返回超时随分辨率、并发/重试/jpg质量、路径/端口 | T-501 | 角色下拉按类别过滤;分辨率选后返回超时按 resolution_timeouts 自动;持久化 config.json | TODO | | T-502 | 满 9 张封面:删第一张再上传 | T-001 | 实测删除确认框并处理;满 9 张流程正确 | TODO | -| T-503 | 密码 / AI Key 加密密钥方案落地 | T-105, T-501 | 密钥不与密文同存;说明威胁模型 | TODO | +| T-503 | 敏感信息本地明文存储提示与日志脱敏 | T-105, T-501 | 首次保存密码/API Key 时提示“本地明文保存”;UI 打码;日志/导出不含密码/API Key;文档说明 config/DB 必须 gitignore | TODO | | T-504 | 多账号并行 / dry-run / 运行日志(V2) | T-402 | 端口不冲突;dry-run 只预览;操作留痕 | TODO | ## 里程碑 -- M1:editor(含采集)模块化、config.json + SQLite 就绪(Phase 0)。 +- M1:editor(含采集)模块化、config.json + SQLite + AI 模型清单后端 + 测试基座就绪(Phase 0)。 - M2:账号管理可用、能登录与检测(Phase 1)。 - M3:导入 + 采集 + 回写旧数据(Phase 2)。 - M4:AI 生成新标题/新封面(Phase 3)。 diff --git a/docs/api.md b/docs/api.md index 3f75bae..e9ad4c8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -6,7 +6,7 @@ - 形态:本地函数 + 子进程(Chrome)+ CDP(`127.0.0.1:`)+ SQLite + openpyxl + AI 服务调用。 - 编码:UTF-8;传 Chrome / `setFileInputFiles` 的路径为 **Windows 绝对路径**。 -- 凭证:登录态在 user-data-dir;密码、AI Key 加密存于 config/DB,不出现在日志/导出明文。 +- 凭证:登录态在 user-data-dir;密码、AI Key 本地明文存于 config/DB,文件必须 gitignore;UI 打码显示,不出现在日志/导出。 - 失败处理:抛带中文说明的异常或返回状态字段;GUI 负责提示,不静默吞错。 ## appconfig 模块(`appconfig.py`,待建) @@ -24,13 +24,13 @@ ai_config() -> dict # default_text_model/default_image_ # resolution/resolution_timeouts response_timeout() -> int # = resolution_timeouts[resolution](返回超时,随分辨率) -# AI 模型清单 config/ai_models.json(含密钥;CRUD 由 ⑤ 设置) +# AI 模型清单 config/ai_models.json(含本地明文密钥;CRUD 由 ⑤ 设置) list_ai_models(category=None) -> list[dict] # category=text/image 过滤;含 connect_timeout_seconds 等 add_ai_model(model) -> None # name 唯一校验 update_ai_model(name, **fields) -> None delete_ai_model(name) -> None # 至少各留一个 text+image;删到剩一禁用 test_ai_model(name) -> dict # 「测试连接」:用 key/url/model 发最小请求 -> {ok, error} -get_model(name) -> dict # 解密 api_key 供调用 +get_model(name) -> dict # 返回模型定义,含 api_key(调用方不得写日志) ``` ## db 模块(`db.py`,待建) @@ -38,28 +38,42 @@ get_model(name) -> dict # 解密 api_key 供调用 SQLite 读写,表见 [架构 5.2](04-architecture.md)。 ```python +connect(path=None) -> sqlite3.Connection # 设置 foreign_keys/WAL/busy_timeout/synchronous/row_factory init_db(path) +create_batch(file_paths, note=None) -> str +get_batch(batch_id) -> Batch|None +list_batches(status=None) -> list[Batch] +update_batch(batch_id, **fields) -> None # 账号 list_accounts() -> list[Account] get_account_by_alias(alias) -> Account|None -add_account(account_name, alias, region_host, debug_port, password_enc=None, note=None) -> Account +add_account(account_name, alias, region_host, debug_port, password=None, note=None) -> Account update_account(alias, **fields) -> None delete_account(alias) -> None # 任务 / 各阶段结果 -insert_tasks(batch_id, rows) -> int # 写输入列 -list_tasks(batch_id=None, stage=None) -> list[Task] -set_collected(task_id, old_title, old_cover_path) -> None # 采集结果,立即写 -set_generated(task_id, new_title, new_cover_path) -> None # AI 结果,立即写 -set_applied(task_id, committed, error=None) -> None # 更新结果,立即写 +insert_tasks(batch_id, rows) -> int # 写输入列;rows 含 source_file_abs/source_sheet/source_row/row_key +list_tasks(batch_id=None, stage=None, status=None, alias=None) -> list[Task] +mark_running(task_id, phase) -> None +mark_failed(task_id, phase, error) -> None +mark_skipped(task_id, reason) -> None +set_collected(task_id, old_title, old_cover_path) -> None # stage=collected,status=success, attempts+1 +set_generated(task_id, new_title, new_cover_path) -> None # stage=generated,status=success, attempts+1 +set_applied(task_id, committed, error=None) -> None # stage=applied,status=success/failed, attempts+1 ``` -`stage` 随各 set_* 推进(imported→collected→generated→applied);任意步失败写 `error` 且 stage 标 failed/skipped。无 confirmed 阶段。 +`stage` 表示最后成功业务阶段(imported→collected→generated→applied);`status` 表示当前处理结果(pending/running/success/failed/skipped/cancelled)。任意步失败写 `last_error` 且 `status=failed`,`stage` 不前进。无 confirmed 阶段。 + +SQLite 连接规则: + +- 每个 worker/线程使用自己的 `connect()`;禁止跨线程共享 connection。 +- `connect()` 必须设置 `PRAGMA foreign_keys=ON`、`journal_mode=WAL`、`busy_timeout=5000`、`synchronous=NORMAL`。 +- DB 写入短事务、单条提交;Excel 回写失败不回滚 DB。 ## excel 模块(`excel.py`,待建,依赖 openpyxl) ```python import_tasks(file_paths: list[str]) -> dict -# 只解析【输入列】:账号名、别名、商品id(+ source_file);输出列运行时回写 +# 只解析【输入列】:账号名、别名、商品id;并记录 source_file/source_file_abs/source_sheet/source_row/row_key # -> {"rows": [...], "stats": {"files": int, "total": int, "valid": int, "invalid": int}} # invalid = 缺别名/商品id 或脏数据的行 @@ -67,10 +81,11 @@ match_summary(rows: list[dict], accounts: list) -> dict # 用 accounts 的别名对 rows 做匹配统计(导入汇总栏用) # -> {"matched": int, "unmatched": int, "by_account": {别名: 行数}, "unmatched_aliases": [..]} -write_back(batch_id, excel_path) -> str +write_back(batch_id, excel_path=None) -> dict # 把【旧标题/旧封面/新标题/新封面/更新状态】批量回写到【原 Excel】 +# excel_path 为空则按 source_file_abs 分组回写本批次涉及的所有原文件 # 原文件被占用(锁) → 抛错,调用方提示“请关闭后重试”,或改用 export_copy -export_copy(batch_id, out_path) -> str # 退路:另存新结果文件,不动原文件 +export_copy(batch_id, out_dir_or_path) -> dict # 退路:另存新结果文件,不动原文件 ``` 列模板见 [架构 5.3](04-architecture.md);别名以“别名”列为权威。 @@ -139,7 +154,7 @@ generate_batch(tasks, prompts, ai_cfg, on_progress, should_stop) -> None - 标题用 `default_text_model`、封面用 `default_image_model`(`appconfig.get_model` 取定义,含 url/key/api_type)。 - 连接超时 = 模型 `connect_timeout_seconds`;**返回超时 = `appconfig.response_timeout()`(随分辨率:512/1k/2k/4k → 180/240/360/600)**。 -- 并发数/重试/分辨率/jpg 质量来自 `appconfig.ai_config()`;Key 加密存、不入日志。 +- 并发数/重试/分辨率/jpg 质量来自 `appconfig.ai_config()`;Key 本地明文存储,但不入日志、不导出。 - 标题快、图片慢:分两段、各用各自并发数;失败按 `retry` 重试,仍失败记 error 不阻塞其余。 - 调用有成本与失败可能:超时、限流、内容安全拒绝都要返回明确错误。 - 生成结果**直接进入 ③ 更新候选**;③ 点击「开始更新」后弹窗批量确认,确认后提交线上。本地留档 + 回写 Excel 供追溯。 @@ -209,7 +224,6 @@ set UPDATE=1 && python prototypes/demo.py # 走完点击「更新」提交 ## 待实现时确认 - AI 服务商/模型/计费;图像 image-to-image 能力与合规。 -- 密码、AI Key 加密的密钥来源(机器派生 / 主口令)。 - 满 9 张删除封面的确认框选择器(需实测)。 - Excel 缺列/脏数据容错(整文件拒绝 vs 逐行跳过)。 - 旧封面下载的图片格式/扩展名处理。 diff --git a/docs/current-state.md b/docs/current-state.md index 008cf3a..cce2d66 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -9,14 +9,14 @@ - 阶段:V0 单账号 CDP 流程已验证;V1 多账号管理 + 5 Tab GUI + Excel/AI 流水线为既定设计,尚未开始编码。 - 技术栈:Python 3.10+,自研 CDP(websocket-client + requests),SQLite(sqlite3)+ `config.json` + openpyxl + AI(服务商待定),GUI PySide6 5 Tab(已定)。 - 生产代码:尚无 `appconfig/db/excel/config/chrome/editor/gui/workers`;现有为验证脚本 + `cdp.py`。 -- 测试:以 `py_compile` + 在测试商品上手动跑 `prototypes/demo.py` 为主,无自动化测试。 +- 测试:当前以 `py_compile` + 在测试商品上手动跑 `prototypes/demo.py` 为主;T-006 将补 `unittest` 自动化测试基座。 - 数据:无 `config.json`、`cmshopee.db`、`chrome_user_data_dir/`(待 Phase 0/1 建立)。 ## 既定设计要点(文档已定) - GUI:**PySide6 5 Tab 流水线**,顺序 ① 导入采集 → ② AI生成 → ③ 更新shopee → ④ 账号管理 → ⑤ 设置;后台任务用 worker/QThread/signal。 - 流水线阶段:imported → collected(采集旧标题/旧封面+下载+回写)→ generated(AI 提示词生成新标题/新封面)→ applied(③ 弹窗批量确认后改 Shopee 并提交)。**无 confirmed、无常驻提交开关。** -- 存储:`config.json`(应用设置)+ `config/ai_models.json`(AI 模型清单与加密 Key)+ SQLite `cmshopee.db`(账号/任务/各阶段结果)+ openpyxl(Excel)+ 本地 `images/`(旧/新封面)。 +- 存储:`config.json`(应用设置)+ `config/ai_models.json`(AI 模型清单与本地明文 Key)+ SQLite `cmshopee.db`(账号/任务/各阶段结果)+ openpyxl(Excel)+ 本地 `images/`(旧/新封面)。 - 多账号隔离:每账号独立 user-data-dir(非 profile)。 - 账号↔任务绑定:以 Excel“别名”列为权威;未匹配略过,结束弹窗汇总。 - 执行:多账号串行、单条失败继续;③ 点击「开始更新」后弹窗确认当前筛选范围和任务数量,确认后逐条点「更新」提交线上。 @@ -48,7 +48,7 @@ - 已完成:无(任务均为 TODO;单账号能力以脚本形式存在,待 T-001 模块化)。 - 正在进行:无。 -- 下一个可领取任务:Phase 0 的 **T-001(`editor.py` 含采集)/ T-002(`appconfig.py`+`config.json`)/ T-003(`db.py`+SQLite)**——三者无相互依赖,可任选其一起步。 +- 下一个可领取任务:Phase 0 的 **T-001(`editor.py` 含采集)/ T-002(`appconfig.py`+`config.json`)/ T-003(`db.py`+SQLite)**——三者无相互依赖,可任选其一起步;AI 真实接入前先做 T-005。 ## 当前可运行内容 @@ -56,6 +56,9 @@ # 语法检查 python -m py_compile *.py +# 单元测试(T-006 后) +python -m unittest discover -s tests + # 单账号闭环(不提交线上) python prototypes/demo.py # 分步(需根目录 cdp.py 可导入) set AUTO=1 && python prototypes/demo.py # 自动(cmd) diff --git a/docs/routes.md b/docs/routes.md index cf121d7..e4f7470 100644 --- a/docs/routes.md +++ b/docs/routes.md @@ -13,7 +13,7 @@ | ① 导入采集 | 导入多个 Excel;任务列表;**采集**商品当前的旧标题/旧封面(只读),封面图下载本地;回写 Excel 旧字段 | 只读,低 | | ② AI生成 | 左侧标题/封面**提示词**;右侧按批次/店铺筛选任务列表;AI 生成新标题/新封面;双击看新旧封面 | 不触线上,中 | | ③ 更新shopee | 对**已生成**任务点击「开始更新」后弹窗确认;确认后打开编辑页换标题+封面并逐条点「更新」提交;结果回写 Excel | **写线上,高** | -| ④ 账号管理 | Shopee 账号(账号名/别名/数据目录/端口/密码加密/登录状态);启动登录、检测登录、生成快捷方式 | 中 | +| ④ 账号管理 | Shopee 账号(账号名/别名/数据目录/端口/密码本地明文仅参考/登录状态);启动登录、检测登录、生成快捷方式 | 中 | | ⑤ 设置 | AI 模型/API Key、本地图片目录、Chrome 路径、默认端口等 | — | 任务的**阶段状态**贯穿各 Tab:`imported → collected → generated → applied`(或 `failed/skipped`)。② 不设逐条人工确认阶段;③ 无常驻提交开关,但点击「开始更新」后必须弹窗确认当前筛选范围和任务数量。各 Tab 聚焦各自阶段的列与按钮,但操作同一批任务(同一 batch)。 @@ -107,11 +107,11 @@ └───────────────────────────────────────────────────────────────┘ ``` -账号弹窗字段:账号名、别名(唯一,Excel 用它匹配)、地区域名、调试端口、密码(加密仅参考)、备注;配置目录按别名生成 slug 只读显示。 +账号弹窗字段:账号名、别名(唯一,Excel 用它匹配)、地区域名、调试端口、密码(本地明文仅参考,UI 打码,不自动登录)、备注;配置目录按别名生成 slug 只读显示。 ## ⑤ 设置 -- AI:服务商 / 文本模型 / 图像模型 / API Key(加密存)。 +- AI:服务商 / 文本模型 / 图像模型 / API Key(本地明文存,UI 打码,不入日志)。 - AI 生成参数:**标题并发数、图片并发数、失败重试次数、分辨率、jpg 质量**。 - 本地图片目录(旧封面下载、新封面生成的存放根目录)。 - Chrome 路径、默认调试端口 / 端口范围、超时、DB 路径。 diff --git a/docs/ui/overview-pipeline.svg b/docs/ui/overview-pipeline.svg index 730c1dd..5c08945 100644 --- a/docs/ui/overview-pipeline.svg +++ b/docs/ui/overview-pipeline.svg @@ -66,7 +66,7 @@ ④ 账号管理 账号名/别名/端口 - 密码加密·仅参考 + 密码本地明文·仅参考 独立 user-data-dir 启动并登录(人工) 检测登录状态 diff --git a/docs/ui/tab4-accounts.svg b/docs/ui/tab4-accounts.svg index bbe22e6..97d4640 100644 --- a/docs/ui/tab4-accounts.svg +++ b/docs/ui/tab4-accounts.svg @@ -63,7 +63,7 @@ 别名* 地区域名 端口/密码 - ••••(加密) + ••••(本地明文) 配置目录(只读):chrome_user_data_dir\nuzhuang 取消 diff --git a/docs/ui/tab5-settings.svg b/docs/ui/tab5-settings.svg index 934b991..921ccb7 100644 --- a/docs/ui/tab5-settings.svg +++ b/docs/ui/tab5-settings.svg @@ -76,6 +76,6 @@ 保存设置 - 模型清单存 config/ai_models.json(密钥加密);选择与参数存 config.json。均 gitignore。 + 模型清单存 config/ai_models.json(密钥本地明文);选择与参数存 config.json。均 gitignore。 diff --git a/progress.md b/progress.md index 43bb1e2..30473cf 100644 --- a/progress.md +++ b/progress.md @@ -180,4 +180,16 @@ - 新增 `workers.py` 作为可选模块承载 PySide6 worker/QThread 编排。 - 下一步:继续处理 SQLite/Excel schema 与 AI 接入任务依赖。 +## 【2026-06-26】补强 · 数据模型 / AI 顺序 / 测试 / SQLite 并发 / 明文敏感信息 + +- 状态:DONE(仅文档) +- 变更:更新 `docs/04-architecture.md`、`docs/api.md`、`docs/06-tasks.md`、`docs/03-tech-stack.md`、`docs/05-coding-rules.md`、`docs/00-ai-start-here.md`、`docs/02-requirements.md`、`docs/current-state.md`、`docs/routes.md`、`AGENTS.md`、`CLAUDE.md` 与 UI 线框。 +- 决策: + - 数据模型补强:新增 `batches` 表;`tasks` 增加 `source_file_abs/source_sheet/source_row/row_key`、`stage/status` 分离、时间戳、attempts、`last_error`,用于可靠回写、重试与断点续跑。 + - SQLite 并发:每个 worker 独立 connection;统一 `foreign_keys=ON`、`journal_mode=WAL`、`busy_timeout=5000`、`synchronous=NORMAL`;短事务单条提交。 + - AI 顺序:先做 T-005 AI 模型清单后端,再做 T-301 真实 AI 接入;T-501 只做设置页 UI。 + - 测试策略:引入 `tests/` + stdlib `unittest` 基座,优先覆盖 appconfig/db/excel/prompts 纯逻辑;CDP/Shopee/真实 AI 作为集成或手动验证。 + - 不做加密:密码与 AI Key 本地明文保存,文件必须 gitignore;UI 打码,日志/导出脱敏,不自动登录。 +- 下一步:继续按任务看板从 Phase 0 开始实现,建议优先 T-003/T-006 或 T-001。 +