1 系统总览 三位一体
Next.js 16 + Vite SPA
端口 3210
OIDC 协议接入
Better Auth 集成
文件上传/预览/RAG
端口 9000/9001
LobeChat 是一个 全栈 AI 对话平台,采用 monorepo 架构(62 个包),支持 Web/桌面/移动端三端部署。认证通过 Better Auth 框架 + Logto 作为 OIDC SSO 提供方;文件存储使用 S3 兼容协议,生产环境用 RustFS(Rust 实现的 MinIO 替代品)。
- 前端: React 19 + Vite SPA + React Router v7
- 后端: Next.js 16 App Router + tRPC v11.8
- 认证: Better Auth v1.4.6 (会话/密码/2FA/Passkey/OAuth)
- 状态: Zustand 5.0 + SWR + React Query
- 样式: antd-style (CSS-in-JS) + Ant Design 6 + @lobehub/ui v5.5
- 动画: Motion (Framer Motion) v12
- 校验: Zod v3 (tRPC 输入校验) + superjson (序列化)
- 数据库: Drizzle ORM v0.45 + PostgreSQL 17 (ParadeDB) + pgvector
- 缓存: Redis 7 (ioredis) — 会话 + 文件预签名 URL
- AI: 40+ LLM 供应商 + OpenAI SDK v4 + 多 Agent 编排
- 国际化: react-i18next v16, 20+ 语言
- 图片处理: Sharp v0.34 (服务端, 仅 AI 生图/视频用)
- 桌面端: Electron (desktop-bridge, electron-client-ipc)
- PWA: vite-plugin-pwa (离线缓存 + Service Worker)
- 可观测性: OpenTelemetry (分布式链路追踪)
- 安全: ssrf-safe-fetch (SSRF 防护) + RBAC 权限表 (已建未接入)
- 搜索: SearXNG (自托管元搜索引擎)
- Monorepo: pnpm workspace, 60 个包 (agent-runtime, model-runtime, 30+ builtin-tool, database, types, const...)
- 测试: Vitest v3 + Playwright (E2E)
- 构建: Vite v7 (SPA) + Next.js 16 (SSR) 双构建, bun 运行脚本
- 认证框架: Better Auth v1.4.6 (开源 Clerk 替代)
- Logto 接入: Generic OIDC Provider (PKCE 增强)
- SSO 11 种: Google, GitHub, Azure, Apple, Logto, Auth0, Keycloak, Okta, Casdoor, Authentik, Cloudflare ZT
- 登录方式: 邮箱密码 (bcrypt) / Magic Link (15min) / OTP (5min) / Passkey (WebAuthn) / OAuth 社交登录
- 会话管理: Cookie (30 天过期) + Redis 二级缓存 (10 分钟 TTL) + DB 兜底
- Webhook: Logto User.Data.Updated (同步头像/姓名) + User.SuspensionStatus.Updated (自动登出)
- 安全: CSRF 防护, Rate Limit (3 次/60 秒), JWKS (RS256), Webhook 签名验证 (HMAC-SHA256)
- 密码策略: 8-64 字符, bcrypt 哈希, Clerk 迁移兼容
- 双因素: TOTP + 备份码 (twoFactorEnabled)
- 邮箱验证: 可选, 验证链接 1 小时过期
- 白名单: AUTH_ALLOWED_EMAILS 限制注册域名/邮箱
- OIDC 服务: 可选启用 JWKS_KEY, LobeChat 自身作为 OIDC Provider
- DB 表: users, accounts, sessions, verifications, two_factor (Better Auth 管理)
- tRPC 中间件链: OpenTelemetry → oidcAuth → userAuth → serverDatabase → 业务逻辑
- 协议: AWS S3 兼容 (@aws-sdk/client-s3 v3)
- 生产后端: RustFS (Rust 重写 MinIO) / MinIO / AWS S3 / Cloudflare R2
- 上传流程: 预签名 PUT URL (1 小时) → 浏览器直传 S3 (不经服务器) → DB 记录
- 下载流程: /f/{fileId} 代理 → Redis 缓存预签名 GET URL (4 分钟 TTL, URL 5 分钟过期) → 302 重定向
- 去重: SHA256 哈希, globalFiles 表 (多用户共享同一 S3 对象)
- 两表设计: files (用户级, 按 userId 隔离) + global_files (全局去重, hash_id 主键)
- 代理 URL: /f/{fileId} 统一入口, 对客户端隐藏 S3 细节
- ACL 模式: S3_SET_ACL=1 公开读 + PUBLIC_DOMAIN 直链 / =0 全部预签名 URL
- 路径样式: S3_ENABLE_PATH_STYLE=1 (MinIO/RustFS) / =0 (AWS 虚拟主机样式)
- 媒体缓存: uploadMedia() 自动设 Cache-Control: public, max-age=31536000 (1 年)
- 大小验证: 服务端通过 S3 HeadObject 验证实际文件大小 (防客户端伪造)
- RAG 管道: 文件上传 → 异步分块 (chunks) → 批量向量嵌入 (batch=50, concurrency=10) → pgvector 语义搜索
- Docker 初始化: rustfs-init 容器用 minio/mc 创建 bucket + 设置匿名读策略
- 配额控制: checkFileStorageUsage 中间件, FileModel.countUsage() 统计用量
2 整体架构 Architecture
2.1 Monorepo 结构
~/Projects/business/20260330-LobeChat公司AI平台/ ├── src/ ← 主应用源码 (Next.js + Vite SPA) │ ├── spa/ ← SPA 入口 (web/mobile/desktop) │ ├── app/ ← Next.js App Router (SSR + API) │ ├── store/ ← Zustand 状态管理 (29+ stores) │ ├── server/ ← tRPC 路由 + 服务层 + 模块 │ ├── libs/ ← Better Auth / tRPC / i18n 配置 │ ├── features/ ← 按领域组织的 UI 组件 │ ├── routes/ ← React Router 页面 │ ├── services/ ← 客户端服务层 │ ├── envs/ ← 环境变量定义 (14 个文件) │ └── config/ ← 应用配置 ├── packages/ ← 62 个 Monorepo 包 │ ├── database/ ← Drizzle ORM schemas + models + migrations │ ├── agent-runtime/ ← Agent 执行引擎 │ ├── model-runtime/ ← 40+ LLM 供应商适配器 │ ├── builtin-tool-*/ ← 30+ 内置工具 │ └── ... ← types, const, utils, config 等 ├── apps/desktop/ ← Electron 桌面应用 ├── docker-compose/ ← 部署编排 │ ├── deploy/ ← 生产部署 (LobeChat+PG+Redis+RustFS+SearXNG) │ └── dev/ ← 开发环境 (只有基础设施) ├── Dockerfile ← 多阶段构建 (343 行) ├── drizzle.config.ts ← 数据库迁移配置 └── package.json ← pnpm + bun, v2.1.47
2.2 请求生命周期
浏览器/客户端 │ ▼ React SPA (Vite 构建, React Router v7) │ ← Zustand Store 管理状态 ▼ tRPC Client (自动生成 React hooks) │ ← superjson 序列化 (Date/Map/Set) ▼ Next.js API Routes (/trpc, /api/*) │ ▼ tRPC Middleware 链 │ 1. OpenTelemetry (分布式追踪) │ 2. OIDC Auth (JWT/JWKS 验证, 可选) │ 3. Better Auth (Session Cookie 验证) │ 4. Server Database (DB 连接注入) │ 5. Key Vaults (密钥解密) ▼ tRPC Router Handler (47 个子路由) │ ← Zod 输入校验 ▼ Service Layer (业务逻辑) │ ├──→ Database Models (Drizzle ORM → PostgreSQL) ├──→ S3 Module (AWS SDK → RustFS/MinIO) ├──→ Redis (会话缓存, 文件 URL 缓存) └──→ LLM Runtime (40+ 供应商, 流式响应)
3 LobeChat 核心源码 LobeChat
3.1 入口文件
Web SPA 入口,导入 desktopRoutes 配置,支持开发代理模式 (/_dangerous_local_dev_proxy),BootErrorBoundary 错误边界包裹。
Electron 桌面入口,使用相同路由配置,无代理 basename。
移动端入口,切换到 mobileRoutes 配置。
3.2 路由系统
文件: src/spa/router/desktopRouter.config.tsx (579 行)
完整路由结构 (点击展开)
/ (主布局 - 持久侧边栏 + 主内容区) ├── /agent/:aid → 对话页面 │ ├── / → 聊天消息 │ ├── /profile → Agent 资料 │ ├── /cron/:cronId → 定时任务 │ └── /channel → 频道管理 ├── /group/:gid → 多 Agent 群聊 ├── /community → 发现市场 │ ├── /agent → 浏览 Agent │ ├── /model → 浏览模型 │ ├── /skill → 浏览技能 │ ├── /mcp → 浏览 MCP │ └── (detail)/:slug → 详情页 ├── /resource → 知识库 │ └── /library/:id/:slug → KB 详情 ├── /settings → 用户设置 │ ├── /profile → 个人资料 │ ├── /provider/:id → LLM 供应商配置 │ └── /:tab → 其他标签页 ├── /memory → 用户记忆系统 │ ├── /identities → 身份 │ ├── /contexts → 上下文 │ ├── /preferences → 偏好 │ ├── /experiences → 经验 │ └── /activities → 活动 ├── /video → 视频生成 ├── /image → 图片生成 ├── /eval → 评估基准 │ └── /bench/:id/runs/:id → 评测结果 ├── /page → 笔记/页面 │ └── /:id → 编辑页面 ├── /onboarding → 新手引导 (独立布局) └── /share/t/:id → 话题分享 (独立布局)
3.3 LLM / Agent 集成
每个 LLM 供应商一个目录 (openai/, anthropic/, azure/, ollama/, bedrock/ 等),实现统一的 ModelRuntime 接口。运行时通过 runtimeMap.ts 注册表动态路由。
核心组件:GeneralChatAgent (单 Agent), GroupOrchestrationRuntime (多 Agent 协调), GroupOrchestrationSupervisor (Agent 路由/委派), UsageCounter (Token 计费), InterventionChecker (人工审批)。
4 认证系统 & Logto 集成 Logto
4.1 认证架构概览
4.2 核心配置文件
src/libs/better-auth/define-config.ts (333 行) — 主认证配置
代理配置 (开发环境)
检测 NODE_ENV=development 时读取 HTTPS_PROXY/HTTP_PROXY,用 undici ProxyAgent 设置全局代理——因为 Node.js 原生 fetch 不走系统代理,开发时 OAuth Token Exchange 需要。
认证常量
| 常量 | 值 | 说明 |
|---|---|---|
VERIFICATION_LINK_EXPIRES_IN | 3600s (1h) | 邮箱验证链接过期 |
MAGIC_LINK_EXPIRES_IN | 900s (15min) | Magic Link 过期 |
OTP_EXPIRES_IN | 300s (5min) | 手机验证码过期 |
Passkey 配置
getPasskeyRpID() 从 APP_URL 安全提取 hostname 作为 WebAuthn Relying Party ID。未设置 APP_URL 时返回 undefined(兼容 E2E 测试)。
密码策略
8-64 字符,bcrypt 哈希,支持 Clerk 迁移(clerkMigration 钩子自动转换旧密码哈希)。
会话管理
session: {
cookieCache: { enabled: true, maxAge: 600 }, // 10 分钟 Cookie 缓存
expiresIn: 2592000, // 30 天过期
updateAge: 86400, // 24h 刷新
}
secondaryStorage: Redis // Redis 作为二级会话存储
钩子 (Hooks)
after createUser: 新用户注册后创建UserModel关联记录after linkAccount: OAuth 关联后同步头像/姓名到 LobeChat 用户表before deleteUser: 删除所有关联数据(消息/文件/设置)
速率限制
rateLimit: {
storage: 'secondary-storage', // Redis
customRules: {
'/sign-in/email': { max: 3, window: 60 },
'/sign-up/email': { max: 3, window: 60 },
'/forget-password': { max: 3, window: 60 },
'/magic-link/*': { max: 3, window: 60 },
'/email-otp/*': { max: 3, window: 60 },
}
}
src/libs/better-auth/sso/providers/logto.ts — Logto OIDC 接入
配置
{
providerId: 'logto',
type: 'oidc', // Generic OIDC
discoveryUrl: AUTH_LOGTO_ISSUER + '/.well-known/openid-configuration',
clientId: AUTH_LOGTO_ID,
clientSecret: AUTH_LOGTO_SECRET,
scopes: ['openid', 'profile', 'email', 'offline_access'],
pkce: true, // PKCE 安全增强
}
Profile Mapping
mapProfileToUser(profile) {
return {
email: profile.email,
name: profile.name || profile.username,
image: profile.picture, // Logto 头像字段
username: profile.username,
}
}
offline_access scope 获取 Refresh Token,实现长期会话。PKCE 防止授权码拦截攻击。
src/app/(backend)/api/webhooks/logto/route.ts — Logto Webhook
路径: POST /api/webhooks/logto
事件处理
| 事件 | 处理逻辑 |
|---|---|
User.Data.Updated | 同步用户资料:avatar / email / fullName → Better Auth users 表 |
User.SuspensionStatus.Updated | 用户被封禁时自动登出所有会话(revoke sessions) |
签名验证
// 使用 LOGTO_WEBHOOK_SIGNING_KEY 验证请求签名
const signature = headers['logto-signature-sha-256'];
const expected = crypto.createHmac('sha256', signingKey)
.update(rawBody)
.digest('hex');
if (signature !== expected) return 401;
4.3 全部 11 个 SSO 提供方
| # | 提供方 | 类型 | 环境变量 |
|---|---|---|---|
| 1 | OAuth 2.0 | AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET | |
| 2 | GitHub | OAuth 2.0 | AUTH_GITHUB_ID / AUTH_GITHUB_SECRET |
| 3 | Microsoft | OAuth 2.0 | AUTH_MICROSOFT_ENTRA_ID / SECRET / TENANT_ID |
| 4 | Apple | OAuth 2.0 | AUTH_APPLE_ID / SECRET / TEAM_ID / KEY_ID |
| 5 | Logto | OIDC Generic | AUTH_LOGTO_ID / ISSUER / SECRET |
| 6 | Auth0 | OIDC Generic | AUTH_AUTH0_ID / ISSUER / SECRET |
| 7 | Keycloak | OIDC Generic | AUTH_KEYCLOAK_ID / ISSUER / SECRET |
| 8 | Okta | OIDC Generic | AUTH_OKTA_ID / ISSUER / SECRET |
| 9 | Casdoor | OIDC Generic | AUTH_CASDOOR_ID / ISSUER / SECRET |
| 10 | Authentik | OIDC Generic | AUTH_AUTHENTIK_ID / ISSUER / SECRET |
| 11 | Cloudflare ZT | OIDC Generic | AUTH_CLOUDFLARE_ZERO_TRUST_* |
4.4 认证中间件链
tRPC Request │ ▼ createLambdaContext() │ 认证优先级: │ 1. X-API-Key header → 查 DB apiKeys 表 │ 2. Oidc-Auth header → JWKS 验证 (ENABLE_OIDC=true) │ 3. Cookie → Better Auth session 验证 ▼ oidcAuth middleware │ 如果有 OIDC token → 验证 JWT 签名 │ 注入 ctx.oidcAuth = { sub, payload } ▼ userAuth middleware │ 从 ctx 获取 userId │ 注入 ctx.userId ▼ serverDatabase middleware │ 创建 Drizzle DB 实例 │ 注入 ctx.serverDB ▼ Router Handler │ ctx.userId + ctx.serverDB 可用 │ 创建 Model 实例处理业务逻辑
4.5 数据库 Schema
文件: packages/database/src/schemas/betterAuth.ts
| 表名 | 关键字段 | 用途 |
|---|---|---|
users | id, email, fullName, avatar, phone, username, role, banned, twoFactorEnabled | 用户主表 (email 归一化) |
accounts | id, userId, providerId, accountId, accessToken, refreshToken | OAuth 关联账号 |
sessions | id, userId, token, expiresAt, ipAddress, userAgent | 会话记录 |
verifications | id, identifier, value, expiresAt | 验证码/Magic Link |
two_factor | id, userId, secret, backupCodes | 双因素认证 |
5 文件存储 & MinIO/RustFS MinIO
5.1 存储架构
┌─────────────────────────────────────────────┐ │ CLIENT (浏览器) │ │ UploadService → XMLHttpRequest PUT │ └──────────────┬──────────────────────────────┘ │ 1. 获取预签名 URL ▼ ┌──────────────────────────────────────────────┐ │ tRPC API 层 │ │ upload.createS3PreSignedUrl({pathname}) │ │ file.createFile({hash, type, name, size}) │ │ /(backend)/f/[id] (文件代理路由) │ └──────────────┬──────────────────────────────┘ │ 2. S3 命令 ▼ ┌──────────────────────────────────────────────┐ │ Service 层 │ │ FileService (门面) → S3StaticFileImpl │ └──────────────┬──────────────────────────────┘ │ 3. AWS SDK v3 ▼ ┌──────────────────────────────────────────────┐ │ S3 兼容存储 │ │ RustFS / MinIO / AWS S3 / Cloudflare R2 │ └──────────────────────────────────────────────┘
5.2 核心源码文件
src/server/modules/S3/index.ts (215 行) — S3 客户端模块
S3 基类方法
| 方法 | 功能 | 细节 |
|---|---|---|
constructor() | 初始化 S3Client | credentials + endpoint + forcePathStyle + region |
createPreSignedUrl(key) | 生成上传 URL | PutObjectCommand, 1 小时有效 |
createPreSignedUrlForPreview(key, expiresIn?) | 生成预览 URL | GetObjectCommand, 默认 2 小时 |
uploadBuffer(path, buffer, contentType) | 服务端上传 | PutObjectCommand + Cache-Control |
uploadMedia(key, buffer) | 上传媒体文件 | Cache-Control: public, max-age=31536000 (1 年缓存) |
getFileContent(key) | 获取文本内容 | UTF-8 字符串 |
getFileByteArray(key) | 获取二进制 | Uint8Array |
getFileMetadata(key) | 获取元数据 | HeadObjectCommand → contentLength + contentType |
deleteFile(key) | 删除文件 | DeleteObjectCommand |
deleteFiles(keys) | 批量删除 | DeleteObjectsCommand (批量) |
FileS3 包装类
继承 S3,从 fileEnv 自动读取配置初始化,用于依赖注入。
src/server/services/file/index.ts (366 行) — FileService 门面
关键方法
| 方法 | 功能 | 核心逻辑 |
|---|---|---|
createFileRecord() | 创建文件记录 | SHA256 去重 via globalFiles → 返回代理 URL /f/{fileId} |
getFullFileUrl() | 智能 URL 解析 | ACL 公开 + 有 PUBLIC_DOMAIN → 直链; 否则 → 预签名 URL |
getKeyFromFullUrl() | URL → S3 Key | /f/{fileId} → DB 查询; legacy S3 URL → pathname 解析 |
uploadBase64() | Base64 上传 | base64 → Buffer → uploadMedia → createFileRecord |
uploadFromUrl() | 外部 URL 上传 | fetch → Buffer → uploadMedia → createFileRecord |
downloadFileToLocal() | 下载到临时目录 | 返回 cleanup() 清理函数 |
createFileRecord() 先查 globalFiles 表(SHA256 哈希),如果已存在则复用同一 S3 对象,只创建新的 files 记录(不同 fileId)。删除时只有没有任何 files 引用该 hash 时才删 S3 对象。
src/app/(backend)/f/[id]/route.ts (91 行) — 文件代理路由
流程
GET /f/{fileId}
│
├─ 1. Redis 查缓存: key = 'file-proxy:{fileId}'
│ 命中? → 302 重定向到缓存的预签名 URL
│
├─ 2. 未命中 → DB 查询: FileModel.getFileById(fileId)
│ ⚠️ 无 userId 过滤 (公开可访问)
│
├─ 3. 生成预签名 GET URL (5 分钟有效)
│
├─ 4. 写入 Redis: TTL = 240s (4 分钟)
│ (比 URL 有效期短 1 分钟,安全余量)
│
└─ 5. 返回 302 Location → 预签名 URL
5.3 上传完整流程
步骤 1: 生成路径 uploadService.generateFilePathMetadata() → filename = '{uuid}.{ext}' → dirname = 'files/{timestamp_hour}' → pathname = 'files/2026-04-07/{uuid}.png' 步骤 2: 获取预签名 URL tRPC: upload.createS3PreSignedUrl({pathname}) → server: FileS3.createPreSignedUrl(pathname) → AWS SDK: getSignedUrl(PutObjectCommand, {expiresIn: 3600}) ← 返回: https://rustfs:9000/lobe/files/2026-04-07/{uuid}.png?X-Amz-Signature=... 步骤 3: 直传 S3 XMLHttpRequest PUT → 预签名 URL → 浏览器直接上传到 RustFS/MinIO (不经过 LobeChat 服务器) → 支持进度回调 (xhr.upload.onprogress) ← 200 OK 步骤 4: 创建数据库记录 tRPC: file.createFile({hash, fileType, name, size, url}) → server: FileModel.checkHash(sha256) → 检查去重 → server: FileService.getFileMetadata(url) → 验证实际文件大小 (防伪造) → server: FileModel.create() → INSERT files + globalFiles ← 返回: {fileId: 'xxx', url: '/f/xxx'} 步骤 5: 使用 客户端使用 /f/{fileId} 作为统一 URL → 图片: <img src="/f/{fileId}" /> → 下载: window.open('/f/{fileId}')
5.4 数据库 Schema (文件相关)
| 表 | 关键字段 | 用途 |
|---|---|---|
global_files | hash_id (PK, SHA256), file_type, size, url, metadata, creator | 去重层 — 相同内容只存一份 S3 对象 |
files | id, user_id, file_hash (FK→global_files), name, size, url, source, parent_id, chunk_task_id, embedding_task_id | 用户文件记录 — 每个用户独立引用 |
documents | id, title, content, file_type, pages, source_type, file_id, knowledge_base_id, parent_id | 文档/文件夹 — RAG 知识库组织 |
files 表是用户级别的(每个用户看到自己的文件列表),global_files 是全局去重的(SHA256 唯一)。User A 和 User B 上传同一文件,S3 只存一份,但各自有独立的 fileId 和代理 URL。删除 User A 的文件不影响 User B。只有当所有 files 记录都删除后,才清理 S3 对象。
6 数据库层 Drizzle ORM
6.1 技术选型
- ORM: Drizzle ORM v0.45.1 (TypeScript-first, SQL-like API)
- 数据库: PostgreSQL 17 (ParadeDB 版,含 pgvector)
- 驱动: node-postgres (标准) 或 neon-serverless (Serverless)
- 迁移: Drizzle Kit → SQL 文件 →
packages/database/migrations/ - 加密:
KEY_VAULTS_SECRET加密 API Key / Proxy URL 等敏感字段
6.2 全部 29 个 Schema 文件
点击展开完整 Schema 列表
| # | 文件 | 表 | 说明 |
|---|---|---|---|
| 1 | agent.ts | agents, agent configs | AI Agent 定义 |
| 2 | agentBotProvider.ts | agent_bot_providers | Agent 供应商配置 |
| 3 | agentCronJob.ts | agent_cron_jobs | 定时 Agent 任务 |
| 4 | agentDocuments.ts | agent_documents | Agent 知识文档 |
| 5 | agentEvals.ts | agent_evals, benchmarks | Agent 评估 |
| 6 | agentSkill.ts | agent_skills | Agent 技能 |
| 7 | aiInfra.ts | ai_infra configs | AI 基础设施配置 |
| 8 | apiKey.ts | api_keys | 用户 API Key (带过期) |
| 9 | asyncTask.ts | async_tasks | 后台任务队列 |
| 10 | betterAuth.ts | users, accounts, sessions, verifications, two_factor | Better Auth 认证表 |
| 11 | chatGroup.ts | chat_groups | 多方对话群组 |
| 12 | file.ts | files, global_files, documents | 文件 + 去重 + 文档 |
| 13 | generation.ts | generations | 图片/视频生成结果 |
| 14 | message.ts | messages, message_groups, thread_mappings | 聊天消息 + 嵌套树 |
| 15 | nextauth.ts | (legacy NextAuth) | 旧版认证 (保留兼容) |
| 16 | notification.ts | notifications | 用户通知 |
| 17 | oidc.ts | oidc configs | OIDC 服务配置 |
| 18 | rag.ts | chunks, embeddings, knowledge_bases, file_chunks | RAG 系统 |
| 19 | ragEvals.ts | rag evaluation datasets | RAG 评估 |
| 20 | rbac.ts | roles, permissions | RBAC 权限控制 |
| 21 | relations.ts | (Drizzle relations 定义) | 表关系映射 |
| 22 | session.ts | sessions (chat) | 聊天会话 |
| 23 | task.ts | tasks | 任务管理 |
| 24 | topic.ts | topics, topic_threads | 对话主题 |
| 25 | user.ts | user_settings, user_installed_plugins | 用户设置 + 插件 |
| 26-29 | userMemories/*.ts | persona, contexts, preferences, experiences, activities | 用户记忆系统 |
6.3 消息表设计 (核心)
messages {
id TEXT PK
role TEXT -- 'user' | 'assistant' | 'system' | 'tool'
content TEXT -- 消息内容 (Markdown)
editorData JSONB -- 富文本编辑器数据
summary TEXT -- AI 摘要
reasoning TEXT -- 思维链 (Chain of Thought)
search JSONB -- 搜索结果
metadata JSONB -- 自定义元数据
model TEXT -- 使用的模型 (gpt-4o, claude-3.5-sonnet...)
provider TEXT -- 供应商 (openai, anthropic...)
tools JSONB -- 工具调用记录
error JSONB -- 错误信息
traceId TEXT -- OpenTelemetry 追踪 ID
favorite BOOLEAN
-- 外键关系
userId FK → users
sessionId FK → sessions
topicId FK → topics
threadId FK → threads
parentId FK → messages (自引用, 树结构)
quotaId FK → messages (引用消息)
agentId FK → agents
groupId FK → chat_groups
messageGroupId FK → message_groups (并行多模型)
}
message_groups { -- 并行多模型对话
id, topicId, userId
parentGroupId FK (自引用)
parentMessageId FK
type 'parallel' | 'compression'
content, editorData, metadata
}
7 状态管理 Zustand
7.1 Store 组合模式
文件: src/store/chat/store.ts (82 行)
// Zustand Store 用 Slice 模式组合,每个领域一个 slice ChatStore = ChatStoreState & ChatStoreAction ChatStoreAction 包含: ├── ChatMessageAction -- 消息 CRUD, 线程 ├── ChatThreadAction -- 线程操作 ├── ChatAIChatAction -- LLM 流式对话控制 ├── ChatTopicAction -- 主题管理 ├── ChatTranslateAction -- 消息翻译 ├── ChatTTSAction -- 文本转语音 ├── ChatPluginAction -- 插件执行 ├── ChatBuiltinToolAction -- 搜索/代码解释器 ├── ChatPortalAction -- 弹窗/侧边栏 ├── OperationActions -- 撤销/重做 └── ChatAIAgentAction -- Agent 编排 // 创建方式 createWithEqualityFn()( subscribeWithSelector( // 细粒度订阅 devtools( // Redux DevTools createStore // 组合所有 slices ) ), shallow // 浅比较 )
7.2 全部 Store 列表
消息、线程、主题、AI 对话、插件、TTS、翻译、Agent 编排。最复杂的 store,12 个 slices。
Agent CRUD、群组管理、收藏。
用户设置、偏好、主题切换。
上传进度、文件列表管理。
全局设置、主题、语言、侧边栏状态。
聊天会话生命周期。
7.3 Slice 内部结构
src/store/chat/slices/message/ ├── actions/ │ ├── index.ts -- 导出所有 action │ ├── publicApi.ts -- 用户触发: addMessage, updateMessage │ ├── internals.ts -- 内部操作 │ ├── optimisticUpdate.ts -- 乐观更新 (先改 UI, 再同步 DB) │ ├── query.ts -- 消息查询 │ └── runtimeState.ts -- loading/error 状态 ├── selectors/ │ ├── displayMessage.ts -- UI 格式化 │ ├── dbMessage.ts -- 原始 DB 数据 │ ├── messageState.ts -- 元数据 (加载中, 错误) │ └── chat.ts -- 对话专用查询 ├── reducer.ts -- 不可变更新 └── supervisor.ts -- 批量操作协调
8 tRPC API 层 47 路由
8.1 路由架构
文件: src/server/routers/lambda/index.ts (122 行)
全部 47+ 个 tRPC 子路由
| 分类 | 路由 | 说明 |
|---|---|---|
| Agent | agent | Agent CRUD |
agentBotProvider | Bot 供应商配置 | |
agentCronJob | 定时任务 | |
agentDocument | Agent 文档 | |
agentEval | Agent 评估 | |
agentSkills | Agent 技能 | |
agentGroup | Agent 群组 | |
| AI | aiChat | AI 对话 (流式) |
aiModel | 模型配置 | |
aiProvider | AI 供应商管理 | |
| 对话 | message | 消息 CRUD |
topic | 主题管理 | |
thread | 线程操作 | |
session / sessionGroup | 会话管理 | |
| 文件 | file | 文件 CRUD + 去重 |
upload | 预签名 URL 生成 | |
document | 文档管理 | |
| 知识库 | knowledge / knowledgeBase | 知识库管理 |
chunk | 文档分块 + 嵌入 | |
ragEval | RAG 评估 | |
| 生成 | image | 图片生成 |
video | 视频生成 | |
generation / generationBatch / generationTopic | 生成记录 | |
| 用户 | user | 用户配置 |
userMemory / userMemories | 用户记忆 | |
| 其他 | plugin, search, market, config, exporter, importer, share, notification, notebook, apiKey, usage, task, brief, home, device, comfyui, klavis, oauthDeviceFlow | 各功能模块 |
| 商业 | accountDeletion, referral, spend, subscription, topUp | 付费/订阅功能 |
8.2 tRPC 中间件栈
// 公开路由 publicProcedure = t.procedure.use(openTelemetry) // 认证路由 authedProcedure = t.procedure .use(openTelemetry) // 分布式追踪 .use(oidcAuth) // OIDC JWT 验证 (可选) .use(userAuth) // Better Auth Session 验证 // 文件路由 (额外检查) fileProcedure = authedProcedure .use(checkFileStorageUsage) // 存储配额检查 .use(serverDatabase) // DB 连接注入
9 Docker 部署 编排
9.1 生产环境架构
文件: docker-compose/deploy/docker-compose.yml
┌─────────────────────────────────────────────────────────────┐ │ docker-compose (lobe-network) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │ │ lobehub │ │ postgres │ │ redis │ │ rustfs │ │ │ │ :3210 │ │ :5432 │ │ :6379 │ │ :9000/:9001│ │ │ │ │ │ │ │ │ │ │ │ │ │ Next.js │ │ ParadeDB │ │ Redis 7 │ │ S3 兼容 │ │ │ │ + tRPC │ │ PG 17 │ │ Alpine │ │ 对象存储 │ │ │ └─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬──────┘ │ │ │ │ │ │ │ │ ├── SQL ──────┘ │ │ │ │ ├── Redis ──────────────────┘ │ │ │ └── S3 API ────────────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ rustfs-init │ │ searxng │ │ │ │ (one-shot) │ │ :8080 │ │ │ │ 创建 bucket │ │ 搜索引擎 │ │ │ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘
9.2 Dockerfile 多阶段构建
| 阶段 | 镜像 | 职责 | 产出 |
|---|---|---|---|
base | node:24-slim | 依赖安装 + 证书 | node_modules |
builder | base | Next.js 构建 + Vite SPA + Drizzle | .next/standalone + .next/static + spa/ |
app | busybox | 资源收集 | 精简的应用文件 |
scratch | node:24-slim | 最终运行镜像 | ~200MB, 非 root (nodejs:1001) |
9.3 RustFS 初始化
# rustfs-init 服务 (一次性容器)
mc alias set rustfs "http://rustfs:9000" admin ${SECRET}
mc mb "rustfs/lobe" --ignore-existing # 创建 bucket
mc anonymous set-json "/bucket.config.json" "rustfs/lobe" # 设置匿名读权限
bucket.config.json 设置 lobe bucket 的匿名只读策略,允许公开访问已上传文件。
10 环境变量全表 配置中心
| 变量 | 说明 | 默认值 |
|---|---|---|
APP_URL | 公开访问 URL (https://chat.example.com) | 必填 |
INTERNAL_APP_URL | 内部服务间 URL (Docker 内部通信) | 必填 |
PORT | 服务端口 | 3210 |
NODE_ENV | 运行环境 | production |
ENABLE_BUSINESS_FEATURES | 启用商业功能 | false |
| 变量 | 说明 | 默认值 |
|---|---|---|
AUTH_SECRET | 32 字节 base64 随机字符串 | 必填 |
AUTH_SSO_PROVIDERS | 逗号分隔的 SSO 列表 (google,logto...) | 空 |
AUTH_ALLOWED_EMAILS | 注册白名单 (域名或完整邮箱) | 空 (允许所有) |
AUTH_DISABLE_EMAIL_PASSWORD | 禁用邮箱密码, 强制 SSO | false |
AUTH_EMAIL_VERIFICATION | 要求邮箱验证 | false |
AUTH_LOGTO_ID | Logto 应用 ID | - |
AUTH_LOGTO_ISSUER | Logto Endpoint URL | - |
AUTH_LOGTO_SECRET | Logto 应用密钥 | - |
LOGTO_WEBHOOK_SIGNING_KEY | Logto Webhook 签名密钥 | - |
JWKS_KEY | OIDC 服务模式 JWKS (RS256 RSA) | - |
| 变量 | 说明 | 默认值 |
|---|---|---|
S3_ENDPOINT | S3 端点 URL | 必填 |
S3_ACCESS_KEY_ID | 访问密钥 | 必填 |
S3_SECRET_ACCESS_KEY | 秘密密钥 | 必填 |
S3_BUCKET | 存储桶名 | 必填 |
S3_REGION | 区域 | us-east-1 |
S3_ENABLE_PATH_STYLE | 路径样式 (MinIO/RustFS 必须为 1) | false |
S3_PUBLIC_DOMAIN | 公开域名 (CDN) | 空 |
S3_SET_ACL | 设置 public-read ACL | 0 |
S3_PREVIEW_URL_EXPIRE_IN | 预签名 URL 过期秒数 | 7200 |
| 变量 | 说明 | 默认值 |
|---|---|---|
DATABASE_URL | PostgreSQL 连接字符串 | 必填 |
DATABASE_DRIVER | 驱动 (node / neon) | node |
KEY_VAULTS_SECRET | 32 字节 base64, 加密敏感字段 | 必填 |
MIGRATION_DB | 设为 "1" 启动时跑迁移 | 空 |
| 变量 | 说明 | 默认值 |
|---|---|---|
REDIS_URL | Redis 连接字符串 | 必填 |
REDIS_DATABASE | 数据库索引 | 0 |
REDIS_PREFIX | Key 前缀 | lobechat |
REDIS_TLS | 启用 TLS | false |
11 关键数据流 端到端
11.1 用户登录流程 (Logto SSO)
1. 用户点击 "使用 Logto 登录" → Better Auth Client: signIn.social({ provider: 'logto' }) → 302 重定向 → Logto 授权页 2. Logto 授权 → 用户输入凭据 / 选择社交登录 → Logto 验证 → 生成 authorization_code → 302 回调 → LobeChat /api/auth/callback/logto 3. Token Exchange → Better Auth Server: 用 code 换 access_token + id_token → 解析 id_token → 获取 email, name, picture → mapProfileToUser() → 映射到 LobeChat 用户结构 4. 用户创建/关联 → 查找已有用户 (by email) → 已有: linkAccount (关联 Logto 账号) → 新用户: createUser → Hook: UserModel.create() → 同步头像/姓名到 LobeChat users 表 5. 会话创建 → 创建 Better Auth Session (30 天) → 写入 Redis (10 分钟缓存) → Set-Cookie: better-auth.session_token=... → 302 重定向 → / 6. 后续请求 → Cookie → tRPC middleware → userAuth → Redis 缓存命中 → 直接获取 userId (无 DB 查询) → Redis 未命中 → 查 sessions 表 → 刷新缓存
11.2 发送消息 → AI 回复
1. 用户输入消息 → ChatStore.sendMessage(content) → 乐观更新: 立即显示用户消息 2. tRPC 调用 → aiChat.sendMessageInServer({ messages: [...history, newMessage], model: 'gpt-4o', provider: 'openai' }) 3. 服务端处理 → 认证中间件链 (OIDC → Session → DB) → initModelRuntimeFromDB(db, userId, 'openai') → 从 DB 加载用户配置的 API Key (解密) → 实例化 OpenAI Provider 4. LLM 调用 → modelRuntime.generateStream({ messages, tools, temperature, ... }) → OpenAI API: POST /v1/chat/completions (stream: true) 5. 流式响应 → SSE: data: {delta: "你", ...} → StreamingHandler 实时更新 Store → UI 实时渲染 (打字机效果) 6. 完成 → 保存 assistant 消息到 DB → 更新 Token 计数 (UsageCounter) → OpenTelemetry: 记录 traceId
11.3 文件上传 → RAG 嵌入
1. 用户拖入 PDF → UploadService.uploadFileToS3(file) 2. 预签名 + 直传 → tRPC: upload.createS3PreSignedUrl → 获取 PUT URL → XHR PUT → RustFS/MinIO (浏览器直传) → tRPC: file.createFile → DB 记录 (files + globalFiles) 3. 文档解析 (异步) → chunk.createParseFileTask({fileId}) → AsyncTask: 解析 PDF → 提取文本 → 分块 (chunks) → 写入 chunks 表 + file_chunks 关联 4. 向量嵌入 (异步) → chunk.createEmbeddingChunksTask({fileId}) → 批量调用 Embedding API (batch=50, concurrency=10) → pgvector: INSERT embeddings (1536 维向量) 5. 语义搜索 (使用时) → chunk.semanticSearchChunks({query, topK: 10}) → pgvector: cosine_distance 搜索 → 返回最相关的文档块 + 相似度分数
12 Manus 平台规划 米乐优品
Manus AI 生成的 MILEJOY 内部 AI 平台定制化全案(品牌 / 权限 / 业务插件 / 部署运维)。
12.1 平台整体规划
米乐优品 80 人跨境电商团队内部 AI 平台,基于 LobeChat 二开。解决生图、财务核对、视频生成、物流数据对接四大核心痛点。
Docker Compose 容器化 → LobeChat + PostgreSQL + Redis + RustFS + SearXNG。Dashboard 监控 API 调用量/Token 消耗/模型响应时间。
12.2 权限与角色
| 改造项 | 具体内容 | 涉及文件 |
|---|---|---|
| 用户类型感知 | LobeUser / UserInitializationState 加 role | packages/types/, lambda/user.ts |
| 导航过滤 | useCategory.tsx 按 admin/member 隐藏菜单 | settings/hooks/ |
| 路由守卫 | 非管理员访问 /settings/provider 等 → 拦截跳转 | settings/index.tsx |
| 管理员页面 | 用户列表 / 角色分配 / 封禁解禁 | 新建 settings/admin/ |
| 成本控制 | 按部门分配 Token + 存储配额 | RBAC 表 + tRPC 中间件 |
12.3 业务插件规划
- 接入 DALL-E 3 / Midjourney / Stable Diffusion
- 接入 Runway / Kling 视频生成
- "商品主图助手"/"营销海报设计师" 预设 Prompt
- 生成结果自动推送到素材库
- 对接 ERP / PayPal / Payoneer API
- Code Interpreter 分析 Excel/CSV
- 自动对账 + 差异分析 + 可视化报告
- RBAC 限制仅财务部可访问
- 集成 17TRACK / AfterShip / DHL / FedEx
- "物流客服助手" 输入订单号自动查轨迹
- Cron Job 定期批量查询异常件
- 自动向物流专员发预警通知
- Default 插件:标准 API + 可选 UI
- Markdown 插件:纯文本返回
- Standalone 插件:iframe 独立应用
- Plugin Gateway 网关 + Plugin Index 市场
12.4 品牌替换
| 步骤 | 方式 | 范围 |
|---|---|---|
| 1. 快速 | 环境变量 NEXT_PUBLIC_BRAND_LOGO=url | 仅主 Logo |
| 2. 源码 | branding.ts: BRANDING_NAME='米乐优品' | 全局名称 + Logo + 链接 |
| 3. 资源 | 替换 public/ 下 favicon/PWA/OG 图 | 浏览器图标 + 社交分享 |
| 4. 主题 | 主色调 #FF8C00(米乐优品橙) | CSS 变量 + Ant Design Token |
12.5 全部物料预览
全部 43 个品牌资源(Logo / OG 图 / favicon ×14 / PWA 图标 ×8 / 启动画面):

横向 Logo

Logo 白色版

完整 Logo

深色背景

OG v2

OG v1

OG 宽版

欢迎 Banner

图标 512

图标 256

Maskable 512
PWA 512
PWA 384
PWA 192

启动画面
另有 favicon ×14 (.ico)、小尺寸图标 (16/32/48/64/72/96/128/144/152px)、Apple Touch Icon、竖版启动画面 1242×2688
全部 23 张 UI 物料 + 3 个视频动画 + CSS/JSON Token:

登录 Mockup

登录背景·浅

登录背景·深

侧边栏·折叠

侧边栏·浅

侧边栏·深

欢迎页·浅

欢迎页·深

色板
AI 头像
用户头像

空对话

无搜索结果

空知识库

空插件

错误页

网络错误

启动画面·首帧

启动画面·末帧

片头·首帧

片头·末帧

产品演示封面

培训视频封面
视频动画 (3)
启动画面动画
Logo 片头动画
AI 思考动画
配置文件:milejoy-theme-tokens.css + milejoy-design-tokens.json + milejoy-loading-animations.css
全部 12 个形态(3 基础 + 9 表情 + 6 场景):

正面

侧面

四分之三

欢迎
竖拇指

庆祝

思考

困惑

爱心

加载中

睡觉

错误

财务场景

物流场景

生图场景

双11场景
全部 25 套企业表情包:

hello

good-job

received

fighting

are-you-there

hahaha

ok
thumbsup

hug

speechless

shipped

orders-booming

rush-order

off-work

slacking

meeting

reconciling

five-star

thanks-boss

working-hard

goodbye

dont-rush

tea-time

666

goodnight
全部 12 张(功能海报 + 品牌海报 + 社交媒体图):

平台发布

品牌文化

AI 生图

视频生成

财务核对

物流追踪

团建

培训

招聘

朋友圈

公众号封面

小红书
3 个可用的 LobeChat 插件 manifest(物流/财务/生图),符合 chat-plugin-sdk 规范:
milejoy-logistics/manifest.json — 物流追踪插件
{
"identifier": "milejoy-logistics",
"api": [
{
"name": "queryLogistics",
"description": "根据运单号查询跨境电商包裹的实时物流轨迹",
"parameters": {
"properties": {
"trackingNumber": { "type": "string" },
"carrier": { "enum": ["DHL","FedEx","UPS","SF-Express","4PX","auto"] }
},
"required": ["trackingNumber"]
}
},
{
"name": "batchQueryStatus",
"description": "批量查询多个运单号的最新物流状态(最多50个)"
},
{
"name": "queryAnomalyPackages",
"description": "查询异常包裹(长时间未更新/清关异常/退件/投递失败)",
"parameters": {
"properties": {
"anomalyType": { "enum": ["all","stalled","customs_hold","returned","delivery_failed"] },
"daysThreshold": { "type": "number", "description": "未更新天数阈值,默认7天" }
}
}
}
],
"ui": { "url": "[部署地址已隐]", "height": 450 }
}
财务核对和图片生成插件 manifest 结构类似,在 milejoy_plugin_examples.zip (8KB)

插件架构图

开发流程图
Manus 全部产出清单(6 个 zip,共 400MB+)
| 包 | 大小 | 内容 |
|---|---|---|
brand_assets.zip | 29MB | favicon ×14、PWA 图标 ×8、Apple Touch Icon、OG 图、横向 Logo |
mascot_assets.zip | 95MB | 吉祥物 3 基础形态 + 9 表情(竖拇指/欢迎/庆祝/思考/困惑/爱心/加载/睡觉/错误) |
stickers_pack.zip | 116MB | 企业表情包 25 套(你好/收到/加油/好的/爆单/摸鱼/下班/晚安/666...) |
posters_pack.zip | 66MB | 海报 10 张(平台发布/AI 生图/视频/物流/财务/团建/培训/招聘/品牌/朋友圈) |
ui_video_assets.zip | 96MB | 登录页(浅/深) + Mockup + 侧边栏 Logo(折叠/展开/浅/深) + 欢迎页(浅/深) + 主题 CSS/JSON |
plugin_examples.zip | 8KB | 3 个插件 manifest.json(物流/财务/生图)+ plugin-index 索引 |
另有 6 篇方案文档 (.md)、6 篇研究笔记、design-tokens.json、loading-animations.css、3 个视频动画 (.mp4)。
原目录:~/.Trash/定制化开源项目以适配公司内部需求/
13 NotebookLM 知识库接入 MCP
把 Google NotebookLM 个人账号下的所有专业笔记和文档,通过 MCP(Model Context Protocol)暴露给 LobeChat 的 AI 助理,让公司全员共享同一份知识库 — 无需迁移文档、无需付费、AI 自动调用、答案带原文引用。
13.1 业务场景
客户咨询时需要快速调用公司内部专业知识(产品手册、售后政策、技术文档等),手动查阅 NotebookLM 太慢。
所有专业文件已经存在 个人 NotebookLM 账号 里,不想迁移;公司有 80 人需要访问,但只有一个 Google 账号;不想付 NotebookLM Enterprise 费用。
用浏览器自动化工具(patchright + 真 Chrome)保持账号登录态,包成 MCP server,挂到 LobeChat 全员共享的助理上。
免费版每日查询配额(估测 50-100 次/账号/天);Google 可能检测到自动化(建议长期用专用账号);登录态偶尔失效(需重新 OAuth)。
13.2 整体架构
公司员工 (80 人)
│
├─→ LobeChat 网页 (https://lobehub.kang-kang.com)
│ │
│ ├─ 助理对话界面
│ └─ MCP 工具调用 (16 个工具)
│ │
│ ▼
│ tRPC 后端 (lobechat-prod 容器)
│ │
│ └── HTTP POST /mcp ──→ http://10.0.1.1:3289/mcp (host gateway)
│ │
│ ▼
│ mcp-proxy (systemd 常驻)
│ 端口 3289 / API Key 鉴权
│ │
│ ▼ stdio
│ notebooklm-mcp (npm CLI)
│ │
│ ▼
│ Patchright + Google Chrome 147
│ 持久化 Chrome profile + 已登录态
│ │
│ ▼
└────────────────────────→ https://notebooklm.google.com (个人账号)
│
▼
Gemini 2.5 + 用户上传的所有 sources
│
▼
带原文引用的回答
13.3 核心组件
| 组件 | 位置 | 作用 |
|---|---|---|
| notebooklm-mcp | 云端 VPSnpm i -g notebooklm-mcp@1.2.1 |
核心 MCP server。用 patchright + Chrome 模拟人类操作 NotebookLM 网页。暴露 16 个 MCP 工具:ask_question、list_notebooks、add_notebook、search_notebooks 等。 |
| mcp-proxy | 云端 VPSnpm i -g mcp-proxy |
把 stdio MCP 包装成 HTTP/SSE。LobeChat 网页版只支持 HTTP MCP,不支持 stdio,所以必须套这一层。监听 0.0.0.0:3289,X-API-Key 鉴权。 |
| Patchright + Chrome | /root/.cache/ms-playwright/Google Chrome 147 |
Patchright 是 Playwright 的 stealth fork,用真 Chrome 而非 chromium-headless-shell。带人性化打字延迟、鼠标移动模拟,降低 Google 反爬虫检测概率。 |
| 登录态 (browser_state) | /root/.local/share/notebooklm-mcp/browser_state/state.json/root/.local/share/notebooklm-mcp/chrome_profile/ |
Google OAuth 完成后保存的 cookies + localStorage + Chrome profile。从本地 Mac 完成首次 OAuth 后 scp 到云端,保证云端无头环境也能直接复用登录态。 |
| systemd service | /etc/systemd/system/notebooklm-mcp.service |
常驻进程管理。开机自启,崩溃自动重启。日志写到 /var/log/notebooklm-mcp.log。 |
| LobeChat 集成 | PostgreSQL user_installed_plugins 表 |
给每个 LobeChat 用户的 user_installed_plugins 表插入一条 customPlugin 记录,type=mcp,customParams.mcp.url 指向 http://10.0.1.1:3289/mcp,headers 带 X-API-Key。 |
13.4 部署步骤(已完成的全流程)
1️⃣ 本地 Mac 完成 Google OAuth 登录
- 直接调用 AuthManager.performSetup() 弹出真 Chrome 浏览器
- 用户在浏览器里完成 Google 登录授权(30 秒)
- Session 保存到 ~/Library/Application Support/notebooklm-mcp/
2️⃣ 把登录态打包上传到云端
- tar -czf nbm-data.tar.gz -C "~/Library/Application Support/notebooklm-mcp" .
- scp nbm-data.tar.gz root@76.13.31.179:/tmp/
- tar -xzf 到 /root/.local/share/notebooklm-mcp/
- 清理 macOS 元数据 (._*) + chown root
3️⃣ 云端 VPS 安装运行环境
- npm install -g notebooklm-mcp@1.2.1
- npx patchright install chrome (Google Chrome 147)
- npm install -g mcp-proxy
4️⃣ 写 systemd service 让 mcp-proxy 常驻
- ExecStart: mcp-proxy --host 0.0.0.0 --port 3289 \
--apiKey nbm-secret-mile-2026 -- /usr/bin/notebooklm-mcp
- systemctl enable + start notebooklm-mcp
5️⃣ LobeChat 容器内部访问云端 mcp-proxy
- lobechat-prod 在 coolify 网络,gateway 是 10.0.1.1
- 容器内 fetch http://10.0.1.1:3289/mcp 可达
- 不需要公网域名,不需要 HTTPS
6️⃣ 数据库注入 MCP 插件配置(绕过 UI 操作)
- fetch tools/list 拿到 16 个工具的 schema
- 构造 LobeChatPluginManifest(api[]、meta、mcpParams)
- 构造 customParams(mcp.url、mcp.headers、mcp.auth)
- INSERT 到 user_installed_plugins,给所有 3 个用户都注入
13.5 16 个 MCP 工具
| 分类 | 工具 | 说明 |
|---|---|---|
| 查询类 | ask_question |
核心工具。向 NotebookLM 提问,可选 notebook_id(已注册)或 notebook_url(即兴),支持 session_id 实现多轮上下文对话。 |
get_health | 检查认证状态、活跃 session 数。 | |
list_notebooks | 列出已注册到 library 的 notebook 元数据(name/topics/use_cases/url)。 | |
get_notebook | 获取单个 notebook 的详细信息。 | |
select_notebook | 设置默认活跃 notebook,后续 ask_question 不传 notebook_id 时用此 notebook。 | |
| 库管理 | add_notebook |
注册一个 notebook URL 到 library,需要提供 name、description、topics、use_cases,让 AI 知道何时调用它。 |
update_notebook | 更新已注册 notebook 的元数据。 | |
remove_notebook | 从 library 移除 notebook(危险操作,需要用户确认)。 | |
search_notebooks | 按关键词搜索 library,匹配 name/description/topics/tags。 | |
get_library_stats | library 统计(总数、各 notebook 的使用次数等)。 | |
| Session 管理 | list_sessions |
列出活跃 session(age、message count、last activity)。 |
close_session | 关闭指定 session。 | |
reset_session | 重置 session 的对话历史(保留同一个 session_id)。 | |
| 认证 & 维护 | setup_auth |
开启浏览器手动 Google 登录(仅在没有可显示器的环境之外可用)。 |
re_auth | 切换 Google 账号或重新认证。 | |
cleanup_data | 深度清理所有数据。 |
13.6 AI 调用流程
用户在 LobeChat 输入:
"查一下退货政策对超过 30 天的订单怎么处理"
│
▼
LobeChat AI(启用了 NotebookLM 插件)
│
├─ 看到 16 个工具可用
├─ 决定调用 ask_question
│
▼
ask_question({
question: "退货政策对超过 30 天的订单怎么处理?",
notebook_url: "https://notebooklm.google.com/notebook/xxx"
})
│
▼
HTTP POST → http://10.0.1.1:3289/mcp(带 X-API-Key 头)
│
▼
mcp-proxy 转发到 stdio
│
▼
notebooklm-mcp:
1. 创建 session(或复用)
2. Patchright 启动 Chrome(headless),加载已保存的 auth state
3. 导航到 notebook URL
4. 在输入框模拟人类打字输入问题
5. 等待 NotebookLM 返回答案
6. 解析答案 + 引用 + 提取
│
▼
返回 JSON:{ status, question, answer, session_id, notebook_url }
│
▼
LobeChat AI 接收答案,整理后发给用户
(带原文引用,便于追溯)
13.7 当前关键文件路径(云端 76.13.31.179)
| 用途 | 路径 |
|---|---|
| notebooklm-mcp 安装位置 | /usr/lib/node_modules/notebooklm-mcp/ |
| 登录态 + Chrome profile | /root/.local/share/notebooklm-mcp/ |
| library.json (notebook 索引) | /root/.local/share/notebooklm-mcp/library.json |
| Patchright Chrome | /root/.cache/ms-playwright/ |
| systemd unit | /etc/systemd/system/notebooklm-mcp.service |
| 运行日志 | /var/log/notebooklm-mcp.log |
| HTTP MCP 端点 | http://10.0.1.1:3289/mcp (容器视角)http://127.0.0.1:3289/mcp (host 视角) |
| API Key(X-API-Key 头) | nbm-secret-mile-2026 |
13.8 待决策:如何"分库"
当前 library 是空的(add_notebook 没注册任何 notebook)。AI 不知道你账号下有哪些 notebook,必须在对话里手动给它 URL 才能查询。
方案 A:批量注册所有重要 notebook(推荐)
给每个 notebook 调用一次 add_notebook,提供完整元数据:
add_notebook({
url: "https://notebooklm.google.com/notebook/abc123",
name: "TESERY 充电桩产品手册",
description: "包含所有 TESERY 充电桩的安装、使用、故障排查文档",
topics: ["充电桩", "TESERY", "安装", "故障"],
use_cases: ["客户问充电桩问题", "技术支持咨询"],
content_types: ["product_manual", "troubleshooting"]
})
注册后 AI 调用 list_notebooks 可看到所有可用 notebook,根据用户问题自动选最相关的。需要提前准备每个 notebook 的元数据。
方案 B:每次对话手动指定 URL
不注册 library,用户在对话里告诉 AI:"用这个 notebook 查 ..." + 粘贴 URL。简单但不智能。
方案 C:在助理 system prompt 里硬编码索引
你有以下知识库可以查询,根据问题主题选择最匹配的 URL: - 充电桩问题 → https://notebooklm.google.com/notebook/xxx1 - 售后政策 → https://notebooklm.google.com/notebook/xxx2 - 销售话术 → https://notebooklm.google.com/notebook/xxx3 当用户问相关问题时,调用 ask_question 工具,传对应的 notebook_url。
简单粗暴但有效,不需要 add_notebook。可以为不同部门(销售/技术/财务)做不同助理,每个助理只看到自己部门的 notebook。
方案 D:按助理隔离知识库(权限粒度最细)
未来可以做:
- "销售助理" — system prompt 里只列销售相关 notebook
- "技术助理" — 只列技术文档相关 notebook
- "财务助理" — 只列财务相关 notebook
- LobeChat 助理可以设置访问控制,指定哪些用户能用哪些助理
13.9 已知限制 & 风险
| 类别 | 说明 | 缓解措施 |
|---|---|---|
| 每日配额 | NotebookLM 免费版估测 50-100 次问答/账号/天 | 升级 Google AI Pro($20/月,配额 5x);缓存重复问题;按部门分账号 |
| Google 检测 | 自动化访问可能被风控(账号被锁、要求二次验证) | 用专用账号(不要主账号);patchright stealth 模式已开启;不要短时间发太多请求 |
| 登录态失效 | Cookies 会过期;Google 会要求定期重新验证 | 每 1-2 周重新跑一次 OAuth;脚本化重新登录流程;监控 get_health 状态 |
| 响应延迟 | 每次问答 3-15 秒(要启动 Chrome、加载页面、等 Gemini 响应) | 常驻 Chrome 进程(已开启);session 复用减少重启开销;缓存常见问题 |
| 用户隔离 | 所有公司用户共享同一个 Google 账号 NotebookLM | 账号级别没有隔离;通过 LobeChat 助理级别做权限控制 |
| 跨用户配额竞争 | 所有用户消耗同一个账号的配额 | 记录调用统计;高峰期降级(让 AI 优先回答常见问题) |
13.10 维护命令
# 查看 service 状态
systemctl status notebooklm-mcp
# 看实时日志
tail -f /var/log/notebooklm-mcp.log
# 重启 service
systemctl restart notebooklm-mcp
# 测试 HTTP 端点是否健康
curl -s -X POST "http://127.0.0.1:3289/mcp" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "X-API-Key: nbm-secret-mile-2026" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
# 查看数据库里所有用户的 NotebookLM 插件
docker exec gis3jj74958vecvetgbq6e4u psql -U postgres -d lobechat \
-c "SELECT user_id, identifier, jsonb_array_length(manifest->'api') AS tools FROM user_installed_plugins;"
# 重新登录 Google(在本地 Mac 跑)
node /tmp/notebooklm-login.mjs
# 然后 scp 新的 session 到云端
tar -czf /tmp/nbm-data.tar.gz -C "~/Library/Application Support/notebooklm-mcp" .
scp /tmp/nbm-data.tar.gz root@76.13.31.179:/tmp/
ssh root@76.13.31.179 "rm -rf /root/.local/share/notebooklm-mcp/* && tar -xzf /tmp/nbm-data.tar.gz -C /root/.local/share/notebooklm-mcp/ && find /root/.local/share/notebooklm-mcp -name '._*' -delete && chown -R root:root /root/.local/share/notebooklm-mcp && systemctl restart notebooklm-mcp"
13.11 给新员工配 NotebookLM 工具
当前是直接 INSERT 到 user_installed_plugins。新员工注册 LobeChat 后,他不会自动有这个插件 — 需要手动 INSERT 一条记录。
方案 1:手动 SQL(重复跑 inject-mcp.sql 加新 user_id)
方案 2:写一个 PostgreSQL trigger,每次 INSERT users 时自动给新用户注入 NotebookLM 插件
方案 3:把 NotebookLM 助理设置为公开市场助理,公司所有员工都能从市场添加
14 Q&A 问答区 持续更新
"上传图片就报 407 / Error while downloading 192.168.2.221:19000/..." —— 部署在内网的 LobeChat 接云端 AI 时必撞,原因和方案见下方第一条 Q&A。
Error while downloading http://192.168.2.221:19000/... Upstream status code: 407,换模型也不行,怎么办?根因一句话:LobeChat 把 MinIO 的内网地址当作图片 URL 发给云端 AI,云端 AI 服务器在海外/公网,下载不到 192.168.2.221 这个内网 IP,被中间代理拦截返回 407 Proxy Authentication Required。和你在哪台机器访问 LobeChat 完全无关,文字聊天没事是因为没带文件 URL。
为什么是"下载链接"而不是"上传图片"?—— 预签名 URL 直传机制
LobeChat 走的是 S3 标准最佳实践:浏览器直传 MinIO,对话里只放 URL。流程:
浏览器 ──①PUT 预签名 URL──> MinIO (192.168.2.221:19000) 浏览器 ──②对话+图片URL──> LobeChat 后端 LobeChat ──③转发对话+URL──> OpenAI / Claude / Poe (云端) 云端 AI ──④回头 GET 这个 URL──> ❌ 拉不到内网,407
三个原因这么设计:① 大文件不占 LobeChat 服务器带宽和内存;② 消息体只有几百字节 URL 不是几 MB base64,省 token 上下文;③ 多轮对话同一张图复用一个 URL。OpenAI 协议原生支持 image_url 字段,LobeChat 默认就走这条。
协议层面 image_url 也接受 data:image/png;base64,... 内嵌,但 LobeChat 没走这个分支。Claude/Gemini 原生 SDK 是 base64 内嵌,所以接原生端点不会撞这个问题;但如果接的是 api.xxx.com/v1 这种 OpenAI 兼容聚合站,100% 按 URL 下载解析,必撞。
三种部署形态对比
| ① 纯内网(你现在) | LobeChat + MinIO 都在 192.168.2.221 | 多模态必挂 |
| ② 内网部署 + 文件走公网 | 主体不动,只把 MinIO 19000 通过公网域名暴露 | 推荐 |
| ③ 全云端 | LobeChat + MinIO 都在 VPS | 对外服务才用 |
| ④ 本地 LLM | Ollama/vLLM 在同一内网 | 不需要公网,但模型能力弱 |
推荐方案:MinIO 套公网域名(最小改动)
1. 加反代(Caddy 例子,或用 Cloudflare Tunnel 不开放路由器端口):
s3.kang-kang.com {
reverse_proxy 192.168.2.221:19000
}
2. 改 LobeChat 环境变量:
S3_PUBLIC_DOMAIN=https://s3.kang-kang.com S3_ENDPOINT=https://s3.kang-kang.com S3_ENABLE_PATH_STYLE=1
3. 改 MinIO 环境变量(必须,否则签名 host 不一致会 SignatureDoesNotMatch):
MINIO_SERVER_URL=https://s3.kang-kang.com MINIO_BROWSER_REDIRECT_URL=https://console.kang-kang.com
4. 重启 LobeChat + MinIO,重新上传一张图测试,新 URL 应是 https://s3.kang-kang.com/lobe/files/...。
- 旧的已上传文件 URL 是带内网 host 签名的,改完后旧文件会 404,需要重新上传或写脚本批量改 DB 里 host
- Coolify env var 必须走 API/Eloquent 写入(自动加密),不能裸 SQL UPDATE
- 不要直接把 19000 端口在路由器上暴露公网,优先 Cloudflare Tunnel / frp / WireGuard 反穿,安全得多
- 这是所有云端 LLM 的通用限制,不是 LobeChat bug;只要你接的是云端 API,MinIO 就必须公网可达
Better Auth 是 LobeChat 的认证框架(类似 Clerk/NextAuth 的开源替代),负责管理会话、密码哈希、Cookie、2FA、Passkey 等所有认证基础设施。Logto 只是作为一个 SSO Provider(OIDC 协议)接入 Better Auth。
简单说:Better Auth 是"认证引擎",Logto 是"其中一个登录入口"。这种设计的好处是 LobeChat 可以同时支持 11 种 SSO + 邮箱密码 + Magic Link + Passkey,而不被任何一个 IdP 绑死。
RustFS 是 MinIO 的 Rust 重写版,API 完全兼容。LobeChat 的 docker-compose 默认用 RustFS 但代码里用的是标准 AWS SDK v3——所以可以无缝切换到 MinIO、AWS S3、Cloudflare R2 等任何 S3 兼容存储。配置只需改环境变量,代码零改动。
采用预签名 URL 直传模式:客户端先从服务端获取一个 1 小时有效的 PUT 预签名 URL,然后浏览器直接上传到 S3。这样大文件不会占用 LobeChat 服务器的带宽和内存,服务器只处理轻量的元数据。这是 S3 最佳实践。
- 第 1 节 · 技术栈卡片:React 19 / Vite / React Router v7 各自干什么?
- 第 1 节 · LobeChat 技术栈:改了代码页面没变?
- 第 1 节 · LobeChat 技术栈:用户管理为什么失败?(3 层源码问题)
- 第 1 节 · 后端:Next.js / tRPC 各自干什么?
- 第 1 节 · 后端:登录了为什么还要验证?
- 第 1 节 · 后端:内网也要这套认证?
- 第 1 节 · AI:API Key 放哪?多人会冲突吗?
- 第 1 节 · AI:Poe 类集合商配了没用?
- 第 1 节 · AI:云端缓存会泄露吗?
- 第 1 节 · 样式:品牌/Logo/颜色能改吗?