zhoujie d88b4e9a3b ♻️ refactor(config): 实现配置安全存储与原子写
- 新增 `get_secure()` 和 `set_secure()` 方法,优先从环境变量或系统 keyring 读取敏感配置,`config.json` 中仅存储占位符
- 将 `save()` 方法改为使用临时文件 + `os.replace()` 的原子写入,防止进程中断导致配置文件损坏
- 在 `add_llm_provider()` 和 `get_active_llm()` 中集成安全配置读写,自动迁移旧版明文 API Key

♻️ refactor(analytics): 实现分析数据原子写

- 将 `_save_analytics()` 和 `_save_weights()` 方法改为使用临时文件 + `os.replace()` 的原子写入
- 确保在写入过程中进程被终止时,原始数据文件保持完整

♻️ refactor(main): 增强发布功能健壮性与代码模块化

- 在 `publish_to_xhs()` 中增加发布前输入校验【标题长度、图片数量、文件存在性】并在 `finally` 块中自动清理本次生成的临时图片文件
- 为全局笔记列表缓存 `_cached_proactive_entries` 和 `_cached_my_note_entries` 引入 `threading.RLock` 保护,新增 `_set_cache()` 和 `_get_cache()` 线程安全操作函数
- 将「内容创作」Tab 的 UI 构建代码拆分至 `ui/tab_create.py` 模块,主文件通过 `build_tab()` 函数调用并组装
- 将 Gradio 应用的 CSS 和主题配置提取为模块级变量,提升可维护性

📦 build(deps): 新增 keyring 依赖

- 在 `requirements.txt` 中添加 `keyring>=24.0.0` 以支持系统凭证管理

📝 docs(openspec): 新增生产就绪审计文档

- 在 `openspec/changes/archive/2026-02-24-production-readiness-audit/` 下新增设计文档、提案、任务清单及各功能规格说明
- 将核心功能规格同步至 `openspec/specs/` 目录
2026-02-24 21:53:36 +08:00

4.1 KiB
Raw Blame History

1. 依赖与环境准备

  • 1.1 在 requirements.txt 中添加 keyring>=24.0.0
  • 1.2 运行 pip install keyring 并验证在当前系统Windows可正常使用 keyring.get_password / keyring.set_password

2. 安全配置secure-config

  • 2.1 在 config_manager.py 中新增 get_secure(key: str) -> str 方法:优先读取环境变量 AUTOBOT_<KEY.upper()>,其次读取系统 keyring最后回退 config.json 明文(自动迁移一次),捕获 keyring.errors.NoKeyringError 并降级
  • 2.2 在 config_manager.py 中新增 set_secure(key: str, value: str) 方法:写入系统 keyring降级模式下写入 config.json),并将 config.json 对应字段更新为占位符 "[keyring]"
  • 2.3 将 main.py 中 LLM 提供商的 api_key 读写全部替换为 cfg.get_secure() / cfg.set_secure()
  • 2.4 手动测试:重启应用后 config.jsonapi_key 已变为 "[keyring]"LLM 连接功能正常

3. JSON 原子写atomic-persistence

  • 3.1 在 config_manager.pysave() 方法中将直接 open(CONFIG_FILE, "w") 改为 tempfile.mkstemp(dir=<same_dir>) + 写入 + os.replace() 原子重命名
  • 3.2 在 analytics_service.py_save_analytics() 方法中同样改为原子写
  • 3.3 在 analytics_service.py_save_weights() 方法中同样改为原子写
  • 3.4 测试:在写入过程中(time.sleep 模拟)验证目标文件仍完整,临时文件被清理

4. 线程安全缓存thread-safe-cache

  • 4.1 在 main.py 顶部声明 _cache_lock = threading.RLock()
  • 4.2 新增内部函数 _set_cache(name: str, entries: list)_get_cache(name: str) -> list,内部使用 with _cache_lock: 保护
  • 4.3 将 _fetch_and_cache() 中对 _cached_proactive_entries / _cached_my_note_entries 的直接赋值改为调用 _set_cache()
  • 4.4 将 _pick_from_cache() 中读取缓存改为调用 _get_cache()(在锁内完成列表快照拷贝)
  • 4.5 将 fetch_my_notes() 中对 _cached_my_note_entries 的直接赋值改为调用 _set_cache()

5. 发布前输入校验publish-input-validation

  • 5.1 在 publish_to_xhs() 函数内、MCP 调用前添加标题长度校验(len(title) > 20 返回错误)
  • 5.2 添加图片数量下限校验(len(image_paths) == 0 返回「至少需要 1 张图片」)
  • 5.3 添加图片数量上限校验(len(image_paths) > 18 返回含实际数量的错误消息)
  • 5.4 添加图片文件存在性校验(遍历 image_paths,发现不存在的文件时返回含路径的错误)
  • 5.5 在 Gradio UI 的发布按钮标题输入框旁添加字符计数提示(gr.Textboxinfo 参数)

6. 临时文件生命周期temp-file-lifecycle

  • 6.1 在 publish_to_xhs() 中记录本次写入的 AI 临时图片路径到局部变量 ai_temp_files = []
  • 6.2 在函数末尾添加 finally: 块,遍历 ai_temp_files 逐一调用 os.remove(),捕获 OSError 仅记录 logger.warning
  • 6.3 验证:发布成功后 _temp_publish/ 目录中的 ai_*.jpg 文件已被删除;发布失败后同样被清理

7. UI 模块拆分ui-module-split

  • 7.1 创建 ui/ 目录,添加 ui/__init__.py(空文件)
  • 7.2 创建 ui/tab_create.py,将 main.py 中「内容创作 Tab」的所有 Gradio 组件定义和 .click() / .change() 事件绑定代码迁移至该文件,导出 build_tab(cfg, mcp_url_box, ...) 函数
  • 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 级别日志