2024-10-24 09:23:39 +08:00

794 lines
31 KiB
Python

# -*- 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