460 lines
15 KiB
Python
460 lines
15 KiB
Python
#!/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)
|
||
|