# -*- coding:utf-8 -*- """ @Author : xuxingchen @Contact : xuxingchen@sinochem.com @Desc : 设备信息表 增&改&查 """ import csv import os from datetime import datetime from typing import List, Optional from pydantic import BaseModel, field_validator from device.call import ServicesCall, DelFaceItem, AddFaceItem from models.houses import HousesTable from utils import logger from utils.database import BaseTable, get_table_handler from utils.misc import InvalidException, now_datetime_second, decrypt_number class DeviceRegister(BaseModel): device_mac: str device_name: str device_desc: str third_local_device_id: str device_status: str = "离线" device_sct: Optional[str] = None # 以下需要查询已注册设备信息获取 gateway_id: Optional[int] = None gateway_name: Optional[str] = None # 以下需要查询产品信息表获取 product_name: str node_type: str class Device(BaseModel): device_id: int device_name: str node_type: str product_name: str class AccessDevice(BaseModel): device_id: int building_ids: list[str] @field_validator("device_id") def check_device_id(cls, value): th = get_table_handler() if DevicesTable.bind_exits(th, value): raise InvalidException(f"设备:{value} 已被添加过关联") if not DevicesTable.exits(th, value): raise InvalidException(f"设备:{value} 不存在") return value @field_validator("building_ids") def check_authorized_scope(cls, value): th = get_table_handler() for i in value: if not HousesTable.building_exists(th, i): raise InvalidException(f"楼栋:{i} 不存在") return value class AccessDevicesScope(BaseModel): device_type: str info: List[AccessDevice] @field_validator("device_type") def check_device_type(cls, value): if value in ["大门闸机", "大门门禁"]: return value else: raise InvalidException("请提供正确的设备类型:[大门闸机, 大门门禁]") class BuildingDevice(BaseModel): device_id: int bind_unit_id: str @field_validator("device_id") def check_device_id(cls, value): th = get_table_handler() if DevicesTable.bind_exits(th, value): raise InvalidException(f"设备:{value} 已被添加过关联") if not DevicesTable.exits(th, value): raise InvalidException(f"设备:{value} 不存在") return value @field_validator("bind_unit_id") def check_bind_unit_id(cls, value): th = get_table_handler() if not HousesTable.unit_exists(th, value): raise InvalidException(f"单元:{value} 不存在") return value class BuildingDevicesScope(BaseModel): info: List[BuildingDevice] class SearchDevicesInfo(BaseModel): search_type: Optional[str] = None search_key: Optional[str] = None @field_validator("search_type") def check_search_type(cls, value): types = { "设备名称": "device_name", "MAC地址": "device_mac", "设备ID": "device_id" } if value in types: return types[value] else: raise InvalidException(f"请提供正确的类型:{list(types.keys())}") class DevicesTable(BaseTable): @staticmethod def check(table_handler: BaseTable): """检测是否存在当前表""" table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices'") if table_handler.cursor.fetchone() is None: table_handler.execute( f""" CREATE TABLE devices ( device_id INTEGER PRIMARY KEY AUTOINCREMENT, device_mac TEXT UNIQUE, device_name TEXT, device_desc TEXT, third_local_device_id TEXT, device_status TEXT, device_sct TEXT, gateway_id INTEGER, gateway_name TEXT, product_name TEXT, node_type TEXT, last_online_datetime TEXT, register_datetime TEXT ) """ ) init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/devices.csv") if os.path.exists(init_config_path): with open(init_config_path, newline='', encoding='utf8') as csvfile: csvreader = csv.reader(csvfile) head = next(csvreader) data = [] if len(head) == 12: for row in csvreader: device_mac = row[0].strip() device_name = row[1].strip() device_desc = row[2].strip() if row[2].strip() else None third_local_device_id = row[3].strip() if row[3].strip() else None device_status = row[4].strip() if row[4].strip() else "离线" device_sct = row[5].strip() if row[5].strip() else None gateway_id = int(row[6].strip()) if row[6].strip() else None gateway_name = row[7].strip() if row[7].strip() else None product_name = row[8].strip() node_type = row[9].strip() last_online_datetime = row[10].strip() if row[10].strip() else None register_datetime = row[11].strip() if row[11].strip() else None data.append((device_mac, device_name, device_desc, third_local_device_id, device_status, device_sct, gateway_id, gateway_name, product_name, node_type, last_online_datetime, register_datetime)) table_handler.executemany( f""" INSERT INTO devices (device_mac, device_name, device_desc, third_local_device_id, device_status, device_sct, gateway_id, gateway_name, product_name, node_type, last_online_datetime, register_datetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (device_mac) DO NOTHING """, data ) table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices_scope'") if table_handler.cursor.fetchone() is None: table_handler.execute( f""" CREATE TABLE devices_scope ( device_id INT, device_type TEXT, bind_unit_id TEXT, UNIQUE (device_id, bind_unit_id) ) """ ) init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/devices_scope.csv") if os.path.exists(init_config_path): with open(init_config_path, newline='', encoding='utf8') as csvfile: csvreader = csv.reader(csvfile) head = next(csvreader) data = [] if len(head) == 3: for row in csvreader: device_id = row[0].strip() device_type = row[1].strip() bind_unit_id = row[2].strip() data.append((device_id, device_type, bind_unit_id)) table_handler.executemany( f""" INSERT INTO devices_scope (device_id, device_type, bind_unit_id) VALUES (?, ?, ?) ON CONFLICT (device_id, bind_unit_id) DO NOTHING """, data ) table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices_auth'") if table_handler.cursor.fetchone() is None: table_handler.execute( f""" CREATE TABLE devices_auth ( device_id INT, _id TEXT, record_type TEXT, start_date TEXT, expire_date TEXT, add_datetime TEXT, update_datetime TEXT, UNIQUE (device_id, _id, record_type) ) """ ) init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/devices_auth.csv") if os.path.exists(init_config_path): with open(init_config_path, newline='', encoding='utf8') as csvfile: csvreader = csv.reader(csvfile) head = next(csvreader) data = [] if len(head) == 5: for row in csvreader: device_id = row[0].strip() _id = row[1].strip() record_type = row[2].strip() start_date = row[3].strip() expire_date = row[4].strip() data.append((device_id, _id, record_type, start_date, expire_date, now_datetime_second(), now_datetime_second())) table_handler.executemany( f""" INSERT INTO devices_auth (device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (device_id, _id, record_type) DO NOTHING """, data ) @staticmethod def insert_by_device_register(table_handler: BaseTable, obj: DeviceRegister): register_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") table_handler.execute( """ INSERT INTO devices (device_mac, device_name, device_desc, third_local_device_id, device_status, device_sct, gateway_id, gateway_name, product_name, node_type, register_datetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (device_mac) DO UPDATE SET device_name=?, device_desc=?, third_local_device_id=?, device_status=?, device_sct=?, gateway_id=?, gateway_name=?, product_name=?, node_type=?, register_datetime=? """, (obj.device_mac, obj.device_name, obj.device_desc, obj.third_local_device_id, obj.device_status, obj.device_sct, obj.gateway_id, obj.gateway_name, obj.product_name, obj.node_type, register_datetime, obj.device_name, obj.device_desc, obj.third_local_device_id, obj.device_status, obj.device_sct, obj.gateway_id, obj.gateway_name, obj.product_name, obj.node_type, register_datetime) ) table_handler.query( """ SELECT device_id FROM devices WHERE device_mac = ? """, (obj.device_mac,) ) res = table_handler.cursor.fetchall() if res: return res[0][0] return None @staticmethod def insert_device_auth_record(table_handler: BaseTable, device_id: int, _id: str, start_date: str, expire_date: str, record_type: str = '人行'): table_handler.execute( """ INSERT INTO devices_auth (device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (device_id, _id, record_type) DO UPDATE SET start_date=?, expire_date=?, update_datetime=? """, (device_id, _id, record_type, start_date, expire_date, now_datetime_second(), now_datetime_second(), start_date, expire_date, now_datetime_second()) ) @staticmethod def get_devices_info(table_handler: BaseTable): """获取对应设备的基本信息""" table_handler.query( """ SELECT device_name, third_local_device_id, device_id, device_status, gateway_name, product_name, node_type, last_online_datetime, register_datetime FROM devices WHERE node_type != '网关设备' """ ) res = table_handler.cursor.fetchall() if res: devices_info = [] for item in res: devices_info.append({ "device_name": item[0], "third_local_device_id": item[1], "device_id": item[2], "device_status": item[3], "gateway_name": item[4] if item[4] else "", "product_name": item[5], "node_type": item[6], "last_online_datetime": item[7] if item[7] else "", "register_datetime": item[8] }) return devices_info return None @staticmethod def get_device_info(table_handler: BaseTable, device_id: int): """获取对应设备的基本信息""" table_handler.query( """ SELECT device_name, node_type, product_name FROM devices WHERE device_id = ? """, (device_id,) ) res = table_handler.cursor.fetchall() if res: return Device(device_id=device_id, device_name=res[0][0], node_type=res[0][1], product_name=res[0][2]) return None @staticmethod def get_device_name(table_handler: BaseTable, device_id: int): """获取对应设备名""" table_handler.query( """ SELECT device_name FROM devices WHERE device_id = ? """, (device_id,) ) res = table_handler.cursor.fetchall() if res: return res[0][0] return "" @staticmethod def get_device_scope_type(table_handler: BaseTable, device_id: int): """获取对应设备门禁类型""" table_handler.query( """ SELECT device_type FROM devices_scope WHERE device_id = ? """, (device_id,) ) res = table_handler.cursor.fetchall() if res: return res[0][0] return "" @staticmethod def get_device_ids_by_unit_id(table_handler: BaseTable, unit_id: str) -> list: """查询对应单元权限内的设备ID""" table_handler.query( """ SELECT GROUP_CONCAT(device_id) AS device_ids FROM devices_scope WHERE bind_unit_id = ? """, (unit_id,) ) res = table_handler.cursor.fetchall() if res: return res[0][0].split(',') else: return [] @staticmethod def get_device_ids(table_handler: BaseTable, filter_name: Optional[str] = None, filter_online: bool = True): """获取相关的设备ID""" sub_sql_list = [] if filter_online: sub_sql_list.append("device_status = '在线'") if filter_name is not None: sub_sql_list.append(f"product_name like '%{filter_name}%'") if len(sub_sql_list) > 0: sub_sql = "WHERE " + " AND ".join(sub_sql_list) else: sub_sql = "" table_handler.query(f"SELECT GROUP_CONCAT(device_id) device_ids FROM devices {sub_sql}") res = table_handler.cursor.fetchall() if res: return [i for i in res[0][0].split(',')] else: return [] @staticmethod def get_auth_interval(table_handler: BaseTable, _id: str, device_id: int, record_type: str = '人行'): """获取对应ID授权在设备上的有效期""" table_handler.query( """ SELECT start_date, expire_date FROM devices_auth WHERE device_id = ? and _id = ? and record_type = ? """, (device_id, _id, record_type) ) res = table_handler.cursor.fetchall() if res: return res[0][0], res[0][1] return "", "" @staticmethod def update_device_status(table_handler: BaseTable, device_id: int, device_status: str): """更新设备在线状态""" if device_status == "在线": last_online_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") set_sql = f"device_status='在线', last_online_datetime='{last_online_datetime}' " else: set_sql = f"device_status='离线' " table_handler.execute( f""" UPDATE devices SET {set_sql} WHERE device_id={device_id} """ ) @staticmethod def offline_gateway(table_handler: BaseTable, device_id: int): """用于在网关下线时同时下线网关下的所有设备""" table_handler.execute( f""" UPDATE devices SET device_status='离线' WHERE device_id={device_id} or gateway_id={device_id} """ ) @staticmethod def get_access_devices_info(table_handler: BaseTable, is_associated: bool, device_name: str = None, product_name: str = None, device_mac: str = None, device_id: int = None): if is_associated: sub_sql = "WHERE d.node_type != '网关设备' AND product_name not like '%停车场%' AND device_type is not null " else: sub_sql = "WHERE d.node_type != '网关设备' AND product_name not like '%停车场%' AND device_type is null " if device_name: sub_sql += f"AND device_name = '{device_name}'" elif product_name: sub_sql += f"AND product_name = '{product_name}'" elif device_mac: sub_sql += f"AND device_mac = '{device_mac}'" elif device_id: sub_sql += f"AND d.device_id = {device_id}" table_handler.query( f""" SELECT d.device_id, device_name, device_mac, device_status, ds.device_type, GROUP_CONCAT(t.building_name || '-' || t.unit_name) as scopes, product_name FROM devices d LEFT JOIN devices_scope ds ON ds.device_id = d.device_id LEFT JOIN (SELECT DISTINCT unit_id, unit_name, building_id, building_name FROM houses) t ON t.unit_id = ds.bind_unit_id {sub_sql} GROUP BY d.device_id, device_name, device_mac, device_status, ds.device_type, product_name """ ) res = table_handler.cursor.fetchall() if res: devices_info = [] for i in res: devices_info.append({ "device_id": i[0], "device_name": i[1], "device_mac": i[2], "device_status": i[3], "device_type": i[4] if i[4] else "", "authorized_scope": i[5].split(",") if i[5] else [], "product_name": i[6] }) return {"devices": devices_info} return {"devices": []} @staticmethod def get_associated_access_devices_info(table_handler: BaseTable, search_type: Optional[str] = None, search_key: Optional[str] = None): if search_type: if search_type == "device_id": sub_sql = f"WHERE ds.device_id = {search_key}" elif search_type == "device_name": sub_sql = f"WHERE d.device_name like '%{search_key}%'" else: sub_sql = f"WHERE d.{search_type} = '{search_key}'" else: sub_sql = "" table_handler.query( f""" SELECT ds.device_id as _id, d.device_name as _name, d.device_mac as _mac, d.device_status as _status FROM (SELECT DISTINCT device_id FROM devices_scope) ds LEFT JOIN devices d ON ds.device_id = d.device_id {sub_sql} """ ) res = table_handler.cursor.fetchall() if res: devices_info = [] for i in res: devices_info.append({ "device_id": i[0], "device_name": i[1], "device_mac": i[2], "device_status": i[3] }) return {"devices": devices_info} return {"devices": []} @staticmethod def auto_auth_by_unit_id(table_handler: BaseTable, device_id: int, unit_id: str): """查询对应单元下相关住户,对具备人脸的住户授权""" table_handler.query( """ SELECT h.householder_id, name, phone, face_url FROM householders h LEFT JOIN householders_type ht ON ht.householder_id=h.householder_id LEFT JOIN houses ON ht.room_id=houses.room_id WHERE h.type = '住户' AND (face_url != '' or face_url is not null ) AND houses.unit_id = ? """, (unit_id,) ) res = table_handler.cursor.fetchall() if res: # 人员人脸下放 for i in res: sc = ServicesCall() face_item = AddFaceItem( user_id=i[0], name=i[1], phone_number=decrypt_number(i[2]), face_url=i[3], device_ids=str([device_id]) ) _callback, code, msg = sc.add_face(device_id, face_item) if _callback: DevicesTable.insert_device_auth_record(table_handler, device_id, face_item.user_id, face_item.start_date, face_item.expire_date) @staticmethod def add_access_devices(table_handler: BaseTable, device_type: str, objs: List[AccessDevice]): data = [] for obj in objs: for bind_building_id in obj.building_ids: unit_ids = HousesTable.get_unit_ids(table_handler, bind_building_id) for unit_id in unit_ids: data.append((obj.device_id, device_type, unit_id, device_type)) DevicesTable.auto_auth_by_unit_id(table_handler, obj.device_id, unit_id) try: table_handler.executemany( """ INSERT INTO devices_scope (device_id, device_type, bind_unit_id) VALUES (?, ?, ?) ON CONFLICT (device_id, bind_unit_id) DO UPDATE SET device_type = ? """, data ) return True except Exception as e: logger.Logger.error(f"DevicesTable.add_access_devices: {type(e).__name__}, {e}") raise InvalidException(f"DevicesTable.add_access_devices: {type(e).__name__}, {e}") @staticmethod def add_building_devices(table_handler: BaseTable, device_type: str, objs: List[BuildingDevice]): data = [] for obj in objs: data.append((obj.device_id, device_type, obj.bind_unit_id)) DevicesTable.auto_auth_by_unit_id(table_handler, obj.device_id, obj.bind_unit_id) try: table_handler.executemany( """ INSERT INTO devices_scope (device_id, device_type, bind_unit_id) VALUES (?, ?, ?) ON CONFLICT (device_id, bind_unit_id) DO NOTHING """, data ) return True except Exception as e: logger.Logger.error(f"DevicesTable.add_building_devices: {type(e).__name__}, {e}") return False @staticmethod def add_update_device_auth(table_handler: BaseTable, device_id, _ids: list[str], record_type: str, start_date: str, expire_date: str): """对应设备批量授权记录增加""" data = [] for _id in _ids: _datetime = now_datetime_second() data.append((device_id, _id, record_type, start_date, expire_date, _datetime, _datetime, start_date, expire_date, _datetime)) try: table_handler.executemany( """ INSERT INTO devices_auth (device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (device_id, _id, record_type) DO UPDATE SET start_date = ?, expire_date = ?, update_datetime = ? """, data ) return True except Exception as e: logger.Logger.error(f"DevicesTable.add_device_auth: {type(e).__name__}, {e}") return False @staticmethod def get_auth_device_ids(table_handler: BaseTable, _id: str, record_type: str = '人行'): """获取ID的所有授权设备""" table_handler.query( """ SELECT device_id FROM devices_auth WHERE _id = ? AND record_type = ? """, (_id, record_type) ) res = table_handler.cursor.fetchall() if res: return [i[0] for i in res] else: return [] @staticmethod def get_auth_device_info(table_handler: BaseTable, _id: str, record_type: str = '人行'): """获取ID当前所有授权设备基础信息""" table_handler.query( """ SELECT devices_auth.device_id, device_name, device_mac, device_status FROM devices_auth LEFT JOIN devices ON devices.device_id = devices_auth.device_id WHERE _id = ? AND record_type = ? """, (_id, record_type) ) res = table_handler.cursor.fetchall() if res: return [{"device_id": i[0], "device_name": i[1], "device_mac": i[2], "device_status": i[3]} for i in res] else: return [] @staticmethod def get_auth_householders_info(table_handler: BaseTable, device_id: int): """获取关联授权下的所有完成下放的人员信息""" table_handler.query( """ SELECT da._id, name, phone, face_url, da.start_date, da.expire_date FROM devices_auth da LEFT JOIN householders h ON da._id = h.householder_id WHERE device_id = ? AND record_type = '人行' """, (device_id,) ) res = table_handler.cursor.fetchall() if res: return [{"id": i[0], "name": i[1], "phone": i[2], "url": i[3], "sdate": i[4], "edate": i[5]} for i in res] else: return [] @staticmethod def delete_householder_all_auth(table_handler: BaseTable, _id: str): table_handler.execute( """ DELETE FROM devices_auth WHERE _id = ? AND record_type = '人行' """, (_id,) ) @staticmethod def delete_invalid_auth_record(table_handler: BaseTable, device_id: int, _id: str, record_type: str = '人行'): table_handler.execute( """ DELETE FROM devices_auth WHERE device_id = ? AND _id = ? AND record_type = ? """, (device_id, _id, record_type) ) @staticmethod def delete_access_device_info(table_handler: BaseTable, device_id: int): # 1. 确认设备ID有效 if not DevicesTable.exits(table_handler, device_id): raise InvalidException(f"设备:{device_id} 不存在") # 2. 获取关联授权下的所有完成下放的人员信息 householder_infos = DevicesTable.get_auth_householders_info(table_handler, device_id) if len(householder_infos) > 0: # 3. 移除对应设备中所有的人员信息 sc = ServicesCall() success_ids = [] for index, householder_info in enumerate(householder_infos): _callback, code, _ = sc.del_face(device_id, DelFaceItem(user_id=str(householder_info["id"]), device_ids=str([device_id]))) if not _callback: # 存在异常时,回滚已删除掉的人脸 failed = [] msgs = [] for success_id in success_ids: _back, code, msg = sc.add_face(device_id, AddFaceItem( name=householder_infos[success_id]["name"], user_id=str(householder_infos[success_id]["id"]), phone_number=householder_infos[success_id]["phone"], face_url=householder_infos[success_id]["url"], device_ids=str([device_id]), start_date=householder_infos[success_id]["sdate"], expire_date=householder_infos[success_id]["edate"] )) if not _back: failed.append(householder_infos[success_id]["id"]) msgs.append(msg) if len(failed) == 0: raise InvalidException( f"住户 - {householder_info['id']} 人脸授权移除失败,错误码{code}, {msgs}, 已完成回滚") else: raise InvalidException( f"住户 - {householder_info['id']} 人脸授权移除失败,错误码{code}, {msgs}, 回滚失败") success_ids.append(index) # 4. 移除设备的关联记录 table_handler.execute( """ DELETE FROM devices_auth WHERE device_id = ? AND record_type = '人行' """, (device_id,) ) table_handler.execute( """ DELETE FROM devices_scope WHERE device_id = ? """, (device_id,) ) return {"status": True} @staticmethod def exits(table_handler: BaseTable, device_id: int): table_handler.query( """ SELECT device_id FROM devices WHERE device_id = ? """, (device_id,) ) res = table_handler.cursor.fetchall() if res: return True else: return False @staticmethod def bind_exits(table_handler: BaseTable, device_id: int): table_handler.query( """ SELECT bind_unit_id FROM devices_scope WHERE device_id = ? """, (device_id,) ) res = table_handler.cursor.fetchall() if res: return True else: return False