mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
Merge branch 'main' of https://github.com/dataease/SQLBot
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
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 未启用缓存, 可使用多进程模式")
|
||||
|
||||
+2
-3
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"password_reset_successful": "重置密码成功"
|
||||
},
|
||||
"dashboard": {
|
||||
"new_tab": "新建Tab",
|
||||
"length_limit64": "字段长度需要在1-64之间",
|
||||
"sort_column": "排序字段",
|
||||
"sort_type": "排序方式",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -53,10 +53,6 @@ export class Line extends BaseG2Chart {
|
||||
nice: true,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
tooltip: { series: series.length > 0, shared: true },
|
||||
},
|
||||
|
||||
children: [
|
||||
{
|
||||
type: 'line',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { type CanvasItem } from '@/utils/canvas.ts'
|
||||
import { ref, toRefs, type PropType } from 'vue'
|
||||
import { ref, toRefs, type PropType, computed } from 'vue'
|
||||
import ResizeHandle from '@/views/dashboard/canvas/ResizeHandle.vue'
|
||||
import DragHandle from '@/views/dashboard/canvas/DragHandle.vue'
|
||||
import ComponentBar from '@/views/dashboard/canvas/ComponentBar.vue'
|
||||
@@ -55,6 +55,8 @@ const shapeClick = (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
const dragDandleValue = computed(() => props.canEdit && !props.configItem.editing)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -78,7 +80,7 @@ const shapeClick = (e: MouseEvent) => {
|
||||
:canvas-id="canvasId"
|
||||
@enlarge-view="() => emits('enlargeView')"
|
||||
></component-bar>
|
||||
<template v-if="canEdit">
|
||||
<template v-if="dragDandleValue">
|
||||
<drag-handle></drag-handle>
|
||||
</template>
|
||||
<slot></slot>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const emits = defineEmits(['finish'])
|
||||
const resource = ref(null)
|
||||
const state = reactive({
|
||||
id: null,
|
||||
opt: null,
|
||||
@@ -84,21 +85,11 @@ const resourceFormRules = ref({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: state.placeholder,
|
||||
trigger: 'change',
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
message: state.placeholder,
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 1,
|
||||
max: 64,
|
||||
message: t('dashboard.length_limit64'),
|
||||
trigger: 'change',
|
||||
},
|
||||
{ required: true, trigger: 'blur' },
|
||||
],
|
||||
pid: [
|
||||
{
|
||||
@@ -131,23 +122,28 @@ const propsTree = {
|
||||
const showPid = false
|
||||
|
||||
const saveResource = () => {
|
||||
const params = {
|
||||
id: state.id,
|
||||
node_type: state.nodeType,
|
||||
name: resourceForm.name,
|
||||
opt: state.opt,
|
||||
pid: resourceForm.pid,
|
||||
type: 'dashboard',
|
||||
level: state.nodeType === 'folder' ? 0 : 1,
|
||||
}
|
||||
saveDashboardResource(params, function (rsp: any) {
|
||||
const messageTips = t('common.save_success')
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: messageTips,
|
||||
})
|
||||
emits('finish', { opt: state.opt, resourceId: rsp.id })
|
||||
resetForm()
|
||||
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
resource.value?.validate((result) => {
|
||||
if (result) {
|
||||
const params = {
|
||||
id: state.id,
|
||||
node_type: state.nodeType,
|
||||
name: resourceForm.name,
|
||||
opt: state.opt,
|
||||
pid: resourceForm.pid,
|
||||
type: 'dashboard',
|
||||
level: state.nodeType === 'folder' ? 0 : 1,
|
||||
}
|
||||
saveDashboardResource(params, function (rsp: any) {
|
||||
const messageTips = t('common.save_success')
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: messageTips,
|
||||
})
|
||||
emits('finish', { opt: state.opt, resourceId: rsp.id })
|
||||
resetForm()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ const state = reactive({
|
||||
function addTab() {
|
||||
const newTab = {
|
||||
name: guid('tab'),
|
||||
title: 'New Tab',
|
||||
title: t('dashboard.new_tab'),
|
||||
componentData: [],
|
||||
closable: true,
|
||||
}
|
||||
|
||||
@@ -124,6 +124,10 @@ onMounted(() => {
|
||||
overflow-y: auto !important;
|
||||
position: relative;
|
||||
padding: 12px !important;
|
||||
div::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
.rich-text-empty {
|
||||
position: absolute;
|
||||
width: calc(100% - 24px);
|
||||
|
||||
@@ -102,6 +102,10 @@ defineExpose({
|
||||
background: #fff;
|
||||
padding: 12px !important;
|
||||
border-radius: 12px;
|
||||
div::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
.header-bar {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
|
||||
@@ -94,6 +94,7 @@ const editCanvasName = () => {
|
||||
const closeEditCanvasName = () => {
|
||||
nameEdit.value = false
|
||||
if (!inputName.value || !inputName.value.trim()) {
|
||||
ElMessage.warning(t('dashboard.length_1_64_characters'))
|
||||
return
|
||||
}
|
||||
if (inputName.value.trim() === dashboardInfo.value.name) {
|
||||
|
||||
@@ -36,6 +36,8 @@ const addComponent = (componentType: string, viewInfo?: any) => {
|
||||
const subTabName = guid('tab')
|
||||
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
component.propValue[0].name = subTabName
|
||||
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
component.propValue[0].title = t('dashboard.new_tab')
|
||||
component.activeTabName = subTabName
|
||||
}
|
||||
component.y = 100
|
||||
|
||||
Reference in New Issue
Block a user