## Context 当前项目是一个单机运行的小红书 AI 爆文自动化工具,使用 Gradio 作为 UI 框架,SQLite 为发布队列持久化,JSON 文件存储配置与分析数据。随着功能迭代至 V2.0,主文件 `main.py` 已超过 4400 行,并积累了多处生产风险:API Key 以明文写入 `config.json`、全局列表变量被多个回调线程无锁读写、`_temp_publish/` 目录的临时图片在发布后未清理、JSON 文件采用直接覆盖写入(电量耗尽或进程中断时会写出空文件)、发布前无标题/正文/图片数量的校验。 ## Goals / Non-Goals **Goals:** - 消除 6 项已知的生产风险,使项目能安全、稳定地长期自动运营 - 保持所有 Gradio 回调函数签名不变(不破坏现有 UI 绑定) - 每项改动独立可部署,不相互耦合 - 为后续功能拓展提供更清晰的代码结构基础 **Non-Goals:** - 重写为 Web 服务或引入数据库 ORM - 改变现有 UI 交互逻辑或视觉设计 - 实现性能优化或多账号支持 - 修改 MCP / SD / LLM 服务接口 ## Decisions ### 决策 1:敏感配置存储方案选 keyring + 环境变量覆盖,放弃纯加密文件 **选项 A(选定)**: 使用 `keyring` 库将 API Key 存入系统凭证管理器(Windows Credential Manager / macOS Keychain / Linux Secret Service),`config.json` 中仅保留占位符 `"[keyring]"`;同时支持 `AUTOBOT_API_KEY_` 环境变量在无 GUI 场景(Docker / CI)下覆盖。 **选项 B(放弃)**: 自行用 `Fernet` 加密后写入 `config.json`。缺点:密钥仍需本地存储,安全提升有限,且增加密钥管理复杂度。 **选项 C(放弃)**: 要求用户全部改用环境变量。对当前 Gradio UI 用户体验极差,用户每次重启需重新设置。 **结论**:`keyring` 方案对 Windows 单机用户最为透明,且 Docker 场景通过环境变量无缝降级。新增 `ConfigManager.get_secure(key)` / `set_secure(key, value)` 接口,内部优先读取环境变量,其次读 keyring,最后回退旧版明文(自动迁移一次)。 --- ### 决策 2:全局缓存改用模块级 `threading.RLock` 保护,不引入新类 **选项 A(选定)**: 在 `main.py` 模块顶层声明一个 `_cache_lock = threading.RLock()`,所有读写 `_cached_proactive_entries` / `_cached_my_note_entries` 的函数用 `with _cache_lock:` 包裹。 **选项 B(放弃)**: 封装为 `CacheManager` 类。当前代码耦合 Gradio UI 较深,引入类会导致较大重构,收益不成比例。 **结论**:最小侵入方案,改动 5 处函数约 20 行,可在一个 PR 内完成。 --- ### 决策 3:JSON 原子写使用 tempfile + os.replace Python 标准库 `tempfile.NamedTemporaryFile` + `os.replace`(同目录)在 POSIX 和 Windows 均为原子操作(Windows Vista+ 支持)。无需引入新依赖。 适用范围:`ConfigManager.save()`、`AnalyticsService._save_analytics()`、`AnalyticsService._save_weights()`。 --- ### 决策 4:临时文件清理在发布回调内同步执行 在 `publish_to_xhs()` 函数的 try/finally 块中清理 `_temp_publish/` 下以本次调用 `ai_N.jpg` 命名的文件。不使用全目录清空,以免并发发布时误删其他会话文件(Gradio 多用户虽少见,但更安全)。逐文件删除,删除失败仅打印 warning,不阻断流程。 --- ### 决策 5:发布校验集中在 `publish_to_xhs()` 一处,不分散到 UI 校验逻辑写在业务函数中,而非 Gradio 的 `gr.Textbox` 校验器,保证逻辑可测试,且 UI 重构时不会遗失。 --- ### 决策 6:UI 拆分采用渐进式迁移,不一次性重写 以 `ui/` 目录为目标,先提取最大的 Tab(内容创作 Tab)为 `ui/tab_create.py`,返回 `(components, callbacks)` 元组,`main.py` 调用并注册。其余 Tab 在后续迭代中逐步迁移。本次变更只迁移 `ui/tab_create.py` 作为示范,不强制完成全部拆分。 ## Risks / Trade-offs | 风险 | 缓解措施 | |------|---------| | `keyring` 在部分 Linux headless 环境无后端可用 | 检测到 `keyring.errors.NoKeyringError` 时降级为明文(打印警告),行为与改造前一致 | | `os.replace` 在跨卷(不同磁盘分区)时失败 | 使用 `tempfile.mkstemp(dir=same_dir)` 确保临时文件与目标同卷 | | 并发发布时 `ai_N.jpg` 命名冲突 | 当前 Gradio 为单进程单用户;若未来支持多用户,改用 UUID 命名 | | UI 拆分期间双重维护 | 每次只迁移一个 Tab,迁移完成前旧代码仍有效 | ## Migration Plan 1. **无需数据迁移**:`config.json` 的旧明文 Key 在首次调用 `get_secure()` 时自动读取并写入 keyring,同时将 `config.json` 中对应字段替换为 `"[keyring]"` 占位符,单次完成。 2. **依赖安装**:`pip install keyring` 并更新 `requirements.txt`。 3. **无回滚风险**:各项改动均向后兼容,若 keyring 不可用则自动回退明文模式。 ## Open Questions - 是否需要在 UI 上增加「导出加密备份配置」功能,方便用户迁移设备?(本次范围外,记录待后续评估) - `ui/` 拆分后是否需要引入 pytest-gradio 进行 UI 层单元测试?(本次不实施)