# ============================================================================= # 企微IT智能服务台 — Portal 统一入口 API # ============================================================================= # 说明:统一入口(Portal)相关接口 # 包含: # 1. 获取当前用户角色信息 # 2. 切换当前角色 # 3. 获取角色对应的入口 URL # 所有接口需要有效的 Bearer Token # ============================================================================= import json import logging from typing import Optional from fastapi import APIRouter, Depends from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_current_user, UserInfo from app.config import settings from app.database import get_db from app.models.role import Role from app.models.user_role import UserRole from app.schemas.role import ( PortalUserInfo, RoleResponse, SwitchRoleRequest, SwitchRoleResponse, ) from app.services.token_service import TokenService from app.utils.response import AppException, success_response logger = logging.getLogger(__name__) # HTTP Bearer 认证方案 security = HTTPBearer() # 创建路由器 router = APIRouter(prefix="/portal") # -------------------------------------------------------------------------- # 获取当前用户角色信息 # -------------------------------------------------------------------------- @router.get("/roles") async def get_user_roles( current_user: UserInfo = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """获取当前用户的角色信息。 返回用户的基本信息和角色列表,用于路由选择页展示。 Args: current_user: 当前用户(通过认证依赖注入) db: 数据库会话 Returns: Dict: 统一响应格式,包含用户信息和角色列表 """ # 查询用户拥有的角色 stmt = ( select(Role, UserRole) .join(UserRole, Role.id == UserRole.role_id) .where(UserRole.employee_id == current_user.employee_id) .where( # 过滤已过期的角色 (UserRole.expires_at.is_(None)) | (UserRole.expires_at > func.now()) ) ) result = await db.execute(stmt) role_rows = result.all() # 构建角色列表 roles = [] for role, user_role in role_rows: roles.append( RoleResponse( id=role.id, name=role.name, display_name=role.display_name, description=role.description, permissions=role.permissions or [], is_default=role.is_default, created_at=role.created_at, updated_at=role.updated_at, ) ) # 如果用户没有任何角色,添加默认的 user 角色 if not roles: # 查询 user 角色 user_role_stmt = select(Role).where(Role.name == "user") user_role_result = await db.execute(user_role_stmt) user_role = user_role_result.scalars().first() if user_role: roles.append( RoleResponse( id=user_role.id, name=user_role.name, display_name=user_role.display_name, description=user_role.description, permissions=user_role.permissions or [], is_default=user_role.is_default, created_at=user_role.created_at, updated_at=user_role.updated_at, ) ) # 构建响应 user_info = PortalUserInfo( employee_id=current_user.employee_id, name=current_user.name, department=current_user.department, avatar=current_user.avatar, roles=roles, current_role=current_user.current_role, ) return success_response(data=user_info.model_dump()) # -------------------------------------------------------------------------- # 切换当前角色 # -------------------------------------------------------------------------- @router.post("/switch-role") async def switch_role( body: SwitchRoleRequest, current_user: UserInfo = Depends(get_current_user), db: AsyncSession = Depends(get_db), credentials: HTTPAuthorizationCredentials = Depends(security), ): """切换当前角色。 更新 Redis Token 中的 current_role 字段,返回目标角色的入口 URL。 Args: body: 切换角色请求 current_user: 当前用户(通过认证依赖注入) db: 数据库会话 Returns: Dict: 统一响应格式,包含切换后的角色和重定向 URL """ # 验证用户是否有目标角色 stmt = ( select(Role) .join(UserRole, Role.id == UserRole.role_id) .where(UserRole.employee_id == current_user.employee_id) .where(Role.name == body.new_role) ) result = await db.execute(stmt) target_role = result.scalars().first() if not target_role: raise AppException(4003, f"没有 {body.new_role} 角色权限") # 更新 Redis Token 中的 current_role from app.dependencies import get_redis redis_client = await get_redis() token_service = TokenService(redis_client) # 从请求头获取 token token = credentials.credentials switch_success = await token_service.switch_role(token, body.new_role) if not switch_success: raise AppException(4003, "角色切换失败") # 获取目标角色的入口 URL redirect_url = _get_role_url(body.new_role) logger.info(f"用户 {current_user.employee_id} 切换角色到 {body.new_role}") return success_response( data=SwitchRoleResponse( current_role=body.new_role, redirect_url=redirect_url, ).model_dump() ) # -------------------------------------------------------------------------- # 获取角色对应的入口 URL # -------------------------------------------------------------------------- @router.get("/entry/{role_name}") async def get_role_entry( role_name: str, current_user: UserInfo = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """获取角色对应的入口 URL。 Args: role_name: 角色标识 current_user: 当前用户(通过认证依赖注入) db: 数据库会话 Returns: Dict: 统一响应格式,包含角色信息和入口 URL """ # 验证用户是否有目标角色 stmt = ( select(Role) .join(UserRole, Role.id == UserRole.role_id) .where(UserRole.employee_id == current_user.employee_id) .where(Role.name == role_name) ) result = await db.execute(stmt) target_role = result.scalars().first() if not target_role: raise AppException(4003, f"没有 {role_name} 角色权限") # 获取入口 URL redirect_url = _get_role_url(role_name) return success_response( data={ "role": role_name, "url": redirect_url, "display_name": target_role.display_name, } ) # -------------------------------------------------------------------------- # 辅助函数:获取角色对应的 URL # -------------------------------------------------------------------------- def _get_role_url(role_name: str) -> str: """获取角色对应的前端 URL。 Args: role_name: 角色标识 Returns: str: 前端 URL """ role_urls = { "user": "/itdesk/", "agent": "/itagent/", "admin": "/itadmin/", } return role_urls.get(role_name, "/itdesk/")