feat: support system variable

This commit is contained in:
junjun
2026-02-05 10:15:46 +08:00
parent e930d68331
commit d9129073b9
15 changed files with 447 additions and 50 deletions
@@ -0,0 +1,82 @@
"""062_system_variable
Revision ID: ed947895d470
Revises: 547df942eb90
Create Date: 2026-01-26 10:16:59.877303
"""
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from alembic import op
from sqlalchemy import String, BigInteger, DateTime
from sqlalchemy.dialects import postgresql
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.sql import table, column
# revision identifiers, used by Alembic.
revision = 'ed947895d470'
down_revision = 'c8751179a8de'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('system_variable',
sa.Column('id', sa.BigInteger(), sa.Identity(always=True), nullable=False),
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=128), nullable=False),
sa.Column('var_type', sqlmodel.sql.sqltypes.AutoString(length=128), nullable=False),
sa.Column('type', sqlmodel.sql.sqltypes.AutoString(length=128), nullable=False),
sa.Column('value', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('create_time', sa.DateTime(), nullable=True),
sa.Column('create_by', sa.BigInteger(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
variable_table = table(
"system_variable",
column("id", BigInteger),
column("name", String),
column("var_type", String),
column("type", String),
column("value", JSONB),
column("create_time", DateTime),
column("create_by", BigInteger),
)
op.bulk_insert(
variable_table,
[
{
"name": "i18n_variable.name",
"var_type": "text",
"type": "system",
"value": ["name"],
"create_time": None,
"create_by": None
},
{
"name": "i18n_variable.account",
"var_type": "text",
"type": "system",
"value": ["account"],
"create_time": None,
"create_by": None
},
{
"name": "i18n_variable.email",
"var_type": "text",
"type": "system",
"value": ["email"],
"create_time": None,
"create_by": None
}
]
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('system_variable')
# ### end Alembic commands ###
@@ -0,0 +1,29 @@
"""063_user
Revision ID: 8ff90df7871d
Revises: ed947895d470
Create Date: 2026-01-26 15:30:11.348083
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '8ff90df7871d'
down_revision = 'ed947895d470'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('sys_user', sa.Column('system_variables', postgresql.JSONB(astext_type=sa.Text()), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('sys_user', 'system_variables')
# ### end Alembic commands ###
+3 -1
View File
@@ -5,7 +5,7 @@ from apps.dashboard.api import dashboard_api
from apps.data_training.api import data_training
from apps.datasource.api import datasource, table_relation, recommended_problem
from apps.mcp import mcp
from apps.system.api import login, user, aimodel, workspace, assistant, parameter, apikey
from apps.system.api import login, user, aimodel, workspace, assistant, parameter, apikey, variable_api
from apps.terminology.api import terminology
from apps.settings.api import base
#from audit.api import audit_api
@@ -30,4 +30,6 @@ api_router.include_router(apikey.router)
api_router.include_router(recommended_problem.router)
api_router.include_router(variable_api.router)
#api_router.include_router(audit_api.router)
+130 -35
View File
@@ -4,10 +4,12 @@
from typing import List, Dict
from apps.datasource.models.datasource import CoreField, CoreDatasource
from apps.db.constant import DB
from common.core.deps import SessionDep
from apps.system.models.system_variable_model import SystemVariable
from common.core.deps import SessionDep, CurrentUser
def transFilterTree(session: SessionDep, tree_list: List[any], ds: CoreDatasource) -> str | None:
def transFilterTree(session: SessionDep, current_user: CurrentUser, tree_list: List[any],
ds: CoreDatasource) -> str | None:
if tree_list is None:
return None
res: List[str] = []
@@ -15,13 +17,13 @@ def transFilterTree(session: SessionDep, tree_list: List[any], ds: CoreDatasourc
tree = dto.tree
if tree is None:
continue
tree_exp = transTreeToWhere(session, tree, ds)
tree_exp = transTreeToWhere(session, current_user, tree, ds)
if tree_exp is not None:
res.append(tree_exp)
return " AND ".join(res)
def transTreeToWhere(session: SessionDep, tree: any, ds: CoreDatasource) -> str | None:
def transTreeToWhere(session: SessionDep, current_user: CurrentUser, tree: any, ds: CoreDatasource) -> str | None:
if tree is None:
return None
logic = tree['logic']
@@ -32,16 +34,16 @@ def transTreeToWhere(session: SessionDep, tree: any, ds: CoreDatasource) -> str
for item in items:
exp: str = None
if item['type'] == 'item':
exp = transTreeItem(session, item, ds)
exp = transTreeItem(session, current_user, item, ds)
elif item['type'] == 'tree':
exp = transTreeToWhere(session, item['sub_tree'], ds)
exp = transTreeToWhere(session, current_user, item['sub_tree'], ds)
if exp is not None:
list.append(exp)
return '(' + f' {logic} '.join(list) + ')' if len(list) > 0 else None
def transTreeItem(session: SessionDep, item: Dict, ds: CoreDatasource) -> str | None:
def transTreeItem(session: SessionDep, current_user: CurrentUser, item: Dict, ds: CoreDatasource) -> str | None:
res: str = None
field = session.query(CoreField).filter(CoreField.id == int(item['field_id'])).first()
if field is None:
@@ -49,6 +51,8 @@ def transTreeItem(session: SessionDep, item: Dict, ds: CoreDatasource) -> str |
db = DB.get_db(ds.type)
whereName = db.prefix + field.field_name + db.suffix
whereTerm = transFilterTerm(item['term'])
if item['filter_type'] == 'enum':
if len(item['enum_value']) > 0:
if ds['type'] == 'sqlServer' and (
@@ -57,38 +61,113 @@ def transTreeItem(session: SessionDep, item: Dict, ds: CoreDatasource) -> str |
else:
res = "(" + whereName + " IN ('" + "','".join(item['enum_value']) + "'))"
else:
value = item['value']
whereTerm = transFilterTerm(item['term'])
whereValue = ''
# if system variable, do check and get value
# new field: value_type(variable or normal), variable_id
value_type = item.get('value_type')
if value_type and value_type == 'variable':
# get system variable
variable_id = item.get('variable_id')
if variable_id is not None:
sys_variable = session.query(SystemVariable).filter(SystemVariable.id == variable_id).first()
# do inner system variable
if sys_variable.type == 'system':
res = whereName + whereTerm + getSysVariableValue(sys_variable, current_user)
else:
# check user variable
user_variables = current_user.system_variables
if user_variables is None or len(user_variables) == 0 or not userHaveVariable(user_variables,
sys_variable):
return None
else:
# get user variable
u_variable = None
for u in user_variables:
if u.get('variableId') == sys_variable.id:
u_variable = u
break
if u_variable is None:
return None
if item['term'] == 'null':
whereValue = ''
elif item['term'] == 'not_null':
whereValue = ''
elif item['term'] == 'empty':
whereValue = "''"
elif item['term'] == 'not_empty':
whereValue = "''"
elif item['term'] == 'in' or item['term'] == 'not in':
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = "(N'" + "', N'".join(value.split(",")) + "')"
# check value
values = u_variable.get('variableValues')
if sys_variable.var_type == 'text':
set_sys = set(sys_variable.value)
values = [x for x in values if x in set_sys]
if values is None or len(values) == 0:
return None
elif sys_variable.var_type == 'number':
if (sys_variable.value[0] is not None and values[0] < sys_variable.value[0]) or (
sys_variable.value[1] is not None and values[0] > sys_variable.value[1]):
return None
elif sys_variable.var_type == 'datetime':
if (sys_variable.value[0] is not None and values[0] < sys_variable.value[0]) or (
sys_variable.value[1] is not None and values[0] > sys_variable.value[1]):
return None
# build exp
whereValue = ''
if item['term'] == 'null':
whereValue = ''
elif item['term'] == 'not_null':
whereValue = ''
elif item['term'] == 'empty':
whereValue = "''"
elif item['term'] == 'not_empty':
whereValue = "''"
elif item['term'] == 'in' or item['term'] == 'not in':
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = "(N'" + "', N'".join(values) + "')"
else:
whereValue = "('" + "', '".join(values) + "')"
elif item['term'] == 'like' or item['term'] == 'not like':
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = f"N'%{values[0]}%'"
else:
whereValue = f"'%{values[0]}%'"
else:
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = f"N'{values[0]}'"
else:
whereValue = f"'{values[0]}'"
res = whereName + whereTerm + whereValue
else:
whereValue = "('" + "', '".join(value.split(",")) + "')"
elif item['term'] == 'like' or item['term'] == 'not like':
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = f"N'%{value}%'"
else:
whereValue = f"'%{value}%'"
return None
else:
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = f"N'{value}'"
else:
whereValue = f"'{value}'"
value = item['value']
whereValue = ''
res = whereName + whereTerm + whereValue
if item['term'] == 'null':
whereValue = ''
elif item['term'] == 'not_null':
whereValue = ''
elif item['term'] == 'empty':
whereValue = "''"
elif item['term'] == 'not_empty':
whereValue = "''"
elif item['term'] == 'in' or item['term'] == 'not in':
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = "(N'" + "', N'".join(value.split(",")) + "')"
else:
whereValue = "('" + "', '".join(value.split(",")) + "')"
elif item['term'] == 'like' or item['term'] == 'not like':
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = f"N'%{value}%'"
else:
whereValue = f"'%{value}%'"
else:
if ds.type == 'sqlServer' and (
field.field_type == 'nchar' or field.field_type == 'NCHAR' or field.field_type == 'nvarchar' or field.field_type == 'NVARCHAR'):
whereValue = f"N'{value}'"
else:
whereValue = f"'{value}'"
res = whereName + whereTerm + whereValue
return res
@@ -124,3 +203,19 @@ def transFilterTerm(term: str) -> str:
if term == "between":
return " BETWEEN "
return ""
def userHaveVariable(user_variables: List, sys_variable: SystemVariable):
for u in user_variables:
if sys_variable.id == u.get('variableId'):
return True
return False
def getSysVariableValue(sys_variable: SystemVariable, current_user: CurrentUser):
if sys_variable.value[0] == 'name':
return current_user.name
if sys_variable.value[0] == 'account':
return current_user.account
if sys_variable.value[0] == 'email':
return current_user.email
+4
View File
@@ -107,6 +107,10 @@ tags_metadata = [
{
"name": "Audit",
"description": f"{PLACEHOLDER_PREFIX}audit_api"
},
{
"name": "System_variable",
"description": f"{PLACEHOLDER_PREFIX}variable_api"
}
]
+7 -1
View File
@@ -190,5 +190,11 @@
"delete_resource_api": "Delete Resource",
"create_canvas_api": "Create Dashboard",
"update_canvas_api": "Update Dashboard",
"check_name_api": "Name Validation"
"check_name_api": "Name Validation",
"variable_api": "System Variable",
"variable_save": "Save",
"variable_delete": "Delete",
"variable_list": "List",
"variable_page": "Pager"
}
+7 -1
View File
@@ -190,5 +190,11 @@
"delete_resource_api": "删除资源",
"create_canvas_api": "新建仪表板",
"update_canvas_api": "更新仪表板",
"check_name_api": "名称校验"
"check_name_api": "名称校验",
"variable_api": "系统变量",
"variable_save": "保存变量",
"variable_delete": "删除变量",
"variable_list": "获取变量",
"variable_page": "获取变量分页"
}
+34
View File
@@ -0,0 +1,34 @@
# Author: Junjun
# Date: 2026/1/26
from typing import List
from fastapi import APIRouter
from apps.swagger.i18n import PLACEHOLDER_PREFIX
from apps.system.crud.system_variable import save, delete, list_all, list_page
from apps.system.models.system_variable_model import SystemVariable
from common.core.config import settings
from common.core.deps import SessionDep, CurrentUser, Trans
router = APIRouter(tags=["System_variable"], prefix="/sys_variable")
path = settings.EXCEL_PATH
@router.post("/save", response_model=None, summary=f"{PLACEHOLDER_PREFIX}variable_save")
async def save_variable(session: SessionDep, user: CurrentUser, trans: Trans, variable: SystemVariable):
return save(session, user, trans, variable)
@router.post("/delete",response_model=None, summary=f"{PLACEHOLDER_PREFIX}variable_delete")
async def delete_variable(session: SessionDep, ids: List[int]):
return delete(session, ids)
@router.post("/listAll",response_model=None, summary=f"{PLACEHOLDER_PREFIX}variable_list")
async def list_all_data(session: SessionDep, trans: Trans, variable: SystemVariable = None):
return list_all(session, trans, variable)
@router.post("/listPage/{pageNum}/{pageSize}",response_model=None, summary=f"{PLACEHOLDER_PREFIX}variable_page")
async def pager(session: SessionDep, trans: Trans, pageNum: int, pageSize: int,
variable: SystemVariable = None):
return await list_page(session, trans, pageNum, pageSize, variable)
@@ -0,0 +1,98 @@
# Author: Junjun
# Date: 2026/1/26
import datetime
from typing import List
from fastapi import HTTPException
from sqlalchemy import and_
from sqlmodel import select
from apps.system.models.system_variable_model import SystemVariable
from common.core.deps import SessionDep, CurrentUser, Trans
from common.core.pagination import Paginator
from common.core.schemas import PaginationParams
def save(session: SessionDep, user: CurrentUser, trans: Trans, variable: SystemVariable):
checkName(session, trans, variable)
variable.type = 'custom'
if variable.id is None:
variable.create_time = datetime.datetime.now()
variable.create_by = user.id
session.add(variable)
session.commit()
else:
record = session.query(SystemVariable).filter(SystemVariable.id == variable.id).first()
update_data = variable.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(record, field, value)
session.add(record)
session.commit()
return True
def delete(session: SessionDep, ids: List[int]):
session.query(SystemVariable).filter(SystemVariable.id.in_(ids)).delete()
def list_all(session: SessionDep, trans: Trans, variable: SystemVariable):
if variable.name is None:
records = session.query(SystemVariable).order_by(SystemVariable.type.desc()).all()
else:
records = session.query(SystemVariable).filter(
and_(SystemVariable.name.like(f'%{variable.name}%'), SystemVariable.type != 'system')).order_by(
SystemVariable.type.desc()).all()
res = []
for r in records:
data = SystemVariable(**r.__dict__)
if data.type == 'system':
data.name = trans(data.name)
res.append(data)
return res
async def list_page(session: SessionDep, trans: Trans, pageNum: int, pageSize: int, variable: SystemVariable):
pagination = PaginationParams(page=pageNum, size=pageSize)
paginator = Paginator(session)
filters = {}
if variable.name is None:
stmt = select(SystemVariable).order_by(SystemVariable.type.desc())
else:
stmt = select(SystemVariable).where(
and_(SystemVariable.name.like(f'%{variable.name}%'), SystemVariable.type != 'system')).order_by(
SystemVariable.type.desc())
variable_page = await paginator.get_paginated_response(
stmt=stmt,
pagination=pagination,
**filters)
res = []
for r in variable_page.items:
data = SystemVariable(**r)
if data.type == 'system':
data.name = trans(data.name)
res.append(data)
return {"items": res, "page": variable_page.page, "size": variable_page.size, "total": variable_page.total,
"total_pages": variable_page.total_pages}
def checkName(session: SessionDep, trans: Trans, variable: SystemVariable):
if variable.id is None:
records = session.query(SystemVariable).filter(SystemVariable.name == variable.name).all()
if records and len(records) > 0:
raise HTTPException(status_code=500, detail=trans('i18n_variable.name_exist'))
else:
records = session.query(SystemVariable).filter(
and_(SystemVariable.name == variable.name, SystemVariable.id != variable.id)).all()
if records and len(records) > 0:
raise HTTPException(status_code=500, detail=trans('i18n_variable.name_exist'))
def checkValue(session: SessionDep, trans: Trans, values:List):
# values: [{"variableId":1,"variableValues":["a","b"]}]
pass
@@ -0,0 +1,20 @@
# Author: Junjun
# Date: 2026/1/26
from datetime import datetime
from typing import List
from sqlalchemy import Column, BigInteger, Identity, DateTime
from sqlalchemy.dialects.postgresql import JSONB
from sqlmodel import SQLModel, Field
class SystemVariable(SQLModel, table=True):
__tablename__ = "system_variable"
id: int = Field(sa_column=Column(BigInteger, Identity(always=True), nullable=False, primary_key=True))
name: str = Field(max_length=128, nullable=False)
var_type: str = Field(max_length=128, nullable=False)
type: str = Field(max_length=128, nullable=False)
value: List = Field(sa_column=Column(JSONB, nullable=True))
create_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True))
create_by: int = Field(sa_column=Column(BigInteger()))
+10 -8
View File
@@ -1,13 +1,14 @@
from typing import List
from typing import Optional
from sqlmodel import BigInteger, SQLModel, Field
from sqlalchemy import Column, BigInteger
from sqlalchemy.dialects.postgresql import JSONB
from sqlmodel import SQLModel, Field
from common.core.models import SnowflakeBase
from common.core.security import default_md5_pwd
from common.utils.time import get_timestamp
class BaseUserPO(SQLModel):
account: str = Field(max_length=255, unique=True)
oid: int = Field(nullable=False, sa_type=BigInteger(), default=0)
@@ -18,17 +19,18 @@ class BaseUserPO(SQLModel):
origin: int = Field(nullable=False, default=0)
create_time: int = Field(default_factory=get_timestamp, sa_type=BigInteger(), nullable=False)
language: str = Field(max_length=255, default="zh-CN")
system_variables: List = Field(sa_column=Column(JSONB, nullable=True))
class UserModel(SnowflakeBase, BaseUserPO, table=True):
__tablename__ = "sys_user"
class UserPlatformBase(SQLModel):
uid: int = Field(nullable=False, sa_type=BigInteger())
origin: int = Field(nullable=False, default=0)
platform_uid: str = Field(max_length=255, nullable=False)
class UserPlatformModel(SnowflakeBase, UserPlatformBase, table=True):
__tablename__ = "sys_user_platform"
+2 -1
View File
@@ -1,5 +1,5 @@
import re
from typing import Optional
from typing import Optional,List
from pydantic import BaseModel, Field, field_validator
@@ -58,6 +58,7 @@ class UserCreator(BaseUser):
status: int = Field(default=1, description=f"{PLACEHOLDER_PREFIX}status")
origin: Optional[int] = Field(default=0, description=f"{PLACEHOLDER_PREFIX}origin")
oid_list: Optional[list[int]] = Field(default=None, description=f"{PLACEHOLDER_PREFIX}oid")
system_variables: Optional[List] = Field(default=None)
""" @field_validator("email")
def validate_email(cls, lang: str) -> str:
+7 -1
View File
@@ -187,5 +187,11 @@
"update_status": "Update Status",
"update_table_relation": "Change Table Relation"
},
"i18n_table_not_exist": "Table not exist"
"i18n_table_not_exist": "Table not exist",
"i18n_variable": {
"name_exist": "Name exist",
"name": "Name",
"account": "Account",
"email": "Email"
}
}
+7 -1
View File
@@ -187,5 +187,11 @@
"update_status": "상태 업데이트",
"update_table_relation": "테이블 관계 변경"
},
"i18n_table_not_exist": "현재 테이블이 존재하지 않습니다"
"i18n_table_not_exist": "현재 테이블이 존재하지 않습니다",
"i18n_variable": {
"name_exist": "변수 이름이 이미 존재합니다",
"name": "이름",
"account": "계정",
"email": "이메일"
}
}
+7 -1
View File
@@ -187,5 +187,11 @@
"update_status": "更新状态",
"update_table_relation": "变更表关系"
},
"i18n_table_not_exist": "当前表不存在"
"i18n_table_not_exist": "当前表不存在",
"i18n_variable": {
"name_exist": "变量名称已存在",
"name": "姓名",
"account": "账号",
"email": "邮箱"
}
}