docs: 补强 V1 数据与测试设计

- 扩展 SQLite schema:新增 batches、Excel 行定位、stage/status、时间戳和重试字段

- 明确 SQLite worker 并发规则:独立 connection、WAL、busy_timeout、短事务

- 调整 AI 接入顺序:先做模型清单后端,再接真实 ai.py,设置页复用后端

- 补充 unittest 测试基座要求和分层验证策略

- 敏感信息改为本地明文保存,配合 gitignore、UI 打码和日志脱敏
This commit is contained in:
chengma
2026-06-26 16:27:33 +08:00
parent 92735ae7a4
commit 3832697fc4
15 changed files with 152 additions and 62 deletions
+29 -15
View File
@@ -6,7 +6,7 @@
- 形态:本地函数 + 子进程(Chrome)+ CDP(`127.0.0.1:<port>`)+ 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 逐行跳过)。
- 旧封面下载的图片格式/扩展名处理。