Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • SillyTavern 服务端架构深度分析

SillyTavern 服务端架构深度分析

本文档深入分析 SillyTavern 项目的服务端架构,包括启动流程、Express 配置、路由系统、安全机制和多用户架构。


目录

  1. 启动流程
  2. Express 应用配置
  3. 路由系统架构
  4. 安全机制
  5. 多用户架构

1. 启动流程

1.1 启动流程 Mermaid 序列图

1.2 核心启动文件职责

文件职责描述
server.js入口文件,负责解析命令行参数,设置全局变量,导入主模块
server-main.js核心启动文件,配置 Express 应用、中间件链、路由、执行预启动任务
server-startup.js服务器启动工具类,处理 HTTP/HTTPS 服务器创建、IPv4/IPv6 双栈监听

1.3 启动阶段详解

Phase 1: 初始化阶段 (server.js)

// 1. 解析命令行参数
const cliArgs = new CommandLineParser().parse(process.argv);
globalThis.DATA_ROOT = cliArgs.dataRoot;
globalThis.COMMAND_LINE_ARGS = cliArgs;

// 2. 切换到服务器目录
process.chdir(serverDirectory);

// 3. 导入主模块
await import('./src/server-main.js');

Phase 2: 应用构建阶段 (server-main.js)

// 1. 创建 Express 实例
const app = express();

// 2. 配置中间件链(详见第2节)
app.use(helmet({ contentSecurityPolicy: false }));
app.use(compression());
// ... 更多中间件

// 3. 路由分层注册
app.use('/api/users', usersPublicRouter);  // 公开 API
app.use(requireLoginMiddleware);            // 认证边界
app.use('/', userDataRouter);               // 私有 API

Phase 3: 预启动任务链

initUserStorage(dataRoot)
    .then(setDnsResolutionOrder)
    .then(ensurePublicDirectoriesExist)
    .then(migrateUserData)          // 数据迁移
    .then(migrateSystemPrompts)     // 系统提示词迁移
    .then(verifySecuritySettings)   // 安全验证
    .then(preSetupTasks)            // 插件加载等
    .then(apply404Middleware)
    .then(() => new ServerStartup(app, cliArgs).start())
    .then(postSetupTasks);

2. Express 应用配置

2.1 中间件链 Mermaid 流程图

2.2 中间件详细配置

2.2.1 安全类中间件

中间件用途配置要点
helmet安全响应头contentSecurityPolicy: false 禁用 CSP
cookie-session会话管理sameSite: 'lax', httpOnly: true, 动态 maxAge
csrf-syncCSRF 保护32字节 token,从 x-csrf-token header 读取
// Cookie Session 配置
app.use(cookieSession({
    name: getCookieSessionName(),        // session-{hostname_hash}
    sameSite: 'lax',
    httpOnly: true,
    maxAge: getSessionCookieAge(),       // 默认400天或配置值
    secret: getCookieSecret(dataRoot),   // 从文件读取或生成
}));

// CSRF 配置
const csrfSyncProtection = csrfSync({
    getTokenFromState: (req) => req.session.csrfToken,
    getTokenFromRequest: (req) => req.headers['x-csrf-token'],
    storeTokenInState: (req, token) => req.session.csrfToken = token,
    size: 32,
});

2.2.2 网络类中间件

中间件用途配置
compressionGzip 压缩默认配置
response-time响应时间统计默认配置
body-parser请求体解析limit: 500mb
cors跨域处理origin: 'null', methods: ['OPTIONS']

2.2.3 自定义安全中间件

// Basic Auth (config.yaml 配置)
if (cliArgs.listen && cliArgs.basicAuthMode) {
    app.use(basicAuthMiddleware);
}

// IP 白名单
if (cliArgs.whitelistMode) {
    const whitelistMiddleware = await getWhitelistMiddleware();
    app.use(whitelistMiddleware);
}

// Host 白名单 (DNS Rebinding 防护)
app.use(hostWhitelistMiddleware);

2.3 请求处理流水线

