Skip to content

tokens 聊天助手移植到 xhs-miniapp-saas 设计文档

  • 日期:2026-08-01
  • 分支:feat/chat-assistant
  • 状态:设计已确认,待实施

1. 背景与目标

1.1 背景

tokens/ 目录是一个独立的 Node.js/Express 聊天客服系统(NearLight 智子A),功能包括:

  • WebSocket 实时私聊(普通用户 ↔ admin 客服)
  • AI 自动流式回复(智谱 GLM / Kimi,原生 web_search 联网搜索)
  • 媒体上传分析(Kimi 多模态视觉 + ffmpeg 视频抽帧)
  • 知识库(用户级 knowledge_docs + 全局 global_knowledge_docs
  • 记忆库(自动分析对话生成记忆切片)
  • RSA-OAEP + AES-256-GCM 端到端传输加密
  • 独立的 JWT RS256 认证 + HttpOnly Cookie + token_version 失效 + WS 票据

技术栈:Express + sql.js (SQLite) + ws + 原生 HTML/CSS/JS 前端。

1.2 目标

将 tokens 全量功能移植到当前 xhs-miniapp-saas 项目,以现有业务为主:

  • 后端:用 Python/FastAPI 全部重写 tokens 的后端逻辑,合并到现有 backend(/api/admin 同一 FastAPI 实例),数据库从 SQLite 迁移到 PostgreSQL(复用现有连接池 + RLS)
  • 前端:用 React + antd 重写聊天页,作为 web 端新首页(/),现有 Dashboard 降级到 /dashboard
  • 用户模型:merchant ↔ AI 单角色对话(merchant 登录后直接与 AI 助手对话),admin 在后台查看/管理所有会话、知识库、记忆库
  • 认证:复用现有 merchant JWT(access token 内存存储)+ WS 一次性 ticket
  • 安全:HTTPS + HMAC-SHA256 请求签名(现有),不保留 RSA+AES 传输加密层
  • AI:复用现有 llm_function_configs 按功能配置,新增 chat 相关 function_key
  • 推进:垂直切片渐进式,每个切片端到端可验证,本地测试通过后再考虑上线

1.3 非目标

  • 不移植 tokens 的独立用户注册体系(用户名/手机号注册),改用现有 merchant 认证
  • 不保留 tokens 的 RSA+AES 端到端加密(用 HTTPS+HMAC 已满足安全约束)
  • 不移植 DuckDuckGo 搜索(国内不可用),联网搜索统一走模型原生 web_search
  • 不移植 tokens 独立部署(PM2/Nginx 3001 端口),合并到现有部署架构
  • 不做 admin 人工介入会话(merchant 优先与 AI 对话,admin 只查看/管理)

2. 架构总览

┌─────────────────────────────────────────────────────────────┐
│  web 端(React + Vite + antd)                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │ 聊天首页 /    │  │ Dashboard    │  │ 文章/封面等   │       │
│  │ (ChatPage)   │  │ /dashboard    │  │ /articles...  │       │
│  └──────┬───────┘  └──────────────┘  └──────────────┘       │
│         │ useChat hook (WS) + signature.ts (HMAC)            │
└─────────┼─────────────────────────────────────────────────────┘
          │ wss + ticket
┌─────────┼─────────────────────────────────────────────────────┐
│  backend(FastAPI,同一实例 127.0.0.1:8000)                  │
│  ┌──────▼───────┐  ┌──────────────┐  ┌──────────────┐        │
│  │ /api/chat/*  │  │ /api/admin/  │  │ /api/article │        │
│  │ WS + REST    │  │ chat/*       │  │ 等现有业务    │        │
│  │ (新增)       │  │ (新增)       │  │              │        │
│  └──────┬───────┘  └──────┬───────┘  └──────────────┘        │
│         │                 │                                   │
│  ┌──────▼─────────────────▼───────────────────────────┐       │
│  │  LLMClientProxy(复用,新增 function_key)          │       │
│  │  chat_reply / chat_memory / chat_media              │       │
│  └────────────────────────────────────────────────────┘       │
└─────────┬─────────────────────────────────────────────────────┘
          │ asyncpg
┌─────────▼─────────────────────────────────────────────────────┐
│  PostgreSQL(现有)                                          │
│  现有表 + 新增:chat_conversations / chat_messages /          │
│  global_knowledge_docs / chat_memory_docs(带 RLS)           │
└──────────────────────────────────────────────────────────────┘

3. 数据库设计

3.1 新增表(PostgreSQL,追加到 backend/app/db/schema.sql

所有带 merchant_id 的表启用 RLS,策略 tenant_isolationcurrent_setting('app.current_merchant_id', true) 隔离。

sql
-- 聊天会话表:每个 merchant 一个与 AI 的会话
CREATE TABLE IF NOT EXISTS chat_conversations (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  merchant_id  UUID NOT NULL REFERENCES merchants(id) ON DELETE CASCADE,
  last_msg_at TIMESTAMPTZ,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (merchant_id)  -- 每个 merchant 仅一个会话
);
CREATE INDEX IF NOT EXISTS idx_chat_conversations_merchant ON chat_conversations(merchant_id);
ALTER TABLE chat_conversations ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON chat_conversations
  USING (merchant_id::text = current_setting('app.current_merchant_id', true))
  WITH CHECK (merchant_id::text = current_setting('app.current_merchant_id', true));

-- 聊天消息表
CREATE TABLE IF NOT EXISTS chat_messages (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  conversation_id UUID NOT NULL REFERENCES chat_conversations(id) ON DELETE CASCADE,
  merchant_id     UUID NOT NULL REFERENCES merchants(id) ON DELETE CASCADE,
  sender_role     TEXT NOT NULL CHECK (sender_role IN ('merchant', 'ai', 'system')),
  content         TEXT NOT NULL,
  media_url       TEXT,
  media_type      TEXT,
  prompt          TEXT,
  is_edited       BOOLEAN NOT NULL DEFAULT false,
  source_doc_ids  TEXT,
  web_searched    BOOLEAN NOT NULL DEFAULT false,
  search_sources  JSONB,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_chat_messages_conv ON chat_messages(conversation_id, created_at);
CREATE INDEX IF NOT EXISTS idx_chat_messages_merchant ON chat_messages(merchant_id, created_at);
ALTER TABLE chat_messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON chat_messages
  USING (merchant_id::text = current_setting('app.current_merchant_id', true))
  WITH CHECK (merchant_id::text = current_setting('app.current_merchant_id', true));

-- 全局知识库(平台共享,无 RLS,所有 merchant 的 AI 共用)
CREATE TABLE IF NOT EXISTS global_knowledge_docs (
  id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title      TEXT NOT NULL,
  content    TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 记忆库(按 merchant 隔离)
CREATE TABLE IF NOT EXISTS chat_memory_docs (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  merchant_id           UUID NOT NULL REFERENCES merchants(id) ON DELETE CASCADE,
  title                 TEXT NOT NULL,
  content               TEXT NOT NULL,
  source_msg_count      INTEGER NOT NULL DEFAULT 0,
  last_analyzed_msg_id  TEXT,
  created_at            TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_chat_memory_docs_merchant ON chat_memory_docs(merchant_id, created_at);
ALTER TABLE chat_memory_docs ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON chat_memory_docs
  USING (merchant_id::text = current_setting('app.current_merchant_id', true))
  WITH CHECK (merchant_id::text = current_setting('app.current_merchant_id', true));

3.2 复用现有表

  • merchants / merchant_profiles:租户主表 + 公司信息
  • merchant_knowledge_chunks:用户级知识库(已有 + pgvector embedding),作为聊天上下文注入
  • llm_function_configs:AI 模型功能级配置(新增 function_key)
  • cover_ai_configs:封面 AI 配置(multimodal_kimi 复用于媒体分析)

3.3 RLS 注意事项

  • global_knowledge_docs 不启用 RLS(平台共享),读取时不注入 merchant_id
  • admin 接口用 AdminDb(无 RLS),可跨租户查看所有会话
  • 会话/消息/记忆表写入时由后端自动填充 merchant_id(从 JWT 解析),不信任前端传入

4. 后端 API 设计

4.1 业务端路由 /api/chat(merchant 鉴权 + HMAC 签名)

新增路由文件 backend/app/routers/chat.py

方法路径功能鉴权
WS/api/chat/ws?ticket=WebSocket 连接ticket(一次性,TTL 30s)
POST/api/chat/ws-ticket获取 WS ticketCurrentSecureUser
GET/api/chat/messages?before_id=&limit=历史消息(分页)CurrentSecureUser
POST/api/chat/upload媒体上传(图片/视频)CurrentSecureUser
GET/api/chat/suggested-questions引导问题CurrentSecureUser

WS ticket 机制:

  1. merchant 通过 POST /api/chat/ws-ticket(带 JWT + HMAC 签名)获取一次性 ticket
  2. ticket 存内存(TTL 30s,绑定 IP),含 merchant_id(当前项目 JWT 无 token_version 机制,ticket 只需绑定 merchant 身份)
  3. WS 连接时 ?ticket=xxx 认证,消费后失效
  4. 复用现有 JWT 校验 merchant 身份

4.2 Admin 路由 /api/admin/chat(admin 鉴权 + 内网隔离)

合并到现有 backend/app/routers/admin.py,新增 chat 子模块:

方法路径功能
GET/api/admin/chat/conversations所有会话列表(含 merchant 信息、最后消息、消息数)
GET/api/admin/chat/conversations/{merchant_id}/messages某 merchant 的消息记录
POST/api/admin/chat/conversations/{merchant_id}/clear清空会话消息 + AI 记忆(保留账号与知识库)
GET/api/admin/chat/global-knowledge/docs全局知识库列表
POST/api/admin/chat/global-knowledge/docs新建全局知识文档
PUT/api/admin/chat/global-knowledge/docs/{id}更新
DELETE/api/admin/chat/global-knowledge/docs/{id}删除
POST/api/admin/chat/global-knowledge/upload上传文件 + AI 智能切片
GET/api/admin/chat/memory/{merchant_id}/docs某 merchant 记忆文档
DELETE/api/admin/chat/memory/docs/{id}删除记忆文档

4.3 文件组织

backend/app/
├── routers/
│   ├── chat.py              # 新增:/api/chat 业务路由
│   └── admin.py             # 修改:追加 /api/admin/chat 子路由
├── services/
│   ├── chat_service.py      # 新增:会话/消息/记忆 CRUD
│   ├── chat_ws.py           # 新增:WebSocket 连接管理 + 消息分发
│   └── chat_ai.py           # 新增:AI 流式回复 + 搜索 + 记忆分析
├── models/
│   └── chat.py              # 新增:Pydantic 模型
└── db/
    └── schema.sql           # 修改:追加 4 张表

5. WebSocket 协议

5.1 客户端 → 服务端

type字段说明
dm{type, content, mediaUrl?, mediaType?}merchant 发送消息(无需 to,固定发给 AI)
request-welcome{type}请求欢迎语(首次进入)
ping{type}心跳

5.2 服务端 → 客户端

type字段说明
dm{id, senderRole, content, mediaUrl, mediaType, timestamp, aiReplied?, webSearched?, searchSources?}消息
system{content}系统消息
welcome{content, timestamp}欢迎语(不持久化)
error{content}错误(含 "sign in" 时前端登出)
pong-心跳响应
ai-reply-start-AI 开始处理
ai-reply-status{message}状态更新("正在搜索...""正在分析图片...")
ai-reply-chunk{chunk}流式文本块
ai-reply-done{id, content, webSearched, searchSources}AI 回复完成
ai-reply-error{content}生成失败

5.3 连接管理

  • 单 merchant 单连接(新连接踢旧连接,close 4002)
  • 心跳:30s ping/pong,超时 terminate
  • 限流:单连接 60 msg/min,单用户 60 msg/min(二级限流)
  • 消息大小限制:4096 字节(媒体走 HTTP 上传,WS 只传 URL)
  • 优雅关闭:服务端 shutdown 时关闭所有连接

6. AI 集成设计

6.1 复用 LLMClientProxy

新增 function_key 到 llm_function_configs

function_key用途默认模型说明
chat_reply主聊天流式回复glm-4.6 或 kimi-k2智谱用原生 web_search 工具,Kimi 用 $web_search
chat_memory对话记忆分析glm-4.6-air 或 deepseek-chat非流式,分析对话生成记忆
chat_media媒体分析复用 multimodal_kimi不单独建配置,直接复用

6.2 流式回复流程(chat_ai.py

merchant 发消息

chat_ws 接收 → 存 chat_messages → 推送 dm 给 merchant

触发 AI 自动回复(异步):
  1. ai-reply-start → 推送
  2. 若有媒体: ai-reply-status "正在分析图片..." → 调 chat_media(Kimi 多模态) → 得 mediaContext
  3. 智谱/Kimi 原生 web_search: ai-reply-status "已启用联网搜索..."
  4. chat_reply 流式生成:
     - 构建 system prompt(品牌、知识库上下文、全局知识库上下文、记忆上下文、最近对话)
     - 注入 mediaContext
     - 逐块 ai-reply-chunk 推送(30ms 节流)
     - 完成后存 chat_messages(sender_role=ai) → ai-reply-done 推送
     - saveAiReply 记录
  5. 断线时 analyzeAndStoreMemory 分析对话生成记忆

6.3 System Prompt 模板

基于 tokens ai.jsbuildMessages,调整:

  • 品牌改为与当前项目一致(智米口袋 / 小红书 SaaS 场景)
  • 注入:merchant_profiles(公司信息)、merchant_knowledge_chunks(用户知识库)、global_knowledge_docs(全局知识库)、chat_memory_docs(记忆)、最近 20 条对话
  • 保留 <followup-questions>["问题1","问题2"]</followup-questions> 追问标签机制(前端解析渲染引导按钮)
  • 输出格式:纯文本,禁止 Markdown/HTML

6.4 联网搜索

  • 智谱 GLM:原生 web_search 工具(search_result: true),模型自主决定
  • Kimi:$web_search 内置工具(builtin_function
  • 不移植 DuckDuckGo 和 searchLinks 抓取逻辑
  • 不移植 decideSearchPlan(改用模型原生搜索,简化流程)

6.5 媒体分析

  • 复用现有 backend/app/services/upload_service.py(图片 base64 / 视频)
  • 图片:读取为 base64 data URI,调 Kimi 多模态
  • 视频:用 ffmpeg 抽帧(每 5s 一帧,最多 4 帧,缩放 512px),转 data URI 数组
  • 服务器需预装 ffmpeg(与 tokens 一致)
  • 复用 multimodal_kimi 功能配置(cover_vision/cover_text 已回退到此)

7. 前端设计

7.1 路由调整(web/src/router/index.tsx

/            → ChatPage(新首页,聊天)
/dashboard   → Dashboard(原首页降级)
/articles    → ArticleList(不变)
...其他不变

侧边栏导航新增"AI 助手"项(首页),Dashboard 降为子项。

7.2 聊天页组件(web/src/pages/Chat/

web/src/pages/Chat/
├── index.tsx              # 主页面:消息列表 + 输入区 + 引导卡片
├── MessageList.tsx        # 消息列表(merchant/ai 气泡 + 媒体 + 流式)
├── MessageInput.tsx      # 输入框 + 媒体上传按钮 + 发送
├── WelcomeCard.tsx        # 欢迎语 + 引导问题按钮
└── chat.types.ts          # 类型定义

7.3 Hooks 与状态

web/src/hooks/
├── useChat.ts             # 新增:WS 连接管理 + 消息收发 + 重连
└── useMessage.ts          # 复用:消息通知

web/src/stores/
└── chat.ts                # 新增:Zustand store(消息列表、连接状态、AI 状态)

7.4 API client(web/src/api/chat.ts

- getWsTicket()        → POST /api/chat/ws-ticket
- getMessages(beforeId)→ GET /api/chat/messages
- uploadMedia(file)    → POST /api/chat/upload
- getSuggestedQs()     → GET /api/chat/suggested-questions

WS 连接:复用 signature.ts 不适用(WS 不走 HMAC),用 ticket 认证。

7.5 设计语言

遵循项目"智米口袋"设计语言(project_memory 约定):

  • 浅色主题、Fraunces 字体
  • 多语义色:珊瑚红(brand)/ 海军蓝(info)/ 翡翠绿(success)/ 琥珀金(warning)/ 紫罗兰(creative/AI)
  • AI 消息气泡用紫罗兰(creative)色调区分
  • 响应式:移动端汉堡菜单 + 侧边栏抽屉

7.6 Admin 后台新增

admin/src/pages/
├── ChatSessions.tsx      # 新增:会话管理(表格 + 详情抽屉)
├── GlobalKnowledge.tsx    # 新增:全局知识库 CRUD + 上传
└── MemoryDocs.tsx         # 新增:记忆库管理

侧边栏新增 3 个导航项。会话管理可点击进入某 merchant 的消息记录详情。

8. 安全设计

8.1 复用现有安全机制

  • JWT HS256 + access token(2h,内存存储)+ refresh token(7d,HttpOnly Cookie)
  • HMAC-SHA256 请求签名(WebCrypto API,signature.ts
  • 限流:login/register/refresh/API 现有限流 + WS 二级限流
  • 内网隔离:admin 接口 AdminIntranetMiddleware + Nginx IP 白名单

8.2 WS 安全

  • WS ticket:一次性,TTL 30s,绑定 IP,含 merchant_id(获取 ticket 时已校验 JWT)
  • wss://(HTTPS 升级)
  • 消息大小限制 4096 字节
  • 不保留 RSA+AES(用 wss + ticket 已足够)

8.3 媒体上传安全

  • 复用现有 upload 安全:magic number 校验、大小限制、存储守卫
  • 媒体 URL 必须以 /uploads/ 开头(防路径遍历)

8.4 知识库内容保护

  • 全局知识库正文不通过 API 泄露给 merchant(只注入 AI prompt)
  • /api/chat/suggested-questions 只返回通用问题,不暴露知识库标题

9. 垂直切片实施计划

每个切片完成后本地测试,全部通过后再考虑上线。

切片 1:数据库 + 基础聊天

后端:

  • schema.sql 追加 4 张表 + RLS
  • models/chat.py:Pydantic 模型
  • services/chat_service.py:会话获取/创建、消息存取
  • services/chat_ws.py:WS 连接管理 + ticket + 基础消息广播(无 AI)
  • routers/chat.py:WS 端点 + ws-ticket + messages 端点
  • main.py 注册路由

前端:

  • pages/Chat/index.tsx:基础聊天 UI(消息列表 + 输入框)
  • hooks/useChat.ts:WS 连接 + 消息收发
  • stores/chat.ts:消息状态
  • api/chat.ts:REST 调用
  • 路由调整:/ → Chat,/dashboard → Dashboard

验证点: merchant 登录后进入首页,发消息能收到系统 echo,消息持久化。

切片 2:AI 流式自动回复

后端:

  • services/chat_ai.py:流式回复 + system prompt 构建
  • llm_function_configs 新增 chat_reply 配置
  • 复用 LLMClientProxy.chat_for_function 流式
  • <followup-questions> 标签机制
  • WS 协议:ai-reply-start/status/chunk/done/error

前端:

  • 流式 AI 回复气泡渲染(临时气泡 → 正式消息)
  • 引导问题按钮组渲染
  • AI 状态提示

验证点: merchant 发消息,AI 流式回复,追问按钮可点击。

切片 3:媒体上传分析

后端:

  • POST /api/chat/upload:图片/视频上传
  • chat_ai.py 接入媒体分析:调 multimodal_kimi 分析图片/视频
  • ffmpeg 视频抽帧
  • ai-reply-status "正在分析图片..."

前端:

  • 媒体上传按钮 + 预览
  • 媒体消息渲染(图片/视频)

验证点: 发图片,AI 基于图片内容回复。

切片 4:知识库

后端:

  • system prompt 注入 merchant_knowledge_chunks(用户级)+ global_knowledge_docs(全局)
  • /api/chat/suggested-questions:基于全局知识库标题生成引导问题
  • GET/POST/PUT/DELETE /api/admin/chat/global-knowledge/*
  • 全局知识库上传 + AI 切片

前端:

  • 欢迎卡片 + 引导问题
  • admin 全局知识库管理页

验证点: AI 回复包含知识库内容;admin 可管理全局知识库。

切片 5:记忆库

后端:

  • chat_ai.py 新增 analyzeAndStoreMemory:merchant 断线时分析对话生成记忆
  • chat_memory_docs CRUD
  • system prompt 注入记忆上下文
  • GET/DELETE /api/admin/chat/memory/*

前端:

  • admin 记忆库管理页

验证点: 断线重连后 AI 记得之前对话要点;admin 可查看/删除记忆。

切片 6:Admin 后台整合

后端:

  • GET /api/admin/chat/conversations 会话列表
  • GET .../conversations/{merchant_id}/messages 消息记录
  • POST .../conversations/{merchant_id}/clear 清空

前端(admin):

  • ChatSessions.tsx:会话管理
  • GlobalKnowledge.tsx:全局知识库
  • MemoryDocs.tsx:记忆库
  • 侧边栏新增导航

验证点: admin 可查看所有 merchant 会话、管理知识库/记忆库。

10. 部署考虑(上线前)

  • Nginx 配置 WS 升级(Upgrade / Connection 头 + proxy_read_timeout
  • 服务器预装 ffmpeg
  • llm_function_configs 初始化 chat_reply / chat_memory 配置
  • 全局知识库初始内容导入
  • 欢迎语模板配置
  • 限流参数调优(WS 连接数、消息频率)
  • 上线前在本地完整测试所有切片

11. 风险与缓解

风险缓解
FastAPI WebSocket 并发性能asyncpg 连接池 + 异步处理,单进程足够;必要时加 uvicorn workers
AI 流式回复超时30s 超时 + 前端重连 + 错误提示
记忆分析耗时异步执行,不阻塞 WS
ffmpeg 未安装媒体分析降级为仅文字回复 + 通知
全局知识库泄露API 不返回正文,只注入 prompt
WS 连接泄漏心跳 + 超时 terminate + 优雅关闭

基于 VitePress 构建 · 由 GitHub Actions 自动部署