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 Q&A 问答区 持续更新
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/颜色能改吗?