Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/b76182342-source_tuwen-agent.json
T

1 line
86 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "b76182342-source/tuwen-agent", "files": {".env.example": {"19fd8f8d7ff3c5382624037d9ddb959691208538": {"t": 1785745177.7517455, "n": 1323, "h": "b84f9aeaf6f38646", "c": "# ============================================================\n# 抖音创作顾问 Agent — 环境变量配置模板\n# 复制此文件为 .env 并填入你的 API 密钥\n# ============================================================\n\n# DeepSeek API(用于 NLP 语义分析和内容生成)\n# 获取地址:https://platform.deepseek.com/api_keys\nDEEPSEEK_API_KEY=sk-your-deepseek-key-here\n\n# Unsplash API(用于图片搜索,英文关键词)\n# 获取地址:https://unsplash.com/developers\nUNSPLASH_ACCESS_KEY=your-unsplash-key-here\n\n# Pexels API(用于图片搜索,支持中文)\n# 获取地址:https://www.pexels.com/api/\nPEXELS_API_KEY=your-pexels-key-here\n\n# 抖音开放平台(可选,用于获取官方热点/音乐数据)\n# 获取地址:https://open.douyin.com/\nDOUYIN_CLIENT_KEY=\nDOUYIN_CLIENT_SECRET=\n\n# HTTP 代理(可选,为空则直连)\n# 格式:http://127.0.0.1:7897\nPROXY_URL=\n\n# Redis 短期记忆(可选,为空则禁用 Redis 缓存功能)\n# 格式:redis://[:password@]host:port/db\n# 设置密码:redis-cli CONFIG SET requirepass \"your-password\"\nREDIS_URL=redis://127.0.0.1:6379/0\n\n# Qdrant 公共向量数据库(可选,用于语义搜索)\nQDRANT_URL=http://127.0.0.1:6333\n\n# Embedding 模型配置\nEMBEDDING_MODEL=BAAI/bge-small-zh-v1.5\nEMBEDDING_DEVICE=cpu\nHF_ENDPOINT=https://hf-mirror.com\n\n# CORS 白名单(逗号分隔,用于前端访问后端 API)\nCORS_ORIGINS=http://localhost:3000,http://localhost:5173,http://127.0.0.1:3000,http://127.0.0.1:5173\n\n# API 认证密钥(用于保护后端 API 端点)\n# 生成方式:python -c \"import secrets; print(secrets.token_urlsafe(32))\"\nAPI_KEY=your-api-key-here\n\n# Playwright 无头模式(1/true/yes 启用无头,默认 false)\nHEADLESS=false\n"}, "e2a271e4404900e4b2fa700751a5577a8b1855a9": {"t": 1785748418.3770843, "n": 1323, "h": "b84f9aeaf6f38646", "c": "# ============================================================\n# 抖音创作顾问 Agent — 环境变量配置模板\n# 复制此文件为 .env 并填入你的 API 密钥\n# ============================================================\n\n# DeepSeek API(用于 NLP 语义分析和内容生成)\n# 获取地址:https://platform.deepseek.com/api_keys\nDEEPSEEK_API_KEY=sk-your-deepseek-key-here\n\n# Unsplash API(用于图片搜索,英文关键词)\n# 获取地址:https://unsplash.com/developers\nUNSPLASH_ACCESS_KEY=your-unsplash-key-here\n\n# Pexels API(用于图片搜索,支持中文)\n# 获取地址:https://www.pexels.com/api/\nPEXELS_API_KEY=your-pexels-key-here\n\n# 抖音开放平台(可选,用于获取官方热点/音乐数据)\n# 获取地址:https://open.douyin.com/\nDOUYIN_CLIENT_KEY=\nDOUYIN_CLIENT_SECRET=\n\n# HTTP 代理(可选,为空则直连)\n# 格式:http://127.0.0.1:7897\nPROXY_URL=\n\n# Redis 短期记忆(可选,为空则禁用 Redis 缓存功能)\n# 格式:redis://[:password@]host:port/db\n# 设置密码:redis-cli CONFIG SET requirepass \"your-password\"\nREDIS_URL=redis://127.0.0.1:6379/0\n\n# Qdrant 公共向量数据库(可选,用于语义搜索)\nQDRANT_URL=http://127.0.0.1:6333\n\n# Embedding 模型配置\nEMBEDDING_MODEL=BAAI/bge-small-zh-v1.5\nEMBEDDING_DEVICE=cpu\nHF_ENDPOINT=https://hf-mirror.com\n\n# CORS 白名单(逗号分隔,用于前端访问后端 API)\nCORS_ORIGINS=http://localhost:3000,http://localhost:5173,http://127.0.0.1:3000,http://127.0.0.1:5173\n\n# API 认证密钥(用于保护后端 API 端点)\n# 生成方式:python -c \"import secrets; print(secrets.token_urlsafe(32))\"\nAPI_KEY=your-api-key-here\n\n# Playwright 无头模式(1/true/yes 启用无头,默认 false)\nHEADLESS=false\n"}}, "docs/API_AUDIT_REPORT.md": {"19fd8f8d7ff3c5382624037d9ddb959691208538": {"t": 1785747223.3550906, "n": 30626, "h": "076f1833f9ae3781", "c": "# 抖音创作顾问 Agent — 前后端接口审计报告\n\n> 审计日期:2026-07-10 \n> 版本:v2.0 \n> 审计范围:全部 8 个路由模块 (35 个端点) + LangGraph 14 节点工作流 + 前端 6 页面数据流\n\n---\n\n## 一、系统架构总览\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│ Frontend (React + Vite :3000) │\n│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │\n│ │Workspace │ │Materials │ │ Publish │ │Analytics │ │DouyinSync/ │ │\n│ │ (核心) │ │ Library │ │ History │ │ │ │Settings │ │\n│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬──────┘ │\n│ │ │ │ │ │ │\n│ ┌────┴─────────────┴────────────┴────────────┴───────────────┴──────┐ │\n│ │ services/api.ts (axios, baseURL='/api') │ │\n│ │ services/websocket.ts (ws://localhost:8000) │ │\n│ │ stores/appStore.ts (Zustand) │ │\n│ └────────────────────────────────┬───────────────────────────────────┘ │\n│ │ Vite Proxy │\n│ /api → :9000 /ws → :8000 │\n└───────────────────────────────────┼─────────────────────────────────────┘\n │\n┌───────────────────────────────────┼─────────────────────────────────────┐\n│ Backend (FastAPI :9000) │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ server.py (App Factory) │ │\n│ │ • CORS Middleware • API Key Auth Middleware │ │\n│ │ • Static Mounts: /personal, /public │ │\n│ └──────────────────────────────────────────────────────────────────┘ │\n│ │ │\n│ ┌─────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐ ┌────────┐ │\n│ │ health │ │conversa-│ │ agent │ │materials│ │publish │ │\n│ │ (1) │ │tions(14)│ │ (3) │ │ (6) │ │ (5) │ │\n│ └─────────┘ └─────────┘ └───┬────┘ └─────────┘ └────────┘ │\n│ │ │\n│ ┌───────────────┼───────────────┐ │\n│ │ analytics(3) │ douyin(5) │ │\n│ └───────────────┴───────────────┘ │\n│ │ │\n│ ┌───────────────┼───────────────┐ │\n│ │ upload(1) │ stats(5) │ │\n│ └───────────────┴───────────────┘ │\n│ │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ LangGraph 工作流 (14 节点 + 6 条件边) │ │\n│ │ │ │\n│ │ initialize → classify_intent ─┬→ generate_copy │ │\n│ │ ├→ optimize_copy │ │\n│ │ ├→ parse_modify_intent │ │\n│ │ ├→ agent_dialogue → END/管线 │ │\n│ │ └→ analyze_emotion │ │\n│ │ ↓ │ │\n│ │ analyze_content → decide_skills → execute_all_skills │ │\n│ │ (Skill1║Skill2║Skill3 并行) │ │\n│ │ ↓ │ │\n│ │ evaluate → check_threshold ─┬→ END (≥4.0 or iter≥3) │ │\n│ │ └→ identify_weak → rerun_skill │ │\n│ │ └→ rerun_evaluate ↩ │ │\n│ └──────────────────────────────────────────────────────────────────┘ │\n│ │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ 持久化层 │ │\n│ │ • Redis (Cache) — DB1: 会话摘要, 每日统计 │ │\n│ │ • SQLite (memory.db) — DB2: 对话消息, 发布数据, 素材库 │ │\n│ │ • Qdrant (Vector Store) — 公共文案/标签语义搜索 │ │\n│ └──────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## 二、后端接口清单(35 个端点)\n\n### 2.1 健康检查 — `routers/health.py`\n\n| # | 方法 | 路径 | 认证 | 请求体 | 响应 | 说明 |\n|---|------|------|------|--------|------|------|\n| 1 | GET | `/api/health` | 无 | — | `{status, timestamp}` | 健康检查,免 API Key 认证 |\n\n**审计发现**:✅ 正常。`server.py` 中 `_PUBLIC_PATHS` 已豁免此路径认证。\n\n---\n\n### 2.2 会话 & 对话管理 — `routers/conversations.py` (14 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 2 | GET | `/api/session` | API Key | — | `{session_id, messages[], ...}` | 获取当前会话(从 `state/current_session.json`) |\n| 3 | POST | `/api/session/save` | API Key | `dict` (合并到 session) | `{success: true}` | 保存会话状态到 JSON 文件 |\n| 4 | POST | `/api/session/reset` | API Key | — | `{success, session}` | 重置会话(删除 JSON 文件) |\n| 5 | POST | `/api/conversations` | API Key | `{title, user_id}` | `{conversation_id}` | 创建对话(生成 `conv_xxx` ID) |\n| 6 | GET | `/api/conversations/{id}` | API Key | — | `Conversation` | 获取单个对话信息 |\n| 7 | GET | `/api/conversations` | API Key | `?user_id&limit` | `Conversation[]` | 列出对话列表 |\n| 8 | DELETE | `/api/conversations/{id}` | API Key | — | `{success}` | 删除对话(级联删除消息) |\n| 9 | POST | `/api/conversations/batch-delete` | API Key | `{conversation_ids: []}` | `{deleted_count, total}` | 批量删除对话 |\n| 10 | PUT | `/api/conversations/{id}` | API Key | `{title}` | `{success}` | 更新对话标题 |\n| 11 | POST | `/api/conversations/messages` | API Key | `{conversation_id, role, content, metadata?}` | `{message_id}` | 添加消息到对话历史 |\n| 12 | GET | `/api/conversations/{id}/messages` | API Key | `?limit` | `Message[]` | 获取对话消息历史 |\n| 13 | GET | `/api/conversations/{id}/context` | API Key | `?max_messages` | `{conversation_id, messages[]}` | 获取对话上下文(供 Agent 使用) |\n| 14 | GET | `/api/conversations/{id}/stats` | API Key | — | `{message_count, ...}` | 获取对话统计信息 |\n| 15 | GET | `/api/conversations/search` | API Key | `?keyword&user_id&limit` | `Conversation[]` | 搜索对话(标题模糊匹配) |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-01** | 🟡 中 | `POST /api/session/save` 无输入验证 — 直接 `session.update(data)` 可造成 JSON 文件被任意键值覆盖 |\n| **B-02** | 🟡 中 | `GET /api/session` 与 `GET /api/conversations/{id}` 数据源不一致 — 前者读 JSON 文件,后者读 SQLite,两者互不同步 |\n| **B-03** | 🟢 低 | `POST /api/conversations/batch-delete` 对 JSON 解析有两层回退(UTF-8→GBK),但删除失败静默忽略,不报告具体哪些 ID 失败 |\n| **B-04** | 🟢 低 | `GET /api/conversations/search` 仅搜索标题而非消息内容,实用性有限 |\n\n---\n\n### 2.3 Agent 执行管线 — `routers/agent.py` (3 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体 | 响应 | 说明 |\n|---|------|------|------|--------|------|------|\n| 16 | POST | `/api/agent/run` | API Key | `{text, tags[], images[], music[], conversation_id?}` | `{creator_content, agent_suggestions, evaluation, execution_log, session_state}` | **核心端点**:委托 LangGraph 全流程执行 |\n| 17 | GET | `/api/agent/status/{session_id}` | API Key | — | `{session_id, status, iteration}` | 查询 session 运行状态 |\n| 18 | POST | `/api/agent/rollback` | API Key | `{conversation_id, skill}` | `{success, restored[]}` | 回滚 Skill 结果到上一轮 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-05** | 🔴 高 | `POST /api/agent/run` 请求体解析有两层编码回退 (`json.loads` → `gbk`),在极端情况下可能解析出乱码数据进入管线 |\n| **B-06** | 🟡 中 | `GET /api/agent/status/{session_id}` 数据源优先级混乱:先查 `memory.list_conversations()` → 再查 JSON 文件 → 最后返回 `not_found`。两个数据源结构不同,返回字段不一致 |\n| **B-07** | 🟡 中 | `POST /api/agent/rollback` 实现简陋 — 仅找回上一条 assistant 消息的 JSON,不区分 Skill 类型,`skill_name` 参数未实际使用 |\n| **B-08** | 🟡 中 | `_analyze_images()` 函数仅从 URL 文件名推理文案,实际图片内容未读取,且 LLM 超时仅 10 秒 |\n| **B-09** | 🟢 低 | 空输入返回 `error: \"empty_input\"` 但 HTTP 状态码为 200,前端无法通过 HTTP 状态码区分错误 |\n\n---\n\n### 2.4 素材管理 — `routers/materials.py` (6 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 19 | GET | `/api/materials` | API Key | `?type` | `Material[]` | 获取素材列表(全部/按类型筛选) |\n| 20 | POST | `/api/materials` | API Key | `{material_type, original_content/image_path/music_name, music_url?}` | `{id}` | 添加素材 |\n| 21 | PUT | `/api/materials/{material_id}` | API Key | `{material_type, original_content/image_path/music_name}` | `{success}` | 更新素材 |\n| 22 | DELETE | `/api/materials/{material_id}` | API Key | — | `{success}` | 删除素材 |\n| 23 | GET | `/api/materials/by-tags` | API Key | `?tags` (逗号分隔) | `Material[]` | 按标签搜索素材 |\n| 24 | GET | `/api/materials/top` | API Key | `?type&limit` | `Material[]` | 获取热门素材 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-10** | 🔴 高 | `POST /api/materials` 和 `PUT /api/materials/{id}` 无 Pydantic 验证模型 — 直接接收 `dict`,无字段校验、无类型检查、无必填验证 |\n| **B-11** | 🟡 中 | `PUT /api/materials/{id}` 中 `material_id` 声明为 `int` 但未做错误处理,非法 ID 会直接抛 FastAPI 异常 |\n| **B-12** | 🟢 低 | `GET /api/materials/by-tags` 要求 tags 为逗号分隔字符串,但前端 `materialApi.getMaterialsByTags` 传的是 `string[]`(axios 会转成 `?tags[]=a&tags[]=b`),**前后端参数格式不匹配** |\n\n---\n\n### 2.5 发布历史 — `routers/publish.py` (5 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 25 | GET | `/api/publish/history` | API Key | — | `PublishHistory[]` | 获取发布历史 |\n| 26 | PUT | `/api/publish/{publish_id}/data` | API Key | `{likes, comments, views}` | `{success}` | 更新发布数据(有 Pydantic 校验) |\n| 27 | GET | `/api/content/{post_id}` | API Key | — | `ContentDetail` (含日趋势) | 获取单条内容详情 |\n| 28 | GET | `/api/publish/{publish_id}` | API Key | — | `PublishDetail` | 获取单条发布详情 |\n| 29 | DELETE | `/api/publish/{publish_id}` | API Key | — | `{success}` | 删除发布记录 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-13** | 🟡 中 | 路径参数类型不一致 — `publish_id` 在 `PUT /api/publish/{publish_id}/data` 中为 `int`,但在 `GET /api/publish/{publish_id}` 中为 `str`,在 `DELETE /api/publish/{publish_id}` 中也为 `str` |\n| **B-14** | 🟡 中 | `GET /api/content/{post_id}` 和 `GET /api/publish/{publish_id}` 功能重叠 — 两者返回相同的 `content_posts` 表数据,但路径不同 (`/content/` vs `/publish/`) |\n| **B-15** | 🟢 低 | 前端 `publishApi.getPublish(id: number)` 传 number,但后端路径参数类型为 `str` — FastAPI 可自动转换,但语义不一致 |\n\n---\n\n### 2.6 数据分析 — `routers/analytics.py` (3 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 30 | GET | `/api/analytics/overview` | API Key | — | `PersonalDataAnalysis` | 数据分析总览 |\n| 31 | GET | `/api/analytics/traffic-trend` | API Key | `?content_id&limit` | `TrafficDailyItem[]` | 流量日趋势 |\n| 32 | GET | `/api/analytics/follower-trend` | API Key | `?content_id&limit` | `FollowerDailyItem[]` | 粉丝日趋势 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-16** | 🟢 低 | `content_id` 参数在路由函数签名中类型为 `int = None`,但前端可能传 `undefined`;FastAPI 会将 `None` 默认值正确处理,但类型注解不精确 |\n\n---\n\n### 2.7 抖音同步 — `routers/douyin.py` (5 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 33 | POST | `/api/douyin/login` | API Key | — | `{success: false}` | **[已禁用]** 触发浏览器登录 |\n| 34 | GET | `/api/douyin/status` | API Key | — | `{logged_in, cookie_count}` | 检查登录状态(从 `douyin_state.json`) |\n| 35 | POST | `/api/douyin/sync` | API Key | `{records: []}` | `{success, synced}` | 手动录入发布数据 |\n| 36 | POST | `/api/douyin/sync-auto` | API Key | — | `{success: false}` | **[已禁用]** 自动拉取数据 |\n| 37 | GET | `/api/douyin/sync-history` | API Key | — | `PublishHistory[]` | 获取同步历史 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-17** | 🔴 高 | `POST /api/douyin/sync` 的 Pydantic ���型 `DouyinSyncRequest` 定义了 `records: List[dict]` 带 `min_items=1`,但路由函数签名使用 `data: dict = None` 而非 Pydantic 模型,**Pydantic 校验完全被绕过** |\n| **B-18** | 🟡 中 | `POST /api/douyin/login` 和 `POST /api/douyin/sync-auto` 已禁用但端点仍暴露,应返回 410 Gone 或直接删除 |\n| **B-19** | 🟢 低 | `GET /api/douyin/sync-history` 与 `GET /api/publish/history` 返回相同数据,属于冗余端点 |\n\n---\n\n### 2.8 图片上传 — `routers/upload.py` (1 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 38 | POST | `/api/upload` | API Key | `multipart/form-data` (file) | `{url, original_name, size}` | 上传图片到 `personal_materials/` |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-20** | 🟡 中 | 文件名清理 `\"\".join(c for c in safe_name if c.isalnum() or c in \"._-\")` 会删除中文字符,用户上传中文文件名会变成纯英文数字 |\n| **B-21** | 🟢 低 | 文件大小无限制,可能被上传大文件耗尽磁盘 |\n\n---\n\n### 2.9 统计 & Qdrant — `routers/stats.py` (5 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 39 | GET | `/api/stats/daily` | API Key | — | `{date, stats, redis_available}` | 今日实时统计(Redis) |\n| 40 | GET | `/api/qdrant/hot-topics` | API Key | `?limit` | `{topics[], qdrant_available}` | 热门话题(Qdrant) |\n| 41 | GET | `/api/qdrant/similar-texts` | API Key | `?text&limit` | `{results[], count}` | 相似文案语义搜索 |\n| 42 | GET | `/api/qdrant/search-tags` | API Key | `?text&limit` | `{results[], count}` | 标签语义搜索 |\n| 43 | POST | `/api/qdrant/index-text` | API Key | `{text, quality_score, tags[], ...}` | `{success, indexed}` | 高质量文案写入 Qdrant |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-22** | 🟡 中 | `POST /api/qdrant/index-text` 无 Pydantic 验证,手动解析 JSON 且只有 `score < 4.0` 校验 |\n| **B-23** | 🟢 低 | `GET /api/qdrant/similar-texts` 和 `GET /api/qdrant/search-tags` 中 `text` 参数通过 query string 传递,长文案可能超出 URL 长度限制 |\n\n---\n\n## 三、前端 API 调用与数据流\n\n### 3.1 API Service 层 ([frontend/src/services/api.ts](frontend/src/services/api.ts))\n\n**axios 实例配置**:\n- `baseURL: '/api'`\n- `timeout: 180000` (3 分钟)\n- 请求拦截器:附加 `X-Request-Id` + `X-API-Key`\n- 响应拦截器:统一错误提示 (500→error, 400→warning, 网络断连→error)\n\n**前端 API 模块与实际后端路由映射**:\n\n| 前端模块 | 前端方法 | 实际请求路径 | 后端路由 |\n|----------|----------|-------------|----------|\n| `agentApi.run` | POST | `/api/agent/run` | `routers/agent.py` |\n| `agentApi.getStatus` | GET | `/api/agent/status/{id}` | `routers/agent.py` |\n| `agentApi.rollback` | POST | `/api/agent/rollback` | `routers/agent.py` |\n| `conversationApi.create` | POST | `/api/conversations` | `routers/conversations.py` |\n| `conversationApi.get` | GET | `/api/conversations/{id}` | `routers/conversations.py` |\n| `conversationApi.list` | GET | `/api/conversations` | `routers/conversations.py` |\n| `conversationApi.delete` | DELETE | `/api/conversations/{id}` | `routers/conversations.py` |\n| `conversationApi.batchDelete` | POST | `/api/conversations/batch-delete` | `routers/conversations.py` |\n| `conversationApi.updateTitle` | PUT | `/api/conversations/{id}` | `routers/conversations.py` |\n| `conversationApi.addMessage` | POST | `/api/conversations/messages` | `routers/conversations.py` |\n| `conversationApi.getHistory` | GET | `/api/conversations/{id}/messages` | `routers/conversations.py` |\n| `conversationApi.getContext` | GET | `/api/conversations/{id}/context` | `routers/conversations.py` |\n| `conversationApi.getStats` | GET | `/api/conversations/{id}/stats` | `routers/conversations.py` |\n| `conversationApi.search` | GET | `/api/conversations/search` | `routers/conversations.py` |\n| `materialApi.getMaterials` | GET | `/api/materials` | `routers/materials.py` |\n| `materialApi.addMaterial` | POST | `/api/materials` | `routers/materials.py` |\n| `materialApi.updateMaterial` | PUT | `/api/materials/{id}` | `routers/materials.py` |\n| `materialApi.deleteMaterial` | DELETE | `/api/materials/{id}` | `routers/materials.py` |\n| `materialApi.getMaterialsByTags` | GET | `/api/materials/by-tags` | `routers/materials.py` |\n| `materialApi.getTopMaterials` | GET | `/api/materials/top` | `routers/materials.py` |\n| `publishApi.getHistory` | GET | `/api/publish/history` | `routers/publish.py` |\n| `publishApi.getPublish` | GET | `/api/publish/{id}` | `routers/publish.py` |\n| `publishApi.updatePublishData` | PUT | `/api/publish/{id}/data` | `routers/publish.py` |\n| `publishApi.delete` | DELETE | `/api/publish/{id}` | `routers/publish.py` |\n| `analyticsApi.getOverview` | GET | `/api/analytics/overview` | `routers/analytics.py` |\n| `analyticsApi.getTrafficTrend` | GET | `/api/analytics/traffic-trend` | `routers/analytics.py` |\n| `analyticsApi.getFollowerTrend` | GET | `/api/analytics/follower-trend` | `routers/analytics.py` |\n| `analyticsApi.getContentDetail` | GET | `/api/content/{post_id}` | `routers/publish.py` |\n| `douyinSyncApi.sync` | POST | `/api/douyin/sync` | `routers/douyin.py` |\n| `douyinSyncApi.healthCheck` | GET | `/api/health` | `routers/health.py` |\n| `douyinSyncApi.getSyncHistory` | GET | `/api/douyin/sync-history` | `routers/douyin.py` |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **F-01** | 🔴 高 | **API Key 硬编码**:`api.ts:30` 中 `X-API-Key: 'your-api-key-here'`,这是一个占位符值,不会被后端验证通过(除非后端恰好也配置了这个值),导致所有 API 调用 401 |\n| **F-02** | 🔴 高 | **rollback 请求体字段名不匹配**:前端 `agentApi.rollback` 发送 `{session_id, target_skill}`,但后端 `POST /api/agent/rollback` 期望 `{conversation_id, skill}` — **完全不匹配** |\n| **F-03** | 🟡 中 | **Mock 模式依赖 localStorage**:每次调用 `isMockMode()` 读 localStorage,在 Settings 页切换后需刷新才能影响已在运行的请求 |\n| **F-04** | 🟡 中 | **materialApi.getMaterialsByTags** 传 `string[]` 给 `params: { tags }`,axios 默认序列化为 `?tags[]=a&tags[]=b`,但后端期望 `?tags=a,b`(逗号分隔字符串) |\n| **F-05** | 🟡 中 | **analyticsApi.getContentDetail** 调用 `/api/content/{postId}` 归于 publish 路由模块,但定义在 analyticsApi 中,归类混乱 |\n| **F-06** | 🟡 中 | **douyinSyncApi.healthCheck** 调用 `/api/health`,与 douyin 同步无关,应归属独立 healthApi |\n| **F-07** | 🟡 中 | **前端未使用 `/api/upload` 端点** — 图片上传功能在前端 `Workspace.tsx` 使用 Ant Design Upload 组件,但图片直接通过 `FileReader` 转 base64 放在 `userInput.images` 中随 `/api/agent/run` 一起提交,绕过了独立上传端点 |\n| **F-08** | 🟢 低 | **WebSocket 连接到 `ws://localhost:8000`** 但后端 `server.py` 运行在 `:9000` 且无 WebSocket 端点定义 — WebSocket service 定义了完整的消息类型和重连逻辑,但**后端未实现 WebSocket 端点** |\n\n---\n\n### 3.2 State 管理 ([frontend/src/stores/appStore.ts](frontend/src/stores/appStore.ts))\n\n**Zustand Store 数据流**:\n\n```\nuseAppStore\n├── conversationId ← localStorage('current_conversation_id')\n├── messages[] ← localStorage('msg_cache_{convId}')\n├── inputText ← localStorage('workspace_draft')\n├── executionStages[]\n├── executionResult\n├── userInput {text, tags[], images[], music[], enable_blackbox}\n├── materials[]\n├── publishHistory[]\n└── analytics\n```\n\n**持久化策略**:\n| 数据 | 存储位置 | 生命周期 |\n|------|----------|----------|\n| conversationId | localStorage | 永久 |\n| messages (缓存) | localStorage (`msg_cache_{id}`) | 永久 (最多 50 条) |\n| inputText (草稿) | localStorage (`workspace_draft`) | 永久 |\n| conversation 数据 | SQLite (via API) | 永久 |\n| session 摘要 | Redis (via API) | 30 天 TTL |\n| executionStages/Result | Zustand (内存) | 会话级别 |\n\n---\n\n### 3.3 页面级数据流详情\n\n**Workspace 页(核心页面)**:\n```\n用户输入文本\n ↓\nagentApi.run({text, tags, images, music, conversation_id})\n ↓\n后端 LangGraph 14节点工作流\n ↓\n返回 {creator_content, agent_suggestions, evaluation, execution_log, session_state}\n ↓\n前端解析为 ChatMessageData 展示\n ↓\n保存到 messages[] + localStorage 缓存\n```\n\n**MaterialLibrary 页**:\n```\nmaterialApi.getMaterials(type?) → 展示素材列表\nmaterialApi.deleteMaterial(id) → 删除素材\nmaterialApi.addMaterial(data) → 添加素材\n```\n\n**PublishHistory 页**:\n```\npublishApi.getHistory() → 展示发布历史列表\npublishApi.getPublish(id) → 查看单条详情\nanalyticsApi.getContentDetail(id)→ 查看含日趋势的详情\npublishApi.delete(id) → 删除记录\n```\n\n**Analytics 页**:\n```\nanalyticsApi.getOverview() → 数据总览\nanalyticsApi.getTrafficTrend() → 流量趋势图\nanalyticsApi.getFollowerTrend() → 粉丝趋势图\n```\n\n**DouyinSync 页**:\n```\ndouyinSyncApi.sync(records) → 手动录入数据\ndouyinSyncApi.getSyncHistory() → 查看同步历史\ndouyinSyncApi.healthCheck() → 检查后端健康\n```\n\n---\n\n## 四、LangGraph 工作流数据流\n\n### 4.1 State 定义 ([backend/graph/state.py](backend/graph/state.py))\n\n`AgentState` 包含 26 个字段,覆盖全流程:\n\n```\n输入层: user_input, conversation_id, enable_blackbox\n意图层: intent (topic|create|optimize|modify|question)\n上下文: session, has_context, prev_copy_text, prev_evaluation,\n prev_tags, prev_images, prev_music\n文案层: copy_text\n组件层: need_tags, need_images, need_music, modify_flags\n情绪层: emotion {mood, intensity, energy, keywords}\nSkill层: final_tags, final_images, final_music\n评估层: evaluation, iteration, best_score, best_evaluation,\n best_final_*, loop_history\n对话层: dialogue_messages, should_enter_workflow, workflow_intent_override\n输出层: result, execution_log, error\n```\n\n### 4.2 节点数据流图\n\n```\n ┌──────────────┐\n │ initialize │ ← DB1(Redis摘要) + DB2(SQLite消息)\n └──────┬───────┘\n │\n ┌──────▼───────┐\n │classify_intent│ ← LLM 五分类\n └──────┬───────┘\n │\n ┌─────────┬───────────┼───────────┬──────────┐\n ▼ ▼ ▼ ▼ ▼\n generate_copy optimize_copy parse_modify agent_dialogue analyze_emotion\n (topic→文案) (optimize→改写) (modify→标记) (ReAct循环) (已create→情绪)\n │ │ │ │ │\n └────┬────┘ │ ┌────┴────┐ │\n │ │ │ END │ │\n ▼ ▼ │ 管线入口 │ │\n analyze_emotion◄──────┘ └─────────┘ │\n │ │\n ▼ │\n analyze_content ← Python: 判断需要哪些组件 │\n │ │\n ▼ │\n decide_skills ← Python: 决定执行哪些Skill │\n │ │\n ▼ │\n execute_all_skills ← ThreadPoolExecutor并行3 Skill │\n │ │\n ▼ │\n evaluate ← Skill4 综合评估 │\n │ │\n ▼ │\n check_threshold ← 评分≥4.0? iter≥3? │\n │ │ │\n ┌────▼──┐ ┌───▼──────────┐ │\n │ END │ │identify_weak │ ← 找最低分维度 │\n └────────┘ └───┬──────────┘ │\n │ │\n ┌────▼─────┐ │\n │rerun_skill│ ← 重跑最弱Skill │\n └────┬─────┘ │\n │ │\n ┌────▼──────┐ │\n │rerun_evaluate│ ← 重新评估 │\n └────┬──────┘ │\n │ │\n └──→ check_threshold (循环) │\n```\n\n### 4.3 条件边路由表\n\n| 条件边 | 源节点 | 条件 | 目标 |\n|--------|--------|------|------|\n| `route_by_intent` | classify_intent | `topic` | generate_copy |\n| | | `create` | analyze_emotion |\n| | | `optimize` + has_context | optimize_copy |\n| | | `modify` + has_context | parse_modify_intent |\n| | | `question` | agent_dialogue |\n| `route_after_modify` | parse_modify_intent | `change_copy=true` | optimize_copy |\n| | | `change_copy=false` | analyze_emotion |\n| `route_after_dialogue` | agent_dialogue | `should_enter_workflow` | analyze_emotion |\n| | | 否则 | END |\n| `route_by_score` | check_threshold | `score ≥ 4.0` | END |\n| | | `score < 4.0 AND iter < 3` | identify_weak |\n| | | `iter ≥ 3` | END |\n| `route_after_weak` | identify_weak | `error == \"no_dimensions\"` | END |\n| | | 否则 | rerun_skill |\n\n---\n\n## 五、数据持久化层\n\n### 5.1 三层存储架构\n\n```\n┌────────────────────────────────────────────────────┐\n│ Redis (Cache) │\n│ • 会话摘要 (DB1): summary:{conv_id} (TTL 30天) │\n│ • 每日统计: daily_stats │\n│ • Qdrant 代理缓存 │\n└────────────────────┬───────────────────────────────┘\n │\n┌────────────────────┴───────────────────────────────┐\n│ SQLite (memory.db) │\n│ 表: │\n│ • content_posts — 发布内容 │\n│ • traffic_daily — 流量日趋势 │\n│ • follower_daily — 粉丝日趋势 │\n│ • tags — 标签库 │\n│ • conversations — 对话会话 │\n│ • conversation_messages — 对话消息 (DB2) │\n│ • personal_material_library — 个人素材库 │\n└────────────────────┬───────────────────────────────┘\n │\n┌────────────────────┴───────────────────────────────┐\n│ Qdrant (Vector Store) │\n│ • public_texts — 公共文案向量 │\n│ • public_tags — 公共标签向量 │\n│ • topic_trends — 热门话题 │\n└────────────────────────────────────────────────────┘\n```\n\n### 5.2 双层记忆(DB1 + DB2)数据流\n\n```\n请求进入 →\n initialize 节点:\n ├── DB1 (Redis) 加载摘要 → prev_copy_text, prev_tags, prev_evaluation\n ├── DB2 (SQLite) 加载消息 → dialogue_messages (仅 agent_dialogue 用)\n └── 兜底: SQLite add_message 旧格式\n\n管线完成 / Agent对话结束 →\n builder.run_agent():\n ├── DB1 (Redis) 保存摘要 ← save_session_summary()\n └── DB2 (SQLite) 保存消息 ← save_dialogue_messages()\n\n agent_dialogue 节点:\n ├── DB1 更新摘要(合并已有 + 新偏好)\n └── DB2 全量保存 ReAct 消息(先清空 agent 消息 → 批量插入)\n```\n\n---\n\n## 六、关键问题汇总\n\n### 🔴 高危 (需立即修复)\n\n| 编号 | 位置 | 问题 |\n|------|------|------|\n| **F-01** | [api.ts:30](frontend/src/services/api.ts#L30) | API Key 硬编码为 `'your-api-key-here'`,所有请求 401 |\n| **F-02** | [api.ts:171-174](frontend/src/services/api.ts#L171-L174) | rollback 请求体字段名与后端不匹配 (`session_id` vs `conversation_id`) |\n| **B-10** | [materials.py:19-32](backend/routers/materials.py#L19-L32) | POST/PUT materials 无 Pydantic 验证 |\n| **B-17** | [douyin.py:61-71](backend/routers/douyin.py#L61-L71) | douyin sync Pydantic 模型定义但未使用 |\n\n### 🟡 中危 (建议近期修复)\n\n| 编号 | 位置 | 问题 |\n|------|------|------|\n| **F-04** | [api.ts:290-293](frontend/src/services/api.ts#L290-L293) | getMaterialsByTags 参数格式前后端不匹配 |\n| **F-08** | [websocket.ts:15](frontend/src/services/websocket.ts#L15) | WebSocket 连接到 8000 但后端无 WS 端点 |\n| **B-01** | [conversations.py:78-85](backend/routers/conversations.py#L78-L85) | session/save 无输入验证 |\n| **B-06** | [agent.py:129-151](backend/routers/agent.py#L129-L151) | status 端点数据源混乱 |\n| **B-07** | [agent.py:160-195](backend/routers/agent.py#L160-L195) | rollback 未使用 skill_name |\n| **B-12** | [materials.py:60-64](backend/routers/materials.py#L60-L64) | tags 参数格式与前端不兼容 |\n| **B-13** | [publish.py](backend/routers/publish.py) | publish_id 类型不一致 (int vs str) |\n| **B-14** | [publish.py](backend/routers/publish.py) | `/content/{id}` 与 `/publish/{id}` 功能重叠 |\n\n### 🟢 低危 (可择机处理)\n\n| 编号 | 位置 | 问题 |\n|------|------|------|\n| **B-03** | [conversations.py:150-175](backend/routers/conversations.py#L150-L175) | 批量删除失败静默忽略 |\n| **B-04** | [conversations.py:239-246](backend/routers/conversations.py#L239-L246) | 搜索仅限标题 |\n| **B-20** | [upload.py:25](backend/routers/upload.py#L25) | 中文文件名被删除 |\n| **B-21** | [upload.py:16](backend/routers/upload.py#L16) | 无文件大小限制 |\n| **B-23** | [stats.py:38-43](backend/routers/stats.py#L38-L43) | 长文本通过 query string 传递 |\n| **F-06** | [api.ts:367-384](frontend/src/services/api.ts#L367-L384) | healthCheck 归错 API 模块 |\n\n---\n\n## 七、数据流一致性检查\n\n### 7.1 请求 → 响应链路完整性\n\n| 功能 | 前端调用 | 后端路由 | Graph 节点 | 持久化 | 状态 |\n|------|----------|----------|-----------|--------|------|\n| 智能创作 | Workspace → agentApi.run | POST /api/agent/run | 14节点完整链路 | DB1+DB2 | ✅ 完整 |\n| 对话管理 | Workspace → conversationApi.* | CRUD 14端点 | initialize (读) | SQLite | ✅ 完整 |\n| 素材浏览 | MaterialLibrary → materialApi.* | CRUD 6端点 | — | SQLite | ⚠️ 参数不匹配 |\n| 发布历史 | PublishHistory → publishApi.* | CRUD 5端点 | — | SQLite | ⚠️ 类型不一致 |\n| 数据分析 | Analytics → analyticsApi.* | 查询 3端点 | — | SQLite | ✅ 完整 |\n| 数据同步 | DouyinSync → douyinSyncApi.* | 同步 5端点 | — | SQLite | ⚠️ 校验被绕过 |\n| 图片上传 | — | POST /api/upload | — | 文件系统+SQLite | ⚠️ 前端未使用 |\n| 实时推送 | websocket.ts | — | — | — | ❌ 后端未实现 |\n\n### 7.2 前后端字段名对照\n\n| 数据流 | 前端字段名 | 后端期望字段名 | 匹配 |\n|--------|-----------|---------------|------|\n| agent/run 请求 | `text, tags, images, music, enable_blackbox` | `text, tags, images, music, conversation_id` | ✅ (enable_blackbox 被后端忽略) |\n| agent/run 响应 | `ExecutionResult` | `{creator_content, agent_suggestions, evaluation, execution_log, session_state}` | ✅ |\n| agent/rollback 请求 | `session_id, target_skill` | `conversation_id, skill` | ❌ 不匹配 |\n| conversations/create 请求 | `title, user_id` | `CreateConversationRequest(title, user_id)` | ✅ |\n| materials 请求 | `{material_type, original_content, ...}` | 直接取 dict key | ⚠️ 无验证 |\n| douyin/sync 请求 | `{records: DouyinSyncRecord[]}` | `{records: List[dict]}` (Pydantic 未使用) | ⚠️ 无验证 |\n\n### 7.3 Vite 代理配置检查\n\n| 代理前缀 | 目标 | 状态 |\n|----------|------|------|\n| `/api` | `http://localhost:9000` | ✅ 正常 |\n| `/personal` | `http://localhost:9000` | ✅ 正常 (静态文件) |\n| `/public` | `http://localhost:9000` | ✅ 正常 (静态文件) |\n| `/ws` | `ws://localhost:8000` | ❌ 后端无 WS (server.py 运行在 9000) |\n\n---\n\n## 八、安全审计\n\n### 8.1 API Key 认证\n\n- ✅ 中间件正确豁免 `/api/health`, `/docs`, `/openapi.json`, `/redoc`\n- ✅ 支持 `Authorization: Bearer <key>` 和 `X-API-Key: <key>` 两种方式\n- ✅ 静态文件路径 `/personal/*`, `/public/*` 免认证\n- ❌ 前端硬编码 API Key (F-01)\n- ⚠️ API Key 从环境变量 `DEEPSEEK_API_KEY` 获取 (命名存在误导 — 实际复用 DeepSeek 的 Key 作为 API Key)\n\n### 8.2 CORS\n\n- ✅ 允许 localhost:3000, localhost:5173 (开发环境)\n- ✅ 限制 Methods: GET, POST, PUT, DELETE\n- ✅ 限制 Headers: Content-Type, Authorization, X-Request-Id\n- ⚠️ 生产环境需更新 `CORS_ORIGINS` 环境变量\n\n### 8.3 输入校验\n\n- ✅ `conversations.py` 使用了 Pydantic 模型\n- ✅ `publish.py` 的 `PublishDataUpdate` 使用了 Pydantic 模型\n- ⚠️ `materials.py` 无 Pydantic 验证\n- ⚠️ `douyin.py` 定义了模型但路由函数未使用\n- ⚠️ `agent.py` 手动解析 JSON + GBK 回退\n\n---\n\n## 九、建议修复优先级\n\n### P0 (立即)\n1. 修复 API Key 硬编码 (F-01)\n2. 修复 rollback 字段名不匹配 (F-02)\n\n### P1 (本周)\n3. 为 materials 路由添加 Pydantic 验证 (B-10)\n4. 修复 douyin sync Pydantic 校验绕过 (B-17)\n5. 统一 materials/by-tags 参数格式 (F-04, B-12)\n6. 统一 publish_id 类型 (B-13)\n\n### P2 (本月)\n7. 实现或移除 WebSocket 端点 (F-08)\n8. 修复 status 端点数据源混乱 (B-06)\n9. 清理已禁用端点 (B-18)\n10. 添加文件上传大小限制 (B-21)\n\n---\n\n*报告由全栈接口审计自动生成 · 2026-07-10*\n"}, "dc3352c7194e791d5b5c1ce6772afeeef4f60491": {"t": 1785748418.5985057, "n": 30626, "h": "076f1833f9ae3781", "c": "# 抖音创作顾问 Agent — 前后端接口审计报告\n\n> 审计日期:2026-07-10 \n> 版本:v2.0 \n> 审计范围:全部 8 个路由模块 (35 个端点) + LangGraph 14 节点工作流 + 前端 6 页面数据流\n\n---\n\n## 一、系统架构总览\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│ Frontend (React + Vite :3000) │\n│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │\n│ │Workspace │ │Materials │ │ Publish │ │Analytics │ │DouyinSync/ │ │\n│ │ (核心) │ │ Library │ │ History │ │ │ │Settings │ │\n│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬──────┘ │\n│ │ │ │ │ │ │\n│ ┌────┴─────────────┴────────────┴────────────┴───────────────┴──────┐ │\n│ │ services/api.ts (axios, baseURL='/api') │ │\n│ │ services/websocket.ts (ws://localhost:8000) │ │\n│ │ stores/appStore.ts (Zustand) │ │\n│ └────────────────────────────────┬───────────────────────────────────┘ │\n│ │ Vite Proxy │\n│ /api → :9000 /ws → :8000 │\n└───────────────────────────────────┼─────────────────────────────────────┘\n │\n┌───────────────────────────────────┼─────────────────────────────────────┐\n│ Backend (FastAPI :9000) │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ server.py (App Factory) │ │\n│ │ • CORS Middleware • API Key Auth Middleware │ │\n│ │ • Static Mounts: /personal, /public │ │\n│ └──────────────────────────────────────────────────────────────────┘ │\n│ │ │\n│ ┌─────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐ ┌────────┐ │\n│ │ health │ │conversa-│ │ agent │ │materials│ │publish │ │\n│ │ (1) │ │tions(14)│ │ (3) │ │ (6) │ │ (5) │ │\n│ └─────────┘ └─────────┘ └───┬────┘ └─────────┘ └────────┘ │\n│ │ │\n│ ┌───────────────┼───────────────┐ │\n│ │ analytics(3) │ douyin(5) │ │\n│ └───────────────┴───────────────┘ │\n│ │ │\n│ ┌───────────────┼───────────────┐ │\n│ │ upload(1) │ stats(5) │ │\n│ └───────────────┴───────────────┘ │\n│ │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ LangGraph 工作流 (14 节点 + 6 条件边) │ │\n│ │ │ │\n│ │ initialize → classify_intent ─┬→ generate_copy │ │\n│ │ ├→ optimize_copy │ │\n│ │ ├→ parse_modify_intent │ │\n│ │ ├→ agent_dialogue → END/管线 │ │\n│ │ └→ analyze_emotion │ │\n│ │ ↓ │ │\n│ │ analyze_content → decide_skills → execute_all_skills │ │\n│ │ (Skill1║Skill2║Skill3 并行) │ │\n│ │ ↓ │ │\n│ │ evaluate → check_threshold ─┬→ END (≥4.0 or iter≥3) │ │\n│ │ └→ identify_weak → rerun_skill │ │\n│ │ └→ rerun_evaluate ↩ │ │\n│ └──────────────────────────────────────────────────────────────────┘ │\n│ │\n│ ┌──────────────────────────────────────────────────────────────────┐ │\n│ │ 持久化层 │ │\n│ │ • Redis (Cache) — DB1: 会话摘要, 每日统计 │ │\n│ │ • SQLite (memory.db) — DB2: 对话消息, 发布数据, 素材库 │ │\n│ │ • Qdrant (Vector Store) — 公共文案/标签语义搜索 │ │\n│ └──────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## 二、后端接口清单(35 个端点)\n\n### 2.1 健康检查 — `routers/health.py`\n\n| # | 方法 | 路径 | 认证 | 请求体 | 响应 | 说明 |\n|---|------|------|------|--------|------|------|\n| 1 | GET | `/api/health` | 无 | — | `{status, timestamp}` | 健康检查,免 API Key 认证 |\n\n**审计发现**:✅ 正常。`server.py` 中 `_PUBLIC_PATHS` 已豁免此路径认证。\n\n---\n\n### 2.2 会话 & 对话管理 — `routers/conversations.py` (14 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 2 | GET | `/api/session` | API Key | — | `{session_id, messages[], ...}` | 获取当前会话(从 `state/current_session.json`) |\n| 3 | POST | `/api/session/save` | API Key | `dict` (合并到 session) | `{success: true}` | 保存会话状态到 JSON 文件 |\n| 4 | POST | `/api/session/reset` | API Key | — | `{success, session}` | 重置会话(删除 JSON 文件) |\n| 5 | POST | `/api/conversations` | API Key | `{title, user_id}` | `{conversation_id}` | 创建对话(生成 `conv_xxx` ID) |\n| 6 | GET | `/api/conversations/{id}` | API Key | — | `Conversation` | 获取单个对话信息 |\n| 7 | GET | `/api/conversations` | API Key | `?user_id&limit` | `Conversation[]` | 列出对话列表 |\n| 8 | DELETE | `/api/conversations/{id}` | API Key | — | `{success}` | 删除对话(级联删除消息) |\n| 9 | POST | `/api/conversations/batch-delete` | API Key | `{conversation_ids: []}` | `{deleted_count, total}` | 批量删除对话 |\n| 10 | PUT | `/api/conversations/{id}` | API Key | `{title}` | `{success}` | 更新对话标题 |\n| 11 | POST | `/api/conversations/messages` | API Key | `{conversation_id, role, content, metadata?}` | `{message_id}` | 添加消息到对话历史 |\n| 12 | GET | `/api/conversations/{id}/messages` | API Key | `?limit` | `Message[]` | 获取对话消息历史 |\n| 13 | GET | `/api/conversations/{id}/context` | API Key | `?max_messages` | `{conversation_id, messages[]}` | 获取对话上下文(供 Agent 使用) |\n| 14 | GET | `/api/conversations/{id}/stats` | API Key | — | `{message_count, ...}` | 获取对话统计信息 |\n| 15 | GET | `/api/conversations/search` | API Key | `?keyword&user_id&limit` | `Conversation[]` | 搜索对话(标题模糊匹配) |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-01** | 🟡 中 | `POST /api/session/save` 无输入验证 — 直接 `session.update(data)` 可造成 JSON 文件被任意键值覆盖 |\n| **B-02** | 🟡 中 | `GET /api/session` 与 `GET /api/conversations/{id}` 数据源不一致 — 前者读 JSON 文件,后者读 SQLite,两者互不同步 |\n| **B-03** | 🟢 低 | `POST /api/conversations/batch-delete` 对 JSON 解析有两层回退(UTF-8→GBK),但删除失败静默忽略,不报告具体哪些 ID 失败 |\n| **B-04** | 🟢 低 | `GET /api/conversations/search` 仅搜索标题而非消息内容,实用性有限 |\n\n---\n\n### 2.3 Agent 执行管线 — `routers/agent.py` (3 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体 | 响应 | 说明 |\n|---|------|------|------|--------|------|------|\n| 16 | POST | `/api/agent/run` | API Key | `{text, tags[], images[], music[], conversation_id?}` | `{creator_content, agent_suggestions, evaluation, execution_log, session_state}` | **核心端点**:委托 LangGraph 全流程执行 |\n| 17 | GET | `/api/agent/status/{session_id}` | API Key | — | `{session_id, status, iteration}` | 查询 session 运行状态 |\n| 18 | POST | `/api/agent/rollback` | API Key | `{conversation_id, skill}` | `{success, restored[]}` | 回滚 Skill 结果到上一轮 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-05** | 🔴 高 | `POST /api/agent/run` 请求体解析有两层编码回退 (`json.loads` → `gbk`),在极端情况下可能解析出乱码数据进入管线 |\n| **B-06** | 🟡 中 | `GET /api/agent/status/{session_id}` 数据源优先级混乱:先查 `memory.list_conversations()` → 再查 JSON 文件 → 最后返回 `not_found`。两个数据源结构不同,返回字段不一致 |\n| **B-07** | 🟡 中 | `POST /api/agent/rollback` 实现简陋 — 仅找回上一条 assistant 消息的 JSON,不区分 Skill 类型,`skill_name` 参数未实际使用 |\n| **B-08** | 🟡 中 | `_analyze_images()` 函数仅从 URL 文件名推理文案,实际图片内容未读取,且 LLM 超时仅 10 秒 |\n| **B-09** | 🟢 低 | 空输入返回 `error: \"empty_input\"` 但 HTTP 状态码为 200,前端无法通过 HTTP 状态码区分错误 |\n\n---\n\n### 2.4 素材管理 — `routers/materials.py` (6 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 19 | GET | `/api/materials` | API Key | `?type` | `Material[]` | 获取素材列表(全部/按类型筛选) |\n| 20 | POST | `/api/materials` | API Key | `{material_type, original_content/image_path/music_name, music_url?}` | `{id}` | 添加素材 |\n| 21 | PUT | `/api/materials/{material_id}` | API Key | `{material_type, original_content/image_path/music_name}` | `{success}` | 更新素材 |\n| 22 | DELETE | `/api/materials/{material_id}` | API Key | — | `{success}` | 删除素材 |\n| 23 | GET | `/api/materials/by-tags` | API Key | `?tags` (逗号分隔) | `Material[]` | 按标签搜索素材 |\n| 24 | GET | `/api/materials/top` | API Key | `?type&limit` | `Material[]` | 获取热门素材 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-10** | 🔴 高 | `POST /api/materials` 和 `PUT /api/materials/{id}` 无 Pydantic 验证模型 — 直接接收 `dict`,无字段校验、无类型检查、无必填验证 |\n| **B-11** | 🟡 中 | `PUT /api/materials/{id}` 中 `material_id` 声明为 `int` 但未做错误处理,非法 ID 会直接抛 FastAPI 异常 |\n| **B-12** | 🟢 低 | `GET /api/materials/by-tags` 要求 tags 为逗号分隔字符串,但前端 `materialApi.getMaterialsByTags` 传的是 `string[]`(axios 会转成 `?tags[]=a&tags[]=b`),**前后端参数格式不匹配** |\n\n---\n\n### 2.5 发布历史 — `routers/publish.py` (5 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 25 | GET | `/api/publish/history` | API Key | — | `PublishHistory[]` | 获取发布历史 |\n| 26 | PUT | `/api/publish/{publish_id}/data` | API Key | `{likes, comments, views}` | `{success}` | 更新发布数据(有 Pydantic 校验) |\n| 27 | GET | `/api/content/{post_id}` | API Key | — | `ContentDetail` (含日趋势) | 获取单条内容详情 |\n| 28 | GET | `/api/publish/{publish_id}` | API Key | — | `PublishDetail` | 获取单条发布详情 |\n| 29 | DELETE | `/api/publish/{publish_id}` | API Key | — | `{success}` | 删除发布记录 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-13** | 🟡 中 | 路径参数类型不一致 — `publish_id` 在 `PUT /api/publish/{publish_id}/data` 中为 `int`,但在 `GET /api/publish/{publish_id}` 中为 `str`,在 `DELETE /api/publish/{publish_id}` 中也为 `str` |\n| **B-14** | 🟡 中 | `GET /api/content/{post_id}` 和 `GET /api/publish/{publish_id}` 功能重叠 — 两者返回相同的 `content_posts` 表数据,但路径不同 (`/content/` vs `/publish/`) |\n| **B-15** | 🟢 低 | 前端 `publishApi.getPublish(id: number)` 传 number,但后端路径参数类型为 `str` — FastAPI 可自动转换,但语义不一致 |\n\n---\n\n### 2.6 数据分析 — `routers/analytics.py` (3 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 30 | GET | `/api/analytics/overview` | API Key | — | `PersonalDataAnalysis` | 数据分析总览 |\n| 31 | GET | `/api/analytics/traffic-trend` | API Key | `?content_id&limit` | `TrafficDailyItem[]` | 流量日趋势 |\n| 32 | GET | `/api/analytics/follower-trend` | API Key | `?content_id&limit` | `FollowerDailyItem[]` | 粉丝日趋势 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-16** | 🟢 低 | `content_id` 参数在路由函数签名中类型为 `int = None`,但前端可能传 `undefined`;FastAPI 会将 `None` 默认值正确处理,但类型注解不精确 |\n\n---\n\n### 2.7 抖音同步 — `routers/douyin.py` (5 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 33 | POST | `/api/douyin/login` | API Key | — | `{success: false}` | **[已禁用]** 触发浏览器登录 |\n| 34 | GET | `/api/douyin/status` | API Key | — | `{logged_in, cookie_count}` | 检查登录状态(从 `douyin_state.json`) |\n| 35 | POST | `/api/douyin/sync` | API Key | `{records: []}` | `{success, synced}` | 手动录入发布数据 |\n| 36 | POST | `/api/douyin/sync-auto` | API Key | — | `{success: false}` | **[已禁用]** 自动拉取数据 |\n| 37 | GET | `/api/douyin/sync-history` | API Key | — | `PublishHistory[]` | 获取同步历史 |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-17** | 🔴 高 | `POST /api/douyin/sync` 的 Pydantic 模型 `DouyinSyncRequest` 定义了 `records: List[dict]` 带 `min_items=1`,但路由函数签名使用 `data: dict = None` 而非 Pydantic 模型,**Pydantic 校验完全被绕过** |\n| **B-18** | 🟡 中 | `POST /api/douyin/login` 和 `POST /api/douyin/sync-auto` 已禁用但端点仍暴露,应返回 410 Gone 或直接删除 |\n| **B-19** | 🟢 低 | `GET /api/douyin/sync-history` 与 `GET /api/publish/history` 返回相同数据,属于冗余端点 |\n\n---\n\n### 2.8 图片上传 — `routers/upload.py` (1 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 38 | POST | `/api/upload` | API Key | `multipart/form-data` (file) | `{url, original_name, size}` | 上传图片到 `personal_materials/` |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-20** | 🟡 中 | 文件名清理 `\"\".join(c for c in safe_name if c.isalnum() or c in \"._-\")` 会删除中文字符,用户上传中文文件名会变成纯英文数字 |\n| **B-21** | 🟢 低 | 文件大小无限制,可能被上传大文件耗尽磁盘 |\n\n---\n\n### 2.9 统计 & Qdrant — `routers/stats.py` (5 端点)\n\n| # | 方法 | 路径 | 认证 | 请求体/参数 | 响应 | 说明 |\n|---|------|------|------|-------------|------|------|\n| 39 | GET | `/api/stats/daily` | API Key | — | `{date, stats, redis_available}` | 今日实时统计(Redis) |\n| 40 | GET | `/api/qdrant/hot-topics` | API Key | `?limit` | `{topics[], qdrant_available}` | 热门话题(Qdrant) |\n| 41 | GET | `/api/qdrant/similar-texts` | API Key | `?text&limit` | `{results[], count}` | 相似文案语义搜索 |\n| 42 | GET | `/api/qdrant/search-tags` | API Key | `?text&limit` | `{results[], count}` | 标签语义搜索 |\n| 43 | POST | `/api/qdrant/index-text` | API Key | `{text, quality_score, tags[], ...}` | `{success, indexed}` | 高质量文案写入 Qdrant |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **B-22** | 🟡 中 | `POST /api/qdrant/index-text` 无 Pydantic 验证,手动解析 JSON 且只有 `score < 4.0` 校验 |\n| **B-23** | 🟢 低 | `GET /api/qdrant/similar-texts` 和 `GET /api/qdrant/search-tags` 中 `text` 参数通过 query string 传递,长文案可能超出 URL 长度限制 |\n\n---\n\n## 三、前端 API 调用与数据流\n\n### 3.1 API Service 层 ([frontend/src/services/api.ts](frontend/src/services/api.ts))\n\n**axios 实例配置**:\n- `baseURL: '/api'`\n- `timeout: 180000` (3 分钟)\n- 请求拦截器:附加 `X-Request-Id` + `X-API-Key`\n- 响应拦截器:统一错误提示 (500→error, 400→warning, 网络断连→error)\n\n**前端 API 模块与实际后端路由映射**:\n\n| 前端模块 | 前端方法 | 实际请求路径 | 后端路由 |\n|----------|----------|-------------|----------|\n| `agentApi.run` | POST | `/api/agent/run` | `routers/agent.py` |\n| `agentApi.getStatus` | GET | `/api/agent/status/{id}` | `routers/agent.py` |\n| `agentApi.rollback` | POST | `/api/agent/rollback` | `routers/agent.py` |\n| `conversationApi.create` | POST | `/api/conversations` | `routers/conversations.py` |\n| `conversationApi.get` | GET | `/api/conversations/{id}` | `routers/conversations.py` |\n| `conversationApi.list` | GET | `/api/conversations` | `routers/conversations.py` |\n| `conversationApi.delete` | DELETE | `/api/conversations/{id}` | `routers/conversations.py` |\n| `conversationApi.batchDelete` | POST | `/api/conversations/batch-delete` | `routers/conversations.py` |\n| `conversationApi.updateTitle` | PUT | `/api/conversations/{id}` | `routers/conversations.py` |\n| `conversationApi.addMessage` | POST | `/api/conversations/messages` | `routers/conversations.py` |\n| `conversationApi.getHistory` | GET | `/api/conversations/{id}/messages` | `routers/conversations.py` |\n| `conversationApi.getContext` | GET | `/api/conversations/{id}/context` | `routers/conversations.py` |\n| `conversationApi.getStats` | GET | `/api/conversations/{id}/stats` | `routers/conversations.py` |\n| `conversationApi.search` | GET | `/api/conversations/search` | `routers/conversations.py` |\n| `materialApi.getMaterials` | GET | `/api/materials` | `routers/materials.py` |\n| `materialApi.addMaterial` | POST | `/api/materials` | `routers/materials.py` |\n| `materialApi.updateMaterial` | PUT | `/api/materials/{id}` | `routers/materials.py` |\n| `materialApi.deleteMaterial` | DELETE | `/api/materials/{id}` | `routers/materials.py` |\n| `materialApi.getMaterialsByTags` | GET | `/api/materials/by-tags` | `routers/materials.py` |\n| `materialApi.getTopMaterials` | GET | `/api/materials/top` | `routers/materials.py` |\n| `publishApi.getHistory` | GET | `/api/publish/history` | `routers/publish.py` |\n| `publishApi.getPublish` | GET | `/api/publish/{id}` | `routers/publish.py` |\n| `publishApi.updatePublishData` | PUT | `/api/publish/{id}/data` | `routers/publish.py` |\n| `publishApi.delete` | DELETE | `/api/publish/{id}` | `routers/publish.py` |\n| `analyticsApi.getOverview` | GET | `/api/analytics/overview` | `routers/analytics.py` |\n| `analyticsApi.getTrafficTrend` | GET | `/api/analytics/traffic-trend` | `routers/analytics.py` |\n| `analyticsApi.getFollowerTrend` | GET | `/api/analytics/follower-trend` | `routers/analytics.py` |\n| `analyticsApi.getContentDetail` | GET | `/api/content/{post_id}` | `routers/publish.py` |\n| `douyinSyncApi.sync` | POST | `/api/douyin/sync` | `routers/douyin.py` |\n| `douyinSyncApi.healthCheck` | GET | `/api/health` | `routers/health.py` |\n| `douyinSyncApi.getSyncHistory` | GET | `/api/douyin/sync-history` | `routers/douyin.py` |\n\n**审计发现**:\n\n| 问题编号 | 严重度 | 问题描述 |\n|----------|--------|----------|\n| **F-01** | 🔴 高 | **API Key 硬编码**:`api.ts:30` 中 `X-API-Key: 'your-api-key-here'`,这是一个占位符值,不会被后端验证通过(除非后端恰好也配置了这个值),导致所有 API 调用 401 |\n| **F-02** | 🔴 高 | **rollback 请求体字段名不匹配**:前端 `agentApi.rollback` 发送 `{session_id, target_skill}`,但后端 `POST /api/agent/rollback` 期望 `{conversation_id, skill}` — **完全不匹配** |\n| **F-03** | 🟡 中 | **Mock 模式依赖 localStorage**:每次调用 `isMockMode()` 读 localStorage,在 Settings 页切换后需刷新才能影响已在运行的请求 |\n| **F-04** | 🟡 中 | **materialApi.getMaterialsByTags** 传 `string[]` 给 `params: { tags }`,axios 默认序列化为 `?tags[]=a&tags[]=b`,但后端期望 `?tags=a,b`(逗号分隔字符串) |\n| **F-05** | 🟡 中 | **analyticsApi.getContentDetail** 调用 `/api/content/{postId}` 归于 publish 路由模块,但定义在 analyticsApi 中,归类混乱 |\n| **F-06** | 🟡 中 | **douyinSyncApi.healthCheck** 调用 `/api/health`,与 douyin 同步无关,应归属独立 healthApi |\n| **F-07** | 🟡 中 | **前端未使用 `/api/upload` 端点** — 图片上传功能在前端 `Workspace.tsx` 使用 Ant Design Upload 组件,但图片直接通过 `FileReader` 转 base64 放在 `userInput.images` 中随 `/api/agent/run` 一起提交,绕过了独立上传端点 |\n| **F-08** | 🟢 低 | **WebSocket 连接到 `ws://localhost:8000`** 但后端 `server.py` 运行在 `:9000` 且无 WebSocket 端点定义 — WebSocket service 定义了完整的消息类型和重连逻辑,但**后端未实现 WebSocket 端点** |\n\n---\n\n### 3.2 State 管理 ([frontend/src/stores/appStore.ts](frontend/src/stores/appStore.ts))\n\n**Zustand Store 数据流**:\n\n```\nuseAppStore\n├── conversationId ← localStorage('current_conversation_id')\n├── messages[] ← localStorage('msg_cache_{convId}')\n├── inputText ← localStorage('workspace_draft')\n├── executionStages[]\n├── executionResult\n├── userInput {text, tags[], images[], music[], enable_blackbox}\n├── materials[]\n├── publishHistory[]\n└── analytics\n```\n\n**持久化策略**:\n| 数据 | 存储位置 | 生命周期 |\n|------|----------|----------|\n| conversationId | localStorage | 永久 |\n| messages (缓存) | localStorage (`msg_cache_{id}`) | 永久 (最多 50 条) |\n| inputText (草稿) | localStorage (`workspace_draft`) | 永久 |\n| conversation 数据 | SQLite (via API) | 永久 |\n| session 摘要 | Redis (via API) | 30 天 TTL |\n| executionStages/Result | Zustand (内存) | 会话级别 |\n\n---\n\n### 3.3 页面级数据流详情\n\n**Workspace 页(核心页面)**:\n```\n用户输入文本\n ↓\nagentApi.run({text, tags, images, music, conversation_id})\n ↓\n后端 LangGraph 14节点工作流\n ↓\n返回 {creator_content, agent_suggestions, evaluation, execution_log, session_state}\n ↓\n前端解析为 ChatMessageData 展示\n ↓\n保存到 messages[] + localStorage 缓存\n```\n\n**MaterialLibrary 页**:\n```\nmaterialApi.getMaterials(type?) → 展示素材列表\nmaterialApi.deleteMaterial(id) → 删除素材\nmaterialApi.addMaterial(data) → 添加素材\n```\n\n**PublishHistory 页**:\n```\npublishApi.getHistory() → 展示发布历史列表\npublishApi.getPublish(id) → 查看单条详情\nanalyticsApi.getContentDetail(id)→ 查看含日趋势的详情\npublishApi.delete(id) → 删除记录\n```\n\n**Analytics 页**:\n```\nanalyticsApi.getOverview() → 数据总览\nanalyticsApi.getTrafficTrend() → 流量趋势图\nanalyticsApi.getFollowerTrend() → 粉丝趋势图\n```\n\n**DouyinSync 页**:\n```\ndouyinSyncApi.sync(records) → 手动录入数据\ndouyinSyncApi.getSyncHistory() → 查看同步历史\ndouyinSyncApi.healthCheck() → 检查后端健康\n```\n\n---\n\n## 四、LangGraph 工作流数据流\n\n### 4.1 State 定义 ([backend/graph/state.py](backend/graph/state.py))\n\n`AgentState` 包含 26 个字段,覆盖全流程:\n\n```\n输入层: user_input, conversation_id, enable_blackbox\n意图层: intent (topic|create|optimize|modify|question)\n上下文: session, has_context, prev_copy_text, prev_evaluation,\n prev_tags, prev_images, prev_music\n文案层: copy_text\n组件层: need_tags, need_images, need_music, modify_flags\n情绪层: emotion {mood, intensity, energy, keywords}\nSkill层: final_tags, final_images, final_music\n评估层: evaluation, iteration, best_score, best_evaluation,\n best_final_*, loop_history\n对话层: dialogue_messages, should_enter_workflow, workflow_intent_override\n输出层: result, execution_log, error\n```\n\n### 4.2 节点数据流图\n\n```\n ┌──────────────┐\n │ initialize │ ← DB1(Redis摘要) + DB2(SQLite消息)\n └──────┬───────┘\n │\n ┌──────▼───────┐\n │classify_intent│ ← LLM 五分类\n └──────┬───────┘\n │\n ┌─────────┬───────────┼───────────┬──────────┐\n ▼ ▼ ▼ ▼ ▼\n generate_copy optimize_copy parse_modify agent_dialogue analyze_emotion\n (topic→文案) (optimize→改写) (modify→标记) (ReAct循环) (已create→情绪)\n │ │ │ │ │\n └────┬────┘ │ ┌────┴────┐ │\n │ │ │ END │ │\n ▼ ▼ │ 管线入口 │ │\n analyze_emotion◄──────┘ └─────────┘ │\n │ │\n ▼ │\n analyze_content ← Python: 判断需要哪些组件 │\n │ │\n ▼ │\n decide_skills ← Python: 决定执行哪些Skill │\n │ │\n ▼ │\n execute_all_skills ← ThreadPoolExecutor并行3 Skill │\n │ │\n ▼ │\n evaluate ← Skill4 综合评估 │\n │ │\n ▼ │\n check_threshold ← 评分≥4.0? iter≥3? │\n │ │ │\n ┌────▼──┐ ┌───▼──────────┐ │\n │ END │ │identify_weak │ ← 找最低分维度 │\n └────────┘ └───┬──────────┘ │\n │ │\n ┌────▼─────┐ │\n │rerun_skill│ ← 重跑最弱Skill │\n └────┬─────┘ │\n │ │\n ┌────▼──────┐ │\n │rerun_evaluate│ ← 重新评估 │\n └────┬──────┘ │\n │ │\n └──→ check_threshold (循环) │\n```\n\n### 4.3 条件边路由表\n\n| 条件边 | 源节点 | 条件 | 目标 |\n|--------|--------|------|------|\n| `route_by_intent` | classify_intent | `topic` | generate_copy |\n| | | `create` | analyze_emotion |\n| | | `optimize` + has_context | optimize_copy |\n| | | `modify` + has_context | parse_modify_intent |\n| | | `question` | agent_dialogue |\n| `route_after_modify` | parse_modify_intent | `change_copy=true` | optimize_copy |\n| | | `change_copy=false` | analyze_emotion |\n| `route_after_dialogue` | agent_dialogue | `should_enter_workflow` | analyze_emotion |\n| | | 否则 | END |\n| `route_by_score` | check_threshold | `score ≥ 4.0` | END |\n| | | `score < 4.0 AND iter < 3` | identify_weak |\n| | | `iter ≥ 3` | END |\n| `route_after_weak` | identify_weak | `error == \"no_dimensions\"` | END |\n| | | 否则 | rerun_skill |\n\n---\n\n## 五、数据持久化层\n\n### 5.1 三层存储架构\n\n```\n┌────────────────────────────────────────────────────┐\n│ Redis (Cache) │\n│ • 会话摘要 (DB1): summary:{conv_id} (TTL 30天) │\n│ • 每日统计: daily_stats │\n│ • Qdrant 代理缓存 │\n└────────────────────┬───────────────────────────────┘\n │\n┌────────────────────┴───────────────────────────────┐\n│ SQLite (memory.db) │\n│ 表: │\n│ • content_posts — 发布内容 │\n│ • traffic_daily — 流量日趋势 │\n│ • follower_daily — 粉丝日趋势 │\n│ • tags — 标签库 │\n│ • conversations — 对话会话 │\n│ • conversation_messages — 对话消息 (DB2) │\n│ • personal_material_library — 个人素材库 │\n└────────────────────┬───────────────────────────────┘\n │\n┌────────────────────┴───────────────────────────────┐\n│ Qdrant (Vector Store) │\n│ • public_texts — 公共文案向量 │\n│ • public_tags — 公共标签向量 │\n│ • topic_trends — 热门话题 │\n└────────────────────────────────────────────────────┘\n```\n\n### 5.2 双层记忆(DB1 + DB2)数据流\n\n```\n请求进入 →\n initialize 节点:\n ├── DB1 (Redis) 加载摘要 → prev_copy_text, prev_tags, prev_evaluation\n ├── DB2 (SQLite) 加载消息 → dialogue_messages (仅 agent_dialogue 用)\n └── 兜底: SQLite add_message 旧格式\n\n管线完成 / Agent对话结束 →\n builder.run_agent():\n ├── DB1 (Redis) 保存摘要 ← save_session_summary()\n └── DB2 (SQLite) 保存消息 ← save_dialogue_messages()\n\n agent_dialogue 节点:\n ├── DB1 更新摘要(合并已有 + 新偏好)\n └── DB2 全量保存 ReAct 消息(先清空 agent 消息 → 批量插入)\n```\n\n---\n\n## 六、关键问题汇总\n\n### 🔴 高危 (需立即修复)\n\n| 编号 | 位置 | 问题 |\n|------|------|------|\n| **F-01** | [api.ts:30](frontend/src/services/api.ts#L30) | API Key 硬编码为 `'your-api-key-here'`,所有请求 401 |\n| **F-02** | [api.ts:171-174](frontend/src/services/api.ts#L171-L174) | rollback 请求体字段名与后端不匹配 (`session_id` vs `conversation_id`) |\n| **B-10** | [materials.py:19-32](backend/routers/materials.py#L19-L32) | POST/PUT materials 无 Pydantic 验证 |\n| **B-17** | [douyin.py:61-71](backend/routers/douyin.py#L61-L71) | douyin sync Pydantic 模型定义但未使用 |\n\n### 🟡 中危 (建议近期修复)\n\n| 编号 | 位置 | 问题 |\n|------|------|------|\n| **F-04** | [api.ts:290-293](frontend/src/services/api.ts#L290-L293) | getMaterialsByTags 参数格式前后端不匹配 |\n| **F-08** | [websocket.ts:15](frontend/src/services/websocket.ts#L15) | WebSocket 连接到 8000 但后端无 WS 端点 |\n| **B-01** | [conversations.py:78-85](backend/routers/conversations.py#L78-L85) | session/save 无输入验证 |\n| **B-06** | [agent.py:129-151](backend/routers/agent.py#L129-L151) | status 端点数据源混乱 |\n| **B-07** | [agent.py:160-195](backend/routers/agent.py#L160-L195) | rollback 未使用 skill_name |\n| **B-12** | [materials.py:60-64](backend/routers/materials.py#L60-L64) | tags 参数格式与前端不兼容 |\n| **B-13** | [publish.py](backend/routers/publish.py) | publish_id 类型不一致 (int vs str) |\n| **B-14** | [publish.py](backend/routers/publish.py) | `/content/{id}` 与 `/publish/{id}` 功能重叠 |\n\n### 🟢 低危 (可择机处理)\n\n| 编号 | 位置 | 问题 |\n|------|------|------|\n| **B-03** | [conversations.py:150-175](backend/routers/conversations.py#L150-L175) | 批量删除失败静默忽略 |\n| **B-04** | [conversations.py:239-246](backend/routers/conversations.py#L239-L246) | 搜索仅限标题 |\n| **B-20** | [upload.py:25](backend/routers/upload.py#L25) | 中文文件名被删除 |\n| **B-21** | [upload.py:16](backend/routers/upload.py#L16) | 无文件大小限制 |\n| **B-23** | [stats.py:38-43](backend/routers/stats.py#L38-L43) | 长文本通过 query string 传递 |\n| **F-06** | [api.ts:367-384](frontend/src/services/api.ts#L367-L384) | healthCheck 归错 API 模块 |\n\n---\n\n## 七、数据流一致性检查\n\n### 7.1 请求 → 响应链路完整性\n\n| 功能 | 前端调用 | 后端路由 | Graph 节点 | 持久化 | 状态 |\n|------|----------|----------|-----------|--------|------|\n| 智能创作 | Workspace → agentApi.run | POST /api/agent/run | 14节点完整链路 | DB1+DB2 | ✅ 完整 |\n| 对话管理 | Workspace → conversationApi.* | CRUD 14端点 | initialize (读) | SQLite | ✅ 完整 |\n| 素材浏览 | MaterialLibrary → materialApi.* | CRUD 6端点 | — | SQLite | ⚠️ 参数不匹配 |\n| 发布历史 | PublishHistory → publishApi.* | CRUD 5端点 | — | SQLite | ⚠️ 类型不一致 |\n| 数据分析 | Analytics → analyticsApi.* | 查询 3端点 | — | SQLite | ✅ 完整 |\n| 数据同步 | DouyinSync → douyinSyncApi.* | 同步 5端点 | — | SQLite | ⚠️ 校验被绕过 |\n| 图片上传 | — | POST /api/upload | — | 文件系统+SQLite | ⚠️ 前端未使用 |\n| 实时推送 | websocket.ts | — | — | — | ❌ 后端未实现 |\n\n### 7.2 前后端字段名对照\n\n| 数据流 | 前端字段名 | 后端期望字段名 | 匹配 |\n|--------|-----------|---------------|------|\n| agent/run 请求 | `text, tags, images, music, enable_blackbox` | `text, tags, images, music, conversation_id` | ✅ (enable_blackbox 被后端忽略) |\n| agent/run 响应 | `ExecutionResult` | `{creator_content, agent_suggestions, evaluation, execution_log, session_state}` | ✅ |\n| agent/rollback 请求 | `session_id, target_skill` | `conversation_id, skill` | ❌ 不匹配 |\n| conversations/create 请求 | `title, user_id` | `CreateConversationRequest(title, user_id)` | ✅ |\n| materials 请求 | `{material_type, original_content, ...}` | 直接取 dict key | ⚠️ 无验证 |\n| douyin/sync 请求 | `{records: DouyinSyncRecord[]}` | `{records: List[dict]}` (Pydantic 未使用) | ⚠️ 无验证 |\n\n### 7.3 Vite 代理配置检查\n\n| 代理前缀 | 目标 | 状态 |\n|----------|------|------|\n| `/api` | `http://localhost:9000` | ✅ 正常 |\n| `/personal` | `http://localhost:9000` | ✅ 正常 (静态文件) |\n| `/public` | `http://localhost:9000` | ✅ 正常 (静态文件) |\n| `/ws` | `ws://localhost:8000` | ❌ 后端无 WS (server.py 运行在 9000) |\n\n---\n\n## 八、安全审计\n\n### 8.1 API Key 认证\n\n- ✅ 中间件正确豁免 `/api/health`, `/docs`, `/openapi.json`, `/redoc`\n- ✅ 支持 `Authorization: Bearer <key>` 和 `X-API-Key: <key>` 两种方式\n- ✅ 静态文件路径 `/personal/*`, `/public/*` 免认证\n- ❌ 前端硬编码 API Key (F-01)\n- ⚠️ API Key 从环境变量 `DEEPSEEK_API_KEY` 获取 (命名存在误导 — 实际复用 DeepSeek 的 Key 作为 API Key)\n\n### 8.2 CORS\n\n- ✅ 允许 localhost:3000, localhost:5173 (开发环境)\n- ✅ 限制 Methods: GET, POST, PUT, DELETE\n- ✅ 限制 Headers: Content-Type, Authorization, X-Request-Id\n- ⚠️ 生产环境需更新 `CORS_ORIGINS` 环境变量\n\n### 8.3 输入校验\n\n- ✅ `conversations.py` 使用了 Pydantic 模型\n- ✅ `publish.py` 的 `PublishDataUpdate` 使用了 Pydantic 模型\n- ⚠️ `materials.py` 无 Pydantic 验证\n- ⚠️ `douyin.py` 定义了模型但路由函数未使用\n- ⚠️ `agent.py` 手动解析 JSON + GBK 回退\n\n---\n\n## 九、建议修复优先级\n\n### P0 (立即)\n1. 修复 API Key 硬编码 (F-01)\n2. 修复 rollback 字段名不匹配 (F-02)\n\n### P1 (本周)\n3. 为 materials 路由添加 Pydantic 验证 (B-10)\n4. 修复 douyin sync Pydantic 校验绕过 (B-17)\n5. 统一 materials/by-tags 参数格式 (F-04, B-12)\n6. 统一 publish_id 类型 (B-13)\n\n### P2 (本月)\n7. 实现或移除 WebSocket 端点 (F-08)\n8. 修复 status 端点数据源混乱 (B-06)\n9. 清理已禁用端点 (B-18)\n10. 添加文件上传大小限制 (B-21)\n\n---\n\n*报告由全栈接口审计自动生成 · 2026-07-10*\n"}}}}