diff --git a/api/app/controllers/__init__.py b/api/app/controllers/__init__.py index 13e66ea7..2cddfb30 100644 --- a/api/app/controllers/__init__.py +++ b/api/app/controllers/__init__.py @@ -33,6 +33,7 @@ from . import ( emotion_config_controller, prompt_optimizer_controller, tool_controller, + home_page_controller, ) from . import user_memory_controllers @@ -70,5 +71,6 @@ manager_router.include_router(emotion_config_controller.router) manager_router.include_router(prompt_optimizer_controller.router) manager_router.include_router(memory_reflection_controller.router) manager_router.include_router(tool_controller.router) +manager_router.include_router(home_page_controller.router) __all__ = ["manager_router"] diff --git a/api/app/controllers/home_page_controller.py b/api/app/controllers/home_page_controller.py new file mode 100644 index 00000000..6665eec1 --- /dev/null +++ b/api/app/controllers/home_page_controller.py @@ -0,0 +1,29 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session + +from app.core.response_utils import success +from app.db import get_db +from app.dependencies import get_current_user +from app.models.user_model import User +from app.schemas.response_schema import ApiResponse +from app.services.home_page_service import HomePageService + +router = APIRouter(prefix="/home-page", tags=["Home Page"]) + +@router.get("/statistics", response_model=ApiResponse) +def get_home_statistics( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """获取首页统计数据""" + statistics = HomePageService.get_home_statistics(db, current_user.tenant_id) + return success(data=statistics, msg="统计数据获取成功") + +@router.get("/workspaces", response_model=ApiResponse) +def get_workspace_list( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """获取工作空间列表""" + workspace_list = HomePageService.get_workspace_list(db, current_user.tenant_id) + return success(data=workspace_list, msg="工作空间列表获取成功") \ No newline at end of file diff --git a/api/app/core/api_key_utils.py b/api/app/core/api_key_utils.py index 877ddd01..fb6b9552 100644 --- a/api/app/core/api_key_utils.py +++ b/api/app/core/api_key_utils.py @@ -3,7 +3,7 @@ import secrets from typing import Optional, Union from datetime import datetime -from app.schemas.api_key_schema import ApiKeyType +from app.models.api_key_model import ApiKeyType from fastapi import Response from fastapi.responses import JSONResponse diff --git a/api/app/repositories/home_page_repository.py b/api/app/repositories/home_page_repository.py new file mode 100644 index 00000000..e37f1f00 --- /dev/null +++ b/api/app/repositories/home_page_repository.py @@ -0,0 +1,137 @@ +from datetime import datetime, timedelta +from sqlalchemy.orm import Session +from sqlalchemy import func +from uuid import UUID +from typing import Dict + +from app.models.end_user_model import EndUser +from app.models.user_model import User +from app.models.workspace_model import Workspace, WorkspaceMember +from app.models.models_model import ModelConfig +from app.models.app_model import App + +class HomePageRepository: + + @staticmethod + def get_model_statistics(db: Session, tenant_id: UUID, month_start: datetime) -> tuple[int, int]: + """获取模型统计数据""" + total_models = db.query(ModelConfig).filter( + ModelConfig.tenant_id == tenant_id, + ModelConfig.is_active == True + ).count() + + new_models_this_month = db.query(ModelConfig).filter( + ModelConfig.tenant_id == tenant_id, + ModelConfig.is_active == True, + ModelConfig.created_at >= month_start + ).count() + + return total_models, new_models_this_month + + @staticmethod + def get_workspace_statistics(db: Session, tenant_id: UUID, month_start: datetime) -> tuple[int, int]: + """获取工作空间统计数据""" + active_workspaces = db.query(Workspace).filter( + Workspace.tenant_id == tenant_id, + Workspace.is_active == True + ).count() + + new_workspaces_this_month = db.query(Workspace).filter( + Workspace.tenant_id == tenant_id, + Workspace.is_active == True, + Workspace.created_at >= month_start + ).count() + + return active_workspaces, new_workspaces_this_month + + @staticmethod + def get_user_statistics(db: Session, tenant_id: UUID, month_start: datetime) -> tuple[int, int]: + """获取用户统计数据""" + workspace_ids = db.query(Workspace.id).filter( + Workspace.tenant_id == tenant_id, + Workspace.is_active == True + ).subquery() + + total_users = db.query(EndUser).join( + App, + EndUser.app_id == App.id + ).filter( + App.workspace_id.in_(workspace_ids), + App.is_active == True, + App.status == "active" + ).count() + + new_users_this_month = db.query(EndUser).join( + App, + EndUser.app_id == App.id + ).filter( + App.workspace_id.in_(workspace_ids), + App.is_active == True, + App.status == "active", + EndUser.created_at >= month_start + ).count() + + return total_users, new_users_this_month + + @staticmethod + def get_app_statistics(db: Session, tenant_id: UUID, week_start: datetime) -> tuple[int, int]: + """获取应用统计数据""" + workspace_ids = db.query(Workspace.id).filter( + Workspace.tenant_id == tenant_id, + Workspace.is_active == True + ).subquery() + + running_apps = db.query(App).filter( + App.workspace_id.in_(workspace_ids), + App.is_active == True, + App.status == "active" + ).count() + + new_apps_this_week = db.query(App).filter( + App.workspace_id.in_(workspace_ids), + App.is_active == True, + App.status == "active", + App.created_at >= week_start + ).count() + + return running_apps, new_apps_this_week + + @staticmethod + def get_workspaces_with_counts(db: Session, tenant_id: UUID) -> tuple[list[Workspace], Dict[UUID, int], Dict[UUID, int]]: + """批量获取工作空间及其统计数据""" + # 获取工作空间列表 + workspaces = db.query(Workspace).filter( + Workspace.tenant_id == tenant_id, + Workspace.is_active == True + ).all() + + workspace_ids = [ws.id for ws in workspaces] + + # 批量获取应用数量 + app_counts = db.query( + App.workspace_id, + func.count(App.id).label('count') + ).filter( + App.workspace_id.in_(workspace_ids), + App.is_active, + App.status == "active" + ).group_by(App.workspace_id).all() + + app_count_dict = {workspace_id: count for workspace_id, count in app_counts} + + # 批量获取用户数量 + user_counts = db.query( + App.workspace_id, + func.count(EndUser.id).label('count') + ).join( + EndUser, + EndUser.app_id == App.id + ).filter( + App.workspace_id.in_(workspace_ids), + App.is_active, + App.status == "active" + ).group_by(App.workspace_id).all() + + user_count_dict = {workspace_id: count for workspace_id, count in user_counts} + + return workspaces, app_count_dict, user_count_dict \ No newline at end of file diff --git a/api/app/schemas/home_page_schema.py b/api/app/schemas/home_page_schema.py new file mode 100644 index 00000000..de223e17 --- /dev/null +++ b/api/app/schemas/home_page_schema.py @@ -0,0 +1,32 @@ +from datetime import datetime +from pydantic import BaseModel, field_serializer +from typing import Optional + +from app.core.api_key_utils import datetime_to_timestamp + + +class HomeStatistics(BaseModel): + """首页统计数据""" + total_models: int + new_models_this_month: int + active_workspaces: int + new_workspaces_this_month: int + total_users: int + new_users_this_month: int + running_apps: int + new_apps_this_week: int + +class WorkspaceInfo(BaseModel): + """工作空间信息""" + id: str + name: str + icon: Optional[str] + description: Optional[str] + app_count: int + user_count: int + created_at: datetime + + @field_serializer('created_at') + @classmethod + def serialize_datetime(cls, v: datetime) -> Optional[int]: + return datetime_to_timestamp(v) \ No newline at end of file diff --git a/api/app/services/home_page_service.py b/api/app/services/home_page_service.py new file mode 100644 index 00000000..909da25f --- /dev/null +++ b/api/app/services/home_page_service.py @@ -0,0 +1,67 @@ +from datetime import datetime, timedelta +from sqlalchemy.orm import Session +from uuid import UUID + +from app.repositories.home_page_repository import HomePageRepository +from app.schemas.home_page_schema import HomeStatistics, WorkspaceInfo + +class HomePageService: + + @staticmethod + def get_home_statistics(db: Session, tenant_id: UUID) -> HomeStatistics: + """获取首页统计数据""" + # 计算时间范围 + now = datetime.now() + month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + week_start = now - timedelta(days=now.weekday()) + week_start = week_start.replace(hour=0, minute=0, second=0, microsecond=0) + + # 获取各项统计数据 + total_models, new_models_this_month = HomePageRepository.get_model_statistics( + db, tenant_id, month_start + ) + + active_workspaces, new_workspaces_this_month = HomePageRepository.get_workspace_statistics( + db, tenant_id, month_start + ) + + total_users, new_users_this_month = HomePageRepository.get_user_statistics( + db, tenant_id, month_start + ) + + running_apps, new_apps_this_week = HomePageRepository.get_app_statistics( + db, tenant_id, week_start + ) + + return HomeStatistics( + total_models=total_models, + new_models_this_month=new_models_this_month, + active_workspaces=active_workspaces, + new_workspaces_this_month=new_workspaces_this_month, + total_users=total_users, + new_users_this_month=new_users_this_month, + running_apps=running_apps, + new_apps_this_week=new_apps_this_week + ) + + @staticmethod + def get_workspace_list(db: Session, tenant_id: UUID) -> list[WorkspaceInfo]: + """获取工作空间列表(优化版本)""" + workspaces, app_count_dict, user_count_dict= HomePageRepository.get_workspaces_with_counts( + db, tenant_id + ) + + workspace_list = [] + for workspace in workspaces: + workspace_info = WorkspaceInfo( + id=str(workspace.id), + name=workspace.name, + icon=workspace.icon, + description=workspace.description, + app_count=app_count_dict.get(workspace.id, 0), + user_count=user_count_dict.get(workspace.id, 0), + created_at=workspace.created_at + ) + workspace_list.append(workspace_info) + + return workspace_list \ No newline at end of file