┌─────────────────────────────────────────────────────────────────┐
│                     Request Processing Flow                      │
├─────────────────────────────────────────────────────────────────┤
│  1. Security Layer                                               │
│     ├── helmet (安全头)                                          │
│     ├── hostWhitelist (Host验证)                                 │
│     └── basicAuth/whitelist (访问控制)                           │
│                                                                  │
│  2. Parsing Layer                                                │
│     ├── body-parser (请求体解析)                                 │
│     ├── cors (跨域处理)                                          │
│     └── cookie-session (会话初始化)                              │
│                                                                  │
│  3. Context Layer                                                │
│     ├── setUserDataMiddleware (用户数据注入)                     │
│     └── csrf-sync (CSRF验证)                                     │
│                                                                  │
│  4. Routing Layer                                                │
│     ├── Static Assets (webpack + express.static)                 │
│     ├── Public API (无需认证)                                    │
│     ├── Auth Middleware (认证检查)                               │
│     └── Private API (需要认证)                                   │
│                                                                  │
│  5. Error Handling                                               │
│     └── 404 Middleware (未匹配路由)                              │
└─────────────────────────────────────────────────────────────────┘

3. 路由系统架构

3.1 路由分层结构

3.2 Public API 详细列表

路由文件功能
GET /server-main.js主页,条件重定向到登录页
GET /loginserver-main.js登录页面
GET /callback/:sourceserver-main.jsOAuth PKCE 回调
GET /csrf-tokenserver-main.js获取 CSRF 令牌
GET /versionserver-main.js版本信息
/api/users/*users-public.js用户公开接口

users-public.js 接口详情:

POST /api/users/list       // 获取用户列表(支持 discreet 模式)
POST /api/users/login      // 用户登录(带速率限制)
POST /api/users/recover-step1  // 密码恢复第一步(发送验证码)
POST /api/users/recover-step2  // 密码恢复第二步(验证并重置密码)

3.3 Private API 详细列表

3.4 废弃端点重定向

server-startup.js 中 redirectDeprecatedEndpoints 函数处理 API 版本迁移:

// 旧端点 → 新端点 (308 永久重定向)
/createcharacter    → /api/characters/create
/renamecharacter    → /api/characters/rename
/editcharacter      → /api/characters/edit
/deletecharacter    → /api/characters/delete
/getcharacters      → /api/characters/all
/savechat           → /api/chats/save
/getchat            → /api/chats/get
// ... 更多重定向

4. 安全机制

4.1 多层安全防护架构

4.2 IP 白名单机制 (whitelist.js)

// 支持配置方式:
// 1. config.yaml whitelist 数组
// 2. whitelist.txt 文件
// 3. Docker 主机自动解析

function isIPInWhitelist(whitelist, ip) {
    return whitelist.some(x => ipMatching.matches(ip, ipMatching.getMatch(x)));
}

// 处理 X-Real-IP / X-Forwarded-For 头
const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false);

4.3 Host 白名单机制 (hostWhitelist.js)

防止 DNS Rebinding 攻击:

const hostWhitelistEnabled = getConfigValue('hostWhitelist.enabled', false);
const hostWhitelist = getConfigValue('hostWhitelist.hosts', []);
const hostWhitelistScan = getConfigValue('hostWhitelist.scan', false);

// 扫描模式:记录未授权 Host,但不阻止
// 启用模式:严格验证 Host 头

4.4 Basic Auth 机制 (basicAuth.js)

支持两种模式:

  1. 全局 Basic Auth:单用户认证
  2. Per-User Basic Auth:多用户映射到 Basic Auth
const usePerUserAuth = PER_USER_BASIC_AUTH && ENABLE_ACCOUNTS;

if (!usePerUserAuth && username === basicAuthUserName && password === basicAuthUserPassword) {
    return callback();  // 全局认证通过
} else if (usePerUserAuth) {
    // 验证用户名对应用户密码
    for (const userHandle of userHandles) {
        if (username === userHandle) {
            const user = await storage.getItem(toKey(userHandle));
            if (user && user.enabled && user.password === getPasswordHash(password, user.salt)) {
                return callback();
            }
        }
    }
}

4.5 CSRF 保护机制

// Token 生成与验证流程
1. 客户端请求 GET /csrf-token
2. 服务端生成 token,存入 req.session.csrfToken
3. 客户端后续请求携带 x-csrf-token header
4. 服务端验证:req.session.csrfToken === req.headers['x-csrf-token']

// 禁用方式:--disableCsrf 命令行参数
// 禁用时返回固定 token: 'disabled'

4.6 安全验证流程 (verifySecuritySettings)

export async function verifySecuritySettings() {
    // 监听模式安全检查
    if (!listen) return;  // localhost 模式跳过
    
    // 强制安全配置检查
    if (!ENABLE_ACCOUNTS) {
        logSecurityAlert('Enable whitelisting, basic authentication or user accounts.');
    }
    
    // 用户密码检查
    const unprotectedUsers = users.filter(x => !x.password);
    const unprotectedAdminUsers = unprotectedUsers.filter(x => x.admin);
    
    if (unprotectedAdminUsers.length > 0) {
        logSecurityAlert('Set a password for all admin users.');
    }
}

5. 多用户架构

5.1 用户数据隔离架构

5.2 用户目录结构 (USER_DIRECTORY_TEMPLATE)

目录键路径用途
root{handle}/用户根目录
characters{handle}/characters/角色卡片
chats{handle}/chats/个人聊天记录
groupChats{handle}/group chats/群组聊天记录
groups{handle}/groups/群组定义
worlds{handle}/worlds/世界信息 (World Info)
backgrounds{handle}/backgrounds/背景图片
avatars{handle}/User Avatars/用户头像
extensions{handle}/extensions/用户扩展
assets{handle}/assets/资源文件
thumbnails{handle}/thumbnails/缩略图缓存
settings{handle}/settings.json用户设置
secrets{handle}/secrets.json密钥存储

5.3 用户模型

interface User {
    handle: string;      // 唯一标识,用于目录名
    name: string;        // 显示名称
    created: number;     // 创建时间戳
    password: string;    // scrypt 哈希值
    salt: string;        // 密码盐值
    enabled: boolean;    // 是否启用
    admin: boolean;      // 是否管理员
}

interface UserDirectoryList {
    root: string;
    characters: string;
    chats: string;
    // ... 20+ 目录路径
}

// 请求对象扩展
interface Request {
    user?: {
        profile: User;
        directories: UserDirectoryList;
    };
}

5.4 用户认证流程

5.5 自动登录机制

支持多种自动登录方式:

// 1. 单用户无密码自动登录
async function singleUserLogin(request) {
    if (userHandles.length === 1) {
        const user = await storage.getItem(toKey(userHandles[0]));
        if (user && !user.password) {
            request.session.handle = userHandles[0];
            return true;
        }
    }
}

// 2. Authelia SSO 登录
async function autheliaUserLogin(request) {
    return headerUserLogin(request, 'Remote-User');
}

// 3. Authentik SSO 登录
async function authentikUserLogin(request) {
    return headerUserLogin(request, 'X-Authentik-Username');
}

// 4. Per-User Basic Auth 登录
async function basicUserLogin(request) {
    // 从 Authorization header 提取用户名
    // 验证密码后自动登录
}

5.6 用户数据中间件

export async function setUserDataMiddleware(request, response, next) {
    // 禁用用户系统时使用默认用户
    if (!ENABLE_ACCOUNTS) {
        request.user = {
            profile: DEFAULT_USER,
            directories: getUserDirectories(DEFAULT_USER.handle),
        };
        return next();
    }

    // 从会话获取用户
    const handle = request.session?.handle;
    if (!handle) return next();

    const user = await storage.getItem(toKey(handle));
    if (!user || !user.enabled) return next();

    request.user = {
        profile: user,
        directories: getUserDirectories(handle),
    };
    
    // 首页访问刷新会话
    if (request.method === 'GET' && request.path === '/') {
        request.session.touch = Date.now();
    }
    
    next();
}

5.7 多用户文件服务

// users.js 中的 Router 提供用户数据文件服务
export const router = express.Router();

router.use('/backgrounds/*', createRouteHandler(req => req.user.directories.backgrounds));
router.use('/characters/*', createRouteHandler(req => req.user.directories.characters));
router.use('/User%20Avatars/*', createRouteHandler(req => req.user.directories.avatars));
router.use('/assets/*', createRouteHandler(req => req.user.directories.assets));
router.use('/user/images/*', createRouteHandler(req => req.user.directories.userImages));
router.use('/scripts/extensions/third-party/*', createExtensionsRouteHandler(req => req.user.directories.extensions));

6. 核心模块交互关系


7. 总结

架构特点

  1. 分层清晰:启动流程、中间件、路由、安全机制分层明确
  2. 安全第一:多层安全防护,从网络层到应用层全面覆盖
  3. 多用户支持:完整的多用户隔离架构,支持多种认证方式
  4. 模块化设计:Endpoints 按功能模块化,易于扩展
  5. 向后兼容:废弃端点通过重定向保持兼容性

关键设计决策

决策说明
node-persist轻量级本地存储,适合单机部署
cookie-session无服务端会话存储,水平扩展友好
express.Router模块化路由,支持按功能拆分
helmet (CSP disabled)允许内联脚本,适合前端框架
数据根目录隔离所有用户数据与代码分离,便于备份和迁移