| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | import json | 
					
						
							| 
									
										
										
										
											2025-09-12 20:09:07 +08:00
										 |  |  |  | from datetime import date | 
					
						
							| 
									
										
										
										
											2025-09-03 13:52:24 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  | from fastapi import APIRouter, Query, HTTPException, Request, Path | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | from mysql.connector import Error as MySQLError | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | from ds.db import db | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  | from schema.device_schema import ( | 
					
						
							|  |  |  |  |     DeviceCreateRequest, DeviceResponse, DeviceListResponse, | 
					
						
							|  |  |  |  |     DeviceStatusHistoryResponse, DeviceStatusHistoryListResponse | 
					
						
							|  |  |  |  | ) | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | from schema.response_schema import APIResponse | 
					
						
							| 
									
										
										
										
											2025-09-02 21:42:09 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | router = APIRouter( | 
					
						
							|  |  |  |  |     prefix="/devices", | 
					
						
							|  |  |  |  |     tags=["设备管理"] | 
					
						
							|  |  |  |  | ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | # 内部工具方法 - 记录设备状态变更历史 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | def record_status_change(client_ip: str, status: int) -> bool: | 
					
						
							|  |  |  |  |     """
 | 
					
						
							|  |  |  |  |     记录设备状态变更历史(写入 device_action 表) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     :param client_ip: 设备IP | 
					
						
							|  |  |  |  |     :param status: 状态(1-在线、0-离线) | 
					
						
							|  |  |  |  |     :return: 操作是否成功 | 
					
						
							|  |  |  |  |     """
 | 
					
						
							|  |  |  |  |     if not client_ip: | 
					
						
							|  |  |  |  |         raise ValueError("客户端IP不能为空") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if status not in (0, 1): | 
					
						
							|  |  |  |  |         raise ValueError("状态必须是0(离线)或1(在线)") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 插入状态变更记录到 device_action | 
					
						
							|  |  |  |  |         insert_query = """
 | 
					
						
							|  |  |  |  |             INSERT INTO device_action  | 
					
						
							|  |  |  |  |             (client_ip, action, created_at, updated_at) | 
					
						
							|  |  |  |  |             VALUES (%s, %s, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) | 
					
						
							|  |  |  |  |         """
 | 
					
						
							|  |  |  |  |         cursor.execute(insert_query, (client_ip, status)) | 
					
						
							|  |  |  |  |         conn.commit() | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return True | 
					
						
							|  |  |  |  |     except MySQLError as e: | 
					
						
							|  |  |  |  |         if conn: | 
					
						
							|  |  |  |  |             conn.rollback() | 
					
						
							|  |  |  |  |         raise Exception(f"记录设备状态变更失败: {str(e)}") from e | 
					
						
							|  |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | # 内部工具方法 - 通过客户端IP增加设备报警次数 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | def increment_alarm_count_by_ip(client_ip: str) -> bool: | 
					
						
							|  |  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |     通过客户端IP增加设备的报警次数 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     :param client_ip: 客户端IP地址 | 
					
						
							|  |  |  |  |     :return: 操作是否成功 | 
					
						
							|  |  |  |  |     """
 | 
					
						
							|  |  |  |  |     if not client_ip: | 
					
						
							|  |  |  |  |         raise ValueError("客户端IP不能为空") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							| 
									
										
										
										
											2025-09-02 21:42:09 +08:00
										 |  |  |  |     try: | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 检查设备是否存在 | 
					
						
							|  |  |  |  |         cursor.execute("SELECT id FROM devices WHERE client_ip = %s", (client_ip,)) | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         device = cursor.fetchone() | 
					
						
							|  |  |  |  |         if not device: | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |             raise ValueError(f"客户端IP为 {client_ip} 的设备不存在") | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-08 17:34:23 +08:00
										 |  |  |  |         # 报警次数加1、并更新时间戳 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         update_query = """
 | 
					
						
							|  |  |  |  |             UPDATE devices  | 
					
						
							|  |  |  |  |             SET alarm_count = alarm_count + 1,  | 
					
						
							|  |  |  |  |                 updated_at = CURRENT_TIMESTAMP  | 
					
						
							|  |  |  |  |             WHERE client_ip = %s | 
					
						
							|  |  |  |  |         """
 | 
					
						
							|  |  |  |  |         cursor.execute(update_query, (client_ip,)) | 
					
						
							|  |  |  |  |         conn.commit() | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return True | 
					
						
							|  |  |  |  |     except MySQLError as e: | 
					
						
							|  |  |  |  |         if conn: | 
					
						
							|  |  |  |  |             conn.rollback() | 
					
						
							| 
									
										
										
										
											2025-09-08 17:34:23 +08:00
										 |  |  |  |         raise Exception(f"更新报警次数失败: {str(e)}") from e | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) | 
					
						
							| 
									
										
										
										
											2025-09-02 21:42:09 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | # ------------------------------ | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  | # 内部工具方法 - 通过客户端IP更新设备在线状态 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | def update_online_status_by_ip(client_ip: str, online_status: int) -> bool: | 
					
						
							|  |  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |     通过客户端IP更新设备的在线状态 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     :param client_ip: 客户端IP地址 | 
					
						
							|  |  |  |  |     :param online_status: 在线状态(1-在线、0-离线) | 
					
						
							|  |  |  |  |     :return: 操作是否成功 | 
					
						
							|  |  |  |  |     """
 | 
					
						
							|  |  |  |  |     if not client_ip: | 
					
						
							|  |  |  |  |         raise ValueError("客户端IP不能为空") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if online_status not in (0, 1): | 
					
						
							|  |  |  |  |         raise ValueError("在线状态必须是0(离线)或1(在线)") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 检查设备是否存在并获取设备ID | 
					
						
							|  |  |  |  |         cursor.execute("SELECT id, device_online_status FROM devices WHERE client_ip = %s", (client_ip,)) | 
					
						
							|  |  |  |  |         device = cursor.fetchone() | 
					
						
							|  |  |  |  |         if not device: | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |             raise ValueError(f"客户端IP为 {client_ip} 的设备不存在") | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 状态无变化则不操作 | 
					
						
							|  |  |  |  |         if device['device_online_status'] == online_status: | 
					
						
							|  |  |  |  |             return True | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         # 更新在线状态和时间戳 | 
					
						
							|  |  |  |  |         update_query = """
 | 
					
						
							|  |  |  |  |             UPDATE devices  | 
					
						
							|  |  |  |  |             SET device_online_status = %s,  | 
					
						
							|  |  |  |  |                 updated_at = CURRENT_TIMESTAMP  | 
					
						
							|  |  |  |  |             WHERE client_ip = %s | 
					
						
							|  |  |  |  |         """
 | 
					
						
							|  |  |  |  |         cursor.execute(update_query, (online_status, client_ip)) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 记录状态变更历史 | 
					
						
							|  |  |  |  |         record_status_change(client_ip, online_status) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         conn.commit() | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         return True | 
					
						
							|  |  |  |  |     except MySQLError as e: | 
					
						
							|  |  |  |  |         if conn: | 
					
						
							|  |  |  |  |             conn.rollback() | 
					
						
							| 
									
										
										
										
											2025-09-08 17:34:23 +08:00
										 |  |  |  |         raise Exception(f"更新设备在线状态失败: {str(e)}") from e | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  | # 创建设备信息接口 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | @router.post("/add", response_model=APIResponse, summary="创建设备信息") | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  | async def create_device(device_data: DeviceCreateRequest, request: Request): | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 检查设备是否已存在 | 
					
						
							| 
									
										
										
										
											2025-09-02 21:42:09 +08:00
										 |  |  |  |         cursor.execute("SELECT * FROM devices WHERE client_ip = %s", (device_data.ip,)) | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         existing_device = cursor.fetchone() | 
					
						
							|  |  |  |  |         if existing_device: | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |             # 更新设备为在线状态 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |             update_online_status_by_ip(client_ip=device_data.ip, online_status=1) | 
					
						
							| 
									
										
										
										
											2025-09-02 21:42:09 +08:00
										 |  |  |  |             return APIResponse( | 
					
						
							|  |  |  |  |                 code=200, | 
					
						
							| 
									
										
										
										
											2025-09-08 17:34:23 +08:00
										 |  |  |  |                 message=f"设备IP {device_data.ip} 已存在、返回已有设备信息", | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |                 data=DeviceResponse(**existing_device) | 
					
						
							| 
									
										
										
										
											2025-09-02 21:42:09 +08:00
										 |  |  |  |             ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 通过 User-Agent 判断设备类型 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         user_agent = request.headers.get("User-Agent", "").lower() | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         device_type = "unknown" | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         if user_agent == "default": | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |             device_type = device_data.params.get("os") if (device_data.params and isinstance(device_data.params, dict)) else "unknown" | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         elif "windows" in user_agent: | 
					
						
							|  |  |  |  |             device_type = "windows" | 
					
						
							|  |  |  |  |         elif "android" in user_agent: | 
					
						
							|  |  |  |  |             device_type = "android" | 
					
						
							|  |  |  |  |         elif "linux" in user_agent: | 
					
						
							|  |  |  |  |             device_type = "linux" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         device_params_json = json.dumps(device_data.params) if device_data.params else None | 
					
						
							| 
									
										
										
										
											2025-09-02 21:42:09 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 插入新设备 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         insert_query = """
 | 
					
						
							|  |  |  |  |             INSERT INTO devices  | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |             (client_ip, hostname, device_online_status, device_type, alarm_count, params) | 
					
						
							|  |  |  |  |             VALUES (%s, %s, %s, %s, %s, %s) | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         """
 | 
					
						
							|  |  |  |  |         cursor.execute(insert_query, ( | 
					
						
							|  |  |  |  |             device_data.ip, | 
					
						
							|  |  |  |  |             device_data.hostname, | 
					
						
							|  |  |  |  |             1, | 
					
						
							|  |  |  |  |             device_type, | 
					
						
							|  |  |  |  |             0, | 
					
						
							|  |  |  |  |             device_params_json | 
					
						
							|  |  |  |  |         )) | 
					
						
							|  |  |  |  |         conn.commit() | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 获取新设备并返回 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         device_id = cursor.lastrowid | 
					
						
							|  |  |  |  |         cursor.execute("SELECT * FROM devices WHERE id = %s", (device_id,)) | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         new_device = cursor.fetchone() | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 记录上线历史 | 
					
						
							|  |  |  |  |         record_status_change(device_data.ip, 1) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         return APIResponse( | 
					
						
							|  |  |  |  |             code=200, | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |             message="设备创建成功", | 
					
						
							|  |  |  |  |             data=DeviceResponse(**new_device) | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |     except MySQLError as e: | 
					
						
							|  |  |  |  |         if conn: | 
					
						
							|  |  |  |  |             conn.rollback() | 
					
						
							| 
									
										
										
										
											2025-09-08 17:34:23 +08:00
										 |  |  |  |         raise Exception(f"创建设备失败: {str(e)}") from e | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |     except json.JSONDecodeError as e: | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         raise Exception(f"设备参数JSON序列化失败: {str(e)}") from e | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |     except Exception as e: | 
					
						
							|  |  |  |  |         if conn: | 
					
						
							|  |  |  |  |             conn.rollback() | 
					
						
							|  |  |  |  |         raise e | 
					
						
							|  |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | # 获取设备列表接口 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  | @router.get("/", response_model=APIResponse, summary="获取设备列表(支持筛选分页)") | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | async def get_device_list( | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         page: int = Query(1, ge=1, description="页码,默认第1页"), | 
					
						
							|  |  |  |  |         page_size: int = Query(10, ge=1, le=100, description="每页条数,1-100之间"), | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         device_type: str = Query(None, description="按设备类型筛选"), | 
					
						
							|  |  |  |  |         online_status: int = Query(None, ge=0, le=1, description="按在线状态筛选") | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | ): | 
					
						
							|  |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         where_clause = [] | 
					
						
							|  |  |  |  |         params = [] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         if device_type: | 
					
						
							|  |  |  |  |             where_clause.append("device_type = %s") | 
					
						
							|  |  |  |  |             params.append(device_type) | 
					
						
							|  |  |  |  |         if online_status is not None: | 
					
						
							|  |  |  |  |             where_clause.append("device_online_status = %s") | 
					
						
							|  |  |  |  |             params.append(online_status) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 统计总数 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         count_query = "SELECT COUNT(*) AS total FROM devices" | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         if where_clause: | 
					
						
							|  |  |  |  |             count_query += " WHERE " + " AND ".join(where_clause) | 
					
						
							|  |  |  |  |         cursor.execute(count_query, params) | 
					
						
							|  |  |  |  |         total = cursor.fetchone()["total"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 分页查询列表 | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         offset = (page - 1) * page_size | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         list_query = "SELECT * FROM devices" | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         if where_clause: | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |             list_query += " WHERE " + " AND ".join(where_clause) | 
					
						
							|  |  |  |  |         list_query += " ORDER BY id DESC LIMIT %s OFFSET %s" | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |         params.extend([page_size, offset]) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |         cursor.execute(list_query, params) | 
					
						
							|  |  |  |  |         device_list = cursor.fetchall() | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         return APIResponse( | 
					
						
							|  |  |  |  |             code=200, | 
					
						
							|  |  |  |  |             message="获取设备列表成功", | 
					
						
							| 
									
										
										
										
											2025-09-04 12:29:27 +08:00
										 |  |  |  |             data=DeviceListResponse( | 
					
						
							|  |  |  |  |                 total=total, | 
					
						
							|  |  |  |  |                 devices=[DeviceResponse(**device) for device in device_list] | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |             ) | 
					
						
							|  |  |  |  |         ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     except MySQLError as e: | 
					
						
							| 
									
										
										
										
											2025-09-08 17:34:23 +08:00
										 |  |  |  |         raise Exception(f"获取设备列表失败: {str(e)}") from e | 
					
						
							| 
									
										
										
										
											2025-09-02 18:51:50 +08:00
										 |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) | 
					
						
							| 
									
										
										
										
											2025-09-10 08:57:56 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | # 获取设备上下线记录接口 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | @router.get("/{device_id}/status-history", response_model=APIResponse, summary="获取设备上下线记录") | 
					
						
							|  |  |  |  | async def get_device_status_history( | 
					
						
							|  |  |  |  |         device_id: int = Path(..., description="设备ID"), | 
					
						
							|  |  |  |  |         page: int = Query(1, ge=1, description="页码,默认第1页"), | 
					
						
							|  |  |  |  |         page_size: int = Query(10, ge=1, le=100, description="每页条数,1-100之间"), | 
					
						
							|  |  |  |  |         start_date: date = Query(None, description="开始日期,格式YYYY-MM-DD"), | 
					
						
							|  |  |  |  |         end_date: date = Query(None, description="结束日期,格式YYYY-MM-DD") | 
					
						
							|  |  |  |  | ): | 
					
						
							|  |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							| 
									
										
										
										
											2025-09-10 08:57:56 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 检查设备是否存在并获取 client_ip | 
					
						
							|  |  |  |  |         cursor.execute("SELECT id, client_ip FROM devices WHERE id = %s", (device_id,)) | 
					
						
							|  |  |  |  |         device = cursor.fetchone() | 
					
						
							|  |  |  |  |         if not device: | 
					
						
							|  |  |  |  |             raise HTTPException(status_code=404, detail=f"设备ID为 {device_id} 的设备不存在") | 
					
						
							|  |  |  |  |         client_ip = device['client_ip'] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         where_clause = ["client_ip = %s"] | 
					
						
							|  |  |  |  |         params = [client_ip] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 日期筛选 | 
					
						
							|  |  |  |  |         if start_date: | 
					
						
							|  |  |  |  |             where_clause.append("DATE(created_at) >= %s") | 
					
						
							|  |  |  |  |             params.append(start_date.strftime("%Y-%m-%d")) | 
					
						
							|  |  |  |  |         if end_date: | 
					
						
							|  |  |  |  |             where_clause.append("DATE(created_at) <= %s") | 
					
						
							|  |  |  |  |             params.append(end_date.strftime("%Y-%m-%d")) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 统计记录总数 | 
					
						
							|  |  |  |  |         count_query = "SELECT COUNT(*) AS total FROM device_action WHERE " + " AND ".join(where_clause) | 
					
						
							|  |  |  |  |         cursor.execute(count_query, params) | 
					
						
							|  |  |  |  |         total = cursor.fetchone()["total"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 分页查询记录 | 
					
						
							|  |  |  |  |         offset = (page - 1) * page_size | 
					
						
							|  |  |  |  |         list_query = f"""
 | 
					
						
							|  |  |  |  |             SELECT * FROM device_action  | 
					
						
							|  |  |  |  |             WHERE {' AND '.join(where_clause)} | 
					
						
							|  |  |  |  |             ORDER BY created_at DESC  | 
					
						
							|  |  |  |  |             LIMIT %s OFFSET %s | 
					
						
							|  |  |  |  |         """
 | 
					
						
							|  |  |  |  |         params.extend([page_size, offset]) | 
					
						
							|  |  |  |  |         cursor.execute(list_query, params) | 
					
						
							|  |  |  |  |         history_list = cursor.fetchall() | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 格式化为响应模型结构 | 
					
						
							|  |  |  |  |         formatted_history = [] | 
					
						
							|  |  |  |  |         for item in history_list: | 
					
						
							|  |  |  |  |             formatted_item = { | 
					
						
							|  |  |  |  |                 "id": item["id"], | 
					
						
							|  |  |  |  |                 "device_id": device_id, | 
					
						
							|  |  |  |  |                 "client_ip": item["client_ip"], | 
					
						
							|  |  |  |  |                 "status": item["action"], | 
					
						
							|  |  |  |  |                 "status_time": item["created_at"] | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |             formatted_history.append(formatted_item) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return APIResponse( | 
					
						
							|  |  |  |  |             code=200, | 
					
						
							|  |  |  |  |             message="获取设备上下线记录成功", | 
					
						
							|  |  |  |  |             data=DeviceStatusHistoryListResponse( | 
					
						
							|  |  |  |  |                 total=total, | 
					
						
							|  |  |  |  |                 history=[DeviceStatusHistoryResponse(**item) for item in formatted_history] | 
					
						
							|  |  |  |  |             ) | 
					
						
							|  |  |  |  |         ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     except MySQLError as e: | 
					
						
							|  |  |  |  |         raise Exception(f"获取设备上下线记录失败: {str(e)}") from e | 
					
						
							|  |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | # 手动更新设备在线状态接口 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | @router.put("/{device_id}/status", response_model=APIResponse, summary="更新设备在线状态") | 
					
						
							|  |  |  |  | async def update_device_status( | 
					
						
							|  |  |  |  |         device_id: int = Path(..., description="设备ID"), | 
					
						
							|  |  |  |  |         status: int = Query(..., ge=0, le=1, description="在线状态(1-在线、0-离线)") | 
					
						
							|  |  |  |  | ): | 
					
						
							|  |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 获取设备 client_ip | 
					
						
							|  |  |  |  |         cursor.execute("SELECT id, client_ip FROM devices WHERE id = %s", (device_id,)) | 
					
						
							|  |  |  |  |         device = cursor.fetchone() | 
					
						
							|  |  |  |  |         if not device: | 
					
						
							|  |  |  |  |             raise HTTPException(status_code=404, detail=f"设备ID为 {device_id} 的设备不存在") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 更新状态 | 
					
						
							|  |  |  |  |         success = update_online_status_by_ip(device['client_ip'], status) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         if success: | 
					
						
							|  |  |  |  |             status_text = "在线" if status == 1 else "离线" | 
					
						
							|  |  |  |  |             return APIResponse( | 
					
						
							|  |  |  |  |                 code=200, | 
					
						
							|  |  |  |  |                 message=f"设备已更新为{status_text}状态", | 
					
						
							|  |  |  |  |                 data={"device_id": device_id, "status": status, "status_text": status_text} | 
					
						
							|  |  |  |  |             ) | 
					
						
							|  |  |  |  |         return APIResponse( | 
					
						
							|  |  |  |  |             code=500, | 
					
						
							|  |  |  |  |             message="更新设备状态失败", | 
					
						
							|  |  |  |  |             data=None | 
					
						
							|  |  |  |  |         ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     except MySQLError as e: | 
					
						
							|  |  |  |  |         raise Exception(f"更新设备状态失败: {str(e)}") from e | 
					
						
							|  |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | # 获取所有去重的客户端IP列表 | 
					
						
							|  |  |  |  | # ------------------------------ | 
					
						
							|  |  |  |  | def get_unique_client_ips() -> list[str]: | 
					
						
							|  |  |  |  |     """获取所有去重的客户端IP列表""" | 
					
						
							| 
									
										
										
										
											2025-09-10 08:57:56 +08:00
										 |  |  |  |     conn = None | 
					
						
							|  |  |  |  |     cursor = None | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         conn = db.get_connection() | 
					
						
							|  |  |  |  |         cursor = conn.cursor(dictionary=True) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         query = "SELECT DISTINCT client_ip FROM devices WHERE client_ip IS NOT NULL" | 
					
						
							|  |  |  |  |         cursor.execute(query) | 
					
						
							|  |  |  |  |         results = cursor.fetchall() | 
					
						
							|  |  |  |  |         return [item['client_ip'] for item in results] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     except MySQLError as e: | 
					
						
							|  |  |  |  |         raise Exception(f"获取客户端IP列表失败: {str(e)}") from e | 
					
						
							|  |  |  |  |     finally: | 
					
						
							|  |  |  |  |         db.close_connection(conn, cursor) |