feat: 增强 WebUI 配置,支持防爬虫和安全 Cookie 设置

pull/1445/head
墨梓柒 2025-12-19 00:44:22 +08:00
parent d3b71a7181
commit 3231995ebd
No known key found for this signature in database
GPG Key ID: 4A65B9DBA35F7635
10 changed files with 107 additions and 47 deletions

View File

@ -75,7 +75,6 @@ max_embedding_workers = 12 # 嵌入/抽取并发线程数
embedding_chunk_size = 16 # 每批嵌入的条数 embedding_chunk_size = 16 # 每批嵌入的条数
info_extraction_workers = 3 # 实体抽取同时执行线程数 info_extraction_workers = 3 # 实体抽取同时执行线程数
enable_ppr = true # 是否启用PPR低配机器可关闭 enable_ppr = true # 是否启用PPR低配机器可关闭
ppr_node_cap = 8000 # 图节点数超过该值时自动跳过PPR
``` ```
- `embedding_dimension` - `embedding_dimension`
@ -101,8 +100,6 @@ ppr_node_cap = 8000 # 图节点数超过该值时自动跳过PPR
- `true`:检索会结合向量+知识图,效果更好,但略慢; - `true`:检索会结合向量+知识图,效果更好,但略慢;
- `false`:只用向量检索,牺牲一定效果,性能更稳定。 - `false`:只用向量检索,牺牲一定效果,性能更稳定。
- `ppr_node_cap`
安全阈值:当图节点数超过阈值时自动跳过 PPR以避免“大图”导致卡顿。
> 调参建议: > 调参建议:
> - 若导入/检索阶段机器明显“顶不住”(>=1MB的大文本且分配配置<4C > - 若导入/检索阶段机器明显“顶不住”(>=1MB的大文本且分配配置<4C

View File

@ -42,7 +42,10 @@ def lpmm_start_up(): # sourcery skip: extract-duplicate-method
logger.info("创建LLM客户端") logger.info("创建LLM客户端")
# 初始化Embedding库 # 初始化Embedding库
embed_manager = EmbeddingManager() embed_manager = EmbeddingManager(
max_workers=global_config.lpmm_knowledge.max_embedding_workers,
chunk_size=global_config.lpmm_knowledge.embedding_chunk_size,
)
logger.info("正在从文件加载Embedding库") logger.info("正在从文件加载Embedding库")
try: try:
embed_manager.load_from_file() embed_manager.load_from_file()

View File

@ -358,12 +358,9 @@ class KGManager:
paragraph_search_result: ParagraphEmbedding的搜索结果paragraph_hash, similarity paragraph_search_result: ParagraphEmbedding的搜索结果paragraph_hash, similarity
embed_manager: EmbeddingManager对象 embed_manager: EmbeddingManager对象
""" """
# 性能保护:关闭或超限时直接返回向量检索结果(仅基于节点规模与开关) # 性能保护:关闭时直接返回向量检索结果
if ( if not global_config.lpmm_knowledge.enable_ppr:
not global_config.lpmm_knowledge.enable_ppr logger.info("PPR 已禁用,使用纯向量检索结果")
or len(self.graph.get_node_list()) > global_config.lpmm_knowledge.ppr_node_cap
):
logger.info("PPR 已禁用或超出阈值,使用纯向量检索结果")
return paragraph_search_result, None return paragraph_search_result, None
# 图中存在的节点总集 # 图中存在的节点总集
existed_nodes = self.graph.get_node_list() existed_nodes = self.graph.get_node_list()

View File

@ -34,6 +34,7 @@ from src.config.official_configs import (
MemoryConfig, MemoryConfig,
DebugConfig, DebugConfig,
DreamConfig, DreamConfig,
WebUIConfig,
) )
from .api_ada_configs import ( from .api_ada_configs import (
@ -347,6 +348,7 @@ class Config(ConfigBase):
response_post_process: ResponsePostProcessConfig response_post_process: ResponsePostProcessConfig
response_splitter: ResponseSplitterConfig response_splitter: ResponseSplitterConfig
telemetry: TelemetryConfig telemetry: TelemetryConfig
webui: WebUIConfig
experimental: ExperimentalConfig experimental: ExperimentalConfig
maim_message: MaimMessageConfig maim_message: MaimMessageConfig
lpmm_knowledge: LPMMKnowledgeConfig lpmm_knowledge: LPMMKnowledgeConfig

View File

@ -597,6 +597,38 @@ class TelemetryConfig(ConfigBase):
"""是否启用遥测""" """是否启用遥测"""
@dataclass
class WebUIConfig(ConfigBase):
"""WebUI配置类"""
enabled: bool = True
"""是否启用WebUI"""
mode: Literal["development", "production"] = "production"
"""运行模式development(开发) 或 production(生产)"""
host: str = "0.0.0.0"
"""WebUI服务器监听地址"""
port: int = 8001
"""WebUI服务器端口"""
anti_crawler_mode: Literal["false", "strict", "loose", "basic"] = "basic"
"""防爬虫模式false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止)"""
allowed_ips: str = "127.0.0.1"
"""IP白名单逗号分隔支持精确IP、CIDR格式和通配符"""
trusted_proxies: str = ""
"""信任的代理IP列表逗号分隔只有来自这些IP的X-Forwarded-For才被信任"""
trust_xff: bool = False
"""是否启用X-Forwarded-For代理解析默认false"""
secure_cookie: bool = False
"""是否启用安全Cookie仅通过HTTPS传输默认false"""
@dataclass @dataclass
class DebugConfig(ConfigBase): class DebugConfig(ConfigBase):
"""调试配置类""" """调试配置类"""
@ -722,6 +754,18 @@ class LPMMKnowledgeConfig(ConfigBase):
embedding_dimension: int = 1024 embedding_dimension: int = 1024
"""嵌入向量维度,应该与模型的输出维度一致""" """嵌入向量维度,应该与模型的输出维度一致"""
max_embedding_workers: int = 3
"""嵌入/抽取并发线程数"""
embedding_chunk_size: int = 4
"""每批嵌入的条数"""
max_synonym_entities: int = 2000
"""同义边参与的实体数上限,超限则跳过"""
enable_ppr: bool = True
"""是否启用PPR低配机器可关闭"""
@dataclass @dataclass
class DreamConfig(ConfigBase): class DreamConfig(ConfigBase):

View File

@ -44,10 +44,9 @@ class MainSystem:
def _setup_webui_server(self): def _setup_webui_server(self):
"""设置独立的 WebUI 服务器""" """设置独立的 WebUI 服务器"""
import os from src.config.config import global_config
webui_enabled = os.getenv("WEBUI_ENABLED", "false").lower() == "true" if not global_config.webui.enabled:
if not webui_enabled:
logger.info("WebUI 已禁用") logger.info("WebUI 已禁用")
return return

View File

@ -124,10 +124,8 @@ SCANNER_SPECIFIC_HEADERS = {
# strict: 严格模式(更严格的检测,更低的频率限制) # strict: 严格模式(更严格的检测,更低的频率限制)
# loose: 宽松模式(较宽松的检测,较高的频率限制) # loose: 宽松模式(较宽松的检测,较高的频率限制)
# basic: 基础模式只记录恶意访问不阻止不限制请求数不跟踪IP # basic: 基础模式只记录恶意访问不阻止不限制请求数不跟踪IP
ANTI_CRAWLER_MODE = os.getenv("WEBUI_ANTI_CRAWLER_MODE", "basic").lower()
# IP白名单配置从配置文件读取逗号分隔
# IP白名单配置从环境变量读取逗号分隔
# 支持格式: # 支持格式:
# - 精确IP127.0.0.1, 192.168.1.100 # - 精确IP127.0.0.1, 192.168.1.100
# - CIDR格式192.168.1.0/24, 172.17.0.0/16 (适用于Docker网络) # - CIDR格式192.168.1.0/24, 172.17.0.0/16 (适用于Docker网络)
@ -236,13 +234,23 @@ def _convert_wildcard_to_regex(wildcard_pattern: str) -> Optional[str]:
return regex return regex
ALLOWED_IPS = _parse_allowed_ips(os.getenv("WEBUI_ALLOWED_IPS", "")) # 从配置读取防爬虫设置(延迟导入避免循环依赖)
def _get_anti_crawler_config():
"""获取防爬虫配置"""
from src.config.config import global_config
return {
'mode': global_config.webui.anti_crawler_mode,
'allowed_ips': _parse_allowed_ips(global_config.webui.allowed_ips),
'trusted_proxies': _parse_allowed_ips(global_config.webui.trusted_proxies),
'trust_xff': global_config.webui.trust_xff
}
# 信任的代理IP配置从环境变量读取逗号分隔 # 初始化配置(将在模块加载时执行)
# 只有在信任的代理IP下才使用X-Forwarded-For头 _config = _get_anti_crawler_config()
# 默认关闭(空),不信任任何代理 ANTI_CRAWLER_MODE = _config['mode']
TRUSTED_PROXIES = _parse_allowed_ips(os.getenv("WEBUI_TRUSTED_PROXIES", "")) ALLOWED_IPS = _config['allowed_ips']
TRUST_XFF = os.getenv("WEBUI_TRUST_XFF", "false").lower() == "true" TRUSTED_PROXIES = _config['trusted_proxies']
TRUST_XFF = _config['trust_xff']
def _get_mode_config(mode: str) -> dict: def _get_mode_config(mode: str) -> dict:

View File

@ -3,10 +3,10 @@ WebUI 认证模块
提供统一的认证依赖支持 Cookie Header 两种方式 提供统一的认证依赖支持 Cookie Header 两种方式
""" """
import os
from typing import Optional from typing import Optional
from fastapi import HTTPException, Cookie, Header, Response, Request from fastapi import HTTPException, Cookie, Header, Response, Request
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import global_config
from .token_manager import get_token_manager from .token_manager import get_token_manager
logger = get_logger("webui.auth") logger = get_logger("webui.auth")
@ -23,23 +23,18 @@ def _is_secure_environment() -> bool:
Returns: Returns:
bool: 如果应该使用 secure cookie 则返回 True bool: 如果应该使用 secure cookie 则返回 True
""" """
# 检查环境变量 # 从配置读取
secure_cookie_env = os.environ.get("WEBUI_SECURE_COOKIE", "") if global_config.webui.secure_cookie:
if secure_cookie_env.lower() in ("true", "1", "yes"): logger.info("配置中启用了 secure_cookie")
logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},启用 secure cookie")
return True return True
if secure_cookie_env.lower() in ("false", "0", "no"):
logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},禁用 secure cookie")
return False
# 检查是否是生产环境 # 检查是否是生产环境
env = os.environ.get("WEBUI_MODE", "").lower() if global_config.webui.mode == "production":
if env in ("production", "prod"): logger.info("WebUI运行在生产模式启用 secure cookie")
logger.info(f"WEBUI_MODE 设置为 {env},启用 secure cookie")
return True return True
# 默认:开发环境不启用(因为通常是 HTTP # 默认:开发环境不启用(因为通常是 HTTP
logger.debug(f"未设置特殊环境变量 (WEBUI_SECURE_COOKIE={secure_cookie_env}, WEBUI_MODE={env}),禁用 secure cookie") logger.debug("WebUI运行在开发模式,禁用 secure cookie")
return False return False
@ -111,7 +106,7 @@ def set_auth_cookie(response: Response, token: str, request: Optional[Request] =
logger.warning("=" * 80) logger.warning("=" * 80)
logger.warning("检测到 HTTP 连接但环境配置要求 HTTPS (secure cookie)") logger.warning("检测到 HTTP 连接但环境配置要求 HTTPS (secure cookie)")
logger.warning("已自动禁用 secure 标志以允许登录,但建议修改配置:") logger.warning("已自动禁用 secure 标志以允许登录,但建议修改配置:")
logger.warning("1. 在 .env 文件中设置: WEBUI_SECURE_COOKIE=false") logger.warning("1. 在配置文件中设置: webui.secure_cookie = false")
logger.warning("2. 如果使用反向代理,请确保正确配置 X-Forwarded-Proto 头") logger.warning("2. 如果使用反向代理,请确保正确配置 X-Forwarded-Proto 头")
logger.warning("=" * 80) logger.warning("=" * 80)
is_secure = False is_secure = False

View File

@ -1,6 +1,5 @@
"""独立的 WebUI 服务器 - 运行在 0.0.0.0:8001""" """独立的 WebUI 服务器 - 运行在 0.0.0.0:8001"""
import os
import asyncio import asyncio
import mimetypes import mimetypes
from pathlib import Path from pathlib import Path
@ -130,9 +129,10 @@ class WebUIServer:
"""配置防爬虫中间件""" """配置防爬虫中间件"""
try: try:
from src.webui.anti_crawler import AntiCrawlerMiddleware from src.webui.anti_crawler import AntiCrawlerMiddleware
from src.config.config import global_config
# 从环境变量读取防爬虫模式false/strict/loose/basic # 从配置读取防爬虫模式
anti_crawler_mode = os.getenv("WEBUI_ANTI_CRAWLER_MODE", "basic").lower() anti_crawler_mode = global_config.webui.anti_crawler_mode
# 注意:中间件按注册顺序反向执行,所以先注册的中间件后执行 # 注意:中间件按注册顺序反向执行,所以先注册的中间件后执行
# 我们需要在CORS之前注册这样防爬虫检查会在CORS之前执行 # 我们需要在CORS之前注册这样防爬虫检查会在CORS之前执行
@ -186,7 +186,7 @@ class WebUIServer:
error_msg = f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用" error_msg = f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用"
logger.error(error_msg) logger.error(error_msg)
logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}") logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}")
logger.error("💡 可以通过环境变量 WEBUI_PORT 修改 WebUI 端口") logger.error("💡 可以在配置文件中修改 webui.port 来更改 WebUI 端口")
logger.error(f"💡 Windows 用户可以运行: netstat -ano | findstr :{self.port}") logger.error(f"💡 Windows 用户可以运行: netstat -ano | findstr :{self.port}")
logger.error(f"💡 Linux/Mac 用户可以运行: lsof -i :{self.port}") logger.error(f"💡 Linux/Mac 用户可以运行: lsof -i :{self.port}")
raise OSError(f"端口 {self.port} 已被占用,无法启动 WebUI 服务器") raise OSError(f"端口 {self.port} 已被占用,无法启动 WebUI 服务器")
@ -212,7 +212,7 @@ class WebUIServer:
if "address already in use" in str(e).lower() or e.errno in (98, 10048): # 98: Linux, 10048: Windows if "address already in use" in str(e).lower() or e.errno in (98, 10048): # 98: Linux, 10048: Windows
logger.error(f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用") logger.error(f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用")
logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}") logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}")
logger.error("💡 可以通过环境变量 WEBUI_PORT 修改 WebUI 端口") logger.error("💡 可以在配置文件中修改 webui.port 来更改 WebUI 端口")
else: else:
logger.error(f"❌ WebUI 服务器启动失败 (网络错误): {e}") logger.error(f"❌ WebUI 服务器启动失败 (网络错误): {e}")
raise raise
@ -257,8 +257,9 @@ def get_webui_server() -> WebUIServer:
"""获取全局 WebUI 服务器实例""" """获取全局 WebUI 服务器实例"""
global _webui_server global _webui_server
if _webui_server is None: if _webui_server is None:
# 从环境变量读取配置 # 从配置读取
host = os.getenv("WEBUI_HOST", "0.0.0.0") from src.config.config import global_config
port = int(os.getenv("WEBUI_PORT", "8001")) host = global_config.webui.host
port = global_config.webui.port
_webui_server = WebUIServer(host=host, port=port) _webui_server = WebUIServer(host=host, port=port)
return _webui_server return _webui_server

