273 lines
9.0 KiB
Python
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
|