diff --git a/.env.example b/.env.example index 10f5bd88..aa73850c 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,9 @@ LOG_DIR="/opt/sqlbot/logs" LOG_FORMAT="%(asctime)s - %(name)s - %(levelname)s:%(lineno)d - %(message)s" SQL_DEBUG=False +CACHE_TYPE="memory" +CACHE_REDIS_URL="redis://127.0.0.1:6379" + # Postgres POSTGRES_SERVER=localhost POSTGRES_PORT=5432 diff --git a/backend/common/core/config.py b/backend/common/core/config.py index f9026b09..482f4d61 100644 --- a/backend/common/core/config.py +++ b/backend/common/core/config.py @@ -61,6 +61,9 @@ class Settings(BaseSettings): DEFAULT_PWD: str = "SQLBot@123456" ASSISTANT_TOKEN_KEY: str = "X-SQLBOT-ASSISTANT-TOKEN" + CACHE_TYPE: Literal["redis", "memory", "None"] = "memory" + CACHE_REDIS_URL: str | None = None # Redis URL, e.g., "redis://[[username]:[password]]@localhost:6379/0" + LOG_LEVEL: str = "INFO" # DEBUG, INFO, WARNING, ERROR LOG_DIR: str = "logs" LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s:%(lineno)d - %(message)s" diff --git a/backend/common/core/sqlbot_cache.py b/backend/common/core/sqlbot_cache.py index 15d58308..02cd2d82 100644 --- a/backend/common/core/sqlbot_cache.py +++ b/backend/common/core/sqlbot_cache.py @@ -7,11 +7,9 @@ from contextlib import asynccontextmanager import asyncio import random from collections import defaultdict - -from apps.system.schemas.auth import CacheName -from apps.system.schemas.system_schema import UserInfoDTO +from common.core.config import settings from common.utils.utils import SQLBotLogUtil - +from fastapi_cache.backends.inmemory import InMemoryBackend # 使用contextvar来跟踪当前线程已持有的锁 _held_locks = contextvars.ContextVar('held_locks', default=set()) # 高效锁管理器 @@ -105,6 +103,8 @@ def cache( @wraps(func) async def wrapper(*args, **kwargs): + if not settings.CACHE_TYPE or settings.CACHE_TYPE.lower() == "none": + return await func(*args, **kwargs) # 生成缓存键 cache_key = used_key_builder( func=func, @@ -116,13 +116,9 @@ def cache( # 防击穿锁 async with _get_cache_lock(cache_key): backend = FastAPICache.get_backend() - # 双重检查 if (cached := await backend.get(cache_key)) is not None: SQLBotLogUtil.debug(f"Cache hit: {cache_key}") - if CacheName.USER_INFO.value in cache_key: - user = UserInfoDTO.model_validate(cached) - SQLBotLogUtil.info(f"User cache hit: [uid: {user.id}, account: {user.account}, oid: {user.oid}]") return cached # 执行函数并缓存结果 @@ -134,9 +130,6 @@ def cache( await backend.set(cache_key, result, actual_expire) SQLBotLogUtil.debug(f"Cache set: {cache_key} (expire: {actual_expire}s)") - if CacheName.USER_INFO.value in cache_key: - user = UserInfoDTO.model_validate(result) - SQLBotLogUtil.info(f"User cache set: [uid: {user.id}, account: {user.account}, oid: {user.oid}]") return result return wrapper @@ -152,6 +145,8 @@ def clear_cache( def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): + if not settings.CACHE_TYPE or settings.CACHE_TYPE.lower() == "none": + return await func(*args, **kwargs) cache_key = custom_key_builder( func=func, namespace=namespace, @@ -170,8 +165,27 @@ def clear_cache( result = await func(*args, **kwargs) if await backend.get(cache_key): await backend.clear(cache_key) - SQLBotLogUtil.info(f"Cache cleared: {cache_key}") + SQLBotLogUtil.debug(f"Cache cleared: {cache_key}") return result return wrapper - return decorator \ No newline at end of file + return decorator + + +def init_sqlbot_cache(): + cache_type: str = settings.CACHE_TYPE + if cache_type == "memory": + FastAPICache.init(InMemoryBackend()) + SQLBotLogUtil.info("SQLBot 使用内存缓存, 仅支持单进程模式") + elif cache_type == "redis": + from fastapi_cache.backends.redis import RedisBackend + import redis.asyncio as redis + from redis.asyncio.connection import ConnectionPool + redis_url = settings.CACHE_REDIS_URL or "redis://localhost:6379/0" + pool = ConnectionPool.from_url(url=redis_url) + redis_client = redis.Redis(connection_pool=pool) + FastAPICache.init(RedisBackend(redis_client), prefix="sqlbot-cache") + SQLBotLogUtil.info(f"SQLBot 使用Redis缓存, 可使用多进程模式") + else: + SQLBotLogUtil.warning("SQLBot 未启用缓存, 可使用多进程模式") + \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 54410660..c4571893 100644 --- a/backend/main.py +++ b/backend/main.py @@ -14,10 +14,9 @@ from alembic import command from fastapi_mcp import FastApiMCP from fastapi.staticfiles import StaticFiles import sqlbot_xpack -from fastapi_cache import FastAPICache -from fastapi_cache.backends.inmemory import InMemoryBackend from common.utils.utils import SQLBotLogUtil +from common.core.sqlbot_cache import init_sqlbot_cache def run_migrations(): alembic_cfg = Config("alembic.ini") @@ -27,7 +26,7 @@ def run_migrations(): @asynccontextmanager async def lifespan(app: FastAPI): run_migrations() - FastAPICache.init(InMemoryBackend()) + init_sqlbot_cache() init_dynamic_cors(app) SQLBotLogUtil.info("✅ SQLBot 初始化完成") yield diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 093e01ce..1de57be8 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "sqlbot-xpack==0.0.3.7", "fastapi-cache2>=0.2.2", "sqlparse>=0.5.3", + "redis>=6.2.0", ] [[tool.uv.index]] name = "default" diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index ee534d96..383cddca 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -32,6 +32,7 @@ "password_reset_successful": "Password reset successful" }, "dashboard": { + "new_tab": "New Tab", "length_limit64": "Field length must be between 1 and 64", "sort_column": "Sort field", "sort_type": "Sort type", diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 6ceb58e2..ca01a0fb 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -34,6 +34,7 @@ "password_reset_successful": "重置密码成功" }, "dashboard": { + "new_tab": "新建Tab", "length_limit64": "字段长度需要在1-64之间", "sort_column": "排序字段", "sort_type": "排序方式", diff --git a/frontend/src/style.less b/frontend/src/style.less index 685e6f51..4680870a 100644 --- a/frontend/src/style.less +++ b/frontend/src/style.less @@ -183,3 +183,11 @@ body { .ed-dialog { border-radius: 12px !important; } + +.tox-tinymce-inline { + z-index: 10 !important; +} + +.ed-message-box__container { + display: block!important; +} diff --git a/frontend/src/views/chat/component/charts/Line.ts b/frontend/src/views/chat/component/charts/Line.ts index bbf0424e..d28f29b9 100644 --- a/frontend/src/views/chat/component/charts/Line.ts +++ b/frontend/src/views/chat/component/charts/Line.ts @@ -53,10 +53,6 @@ export class Line extends BaseG2Chart { nice: true, }, }, - interaction: { - tooltip: { series: series.length > 0, shared: true }, - }, - children: [ { type: 'line', diff --git a/frontend/src/views/dashboard/canvas/CanvasCore.vue b/frontend/src/views/dashboard/canvas/CanvasCore.vue index 37b806c0..9eb1c755 100644 --- a/frontend/src/views/dashboard/canvas/CanvasCore.vue +++ b/frontend/src/views/dashboard/canvas/CanvasCore.vue @@ -16,7 +16,7 @@ const { t } = useI18n() const dashboardStore = dashboardStoreWithOut() const canvasLocked = ref(false) // Is the canvas movement locked, Default false const emits = defineEmits(['parentAddItemBox']) -const { curComponentId, fullscreenFlag } = storeToRefs(dashboardStore) +const { curComponentId, curComponent, fullscreenFlag } = storeToRefs(dashboardStore) let currentInstance: any const props = defineProps({ canvasId: { @@ -409,6 +409,10 @@ function removeItemById(id: number) { const index = canvasComponentData.value.findIndex((item) => item.id === id) if (index >= 0) { removeItem(index) + renderOk.value = false + nextTick(() => { + renderOk.value = true + }) } } @@ -736,8 +740,13 @@ function containerMouseDown(e: MouseEvent) { } infoBox.value.startX = e.pageX infoBox.value.startY = e.pageY - e.preventDefault() - e.stopPropagation() + // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment + if (isMainCanvas(props.canvasId) && curComponent.value?.editing) { + // do SQtext + } else { + e.preventDefault() + e.stopPropagation() + } } function getNowPosition(addSizeX: number, addSizeY: number, moveXSize: number, moveYSize: number) { diff --git a/frontend/src/views/dashboard/canvas/CanvasShape.vue b/frontend/src/views/dashboard/canvas/CanvasShape.vue index 3d93959f..f49de87f 100644 --- a/frontend/src/views/dashboard/canvas/CanvasShape.vue +++ b/frontend/src/views/dashboard/canvas/CanvasShape.vue @@ -1,6 +1,6 @@