Files

288 lines
9.9 KiB
Python
Raw Permalink Normal View History

# =============================================================================
# 企微IT智能服务台 — 终端绑定管理 CRUD 路由
# =============================================================================
# 说明:管理后台用于管理小鱼易联终端与企微会议室的绑定关系
#
# API 端点列表:
# GET /itportal/admin/terminal-bindings — 绑定列表(分页+搜索)
# POST /itportal/admin/terminal-bindings — 新增绑定
# PUT /itportal/admin/terminal-bindings/{id} — 更新绑定
# DELETE /itportal/admin/terminal-bindings/{id} — 删除绑定
# =============================================================================
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.dependencies import get_current_user, UserInfo, require_admin
from app.models.terminal_room_binding import TerminalRoomBinding
from app.schemas.meetingroom import (
TerminalBindingCreate,
TerminalBindingListResponse,
TerminalBindingResponse,
TerminalBindingUpdate,
)
from app.utils.response import AppException, success_response
logger = logging.getLogger(__name__)
# 创建路由器
router = APIRouter(prefix="/itportal/admin/terminal-bindings", tags=["终端绑定管理"])
def _binding_to_response(binding: TerminalRoomBinding) -> dict:
"""将 TerminalRoomBinding ORM 对象转换为响应字典。
Args:
binding: TerminalRoomBinding ORM 对象
Returns:
dict: 响应格式字典
"""
return TerminalBindingResponse(
id=binding.id,
terminal_sn=binding.terminal_sn,
terminal_name=binding.terminal_name,
meetingroom_id=binding.meetingroom_id,
meetingroom_name=binding.meetingroom_name,
location=binding.location,
is_active=binding.is_active,
created_at=binding.created_at.isoformat() if binding.created_at else "",
updated_at=binding.updated_at.isoformat() if binding.updated_at else "",
).model_dump()
# =============================================================================
# GET /itportal/admin/terminal-bindings — 绑定列表
# =============================================================================
@router.get("", response_model=None)
@router.get("/", response_model=None)
@require_admin
async def list_terminal_bindings(
page: int = Query(1, ge=1, description="页码(从1开始)"),
size: int = Query(20, ge=1, le=100, description="每页条数"),
keyword: Optional[str] = Query(None, description="搜索关键词(终端SN/名称/会议室名称)"),
db: AsyncSession = Depends(get_db),
current_user: UserInfo = Depends(get_current_user),
):
"""获取终端绑定列表(分页+搜索)。
需要管理员权限。
Args:
page: 页码(从1开始)
size: 每页条数
keyword: 搜索关键词(模糊匹配终端SN/终端名称/会议室名称)
db: 数据库会话
current_user: 当前登录用户
Returns:
Dict: 统一响应格式,data 含 list 和 total
"""
try:
# 构建查询条件
conditions = []
if keyword:
conditions.append(
or_(
TerminalRoomBinding.terminal_sn.ilike(f"%{keyword}%"),
TerminalRoomBinding.terminal_name.ilike(f"%{keyword}%"),
TerminalRoomBinding.meetingroom_name.ilike(f"%{keyword}%"),
)
)
# 查询总数
count_stmt = select(func.count()).select_from(TerminalRoomBinding)
if conditions:
count_stmt = count_stmt.where(*conditions)
count_result = await db.execute(count_stmt)
total = count_result.scalar() or 0
# 查询列表(分页)
stmt = select(TerminalRoomBinding).order_by(TerminalRoomBinding.id.desc())
if conditions:
stmt = stmt.where(*conditions)
stmt = stmt.offset((page - 1) * size).limit(size)
result = await db.execute(stmt)
bindings = result.scalars().all()
# 转换为响应格式
binding_list = [_binding_to_response(b) for b in bindings]
return success_response(data={
"list": binding_list,
"total": total,
})
except Exception as e:
logger.error(f"查询终端绑定列表异常: {e}", exc_info=True)
raise AppException(1005, f"查询终端绑定列表失败: {str(e)}")
# =============================================================================
# POST /itportal/admin/terminal-bindings — 新增绑定
# =============================================================================
@router.post("", response_model=None)
@router.post("/", response_model=None)
@require_admin
async def create_terminal_binding(
body: TerminalBindingCreate,
db: AsyncSession = Depends(get_db),
current_user: UserInfo = Depends(get_current_user),
):
"""新增终端绑定。
需要管理员权限。
一个终端SN只能绑定一个会议室(terminal_sn 唯一约束)。
Args:
body: 新增绑定请求
db: 数据库会话
current_user: 当前登录用户
Returns:
Dict: 统一响应格式,data 含新建的绑定ID
"""
try:
# 检查 terminal_sn 是否已存在
existing_stmt = select(TerminalRoomBinding).where(
TerminalRoomBinding.terminal_sn == body.terminal_sn
)
existing_result = await db.execute(existing_stmt)
existing = existing_result.scalar_one_or_none()
if existing:
raise AppException(3107, f"终端SN '{body.terminal_sn}' 已绑定会议室,请先删除原有绑定")
# 创建新绑定
binding = TerminalRoomBinding(
terminal_sn=body.terminal_sn,
terminal_name=body.terminal_name,
meetingroom_id=body.meetingroom_id,
meetingroom_name=body.meetingroom_name,
location=body.location,
is_active=True,
)
db.add(binding)
await db.flush() # 获取自增ID
logger.info(
f"新增终端绑定: sn={body.terminal_sn}, room_id={body.meetingroom_id}, "
f"operator={current_user.employee_id}"
)
return success_response(data={"id": binding.id})
except AppException:
raise
except Exception as e:
logger.error(f"新增终端绑定异常: {e}", exc_info=True)
raise AppException(1005, f"新增终端绑定失败: {str(e)}")
# =============================================================================
# PUT /itportal/admin/terminal-bindings/{id} — 更新绑定
# =============================================================================
@router.put("/{binding_id}", response_model=None)
@require_admin
async def update_terminal_binding(
binding_id: int,
body: TerminalBindingUpdate,
db: AsyncSession = Depends(get_db),
current_user: UserInfo = Depends(get_current_user),
):
"""更新终端绑定信息。
需要管理员权限。仅更新传入的字段(部分更新)。
Args:
binding_id: 绑定记录ID
body: 更新请求
db: 数据库会话
current_user: 当前登录用户
Returns:
Dict: 统一响应格式
"""
try:
stmt = select(TerminalRoomBinding).where(TerminalRoomBinding.id == binding_id)
result = await db.execute(stmt)
binding = result.scalar_one_or_none()
if not binding:
raise AppException(3107, "终端绑定不存在")
# 部分更新(仅更新传入的字段)
if body.terminal_name is not None:
binding.terminal_name = body.terminal_name
if body.meetingroom_id is not None:
binding.meetingroom_id = body.meetingroom_id
if body.meetingroom_name is not None:
binding.meetingroom_name = body.meetingroom_name
if body.location is not None:
binding.location = body.location
if body.is_active is not None:
binding.is_active = body.is_active
logger.info(
f"更新终端绑定: id={binding_id}, operator={current_user.employee_id}"
)
return success_response(data={})
except AppException:
raise
except Exception as e:
logger.error(f"更新终端绑定异常: id={binding_id}, error={e}", exc_info=True)
raise AppException(1005, f"更新终端绑定失败: {str(e)}")
# =============================================================================
# DELETE /itportal/admin/terminal-bindings/{id} — 删除绑定
# =============================================================================
@router.delete("/{binding_id}", response_model=None)
@require_admin
async def delete_terminal_binding(
binding_id: int,
db: AsyncSession = Depends(get_db),
current_user: UserInfo = Depends(get_current_user),
):
"""删除终端绑定。
需要管理员权限。
Args:
binding_id: 绑定记录ID
db: 数据库会话
current_user: 当前登录用户
Returns:
Dict: 统一响应格式
"""
try:
stmt = select(TerminalRoomBinding).where(TerminalRoomBinding.id == binding_id)
result = await db.execute(stmt)
binding = result.scalar_one_or_none()
if not binding:
raise AppException(3107, "终端绑定不存在")
await db.delete(binding)
logger.info(
f"删除终端绑定: id={binding_id}, sn={binding.terminal_sn}, "
f"operator={current_user.employee_id}"
)
return success_response(data={})
except AppException:
raise
except Exception as e:
logger.error(f"删除终端绑定异常: id={binding_id}, error={e}", exc_info=True)
raise AppException(1005, f"删除终端绑定失败: {str(e)}")