# -*- coding:utf-8 -*- """ @File : meian_db @Author : xuxingchen @Version : 1.0 @Contact : xuxingchen@sinochem.com @Desc : meian database crud """ import csv import json import os.path import sqlite3 import time import traceback from logger import Logger, new_dc from datetime import datetime from devices.meian_model import HeartBeat, Register, PushRtAccessRecord from devices.common_model import UserInfo from utils import datetime_to_timestamp from config import ENV_TYPE, DEBUG class SQLiteDatabaseEngine: def __init__(self, db_path: str = "demo.db") -> None: self.sqlite3 = sqlite3 self.db_path = db_path self.connection = None self.cursor = None self.connect() def __del__(self): # self.disconnect() pass def connect(self): """连接SQLite 数据库(如果数据库不存在则会自动创建)""" self.connection = self.sqlite3.connect(self.db_path) self.cursor = self.connection.cursor() Logger.init(new_dc(f"🔗 SQLite - {self.db_path} has connect successfully! 🔗", "[1;32m")) def disconnect(self): try: self.cursor.close() self.connection.close() except Exception as e: if type(e).__name__ != "ProgrammingError": Logger.error(f"{type(e).__name__}, {e}") Logger.info(new_dc(f"🔌 Disconnect from SQLite - {self.db_path}! 🔌", "[1m")) def exist(self, table_name): self.cursor.execute( f"SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,), ) result = self.cursor.fetchone() if result: return True else: return False class BaseTable: def __init__(self, connection=None, cursor=None): self.connection = connection self.cursor = cursor def set(self, connection, cursor): self.connection = connection self.cursor = cursor def execute(self, sql: str, params: tuple = ()): self.cursor.execute(sql, params) self.connection.commit() def executemany(self, sql: str, params: list[tuple]): self.cursor.executemany(sql, params) self.connection.commit() def query(self, sql: str, params: tuple = ()): self.cursor.execute(sql, params) class DeviceTable(BaseTable): @staticmethod def check(table_handler: BaseTable): """检测是否存在当前表,并根据devices.csv开始数据初始化""" table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='device'") if table_handler.cursor.fetchone() is None: table_handler.execute( f""" CREATE TABLE device ( device_id TEXT, project_code TEXT, subscribe_topic TEXT, publish_topic TEXT, aiot_id TEXT NULL, register_type INTEGER default 0, PRIMARY KEY (device_id) ) """ ) init_config_path = os.path.join(os.path.dirname((os.path.abspath("__file__"))), "data", "_devices.csv" if ENV_TYPE != 2 else "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) == 5: for row in csvreader: register_type = 0 if row[4] == '' else 1 data.append(tuple([i.strip() for i in row] + [register_type])) table_handler.executemany( f""" INSERT INTO device (project_code, device_id, subscribe_topic, publish_topic, aiot_id, register_type) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (device_id) DO NOTHING """, data ) @staticmethod def get_topics(table_handler): table_handler.query( """ SELECT ( SELECT json_group_array(subscribe_topic) FROM ( SELECT DISTINCT subscribe_topic FROM device ) ) AS subscribe_topics, ( SELECT json_group_array(publish_topic) FROM ( SELECT DISTINCT publish_topic FROM device ) ) AS publish_topics; """ ) res = table_handler.cursor.fetchall() if res: return res[0] else: return None @staticmethod def check_device_id(table_handler, topic, device_id): """device in `publish_topic` and `device_id` column""" table_handler.query( f""" SELECT DISTINCT device_id from device where publish_topic = '{topic}' and device_id = '{device_id}' """ ) if len(table_handler.cursor.fetchall()) > 0: return True else: return False @staticmethod def get_device_topic(table_handler, device_id): """获取对应设备的订阅主题""" table_handler.query( f""" SELECT subscribe_topic, publish_topic from device where device_id = '{device_id}' """ ) res = table_handler.cursor.fetchall() if res: return res[0] else: return None @staticmethod def get_device_register_type(table_handler, device_id): table_handler.query( f""" SELECT register_type from device where device_id = '{device_id}' """ ) res = table_handler.cursor.fetchall() if res and res[0][0] == 1: return True else: return False @staticmethod def get_aiot_id(table_handler, device_id): table_handler.query( f""" SELECT aiot_id from device where device_id = '{device_id}' and register_type = 1 """ ) res = table_handler.cursor.fetchall() if res: return res[0][0] else: return None @staticmethod def get_device_id(table_handler, aiot_id): table_handler.query( f""" SELECT device_id from device where aiot_id = '{aiot_id}' and register_type = 1 """ ) res = table_handler.cursor.fetchall() if res: return res[0][0] else: return None @staticmethod def get_device_ids(table_handler): """获取所有设备的ID""" table_handler.query( f""" SELECT device_id from device """ ) res = table_handler.cursor.fetchall() if res: return res else: return None @staticmethod def get_project_code(table_handler, device_id): table_handler.query( f""" SELECT project_code from device where device_id = '{device_id}' """ ) res = table_handler.cursor.fetchall() if res: return res[0][0] else: return None @staticmethod def update_aiot_id(table_handler: BaseTable, device_id, aiot_id): table_handler.execute( f""" UPDATE device SET aiot_id = '{aiot_id}', register_type = 1 WHERE device_id = '{device_id}' """ ) class GatewayTable(BaseTable): @staticmethod def check(table_handler: BaseTable): """检测是否存在当前表,并根据gateway.json开始数据初始化""" table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='gateway'") if table_handler.cursor.fetchone() is None: table_handler.execute( f""" CREATE TABLE gateway ( gateway_id TEXT NULL, project_code TEXT, gateway_sct TEXT NULL, register_type INTEGER default 0, PRIMARY KEY (gateway_id) ) """ ) init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data", "_gateways.json" if ENV_TYPE != 2 else "gateways.json") if os.path.exists(init_config_path): with open(init_config_path, "r", encoding="utf8") as f: try: gateway_config = json.load(f) data = [] for project_code, gateway_info in gateway_config.items(): data.append((project_code, gateway_info[0], gateway_info[1], 1)) table_handler.executemany( f""" INSERT INTO gateway (project_code, gateway_id, gateway_sct, register_type) VALUES (?, ?, ?, ?) ON CONFLICT (gateway_id) DO NOTHING """, data ) except Exception as e: Logger.error(f"{type(e).__name__}, {e}") if DEBUG: traceback.print_exc() @staticmethod def get_gateway(table_handler, project_code): table_handler.query( f""" SELECT gateway_id, gateway_sct from gateway where project_code = '{project_code}' """ ) res = table_handler.cursor.fetchall() if res: return res[0] else: return None, None @staticmethod def get_registered_gateway(table_handler): table_handler.query( f""" SELECT gateway_id, gateway_sct from gateway where register_type = 1 """ ) res = table_handler.cursor.fetchall() if res: return res else: return None @staticmethod def get_project_code_list(table_handler): table_handler.query( f""" SELECT DISTINCT project_code from gateway """ ) res = table_handler.cursor.fetchall() if res: return res else: return [] @staticmethod def get_registered_sub_aiot_id(table_handler, gateway_id): """查询已注册网关下的所有子设备在aiot平台的设备id""" table_handler.query( f""" SELECT json_group_array(aiot_id) FROM device WHERE project_code IN (SELECT DISTINCT project_code FROM gateway WHERE register_type = 1 and gateway_id = '{gateway_id}') AND register_type = 1 """ ) res = table_handler.cursor.fetchall() if res: return res[0] else: return None class HeartBeatTable(BaseTable): @staticmethod def check(table_handler: BaseTable): table_handler.execute( """ CREATE TABLE IF NOT EXISTS heart_beat ( device_id TEXT, factory_id TEXT, last_heart_beat TEXT, PRIMARY KEY (device_id) ) """ ) @staticmethod def delete(table_handler: BaseTable, obj): table_handler.execute("DELETE FROM heart_beat WHERE device_id=?", (obj.device_id,)) @staticmethod def update(table_handler: BaseTable, obj: HeartBeat, topic: str): if DeviceTable.check_device_id(table_handler, topic, obj.device_id): time_stamp = str(int(time.time())) table_handler.execute( """ INSERT INTO heart_beat (device_id, factory_id, last_heart_beat) VALUES (?, ?, ?) ON CONFLICT (device_id) DO UPDATE SET last_heart_beat=? """, (obj.device_id, obj.factory_id, time_stamp, time_stamp), ) return True else: if DEBUG: Logger.warn( f"device_id - {obj.device_id} is invalid in {topic}, operation was not performed" ) return False @staticmethod def get_last_time(table_handler: BaseTable, device_id): table_handler.query( """ SELECT last_heart_beat FROM heart_beat WHERE device_id = ? """, (device_id,), ) res = table_handler.cursor.fetchall() if res: return res[0][0] else: return None class RegisterTable(BaseTable): @staticmethod def check(table_handler: BaseTable): table_handler.execute( """ CREATE TABLE IF NOT EXISTS register ( device_id TEXT, factory_id TEXT, device_type TEXT, device_position_code TEXT, device_position_desc TEXT, last_register_timestamp TEXT, PRIMARY KEY (device_id) ) """ ) def drop(self): self.execute("drop table register") def delete(self, obj): self.execute( "DELETE FROM register WHERE device_id=? and factory_id=?", ( obj.device_id, obj.factory_id, ), ) def insert(self, obj, topic: str): if DeviceTable.check_device_id(self, topic, obj.device_id): time_stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") self.execute( """ INSERT INTO register (device_id, factory_id, device_type, device_position_code, device_position_desc, last_register_timestamp) VALUES (?, ?, ?, ?, ?, ?) """, ( obj.device_id, obj.factory_id, obj.device_type, obj.device_position_code, obj.device_position_desc, time_stamp, ), ) return True else: if DEBUG: Logger.warn( f"device_id - {obj.device_id} is invalid in {topic}, operation was not performed" ) return False @staticmethod def update(table_handler, obj: Register, topic: str): if DeviceTable.check_device_id(table_handler, topic, obj.device_id): time_stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") table_handler.execute( """ INSERT INTO register (device_id, factory_id, device_type, device_position_code, device_position_desc, last_register_timestamp) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (device_id) DO UPDATE SET device_type=?, device_position_code=?, device_position_desc=?, last_register_timestamp=? """, ( obj.device_id, obj.factory_id, obj.device_type, obj.device_position_code, obj.device_position_desc, time_stamp, obj.device_type, obj.device_position_code, obj.device_position_desc, time_stamp, ), ) return True else: if DEBUG: Logger.warn( f"device_id - {obj.device_id} is invalid in {topic}, operation was not performed" ) return False @staticmethod def get_device_position_desc(table_handler: BaseTable, device_id: str): """根据device_id获取设备的空间描述信息""" table_handler.query( """ SELECT device_position_desc FROM register WHERE device_id = ? """, (device_id,), ) res = table_handler.cursor.fetchall() if res: return res[0][0] else: return None class UserInfoTable(BaseTable): @staticmethod def check(table_handler: BaseTable): table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='user_info'") if table_handler.cursor.fetchone() is None: table_handler.execute( f""" CREATE TABLE user_info ( user_id TEXT, device_id TEXT, name TEXT, user_type INTEGER, qrcode TEXT NULL, face_url TEXT NULL, create_timestamp TEXT, update_timestamp TEXT, PRIMARY KEY (user_id, device_id) ) """ ) table_handler.execute( f""" CREATE INDEX idx_user_info_qrcode ON user_info(qrcode); """ ) init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data", "_users.csv" if ENV_TYPE != 2 else "users.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) == 4: for row in csvreader: user_id = row[0].strip() name = row[1].strip() user_type = 0 if row[2].strip() == "业主" else 1 timestamp = str(int(time.time() * 1000)) device_id = row[3].strip() data.append((user_id, name, user_type, timestamp, timestamp, device_id)) table_handler.executemany( f""" INSERT INTO user_info (user_id, name, user_type, create_timestamp, update_timestamp, device_id) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (user_id, device_id) DO NOTHING """, data ) elif len(head) == 5: for row in csvreader: user_id = row[0].strip() name = row[1].strip() user_type = 0 if row[2].strip() == "业主" else 1 timestamp = str(int(time.time() * 1000)) device_id = row[3].strip() face_url = row[4].strip() data.append((user_id, name, user_type, timestamp, timestamp, device_id, face_url)) table_handler.executemany( f""" INSERT INTO user_info (user_id, name, user_type, create_timestamp, update_timestamp, device_id, face_url) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (user_id, device_id) DO NOTHING """, data ) @staticmethod def delete_redundancy_1(table_handler, timestamp): """用于在每天清理无效的冗余访客数据""" table_handler.execute(f"DELETE FROM user_info WHERE user_type=1 and update_timestamp < {timestamp}") @staticmethod def delete_redundancy_0(table_handler, timestamp): """用于在每天清理无效的冗余业主数据""" table_handler.execute(f"DELETE FROM user_info WHERE user_type=0 " f"and (face_url is NULL or face_url = '') and update_timestamp < {timestamp}") @staticmethod def update(table_handler, obj: UserInfo): time_stamp = str(int(time.time() * 1000)) table_handler.execute( """ INSERT INTO user_info (user_id, name, user_type, create_timestamp, update_timestamp, device_id) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (user_id, device_id) DO UPDATE SET name=?, update_timestamp=? """, (obj.user_id, obj.name, obj.user_type, time_stamp, time_stamp, obj.device_id, obj.name, time_stamp), ) @staticmethod def select_all(table_handler): table_handler.execute("SELECT * FROM user_info") return table_handler.cursor.fetchall() @staticmethod def get_name(table_handler, user_id, device_id): table_handler.query( """ SELECT name FROM user_info WHERE user_id = ? AND device_id = ? """, (user_id, device_id), ) res = table_handler.cursor.fetchall() if res: return res[0][0] else: return None @staticmethod def get_user_by_qrcode(table_handler, qrcode, device_id): table_handler.query( """ SELECT user_id, name FROM user_info WHERE qrcode = ? AND device_id = ? """, (qrcode, device_id), ) res = table_handler.cursor.fetchall() if res: return res[0] else: return None @staticmethod def update_qrcode(table_handler, user_id, device_id, qrcode): time_stamp = str(int(time.time() * 1000)) table_handler.execute( f""" UPDATE user_info SET qrcode = ?, update_timestamp = ? WHERE user_id = ? and device_id = ? """, (qrcode, time_stamp, user_id, device_id) ) @staticmethod def update_face_url(table_handler, user_id, device_id, face_url): time_stamp = str(int(time.time() * 1000)) table_handler.execute( f""" UPDATE user_info SET face_url = ?, update_timestamp = ? WHERE user_id = ? and device_id = ? """, (face_url, time_stamp, user_id, device_id) ) @staticmethod def exists_face_url(table_handler, user_id, device_id): """判断人脸地址是否存在且不为空""" table_handler.query( f""" SELECT face_url FROM user_info WHERE user_id = ? AND device_id = ? AND face_url IS NOT NULL AND face_url != '' """, (user_id, device_id) ) res = table_handler.cursor.fetchall() if res and len(res[0]) > 0: return True else: return False class RecordTable(BaseTable): @staticmethod def check(table_handler: BaseTable): table_handler.execute( """ CREATE TABLE IF NOT EXISTS record ( user_id TEXT, device_id TEXT, record_datetime TEXT ) """ ) @staticmethod def add(table_handler: BaseTable, obj: PushRtAccessRecord): table_handler.execute( """ INSERT INTO record (user_id, device_id, record_datetime) VALUES (?, ?, ?) """, (obj.user_id, obj.device_id, str(datetime_to_timestamp(obj.time))), )