288 lines
9.9 KiB
Python
288 lines
9.9 KiB
Python
|
|
# =============================================================================
|
||
|
|
# 企微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)}")
|