VWED_server/routes/calldevice_api.py

460 lines
15 KiB
Python
Raw Normal View History

2025-07-14 10:29:37 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
呼叫器设备API模块
提供呼叫器设备相关的API接口
"""
from typing import Dict, List, Any, Optional
from fastapi import APIRouter, Body, Query, Path, Request, File, UploadFile, Form, Response
from pydantic import BaseModel
from routes.common_api import format_response, error_response
from utils.logger import get_logger
from services.calldevice_service import CallDeviceService
from routes.model.calldevice_model import CallDeviceModel, DeleteDeviceRequest, ExportDevicesRequest
from utils.crypto_utils import CryptoUtils
import time
import random
import json
# 创建路由
router = APIRouter(
prefix="/api/vwed-calldevice",
tags=["VWED呼叫器设备"]
)
# 设置日志
logger = get_logger("app.calldevice_api")
@router.post("/add")
async def add_call_device(
call_device: CallDeviceModel = Body(..., description="呼叫器设备信息"),
request: Request = Request
):
"""
新增呼叫器设备
Args:
call_device: 呼叫器设备信息包括协议类型品牌IP端口设备名称状态及按钮配置
request: 请求对象
Returns:
包含新增结果的响应
"""
try:
# 将按钮列表转换为字典列表,映射字段名称
buttons_dict = []
api_token = request.headers.get("x-access-token")
if call_device.buttons:
for button in call_device.buttons:
button_dict = {
"signal_name": button.signal_name,
"signal_type": button.signal_type,
"signal_length": button.signal_length,
"register_address": button.register_address,
"function_code": button.function_code,
"remark": button.remark,
"vwed_task_id": button.vwed_task_id,
"long_vwed_task_id": button.long_vwed_task_id
}
buttons_dict.append(button_dict)
result = await CallDeviceService.add_call_device(
protocol=call_device.protocol,
brand=call_device.brand,
ip=call_device.ip,
port=call_device.port,
device_name=call_device.device_name,
status=call_device.status,
slave_id=call_device.slave_id,
buttons=buttons_dict,
api_token=api_token
)
if not result.get("success", False):
return error_response(
message=result.get("message", "新增呼叫器设备失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "新增呼叫器设备成功")
)
except Exception as e:
logger.error(f"新增呼叫器设备异常: {str(e)}")
return error_response(message=f"新增呼叫器设备失败: {str(e)}", code=500)
@router.put("/update/{device_id}")
async def update_call_device(
device_id: str = Path(..., description="设备ID"),
call_device: CallDeviceModel = Body(..., description="呼叫器设备信息")
):
"""
更新呼叫器设备
Args:
device_id: 设备ID
call_device: 呼叫器设备信息包括协议类型品牌IP端口设备名称状态及按钮配置
Returns:
包含更新结果的响应
"""
try:
# 将按钮列表转换为字典列表,映射字段名称
buttons_dict = []
if call_device.buttons:
for button in call_device.buttons:
button_dict = {
"signal_name": button.signal_name,
"signal_type": button.signal_type,
"signal_length": button.signal_length,
"register_address": button.register_address,
"function_code": button.function_code,
"remark": button.remark,
"vwed_task_id": button.vwed_task_id,
"long_vwed_task_id": button.long_vwed_task_id
}
buttons_dict.append(button_dict)
result = await CallDeviceService.update_call_device(
device_id=device_id,
protocol=call_device.protocol,
brand=call_device.brand,
ip=call_device.ip,
port=call_device.port,
device_name=call_device.device_name,
status=call_device.status,
slave_id=call_device.slave_id,
buttons=buttons_dict
)
if not result.get("success", False):
return error_response(
message=result.get("message", "更新呼叫器设备失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "更新呼叫器设备成功")
)
except Exception as e:
logger.error(f"更新呼叫器设备异常: {str(e)}")
return error_response(message=f"更新呼叫器设备失败: {str(e)}", code=500)
@router.post("/batch-delete")
async def delete_call_device_batch(
request: DeleteDeviceRequest = Body(..., description="批量删除设备请求")
):
"""
删除呼叫器设备
Args:
request: 包含要删除的设备ID列表的请求
Returns:
包含删除结果的响应
"""
try:
if not request.ids:
return error_response(
message="设备ID列表不能为空",
code=400
)
result = await CallDeviceService.delete_call_device(device_ids=request.ids)
if not result.get("success", False):
return error_response(
message=result.get("message", "批量删除呼叫器设备失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "批量删除呼叫器设备成功")
)
except Exception as e:
logger.error(f"批量删除呼叫器设备异常: {str(e)}")
return error_response(message=f"批量删除呼叫器设备失败: {str(e)}", code=500)
@router.get("/list")
async def get_call_device_list(
page: int = Query(1, description="页码", ge=1),
page_size: int = Query(10, description="每页数量", ge=1, le=100),
device_name: str = Query(None, description="设备名称搜索"),
ip: str = Query(None, description="IP地址搜索"),
protocol: str = Query(None, description="协议类型搜索"),
status: int = Query(None, description="状态过滤(0:禁用,1:启用)")
):
"""
获取呼叫器设备列表
Args:
page: 页码
page_size: 每页数量
device_name: 设备名称搜索
ip: IP地址搜索
protocol: 协议类型搜索
status: 状态过滤(0:禁用,1:启用)
Returns:
包含设备列表和分页信息的响应
"""
try:
result = await CallDeviceService.get_call_device_list(
page=page,
page_size=page_size,
device_name=device_name,
ip=ip,
protocol=protocol,
status=status
)
if not result.get("success", False):
return error_response(
message=result.get("message", "获取呼叫器设备列表失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "获取呼叫器设备列表成功")
)
except Exception as e:
logger.error(f"获取呼叫器设备列表异常: {str(e)}")
return error_response(message=f"获取呼叫器设备列表失败: {str(e)}", code=500)
@router.get("/detail/{device_id}")
async def get_call_device_detail(
device_id: str = Path(..., description="设备ID")
):
"""
获取呼叫器设备详情
Args:
device_id: 设备ID
Returns:
包含设备详情的响应
"""
try:
result = await CallDeviceService.get_call_device_detail(device_id=device_id)
if not result.get("success", False):
return error_response(
message=result.get("message", "获取呼叫器设备详情失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "获取呼叫器设备详情成功")
)
except Exception as e:
logger.error(f"获取呼叫器设备详情异常: {str(e)}")
return error_response(message=f"获取呼叫器设备详情失败: {str(e)}", code=500)
@router.post("/export-batch")
async def export_batch_calldevice(
export_req: ExportDevicesRequest = Body(..., description="批量导出设备请求")
):
"""
批量导出呼叫器设备
Args:
export_req: 导出请求数据包含要导出的设备ID列表
Returns:
Response: 包含设备配置的加密专有格式文件
"""
try:
result = await CallDeviceService.export_call_devices(device_ids=export_req.ids)
if not result.get("success", False):
return error_response(
message=result.get("message", "批量导出呼叫器设备失败"),
code=400
)
# 获取设备数据
devices_data = result.get("data", [])
# 加密数据并添加文件头、签名和校验和
encrypted_data = CryptoUtils.encrypt_data(devices_data)
# 使用专有文件扩展名 .vdex (VWED Device Export)
filename = f"calldevices_export_{len(export_req.ids)}.vdex" if len(export_req.ids) > 1 else f"calldevice_{export_req.ids[0]}.vdex"
# 返回加密的二进制文件
headers = {
'Content-Disposition': f'attachment; filename={filename}',
'Content-Type': 'application/octet-stream',
'X-Content-Type-Options': 'nosniff', # 防止浏览器嗅探文件类型
"Access-Control-Expose-Headers": "Content-Disposition"
}
return Response(content=encrypted_data, media_type='application/octet-stream', headers=headers)
except Exception as e:
logger.error(f"批量导出呼叫器设备异常: {str(e)}")
return error_response(message=f"批量导出呼叫器设备失败: {str(e)}", code=500)
@router.post("/import")
async def import_calldevice(
file: UploadFile = File(..., description="呼叫器设备配置文件,加密专有格式")
):
"""
导入呼叫器设备
Args:
file: 设备配置文件加密专有格式
Returns:
包含导入结果的响应
"""
try:
# 读取文件内容
content = await file.read()
try:
# 解密并验证数据
devices_data = CryptoUtils.decrypt_data(content)
# 生成唯一后缀以避免名称冲突
timestamp = int(time.time())
rand_num = random.randint(1000, 9999)
rename_suffix = f"-备份-{timestamp}{rand_num}"
# 导入设备
result = await CallDeviceService.import_call_devices(
devices_data=devices_data,
rename_suffix=rename_suffix
)
if not result.get("success", False):
return error_response(
message=result.get("message", "导入呼叫器设备失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "导入呼叫器设备成功")
)
except ValueError as e:
logger.error(f"文件格式无效或已损坏: {str(e)}")
return error_response(f"无法导入呼叫器设备: {str(e)}", 400)
except Exception as e:
logger.error(f"解析呼叫器设备数据失败: {str(e)}")
return error_response(f"解析呼叫器设备数据失败: {str(e)}", 400)
except Exception as e:
logger.error(f"导入呼叫器设备异常: {str(e)}")
return error_response(message=f"导入呼叫器设备失败: {str(e)}", code=500)
@router.post("/monitor/start/{device_id}")
async def start_device_monitor(
device_id: str = Path(..., description="设备ID"),
request: Request = Request
):
"""
启动设备监控
开启一个监控线程持续监控设备按钮状态当检测到按钮按下时触发对应任务
Args:
device_id: 设备ID
Returns:
包含启动结果的响应
"""
try:
# 获取token用于同步到系统任务
tf_api_token = request.headers.get("x-access-token")
result = await CallDeviceService.start_device_monitor(device_id, tf_api_token)
if not result.get("success", False):
return error_response(
message=result.get("message", "启动设备监控失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "启动设备监控成功")
)
except Exception as e:
logger.error(f"启动设备监控异常: {str(e)}")
return error_response(message=f"启动设备监控失败: {str(e)}", code=500)
@router.post("/monitor/stop/{device_id}")
async def stop_device_monitor(
device_id: str = Path(..., description="设备ID")
):
"""
停止设备监控
停止设备的监控线程不再监控按钮状态
Args:
device_id: 设备ID
Returns:
包含停止结果的响应
"""
try:
result = await CallDeviceService.stop_device_monitor(device_id)
if not result.get("success", False):
return error_response(
message=result.get("message", "停止设备监控失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "停止设备监控成功")
)
except Exception as e:
logger.error(f"停止设备监控异常: {str(e)}")
return error_response(message=f"停止设备监控失败: {str(e)}", code=500)
@router.get("/monitor/status/{device_id}")
async def get_device_monitor_status(
device_id: str = Path(..., description="设备ID")
):
"""
获取设备监控状态
获取指定设备的监控状态信息
Args:
device_id: 设备ID
Returns:
包含设备监控状态的响应
"""
try:
result = await CallDeviceService.get_device_monitor_status(device_id)
if not result.get("success", False):
return error_response(
message=result.get("message", "获取设备监控状态失败"),
code=400
)
return format_response(
data=result.get("data", {}),
message=result.get("message", "获取设备监控状态成功")
)
except Exception as e:
logger.error(f"获取设备监控状态异常: {str(e)}")
return error_response(message=f"获取设备监控状态失败: {str(e)}", code=500)