From 8a9362e0fcbab08230194277d32e17cf2b9b8bb0 Mon Sep 17 00:00:00 2001 From: wangjiahao <1522128093@qq.com> Date: Mon, 22 Dec 2025 17:32:34 +0800 Subject: [PATCH] feat: Support recording system operation logs --- .../alembic/versions/055_add_system_logs.py | 43 +++++++++++++++++++ backend/apps/chat/api/chat.py | 14 ++++++ backend/apps/dashboard/api/dashboard_api.py | 20 +++++++++ backend/apps/datasource/api/datasource.py | 21 ++++++++- backend/main.py | 2 + 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 backend/alembic/versions/055_add_system_logs.py diff --git a/backend/alembic/versions/055_add_system_logs.py b/backend/alembic/versions/055_add_system_logs.py new file mode 100644 index 000000000..7cb22fe44 --- /dev/null +++ b/backend/alembic/versions/055_add_system_logs.py @@ -0,0 +1,43 @@ +"""empty message + +Revision ID: 3d4bd2d673dc +Revises: 24e961f6326b +Create Date: 2025-12-19 13:30:54.743171 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '3d4bd2d673dc' +down_revision = '24e961f6326b' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('sys_logs', + sa.Column('id', sa.BIGINT(), autoincrement=True, nullable=False), + sa.Column('operation_type', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('operation_detail', sa.TEXT(), autoincrement=False, nullable=True), + sa.Column('user_id', sa.BIGINT(), autoincrement=False, nullable=True), + sa.Column('operation_status', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('oid', sa.BIGINT(), autoincrement=False, nullable=True), + sa.Column('ip_address', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('user_agent', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('execution_time', sa.BIGINT(), autoincrement=False, nullable=True), + sa.Column('error_message', sa.TEXT(), autoincrement=False, nullable=True), + sa.Column('create_time', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), + sa.Column('module', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('remark', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('resource_id',sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('request_method', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('request_path', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('sys_logs_pkey')) + ) + + +def downgrade(): + op.drop_table('sys_logs') diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index ce96cf232..09888f7d7 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -21,6 +21,8 @@ from common.core.deps import CurrentAssistant, SessionDep, CurrentUser, Trans from common.utils.command_utils import parse_quick_command from common.utils.data_format import DataFormat +from sqlbot_xpack.audit.models.log_model import OperationType, OperationDetails, OperationModules +from sqlbot_xpack.audit.schemas.logger_decorator import system_log, LogConfig router = APIRouter(tags=["Data Q&A"], prefix="/chat") @@ -80,6 +82,12 @@ async def rename(session: SessionDep, chat: RenameChat): @router.delete("/{chart_id}", response_model=str, summary=f"{PLACEHOLDER_PREFIX}delete_chat") +@system_log(LogConfig( + operation_type=OperationType.DELETE_QA, + operation_detail=OperationDetails.DELETE_QA_DETAILS, + module=OperationModules.QA, + resource_id_expr="chart_id" +)) async def delete(session: SessionDep, chart_id: int): try: return delete_chat(session=session, chart_id=chart_id) @@ -92,6 +100,12 @@ async def delete(session: SessionDep, chart_id: int): @router.post("/start", response_model=ChatInfo, summary=f"{PLACEHOLDER_PREFIX}start_chat") @require_permissions(permission=SqlbotPermission(type='ds', keyExpression="create_chat_obj.datasource")) +@system_log(LogConfig( + operation_type=OperationType.CREATE_QA, + operation_detail=OperationDetails.CREATE_QA_DETAILS, + module=OperationModules.QA, + result_id_expr="id" +)) async def start_chat(session: SessionDep, current_user: CurrentUser, create_chat_obj: CreateChat): try: return create_chat(session, current_user, create_chat_obj) diff --git a/backend/apps/dashboard/api/dashboard_api.py b/backend/apps/dashboard/api/dashboard_api.py index 17de0030c..7af75d76c 100644 --- a/backend/apps/dashboard/api/dashboard_api.py +++ b/backend/apps/dashboard/api/dashboard_api.py @@ -3,6 +3,8 @@ from apps.dashboard.crud.dashboard_service import list_resource, load_resource, \ create_resource, create_canvas, validate_name, delete_resource, update_resource, update_canvas from apps.dashboard.models.dashboard_model import CreateDashboard, BaseDashboard, QueryDashboard, DashboardResponse +from sqlbot_xpack.audit.models.log_model import OperationType, OperationDetails, OperationModules +from sqlbot_xpack.audit.schemas.logger_decorator import system_log, LogConfig from common.core.deps import SessionDep, CurrentUser router = APIRouter(tags=["dashboard"], prefix="/dashboard") @@ -29,16 +31,34 @@ async def update_resource_api(session: SessionDep, user: CurrentUser, dashboard: @router.delete("/delete_resource/{resource_id}") +@system_log(LogConfig( + operation_type=OperationType.DELETE_DASHBOARD, + operation_detail=OperationDetails.DELETE_DASHBOARD_DETAILS, + module=OperationModules.DASHBOARD, + resource_id_expr="resource_id" +)) async def delete_resource_api(session: SessionDep, resource_id: str): return delete_resource(session, resource_id) @router.post("/create_canvas", response_model=BaseDashboard) +@system_log(LogConfig( + operation_type=OperationType.CREATE_DASHBOARD, + operation_detail=OperationDetails.CREATE_DASHBOARD_DETAILS, + module=OperationModules.DASHBOARD, + result_id_expr="id" +)) async def create_canvas_api(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): return create_canvas(session, user, dashboard) @router.post("/update_canvas", response_model=BaseDashboard) +@system_log(LogConfig( + operation_type=OperationType.UPDATE_DASHBOARD, + operation_detail=OperationDetails.UPDATE_DASHBOARD_DETAILS, + module=OperationModules.DASHBOARD, + resource_id_expr="dashboard.id" +)) async def update_canvas_api(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): return update_canvas(session, user, dashboard) diff --git a/backend/apps/datasource/api/datasource.py b/backend/apps/datasource/api/datasource.py index 5bcfbf0ee..096abb3fd 100644 --- a/backend/apps/datasource/api/datasource.py +++ b/backend/apps/datasource/api/datasource.py @@ -28,7 +28,8 @@ from ..crud.table import get_tables_by_ds_id from ..models.datasource import CoreDatasource, CreateDatasource, TableObj, CoreTable, CoreField, FieldObj, \ TableSchemaResponse, ColumnSchemaResponse, PreviewResponse - +from sqlbot_xpack.audit.models.log_model import OperationType, OperationDetails, OperationModules +from sqlbot_xpack.audit.schemas.logger_decorator import system_log, LogConfig router = APIRouter(tags=["Datasource"], prefix="/datasource") path = settings.EXCEL_PATH @@ -69,6 +70,12 @@ def inner(): @router.post("/add", response_model=CoreDatasource, summary=f"{PLACEHOLDER_PREFIX}ds_add") +@system_log(LogConfig( + operation_type=OperationType.CREATE_DATASOURCE, + operation_detail=OperationDetails.CREATE_DATASOURCE_DETAILS, + module=OperationModules.DATASOURCE, + result_id_expr="id" +)) async def add(session: SessionDep, trans: Trans, user: CurrentUser, ds: CreateDatasource): def inner(): return create_ds(session, trans, user, ds) @@ -87,6 +94,12 @@ def inner(): @router.post("/update", response_model=CoreDatasource, summary=f"{PLACEHOLDER_PREFIX}ds_update") @require_permissions(permission=SqlbotPermission(type='ds', keyExpression="ds.id")) +@system_log(LogConfig( + operation_type=OperationType.UPDATE_DATASOURCE, + operation_detail=OperationDetails.UPDATE_DATASOURCE_DETAILS, + module=OperationModules.DATASOURCE, + resource_id_expr="ds.id" +)) async def update(session: SessionDep, trans: Trans, user: CurrentUser, ds: CoreDatasource): def inner(): return update_ds(session, trans, user, ds) @@ -96,6 +109,12 @@ def inner(): @router.post("/delete/{id}", response_model=None, summary=f"{PLACEHOLDER_PREFIX}ds_delete") @require_permissions(permission=SqlbotPermission(type='ds', keyExpression="id")) +@system_log(LogConfig( + operation_type=OperationType.DELETE_DATASOURCE, + operation_detail=OperationDetails.DELETE_DATASOURCE_DETAILS, + module=OperationModules.DATASOURCE, + resource_id_expr="id" +)) async def delete(session: SessionDep, id: int = Path(..., description=f"{PLACEHOLDER_PREFIX}ds_id")): return delete_ds(session, id) diff --git a/backend/main.py b/backend/main.py index aec271633..8a1ba1703 100644 --- a/backend/main.py +++ b/backend/main.py @@ -21,6 +21,7 @@ from apps.system.crud.assistant import init_dynamic_cors from apps.system.middleware.auth import TokenMiddleware from apps.system.schemas.permission import RequestContextMiddleware +from sqlbot_xpack.audit.schemas.request_context import RequestContextMiddlewareCommon from common.core.config import settings from common.core.response_middleware import ResponseMiddleware, exception_handler from common.core.sqlbot_cache import init_sqlbot_cache @@ -201,6 +202,7 @@ async def custom_swagger_ui(request: Request): app.add_middleware(TokenMiddleware) app.add_middleware(ResponseMiddleware) app.add_middleware(RequestContextMiddleware) +app.add_middleware(RequestContextMiddlewareCommon) app.include_router(api_router, prefix=settings.API_V1_STR) # Register exception handlers