## 1. 依赖与环境准备 - [x] 1.1 在 `requirements.txt` 中添加 `keyring>=24.0.0` - [x] 1.2 运行 `pip install keyring` 并验证在当前系统(Windows)可正常使用 `keyring.get_password` / `keyring.set_password` ## 2. 安全配置(secure-config) - [x] 2.1 在 `config_manager.py` 中新增 `get_secure(key: str) -> str` 方法:优先读取环境变量 `AUTOBOT_`,其次读取系统 keyring,最后回退 `config.json` 明文(自动迁移一次),捕获 `keyring.errors.NoKeyringError` 并降级 - [x] 2.2 在 `config_manager.py` 中新增 `set_secure(key: str, value: str)` 方法:写入系统 keyring(降级模式下写入 `config.json`),并将 `config.json` 对应字段更新为占位符 `"[keyring]"` - [x] 2.3 将 `main.py` 中 LLM 提供商的 `api_key` 读写全部替换为 `cfg.get_secure()` / `cfg.set_secure()` - [ ] 2.4 手动测试:重启应用后 `config.json` 中 `api_key` 已变为 `"[keyring]"`,LLM 连接功能正常 ## 3. JSON 原子写(atomic-persistence) - [x] 3.1 在 `config_manager.py` 的 `save()` 方法中将直接 `open(CONFIG_FILE, "w")` 改为 `tempfile.mkstemp(dir=)` + 写入 + `os.replace()` 原子重命名 - [x] 3.2 在 `analytics_service.py` 的 `_save_analytics()` 方法中同样改为原子写 - [x] 3.3 在 `analytics_service.py` 的 `_save_weights()` 方法中同样改为原子写 - [ ] 3.4 测试:在写入过程中(`time.sleep` 模拟)验证目标文件仍完整,临时文件被清理 ## 4. 线程安全缓存(thread-safe-cache) - [x] 4.1 在 `main.py` 顶部声明 `_cache_lock = threading.RLock()` - [x] 4.2 新增内部函数 `_set_cache(name: str, entries: list)` 和 `_get_cache(name: str) -> list`,内部使用 `with _cache_lock:` 保护 - [x] 4.3 将 `_fetch_and_cache()` 中对 `_cached_proactive_entries` / `_cached_my_note_entries` 的直接赋值改为调用 `_set_cache()` - [x] 4.4 将 `_pick_from_cache()` 中读取缓存改为调用 `_get_cache()`(在锁内完成列表快照拷贝) - [x] 4.5 将 `fetch_my_notes()` 中对 `_cached_my_note_entries` 的直接赋值改为调用 `_set_cache()` ## 5. 发布前输入校验(publish-input-validation) - [x] 5.1 在 `publish_to_xhs()` 函数内、MCP 调用前添加标题长度校验(`len(title) > 20` 返回错误) - [x] 5.2 添加图片数量下限校验(`len(image_paths) == 0` 返回「至少需要 1 张图片」) - [x] 5.3 添加图片数量上限校验(`len(image_paths) > 18` 返回含实际数量的错误消息) - [x] 5.4 添加图片文件存在性校验(遍历 `image_paths`,发现不存在的文件时返回含路径的错误) - [x] 5.5 在 Gradio UI 的发布按钮标题输入框旁添加字符计数提示(`gr.Textbox` 的 `info` 参数) ## 6. 临时文件生命周期(temp-file-lifecycle) - [x] 6.1 在 `publish_to_xhs()` 中记录本次写入的 AI 临时图片路径到局部变量 `ai_temp_files = []` - [x] 6.2 在函数末尾添加 `finally:` 块,遍历 `ai_temp_files` 逐一调用 `os.remove()`,捕获 `OSError` 仅记录 `logger.warning` - [ ] 6.3 验证:发布成功后 `_temp_publish/` 目录中的 `ai_*.jpg` 文件已被删除;发布失败后同样被清理 ## 7. UI 模块拆分(ui-module-split) - [x] 7.1 创建 `ui/` 目录,添加 `ui/__init__.py`(空文件) - [x] 7.2 创建 `ui/tab_create.py`,将 `main.py` 中「内容创作 Tab」的所有 Gradio 组件定义和 `.click()` / `.change()` 事件绑定代码迁移至该文件,导出 `build_tab(cfg, mcp_url_box, ...)` 函数 - [x] 7.3 在 `main.py` 中用 `from ui.tab_create import build_tab` + 调用替换原有内容创作 Tab 代码 - [ ] 7.4 启动应用,验证内容创作 Tab 功能与迁移前完全一致(文案生成、图片生成、发布按钮均正常) ## 8. 集成验证 - [ ] 8.1 启动应用,依次测试:LLM 连接 → 文案生成 → 图片生成 → 发布(包含校验不通过和校验通过两个场景) - [ ] 8.2 检查 `config.json` 中无明文 API Key - [ ] 8.3 检查 `_temp_publish/` 目录在发布后为空(或只含本次以外的文件) - [ ] 8.4 检查 `autobot.log` 中无 ERROR 级别日志