View File

@ -1,5 +1,5 @@
[inner] [inner]
version = "7.2.2" version = "7.2.3"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
# 如果你想要修改配置文件请递增version的值 # 如果你想要修改配置文件请递增version的值
@ -191,8 +191,6 @@ max_embedding_workers = 3 # 嵌入/抽取并发线程数
embedding_chunk_size = 4 # 每批嵌入的条数 embedding_chunk_size = 4 # 每批嵌入的条数
max_synonym_entities = 2000 # 同义边参与的实体数上限,超限则跳过 max_synonym_entities = 2000 # 同义边参与的实体数上限,超限则跳过
enable_ppr = true # 是否启用PPR低配机器可关闭 enable_ppr = true # 是否启用PPR低配机器可关闭
ppr_node_cap = 8000 # 图节点数超过该值时跳过PPR
webui_graph_default_limit = 200 # WebUI /graph 默认返回的最大节点数,避免大图负载
[keyword_reaction] [keyword_reaction]
keyword_rules = [ keyword_rules = [
@ -260,6 +258,22 @@ api_server_allowed_api_keys = [] # 新版API Server允许的API Key列表
[telemetry] #发送统计信息,主要是看全球有多少只麦麦 [telemetry] #发送统计信息,主要是看全球有多少只麦麦
enable = true enable = true
[webui] # WebUI 独立服务器配置
enabled = true # 是否启用WebUI
mode = "production" # 模式: development(开发) 或 production(生产)
host = "0.0.0.0" # WebUI 服务器监听地址
port = 8001 # WebUI 服务器端口
# 防爬虫配置
anti_crawler_mode = "basic" # 防爬虫模式: false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止)
allowed_ips = "127.0.0.1" # IP白名单逗号分隔支持精确IP、CIDR格式和通配符
# 示例: 127.0.0.1,192.168.1.0/24,172.17.0.0/16
trusted_proxies = "" # 信任的代理IP列表逗号分隔只有来自这些IP的X-Forwarded-For才被信任
# 示例: 127.0.0.1,192.168.1.1,172.17.0.1
trust_xff = false # 是否启用X-Forwarded-For代理解析默认false
# 启用后仍要求直连IP在trusted_proxies中才会信任XFF头
secure_cookie = false # 是否启用安全Cookie仅通过HTTPS传输默认false
[experimental] #实验性功能 [experimental] #实验性功能
# 为指定聊天添加额外的prompt配置 # 为指定聊天添加额外的prompt配置
# 格式: ["platform:id:type:prompt内容", ...] # 格式: ["platform:id:type:prompt内容", ...]