Files
ai-agent-admin/backend-fastapi/online_dev/screen_design/screen_service.py
T

273 lines
9.0 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
大屏设计器服务
"""
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import select, func, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.base_service import BaseService
from online_dev.screen_design.screen_model import ScreenProject
from online_dev.screen_design.screen_schema import ScreenProjectCreateIn, ScreenProjectUpdateIn
logger = logging.getLogger(__name__)
class ScreenServiceException(Exception):
"""大屏服务异常"""
pass
class ScreenProjectService(BaseService[ScreenProject, ScreenProjectCreateIn, ScreenProjectUpdateIn]):
"""
大屏项目服务
数据权限:
- 使用 get_list_with_data_scope() 自动应用数据权限
- 支持本人、本部门、本部门及下级、全部等数据范围
"""
model = ScreenProject
# 资源类型(用于数据权限配置)
RESOURCE_TYPE = "screen_project"
RESOURCE_DISPLAY_NAME = "大屏管理"
@classmethod
async def get_list(
cls,
db: AsyncSession,
page: int = 1,
page_size: int = 20,
name: str = None,
code: str = None,
status: str = None,
application_id: str = None,
) -> Tuple[List[ScreenProject], int]:
"""获取大屏项目列表"""
stmt = select(ScreenProject).where(ScreenProject.is_deleted == False)
# 应用过滤
if application_id is not None:
if application_id == "":
stmt = stmt.where(ScreenProject.application_id.is_(None))
else:
stmt = stmt.where(ScreenProject.application_id == application_id)
if name:
stmt = stmt.where(ScreenProject.name.contains(name))
if code:
stmt = stmt.where(ScreenProject.code.contains(code))
if status:
stmt = stmt.where(ScreenProject.status == status)
# 计算总数
count_stmt = select(func.count()).select_from(stmt.subquery())
total_result = await db.execute(count_stmt)
total = total_result.scalar() or 0
# 分页
stmt = stmt.order_by(ScreenProject.sort.asc(), ScreenProject.sys_create_datetime.desc())
stmt = stmt.offset((page - 1) * page_size).limit(page_size)
result = await db.execute(stmt)
items = list(result.scalars().all())
return items, total
@classmethod
async def get_by_code(cls, db: AsyncSession, code: str) -> Optional[ScreenProject]:
"""根据编码获取大屏项目"""
stmt = select(ScreenProject).where(
ScreenProject.code == code,
ScreenProject.is_deleted == False,
)
result = await db.execute(stmt)
return result.scalar_one_or_none()
@classmethod
async def check_code_exists(cls, db: AsyncSession, code: str, exclude_id: str = None) -> bool:
"""检查编码是否存在"""
stmt = select(ScreenProject).where(
ScreenProject.code == code,
ScreenProject.is_deleted == False,
)
if exclude_id:
stmt = stmt.where(ScreenProject.id != exclude_id)
result = await db.execute(stmt)
return result.scalar_one_or_none() is not None
@classmethod
async def publish(cls, db: AsyncSession, project_id: str, access_password: str = None) -> ScreenProject:
"""发布大屏"""
project = await cls.get_by_id(db, project_id)
if not project:
raise ScreenServiceException(f"大屏项目不存在: {project_id}")
if project.status == 'published':
raise ScreenServiceException("大屏已发布")
project.status = 'published'
project.version += 1
project.published_at = datetime.now()
if access_password is not None:
project.access_password = access_password
db.add(project)
await db.flush()
await db.refresh(project)
logger.info(f"大屏发布成功: {project.code}, version={project.version}")
return project
@classmethod
async def unpublish(cls, db: AsyncSession, project_id: str) -> ScreenProject:
"""取消发布大屏"""
project = await cls.get_by_id(db, project_id)
if not project:
raise ScreenServiceException(f"大屏项目不存在: {project_id}")
if project.status == 'draft':
raise ScreenServiceException("大屏未发布")
project.status = 'draft'
db.add(project)
await db.flush()
await db.refresh(project)
logger.info(f"大屏取消发布: {project.code}")
return project
@classmethod
async def set_password(cls, db: AsyncSession, project_id: str, password: str) -> ScreenProject:
"""设置或清除访问密码"""
project = await cls.get_by_id(db, project_id)
if not project:
raise ScreenServiceException(f"大屏项目不存在: {project_id}")
project.access_password = password
db.add(project)
await db.flush()
await db.refresh(project)
logger.info(f"大屏密码已{'设置' if password else '清除'}: {project.code}")
return project
@classmethod
async def verify_password(cls, db: AsyncSession, project_id: str, password: str) -> bool:
"""验证访问密码"""
project = await cls.get_by_id(db, project_id)
if not project:
raise ScreenServiceException(f"大屏项目不存在: {project_id}")
# 如果没有设置密码,直接返回 True
if not project.access_password:
return True
return project.access_password == password
@classmethod
async def copy(
cls,
db: AsyncSession,
project_id: str,
new_code: str,
new_name: str = None,
) -> ScreenProject:
"""复制大屏项目"""
source = await cls.get_by_id(db, project_id)
if not source:
raise ScreenServiceException(f"大屏项目不存在: {project_id}")
# 检查新编码唯一性
if await cls.check_code_exists(db, new_code):
raise ScreenServiceException(f"项目编码已存在: {new_code}")
new_project = ScreenProject(
application_id=source.application_id,
name=new_name or f"{source.name}_副本",
code=new_code,
description=source.description,
status='draft',
version=1,
thumbnail=source.thumbnail,
screen_config=source.screen_config,
sort=source.sort,
)
db.add(new_project)
await db.flush()
await db.refresh(new_project)
logger.info(f"大屏项目复制成功: {source.code} -> {new_code}")
return new_project
@classmethod
async def export_config(cls, db: AsyncSession, project_id: str) -> Dict[str, Any]:
"""导出大屏配置"""
project = await cls.get_by_id(db, project_id)
if not project:
raise ScreenServiceException(f"大屏项目不存在: {project_id}")
return {
'name': project.name,
'code': project.code,
'description': project.description,
'screen_config': project.screen_config,
}
@classmethod
async def check_import(cls, db: AsyncSession, code: str) -> Dict[str, Any]:
"""导入预检查:编码是否冲突"""
code_exists = False
if code:
code_exists = await cls.check_code_exists(db, code)
return {
'code_exists': code_exists,
'can_import': not code_exists,
}
@classmethod
async def import_config(cls, db: AsyncSession, data: Dict[str, Any]) -> ScreenProject:
"""导入大屏配置"""
required_fields = ['name', 'code']
for field in required_fields:
if not data.get(field):
raise ScreenServiceException(f"缺少必要字段: {field}")
if await cls.check_code_exists(db, data['code']):
raise ScreenServiceException(f"项目编码已存在: {data['code']}")
project = ScreenProject(
application_id=data.get('application_id'),
name=data['name'],
code=data['code'],
description=data.get('description', ''),
screen_config=data.get('screen_config', {}),
)
db.add(project)
await db.flush()
await db.refresh(project)
logger.info(f"大屏项目导入成功: {project.code}")
return project
@classmethod
async def batch_delete(cls, db: AsyncSession, project_ids: List[str]) -> int:
"""批量删除大屏项目"""
stmt = (
update(ScreenProject)
.where(ScreenProject.id.in_(project_ids), ScreenProject.is_deleted == False)
.values(is_deleted=True)
)
result = await db.execute(stmt)
count = result.rowcount
logger.info(f"批量删除大屏项目成功: {count}")
return count