diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index 6c0440b8..8374680b 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -674,7 +674,8 @@ async def draft_run_compare( workspace_id=workspace_id, user=current_user ) - if storage_type is None: storage_type = 'neo4j' + if storage_type is None: + storage_type = 'neo4j' user_rag_memory_id = '' if workspace_id: knowledge = knowledge_repository.get_knowledge_by_name( @@ -682,7 +683,8 @@ async def draft_run_compare( name="USER_RAG_MERORY", workspace_id=workspace_id ) - if knowledge: user_rag_memory_id = str(knowledge.id) + if knowledge: + user_rag_memory_id = str(knowledge.id) logger.info( "多模型对比试运行", diff --git a/api/app/controllers/public_share_controller.py b/api/app/controllers/public_share_controller.py index 27884b92..e2e5a250 100644 --- a/api/app/controllers/public_share_controller.py +++ b/api/app/controllers/public_share_controller.py @@ -1,31 +1,23 @@ -from fastapi import APIRouter, Depends, Query, Request, Header +import hashlib +import uuid + +from fastapi import APIRouter, Depends, Query, Request from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session -import uuid -import hashlib -import time -import jwt -from app.services import task_service, workspace_service -from typing import Optional, Dict -from functools import wraps -from app.dependencies import get_current_superuser, get_current_user, get_current_tenant, workspace_access_guard, cur_workspace_access_guard -from app.db import get_db -from app.core.response_utils import success + from app.core.logging_config import get_business_logger -from app.core.exceptions import BusinessException -from app.core.error_codes import BizCode -from app.core.config import settings +from app.core.response_utils import success +from app.db import get_db +from app.dependencies import get_share_user_id, ShareTokenData +from app.repositories import knowledge_repository from app.schemas import release_share_schema, conversation_schema from app.schemas.response_schema import PageData, PageMeta +from app.services import workspace_service +from app.services.auth_service import create_access_token +from app.services.conversation_service import ConversationService from app.services.release_share_service import ReleaseShareService from app.services.shared_chat_service import SharedChatService -from app.services.conversation_service import ConversationService -from app.services.auth_service import create_access_token -from app.dependencies import get_share_user_id, ShareTokenData -from app.models.user_model import User -from app.repositories.app_repository import AppRepository -from app.repositories.workspace_repository import WorkspaceRepository -from app.repositories import knowledge_repository + router = APIRouter(prefix="/public/share", tags=["Public Share"]) logger = get_business_logger() @@ -37,31 +29,31 @@ def get_base_url(request: Request) -> str: def get_or_generate_user_id(payload_user_id: str, request: Request) -> str: """获取或生成用户 ID - + 优先级: 1. 使用前端传递的 user_id 2. 基于 IP + User-Agent 生成唯一 ID - + Args: payload_user_id: 前端传递的 user_id request: FastAPI Request 对象 - + Returns: 用户 ID """ if payload_user_id: return payload_user_id - + # 获取客户端 IP client_ip = request.client.host if request.client else "unknown" - + # 获取 User-Agent user_agent = request.headers.get("user-agent", "unknown") - + # 生成唯一 ID:基于 IP + User-Agent 的哈希 unique_string = f"{client_ip}_{user_agent}" hash_value = hashlib.md5(unique_string.encode()).hexdigest()[:16] - + return f"guest_{hash_value}" @@ -76,13 +68,13 @@ def get_access_token( db: Session = Depends(get_db), ): """获取访问 token - + - 用户通过 user_id + share_token 换取访问 token - 后续请求需要携带此 token """ # 获取或生成 user_id user_id = get_or_generate_user_id(payload.user_id, request) - + # 验证分享链接(可选:验证密码) service = ReleaseShareService(db) try: @@ -93,10 +85,10 @@ def get_access_token( except Exception as e: logger.error(f"获取分享信息失败: {str(e)}") raise - + # 生成 token access_token = create_access_token(user_id, share_token) - + logger.info( "生成访问 token", extra={ @@ -104,7 +96,7 @@ def get_access_token( "user_id": user_id } ) - + return success(data={ "access_token": access_token, "token_type": "Bearer", @@ -123,7 +115,7 @@ def get_shared_release( db: Session = Depends(get_db), ): """获取公开分享的发布版本信息 - + - 无需认证即可访问 - 如果设置了密码保护,需要提供正确的密码 - 如果密码错误或未提供密码,返回基本信息(不含配置详情) @@ -133,7 +125,7 @@ def get_shared_release( share_token=share_data.share_token, password=password ) - + return success(data=info) @@ -147,7 +139,7 @@ def verify_password( db: Session = Depends(get_db), ): """验证分享的访问密码 - + - 用于前端先验证密码,再获取完整信息 """ service = ReleaseShareService(db) @@ -155,7 +147,7 @@ def verify_password( share_token=share_data.share_token, password=payload.password ) - + return success(data={"valid": is_valid}) @@ -163,7 +155,7 @@ def verify_password( "/embed", summary="获取嵌入代码" ) -def get_embed_code( +def get_embed_code( width: str = Query("100%", description="iframe 宽度"), height: str = Query("600px", description="iframe 高度"), request: Request = None, @@ -171,12 +163,12 @@ def get_embed_code( db: Session = Depends(get_db), ): """获取嵌入代码 - + - 返回 iframe 嵌入代码 - 可以自定义宽度和高度 """ base_url = get_base_url(request) if request else None - + service = ReleaseShareService(db) embed_code = service.get_embed_code( share_token=share_data.share_token, @@ -184,7 +176,7 @@ def get_embed_code( height=height, base_url=base_url ) - + return success(data=embed_code) @@ -203,7 +195,7 @@ def list_conversations( db: Session = Depends(get_db), ): """获取分享应用的会话列表 - + - 可以按 user_id 筛选 - 支持分页 """ @@ -214,7 +206,7 @@ def list_conversations( from app.repositories.end_user_repository import EndUserRepository end_user_repo = EndUserRepository(db) new_end_user = end_user_repo.get_or_create_end_user( - app_id=share.app_id, + app_id=share.app_id, other_id=other_id ) logger.debug(new_end_user.id) @@ -226,10 +218,10 @@ def list_conversations( page=page, pagesize=pagesize ) - + items = [conversation_schema.Conversation.model_validate(c) for c in conversations] meta = PageMeta(page=page, pagesize=pagesize, total=total, hasnext=(page * pagesize) < total) - + return success(data=PageData(page=meta, items=items)) @@ -250,17 +242,17 @@ def get_conversation( conversation_id=conversation_id, password=password ) - + # 获取消息 conv_service = ConversationService(db) messages = conv_service.get_messages(conversation_id) - + # 构建响应 conv_dict = conversation_schema.Conversation.model_validate(conversation).model_dump() conv_dict["messages"] = [ conversation_schema.Message.model_validate(m) for m in messages ] - + return success(data=conv_dict) @@ -276,17 +268,17 @@ async def chat( db: Session = Depends(get_db) ): """发送消息并获取回复 - + 使用 Bearer token 认证: - Header: Authorization: Bearer {token} - user_id 和 share_token 从 token 中解码 - + - 支持多轮对话(提供 conversation_id) - 支持流式返回(设置 stream=true) - 如果不提供 conversation_id,会自动创建新会话 """ service = SharedChatService(db) - + # 从依赖中获取 user_id 和 share_token user_id = share_data.user_id share_token = share_data.share_token @@ -299,19 +291,19 @@ async def chat( from app.models.app_model import AppType try: from app.core.exceptions import BusinessException - from app.core.error_codes import BizCode + from app.core.error_codes import BizCode from app.services.app_service import AppService # 验证分享链接和密码 share, release = service._get_release_by_share_token(share_token, password) - + # # Create end_user_id by concatenating app_id with user_id # end_user_id = f"{share.app_id}_{user_id}" - + # Store end_user_id in database with original user_id from app.repositories.end_user_repository import EndUserRepository end_user_repo = EndUserRepository(db) new_end_user = end_user_repo.get_or_create_end_user( - app_id=share.app_id, + app_id=share.app_id, other_id=other_id, original_user_id=user_id # Save original user_id to other_id ) @@ -319,21 +311,21 @@ async def chat( appid=share.app_id """获取存储类型和工作空间的ID""" - + # 直接通过 SQLAlchemy 查询 app from app.models.app_model import App app = db.query(App).filter(App.id == appid).first() if not app: raise BusinessException("应用不存在", BizCode.APP_NOT_FOUND) - + workspace_id = app.workspace_id - + # 直接从 workspace 获取 storage_type(公开分享场景无需权限检查) storage_type = workspace_service.get_workspace_storage_type_without_auth( db=db, workspace_id=workspace_id ) - if storage_type is None: + if storage_type is None: storage_type = 'neo4j' user_rag_memory_id = '' @@ -357,7 +349,7 @@ async def chat( # 获取应用类型 app_type = release.app.type if release.app else None - + # 根据应用类型验证配置 if app_type == "agent": # Agent 类型:验证模型配置 @@ -371,7 +363,7 @@ async def chat( raise BusinessException("多 Agent 应用未配置子 Agent", BizCode.AGENT_CONFIG_MISSING) else: raise BusinessException(f"不支持的应用类型: {app_type}", BizCode.APP_TYPE_NOT_SUPPORTED) - + # 获取或创建会话(提前验证) conversation = service.create_or_get_conversation( share_token=share_data.share_token, @@ -379,7 +371,7 @@ async def chat( user_id=str(new_end_user.id), # 转换为字符串 password=password ) - + logger.debug( "参数验证完成", extra={ @@ -389,12 +381,12 @@ async def chat( "stream": payload.stream } ) - + except Exception as e: # 验证失败,直接抛出异常(会被 FastAPI 的异常处理器捕获) logger.error(f"参数验证失败: {str(e)}") raise - + if app_type == AppType.AGENT: # 流式返回 if payload.stream: @@ -412,7 +404,7 @@ async def chat( user_rag_memory_id=user_rag_memory_id ): yield event - + return StreamingResponse( event_generator(), media_type="text/event-stream", @@ -422,7 +414,7 @@ async def chat( "X-Accel-Buffering": "no" } ) - + # 非流式返回 result = await service.chat( share_token=share_token, @@ -454,7 +446,7 @@ async def chat( user_rag_memory_id=user_rag_memory_id ): yield event - + return StreamingResponse( event_generator(), media_type="text/event-stream", @@ -464,7 +456,7 @@ async def chat( "X-Accel-Buffering": "no" } ) - + # 多 Agent 非流式返回 result = await service.multi_agent_chat( share_token=share_token, @@ -478,7 +470,7 @@ async def chat( storage_type=storage_type, user_rag_memory_id=user_rag_memory_id ) - + return success(data=conversation_schema.ChatResponse(**result)) else: from app.core.exceptions import BusinessException diff --git a/api/app/controllers/service/app_api_controller.py b/api/app/controllers/service/app_api_controller.py index eb22548f..b2256a98 100644 --- a/api/app/controllers/service/app_api_controller.py +++ b/api/app/controllers/service/app_api_controller.py @@ -1,30 +1,28 @@ """App 服务接口 - 基于 API Key 认证""" -import uuid +from typing import Annotated + from fastapi import APIRouter, Depends, Request, Body from sqlalchemy.orm import Session -from typing import Optional, Annotated - from starlette.responses import StreamingResponse from app.core.api_key_auth import require_api_key -from app.db import get_db -from app.core.response_utils import success +from app.core.error_codes import BizCode +from app.core.exceptions import BusinessException from app.core.logging_config import get_business_logger +from app.core.response_utils import success +from app.db import get_db from app.dependencies import get_app_or_workspace -from app.models import AppRelease -from app.repositories import knowledge_repository -from app.schemas import AppChatRequest, conversation_schema from app.models.app_model import App from app.models.app_model import AppType +from app.repositories import knowledge_repository from app.repositories.end_user_repository import EndUserRepository -from app.core.exceptions import BusinessException -from app.core.error_codes import BizCode +from app.schemas import AppChatRequest, conversation_schema +from app.schemas.api_key_schema import ApiKeyAuth from app.services import workspace_service from app.services.app_chat_service import AppChatService, get_app_chat_service -from app.services.app_service import AppService from app.services.conversation_service import ConversationService, get_conversation_service -from app.services.workflow_service import WorkflowService, get_workflow_service -from app.utils.app_config_utils import dict_to_multi_agent_config,dict_to_workflow_config,agent_config_4_app_release +from app.utils.app_config_utils import dict_to_multi_agent_config, dict_to_workflow_config, agent_config_4_app_release +from app.services.app_service import get_app_service, AppService router = APIRouter(prefix="/app", tags=["V1 - App API"]) logger = get_business_logger() @@ -37,8 +35,9 @@ async def list_apps(): # /v1/apps/{resource_id}/chat - -# async def chat( +# @router.post("/chat") +# @require_api_key(scopes=["app"]) +# async def chat2( # request: Request, # api_key_auth: ApiKeyAuth = None, # db: Session = Depends(get_db), @@ -57,7 +56,6 @@ async def list_apps(): # db: db_session # """ # logger.info(f"API Key Auth: {api_key_auth}") -# logger.info(f"Resource ID: {resource_id}") # logger.info(f"Message: {message}") # return success(data={"received": True}, msg="消息已接收") @@ -76,15 +74,21 @@ def _checkAppConfig(app: App): raise BusinessException("不支持的应用类型", BizCode.AGENT_CONFIG_MISSING) @router.post("/chat") -# @require_api_key(scopes=["app"]) +@require_api_key(scopes=["app"]) async def chat( - payload: AppChatRequest, - app: App = Depends(get_app_or_workspace), + request:Request, + api_key_auth: ApiKeyAuth = None, db: Session = Depends(get_db), conversation_service: Annotated[ConversationService, Depends(get_conversation_service)] = None, app_chat_service: Annotated[AppChatService, Depends(get_app_chat_service)] = None, - + app_service: Annotated[AppService, Depends(get_app_service)] = None, + message: str = Body(..., description="聊天消息内容"), ): + body = await request.json() + payload = AppChatRequest(**body) + + other_id = payload.user_id + app = app_service.get_app(api_key_auth.resource_id, api_key_auth.workspace_id) other_id = payload.user_id workspace_id = app.workspace_id end_user_repo = EndUserRepository(db) @@ -176,7 +180,7 @@ async def chat( storage_type=storage_type, user_rag_memory_id=user_rag_memory_id ) - return success(data=conversation_schema.ChatResponse(**result)) + return success(data=conversation_schema.ChatResponse(**result).model_dump(mode="json")) elif app_type == AppType.MULTI_AGENT: # 多 Agent 流式返回 config = dict_to_multi_agent_config(app.current_release.config,app.id) @@ -220,7 +224,7 @@ async def chat( user_rag_memory_id=user_rag_memory_id ) - return success(data=conversation_schema.ChatResponse(**result)) + return success(data=conversation_schema.ChatResponse(**result).model_dump(mode="json")) elif app_type == AppType.WORKFLOW: # 多 Agent 流式返回 config = dict_to_workflow_config(app.current_release.config,app.id) @@ -264,7 +268,7 @@ async def chat( user_rag_memory_id=user_rag_memory_id ) - return success(data=conversation_schema.ChatResponse(**result)) + return success(data=conversation_schema.ChatResponse(**result).model_dump(mode="json")) else: from app.core.exceptions import BusinessException from app.core.error_codes import BizCode diff --git a/api/app/models/multi_agent_model.py b/api/app/models/multi_agent_model.py index 061ecffa..bdaa0a70 100644 --- a/api/app/models/multi_agent_model.py +++ b/api/app/models/multi_agent_model.py @@ -1,26 +1,42 @@ """多 Agent 相关数据模型""" import datetime import uuid +from enum import StrEnum + from sqlalchemy import Column, String, Boolean, DateTime, Integer, Float, Text, ForeignKey from sqlalchemy.dialects.postgresql import UUID, JSON from sqlalchemy.orm import relationship from app.db import Base +class OrchestrationMode(StrEnum): + """图标类型枚举""" + SEQUENTIAL = "sequential" + PARALLEL = "parallel" + CONDITIONAL = "conditional" + +class AggregationStrategy(StrEnum): + """图标类型枚举""" + MERGE = "merge" + VOTE = "vote" + PRIORITY = "priority" class MultiAgentConfig(Base): """多 Agent 配置表""" __tablename__ = "multi_agent_configs" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True) - + # 关联应用 app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False, unique=True, index=True, comment="关联应用") - + # 主 Agent (存储发布版本 ID) - master_agent_id = Column(UUID(as_uuid=True), ForeignKey("app_releases.id"), nullable=False, comment="主 Agent 发布版本 ID") + master_agent_id = Column(UUID(as_uuid=True), ForeignKey("app_releases.id"), nullable=True, comment="主 Agent 发布版本 ID") master_agent_name = Column(String(100), comment="主 Agent 名称") - + + default_model_config_id = Column(UUID(as_uuid=True), ForeignKey("model_configs.id"), nullable=False,server_default=str(uuid.UUID(int=0)), index=True, comment="默认模型配置ID") + # 结构化配置(直接存储 JSON) + model_parameters = Column(JSON, nullable=True, comment="模型参数配置(temperature、max_tokens等)") # 协作模式 orchestration_mode = Column( String(20), @@ -28,7 +44,7 @@ class MultiAgentConfig(Base): default="conditional", comment="协作模式: sequential|parallel|conditional|loop" ) - + # 子 Agent 列表 sub_agents = Column( JSON, @@ -36,13 +52,13 @@ class MultiAgentConfig(Base): default=list, comment="子 Agent 列表: [{'agent_id': 'uuid', 'name': '...', 'role': '...', 'priority': 1}]" ) - + # 路由规则 routing_rules = Column( JSON, comment="路由规则: [{'condition': '...', 'target_agent_id': 'uuid', 'priority': 1}]" ) - + # 执行配置 execution_config = Column( JSON, @@ -50,7 +66,7 @@ class MultiAgentConfig(Base): default=dict, comment="执行配置: {'max_iterations': 5, 'timeout': 60, 'parallel_limit': 3}" ) - + # 结果整合策略 aggregation_strategy = Column( String(20), @@ -58,12 +74,12 @@ class MultiAgentConfig(Base): default="merge", comment="结果整合策略: merge|vote|priority|custom" ) - + # 状态 is_active = Column(Boolean, default=True, nullable=False) created_at = Column(DateTime, default=datetime.datetime.now) updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now) - + # 关系 app = relationship("App") master_agent_release = relationship("AppRelease", foreign_keys=[master_agent_id]) @@ -77,7 +93,7 @@ class AgentInvocation(Base): __tablename__ = "agent_invocations" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True) - + # 调用关系 caller_agent_id = Column( UUID(as_uuid=True), @@ -93,7 +109,7 @@ class AgentInvocation(Base): index=True, comment="被调用者 Agent ID" ) - + # 关联信息 conversation_id = Column( UUID(as_uuid=True), @@ -106,12 +122,12 @@ class AgentInvocation(Base): index=True, comment="父调用 ID(用于追踪调用链)" ) - + # 输入输出 input_message = Column(Text, nullable=False, comment="输入消息") output_message = Column(Text, comment="输出消息") context = Column(JSON, comment="上下文信息") - + # 状态 status = Column( String(20), @@ -121,18 +137,18 @@ class AgentInvocation(Base): comment="状态: pending|running|completed|failed" ) error_message = Column(Text, comment="错误信息") - + # 性能指标 started_at = Column(DateTime, nullable=False, default=datetime.datetime.now, index=True) completed_at = Column(DateTime) elapsed_time = Column(Float, comment="耗时(秒)") token_usage = Column(JSON, comment="Token 使用情况") - + # 元数据 meta_data = Column(JSON, comment="额外元数据") - + created_at = Column(DateTime, default=datetime.datetime.now) - + # 关系 caller = relationship("AgentConfig", foreign_keys=[caller_agent_id]) callee = relationship("AgentConfig", foreign_keys=[callee_agent_id]) diff --git a/api/app/services/app_service.py b/api/app/services/app_service.py index 07625fee..1658ef8b 100644 --- a/api/app/services/app_service.py +++ b/api/app/services/app_service.py @@ -9,8 +9,9 @@ """ import datetime import uuid -from typing import Optional, List, Dict, Any, Tuple, Type +from typing import Optional, List, Dict, Any, Tuple, Annotated +from fastapi import Depends from sqlalchemy import select, func, or_, and_ from sqlalchemy.orm import Session @@ -20,6 +21,7 @@ from app.core.exceptions import ( BusinessException, ) from app.core.logging_config import get_business_logger +from app.db import get_db from app.models import App, AgentConfig, AppRelease, MultiAgentConfig, WorkflowConfig from app.models.app_model import AppStatus, AppType from app.repositories.app_repository import get_apps_by_id @@ -27,6 +29,7 @@ from app.repositories.workflow_repository import WorkflowConfigRepository from app.schemas import app_schema from app.schemas.workflow_schema import WorkflowConfigUpdate from app.services.agent_config_converter import AgentConfigConverter +from app.models import AppShare, Workspace # 获取业务日志器 logger = get_business_logger() @@ -1390,7 +1393,7 @@ class AppService: target_workspace_ids: List[uuid.UUID], user_id: uuid.UUID, workspace_id: Optional[uuid.UUID] = None - ) -> List["AppShare"]: + ) -> AppShare: """分享应用到其他工作空间 Args: @@ -1406,7 +1409,7 @@ class AppService: ResourceNotFoundException: 当应用不存在时 BusinessException: 当应用不在指定工作空间或目标工作空间无效时 """ - from app.models import AppShare, Workspace + logger.info( "分享应用", @@ -1548,7 +1551,7 @@ class AppService: *, app_id: uuid.UUID, workspace_id: Optional[uuid.UUID] = None - ) -> List["AppShare"]: + ) -> List[AppShare]: """列出应用的所有分享记录 Args: @@ -2094,3 +2097,14 @@ async def draft_run_stream( workspace_id=workspace_id ): yield event + + + + +# ==================== 依赖注入函数 ==================== + +def get_app_service( + db: Annotated[Session, Depends(get_db)] +) -> AppService: + """获取工作流服务(依赖注入)""" + return AppService(db) diff --git a/api/app/services/conversation_service.py b/api/app/services/conversation_service.py index f618ea17..122d0d87 100644 --- a/api/app/services/conversation_service.py +++ b/api/app/services/conversation_service.py @@ -1,16 +1,18 @@ """会话服务""" import uuid -from typing import Optional, List, Tuple, Annotated +from typing import Annotated +from typing import Optional, List, Tuple from fastapi import Depends -from sqlalchemy.orm import Session from sqlalchemy import select, desc +from sqlalchemy.orm import Session +from app.core.error_codes import BizCode +from app.core.exceptions import BusinessException +from app.core.exceptions import ResourceNotFoundException +from app.core.logging_config import get_business_logger from app.db import get_db from app.models import Conversation, Message -from app.core.exceptions import ResourceNotFoundException, BusinessException -from app.core.error_codes import BizCode -from app.core.logging_config import get_business_logger logger = get_business_logger() diff --git a/api/app/services/workspace_service.py b/api/app/services/workspace_service.py index 0499a6bf..34ce0610 100644 --- a/api/app/services/workspace_service.py +++ b/api/app/services/workspace_service.py @@ -11,31 +11,26 @@ from app.core.exceptions import BusinessException, PermissionDeniedException from app.core.logging_config import get_business_logger from app.models.user_model import User -# 获取业务逻辑专用日志器 -business_logger = get_business_logger() -from app.models.workspace_model import ( - InviteStatus, - Workspace, - WorkspaceMember, - WorkspaceRole, +from app.schemas.workspace_schema import ( + WorkspaceModelsUpdate, ) +from sqlalchemy.orm import Session +from app.models.workspace_model import Workspace, WorkspaceRole, InviteStatus, WorkspaceMember from app.repositories import workspace_repository from app.repositories.workspace_invite_repository import WorkspaceInviteRepository from app.schemas.workspace_schema import ( - InviteAcceptRequest, - InviteValidateResponse, WorkspaceCreate, + WorkspaceUpdate, WorkspaceInviteCreate, WorkspaceInviteResponse, - WorkspaceMemberUpdate, - WorkspaceModelsUpdate, - WorkspaceUpdate, + InviteValidateResponse, + InviteAcceptRequest, + WorkspaceMemberUpdate ) -from dotenv import load_dotenv -from sqlalchemy.orm import Session # 获取业务逻辑专用日志器 business_logger = get_business_logger() +from dotenv import load_dotenv load_dotenv() def switch_workspace( db: Session, @@ -329,7 +324,7 @@ def _check_workspace_admin_permission(db: Session, workspace_id: uuid.UUID, user ) # 使用统一权限服务检查管理权限 - from app.core.permissions import Action, Resource, Subject, permission_service + from app.core.permissions import permission_service, Subject, Resource, Action # 获取用户的工作空间成员关系 member = workspace_repository.get_member_in_workspace(