This commit is contained in:
2024-10-24 09:04:38 +08:00
parent 2a18f10bcf
commit 641c34b1b3
20 changed files with 2817 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# -*- coding:utf-8 -*-
"""
@File : common_model
@Author : xuxingchen
@Version : 1.0
@Contact : xuxingchen@sinochem.com
@Desc : 公用的数据结构
"""
import threading
from enum import Enum
from typing import Optional
from pydantic import BaseModel
# 错误码
class ErrorCode(Enum):
SUCCESS = (0, "全部完成下发")
PART_SUCCESS = (10, "部分完成下发")
FAILURE = (20, "一个都没下发成功")
INPUT_TYPE_ERROR = (30, "入参数据类型异常")
NEVER_POST_FACE = (40, "用户未下发过人脸")
UNKNOWN = (90, "未知异常")
def __new__(cls, code, message):
obj = object.__new__(cls)
obj._value_ = code
obj.message = message
return obj
class BaseInfo(BaseModel):
def check(self):
for attr in self.__dict__.keys():
# if property can be null, default value should not be set to None
if self.__dict__[attr] is None:
raise ValueError(f"{attr} not allowed to be set to None")
class UserData:
def __init__(self):
self.topic: Optional[str] = None
self.topics: list = []
self.table_handler = None
self.code = None
self.token = None
self.status: dict = {}
self.clients: dict = {}
self.client_id = None
self.lock = threading.Lock() # 添加一个锁用于线程同步
def set_topic(self, value: str):
with self.lock:
self.topic = value
def set_topics(self, value: list):
with self.lock:
self.topics = value
def set_table_handler(self, value):
with self.lock:
self.table_handler = value
def set_code(self, value):
with self.lock:
self.code = value
def set_token(self, value):
with self.lock:
self.token = value
def set_status_add(self, key, value):
with self.lock:
self.status[key] = value
def set_status_remove(self, key):
with self.lock:
if self.status and key in self.status.keys():
self.status.pop(key)
def get_status(self, key):
if self.status and key in self.status.keys():
return self.status[key]
def set_clients(self, value: dict):
with self.lock:
self.clients = value
def set_client_add(self, key, value):
with self.lock:
self.clients[key] = value
def set_client_id(self, key):
with self.lock:
self.client_id = key
class Project(BaseModel):
project_name: str
device_id: str
subscribe_topic: str
publish_topic: str
gateway_id: str
gateway_sct: str
register_type: int
class UserInfo(BaseModel):
user_id: str
device_id: str
name: str
user_type: int # 0:业主 1:访客
qrcode: str = None # 当使用二维码时该值就是美安设备对应的userid
face_url: str = None

View File

@@ -0,0 +1,730 @@
# -*- coding:utf-8 -*-
"""
@File : common_service
@Author : xuxingchen
@Version : 2.0
@Contact : xuxingchen@sinochem.com
@Desc : 业务逻辑服务层
"""
import json
import time
import traceback
import requests
import devices.meian_model as meian_data_entity
import devices.yunfu_model as yunfu_data_entity
import logger
from logger import Logger, speed_ms
from devices.meian_db import DeviceTable, RegisterTable, GatewayTable, HeartBeatTable, UserInfoTable, RecordTable
from devices.common_model import ErrorCode, UserData, UserInfo
from utils import generate_token, to_obj, datetime_to_timestamp, generate_time_token, encode_to_base64, \
extract_building_unit, extract_number
from config import TIMEOUT_SECOND, SLEEP_TIME, BACKEND_URL, BACKEND_ID, BACKEND_SECRET, GATEWAY_CONFIG
class BaseService:
def __init__(self):
self.table_handler = None
self.topic = None
self.clients = None
def set_meta(self, userdata: UserData):
self.table_handler = userdata.table_handler
self.topic = userdata.topic
self.clients = userdata.clients
def handle(self, **kwargs):
"""must be `override`"""
pass
def get_owner_house_floor(user_id, project_code: str, device_position_desc: str) -> int:
"""根据用户ID获取用户房产信息提取楼层信息并返回
1. 该功能只支持业主使用,访客查询不到,默认返回
"""
token_url = f"{BACKEND_URL}/v2/accesskey_auth"
owner_info_url = f"{BACKEND_URL}/v3/realty-master-data/owners/{user_id}"
house_info_url = f"{BACKEND_URL}/v3/realty-master-data/houses"
# get token
data = {'id': BACKEND_ID, 'secret': BACKEND_SECRET}
token_response = requests.post(token_url, json=data)
if token_response.status_code == 200:
logger.Logger.debug(f"token 请求成功,响应内容:{token_response.json()}")
token = token_response.json()["access_token"]
else:
logger.Logger.error(f"token 请求失败,状态码: {token_response.status_code}")
raise Exception(f"token 请求失败,状态码: {token_response.status_code}")
# get house ids
headers = {
"Accept": "application/json",
"Access-Token": token,
"Content-Type": "application/json;charset=UTF-8"
}
owner_info_response = requests.get(owner_info_url, headers=headers)
if owner_info_response.status_code == 200:
owner_info = owner_info_response.json()
logger.Logger.debug(f"owner_info 请求成功,响应内容:{owner_info}")
# 根据 project_code 获取 project_id
if GATEWAY_CONFIG[project_code][2] == "" or owner_info["data"]["project_id"] == GATEWAY_CONFIG[project_code][2]:
house_ids = owner_info["data"]["house_ids"]
else:
logger.Logger.error(f"project id 不匹配,"
f"预置:{GATEWAY_CONFIG[project_code][2]}, 用户信息所属:{owner_info['data']['project_id']}")
raise Exception(f"project id 不匹配")
elif owner_info_response.status_code == 404 and owner_info_response.json()["msg"] == "OWNER_NOT_EXISTS":
logger.Logger.warn(f"owner_info 请求成功但用户ID不存在非住户")
return 1
else:
logger.Logger.error(f"token 请求失败,状态码: {token_response.status_code}")
raise Exception(f"token 请求失败,状态码: {token_response.status_code}")
# 结合设备注册备注 获取楼层信息
data = {
"query": {
"id": {"$in": house_ids}
}
}
house_info_response = requests.post(house_info_url, json=data, headers=headers)
if house_info_response.status_code == 200:
logger.Logger.debug(f"house_info 请求成功,响应内容:{house_info_response.json()}")
building_number, unit_number = extract_building_unit(device_position_desc)
house_info = house_info_response.json()["data"]["list"]
floor = 1
for house_item in house_info:
building_name = extract_number(house_item["building_name"]).zfill(2)
unit_name = extract_number(house_item["unit_name"]).zfill(2)
if building_name == building_number and unit_name == unit_number:
floor = int(house_item["floor_name"])
break
else:
logger.Logger.error(f"house_info 请求失败,状态码: {token_response.status_code}")
raise Exception(f"house_info 请求失败,状态码: {token_response.status_code}")
return floor
class Services:
"""业务逻辑入口"""
class HeartBeatService(BaseService):
def handle(self, msg_obj: meian_data_entity.HeartBeat):
# Logger.debug("设备心跳已接收,正在执行上线 ...", log_path=None)
last_time = HeartBeatTable.get_last_time(self.table_handler, msg_obj.device_id)
# 若无心跳记录或者距离上次心跳记录时间达到5分钟
if (last_time is None or
(time.time() - float(
HeartBeatTable.get_last_time(self.table_handler, msg_obj.device_id))) > (60. * 5.)):
status = HeartBeatTable.update(self.table_handler, msg_obj, self.topic)
if status:
# 执行上线操作
project_code = DeviceTable.get_project_code(self.table_handler, msg_obj.device_id)
if project_code:
gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
if gateway_id is None:
Logger.error("网关不存在")
return -1
aiot_id = DeviceTable.get_aiot_id(self.table_handler, msg_obj.device_id)
if aiot_id is None:
Logger.warn("设备未注册,不执行上线操作")
return -1
topic = f"/jmlink/{aiot_id}/comm/online"
topic_resp = f"/jmlink/{aiot_id}/comm/online_resp"
s = time.time()
# 获取客户端
client_1 = self.clients[gateway_id][0]
userdata = self.clients[gateway_id][1]
userdata.set_status_add("status", False)
userdata.set_status_add("start_timestamp", time.time())
try:
# 订阅回传
client_1.subscribe(topic_resp)
Logger.debug(f"subscribe topics: {topic_resp}")
# 发布事件
token = generate_token()
userdata.set_token(token)
online_json = json.dumps(yunfu_data_entity.Online(messageId=token).__dict__)
client_1.publish(topic, online_json)
while True:
if userdata.status["status"]:
# 拿到结果后写入到数据库中更新数据
if "code" in userdata.status["response"].keys():
error_code = userdata.status["response"]["code"]
if error_code == 0:
Logger.info(f"设备 - {aiot_id} 完成上线")
else:
Logger.error(
f"{topic_resp} 返回错误码: {error_code}, "
f"{yunfu_data_entity.error_code[error_code]}")
break
if time.time() - userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
Logger.debug(
f"等待回复超时: {speed_ms(userdata.status['start_timestamp'])}ms")
break
# Logger.debug(f"{userdata.status} waiting for {topic_resp} ... ")
time.sleep(SLEEP_TIME)
finally:
# 属性复位
client_1.unsubscribe(topic_resp)
# Logger.debug(f"移除订阅: {topic_resp}")
userdata.set_token(None)
userdata.set_status_remove("response")
# Logger.debug(f"{speed_ms(s)}ms")
else:
Logger.warn("上线请求非法,默认无操作")
else:
Logger.debug("未达到重新上线阈值,默认无操作")
pass
class RegisterService(BaseService):
def handle(self, msg_obj: meian_data_entity.Register):
Logger.debug("RegisterService Handle ...")
status = RegisterTable.update(self.table_handler, msg_obj, self.topic)
if status:
if not DeviceTable.get_device_register_type(self.table_handler, msg_obj.device_id):
Logger.debug("Execute register ...")
project_code = DeviceTable.get_project_code(self.table_handler, msg_obj.device_id)
if project_code:
gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
if gateway_id is None:
Logger.error("网关不存在")
return -1
topic = f"/jmlink/{gateway_id}/comm/sub/register"
topic_resp = f"/jmlink/{gateway_id}/comm/sub/register_resp"
# 将连接信息(网关&网关secret)和回传信息发送给回传监听进程
s = time.time()
# 获取客户端
client_1 = self.clients[gateway_id][0]
userdata = self.clients[gateway_id][1]
userdata.set_status_add("status", False)
userdata.set_status_add("start_timestamp", time.time())
try:
# 订阅回传
client_1.subscribe(topic_resp)
Logger.debug(f"subscribe topics: {topic_resp}")
# 发布事件
token = generate_token()
userdata.set_token(token)
register_json = json.dumps(yunfu_data_entity.CommRegister(
messageId=token,
params=yunfu_data_entity.RegisterParam(
deviceName=msg_obj.device_id,
displayName=f"美安-{msg_obj.device_id}-{msg_obj.device_position_desc}"
).__dict__
).__dict__)
Logger.debug(register_json)
client_1.publish(topic, register_json)
while True:
if userdata.status["status"]:
# 拿到结果后写入到数据库中更新数据
if "data" in userdata.status["response"].keys():
resp = userdata.status["response"]["data"][0]
if resp["code"] == 0:
DeviceTable.update_aiot_id(self.table_handler, resp["deviceName"],
resp["deviceId"])
Logger.info(f"设备 - {msg_obj.device_id} 完成注册")
# 每次完成注册后刷新订阅信息
Logger.debug(f"刷新订阅列表中 ...")
sub_aiot_id_list = json.loads(
GatewayTable.get_registered_sub_aiot_id(self.table_handler, gateway_id)[
0])
current_topics = set(
[f"/jmlink/{aiot_id}/tml/service/call" for aiot_id in sub_aiot_id_list])
last_topics = set(userdata.topics)
topics_to_subscribe = current_topics - last_topics
topics_to_unsubscribe = last_topics - current_topics
if topics_to_subscribe or topics_to_unsubscribe:
# 订阅缺少的主题
for topic in list(topics_to_subscribe):
client_1.subscribe(topic)
Logger.debug(f"新增订阅: {topic}")
# 取消订阅多余的主题
for topic in list(topics_to_unsubscribe):
client_1.unsubscribe(topic)
Logger.debug(f"移除订阅: {topic}")
else:
Logger.debug(f"订阅列表无变化")
Logger.debug(f"订阅列表刷新完成 ✅")
else:
Logger.error(
f"{topic_resp} 返回错误码: {resp['code']}, "
f"{yunfu_data_entity.error_code[resp['code']]}")
break
if time.time() - userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
Logger.debug(
f"等待回复超时: {speed_ms(userdata.status['start_timestamp'])}ms")
break
# Logger.debug(f"{userdata.status} waiting for {topic_resp} ... ")
time.sleep(SLEEP_TIME) # 实际运行中设为0.1100ms检测一次
finally:
client_1.unsubscribe(topic_resp)
Logger.debug(f"移除订阅: {topic_resp}")
userdata.set_token(None)
userdata.set_status_remove("response")
Logger.debug(f"{speed_ms(s)}ms")
else:
Logger.error("网关配置表中不存在设备关联的项目信息")
else:
Logger.debug("设备已注册过,默认无操作")
class PushRtAccessRecordService(BaseService):
def handle(self, msg_obj: meian_data_entity.PushRtAccessRecord):
Logger.debug("Execute open_event ...")
# 若存在通行记录,但人员信息表中不存在人员信息,则将通行记录本地保存留用
if msg_obj.user_id.startswith("old-"): # 历史遗留在设备中的人员id应当以old-开头进行插入
try:
RecordTable.add(self.table_handler, msg_obj)
Logger.info("通行事件本地记录成功")
return 1
except Exception as e:
Logger.error(f"{type(e).__name__}, {e}")
if logger.DEBUG:
traceback.print_exc()
# 查询子设备ID
aiot_id = DeviceTable.get_aiot_id(self.table_handler, msg_obj.device_id)
if aiot_id is None:
Logger.warn(f"Device - {aiot_id} is not registered")
return -1
topic = f"/jmlink/{aiot_id}/tml/event/post"
topic_resp = f"/jmlink/{aiot_id}/tml/event/post_resp"
project_code = DeviceTable.get_project_code(self.table_handler, msg_obj.device_id)
if project_code:
gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
# 将连接信息(网关&网关secret)和回传信息发送给回传监听进程
s = time.time()
# 获取客户端
client_1 = self.clients[gateway_id][0]
userdata = self.clients[gateway_id][1]
userdata.set_status_add("status", False)
userdata.set_status_add("start_timestamp", time.time())
# 订阅回传
client_1.subscribe(topic_resp)
Logger.debug(f"subscribe topics: {topic_resp}")
try:
# 数据转换
open_event = yunfu_data_entity.OpenEvent()
open_event.datetime = str(datetime_to_timestamp(msg_obj.time))
open_event.type = yunfu_data_entity.OpenEventType.__dict__[msg_obj.access_mode.upper()].value
open_event.certificate_type = yunfu_data_entity.OpenEventCertificateType.__dict__[
msg_obj.access_mode.upper()].value
# 若是二维码通信根据二维码获取用户信息若是人脸通信根据userid获取用户信息
if msg_obj.access_mode == "qrCode":
user = UserInfoTable.get_user_by_qrcode(self.table_handler, msg_obj.user_id, msg_obj.device_id)
open_event.code = user[0]
open_event.certificate = user[0]
name = user[1]
else:
name = UserInfoTable.get_name(self.table_handler, msg_obj.user_id, msg_obj.device_id)
open_event.code = msg_obj.user_id
open_event.certificate = msg_obj.user_id
open_event.name = name
open_event.check()
token = generate_token()
userdata.set_token(token)
event_json = json.dumps(yunfu_data_entity.EventPost(
messageId=token, eventCode="open_event",
params=open_event.__dict__
).__dict__)
# 发布事件
client_1.publish(topic, event_json)
# 等待回传
while True:
if userdata.status["status"]:
if "code" in userdata.status["response"].keys():
if userdata.status["response"]["code"] != 0:
error_code = userdata.status['response']['errorCode']
Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
f"{meian_data_entity.error_code[error_code]}")
else:
Logger.debug("通行事件推送成功")
break
if time.time() - userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
Logger.error("等待回复超时")
break
# Logger.debug(f"{userdata.status} waiting for {topic_resp} ... ")
time.sleep(SLEEP_TIME)
finally:
# 属性复位
client_1.unsubscribe(topic_resp)
Logger.debug(f"移除订阅: {topic_resp}")
userdata.set_token(None)
userdata.set_status_remove("response")
Logger.debug(f"{speed_ms(s)}ms")
class GetQrCodeService(BaseService):
"""获取访客二维码
1. 将接受的用户信息记录追加或更新到本地DB
2. 根据时间戳和访客id生成一个token字符串作为唯一识别id
3. 将生成的二维码下置给指定设备
4. 将唯一识别id作为二维码字符串返回给平台
"""
def handle(self, msg: dict):
Logger.debug("Execute Get QrCode ...")
code = ErrorCode.SUCCESS
device_id = DeviceTable.get_device_id(self.table_handler, self.topic.split("/tml")[0].split("link/")[1])
project_code = DeviceTable.get_project_code(self.table_handler, device_id)
gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
success_aiot_list = []
aiot_ids = []
qrcode = None
try:
# 根据时间戳和访客id生成一个token字符串作为唯一识别id
qrcode = generate_time_token(msg["params"]["user_id"])
# 将生成的二维码下置给指定设备
aiot_ids = json.loads(msg["params"]["device_ids"])
s = time.time()
for aiot_id in aiot_ids: # 一般情况 aiot_ids 里应该只有一个值
# 查询对应aiot_id实际的美安设备id和订阅主题
device_id = DeviceTable.get_device_id(self.table_handler, aiot_id)
if device_id is None:
continue
# 将接受的用户信息记录追加或更新到本地DB
user_type = 0 if msg["serviceCode"] != "get_visitor_qrcode" else 1 # 业主0 访客1身份类型在插入后就不会再被更新
device_position_desc = RegisterTable.get_device_position_desc(self.table_handler, device_id)
if user_type == 1 or device_position_desc is None:
floor = 1
else:
floor = get_owner_house_floor(msg["params"]["user_id"], project_code, device_position_desc)
user_info = UserInfo(
user_id=msg["params"]["user_id"],
device_id=device_id,
name=msg["params"]["name"],
user_type=user_type
)
UserInfoTable.update(self.table_handler, user_info)
topic, topic_resp = DeviceTable.get_device_topic(self.table_handler, device_id)
# 获取客户端
client_0 = self.clients["center"][0]
userdata_0 = self.clients["center"][1]
userdata_0.set_status_add("status", False)
userdata_0.set_status_add("start_timestamp", time.time())
try:
Logger.debug(f"subscribe topics: {topic_resp}")
# 构建消息体
token = generate_token() # 正式运行应使用 generate_token()
userdata_0.set_token(token)
userdata_0.set_code("qrCodeDownState")
qrcode_json = json.dumps(meian_data_entity.QrCodeInfo(
dataType="qrCodeInfo",
deviceId=device_id,
token=token,
userId=qrcode,
qrCode=encode_to_base64(qrcode),
floor=floor
).__dict__)
# 发布事件
client_0.publish(topic, qrcode_json)
# 等待回传
while True:
if userdata_0.status["status"]:
if "errorCode" in userdata_0.status["response"].keys():
if userdata_0.status["response"]["errorCode"] != 0:
error_code = userdata_0.status['response']['errorCode']
Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
f"{meian_data_entity.error_code[error_code]}")
else:
Logger.info(f"设备 - {device_id} 二维码 - {qrcode} 完成下置")
UserInfoTable.update_qrcode(self.table_handler,
user_info.user_id, device_id, qrcode)
success_aiot_list.append(aiot_id)
break
if time.time() - userdata_0.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
Logger.error("等待回复超时")
break
# Logger.debug(f"{userdata_0.status} waiting for {topic_resp} ... ")
time.sleep(SLEEP_TIME)
finally:
userdata_0.set_token(None)
userdata_0.set_code(None)
userdata_0.set_status_remove("response")
Logger.debug(f"{speed_ms(s)}ms")
except Exception as e:
Logger.error(f"{type(e).__name__}, {e}")
if type(e).__name__ == "TypeError":
code = ErrorCode.INPUT_TYPE_ERROR
else:
code = ErrorCode.UNKNOWN
# 将唯一识别id作为二维码字符串返回给平台
# 获取客户端
client_1 = self.clients[gateway_id][0]
userdata = self.clients[gateway_id][1]
userdata.set_status_add("status", False)
userdata.set_status_add("start_timestamp", time.time())
try:
if code == ErrorCode.SUCCESS:
if set(success_aiot_list) == set(aiot_ids): # 全部完成下发
Logger.debug("指令完成下发")
elif success_aiot_list: # 完成一部分下发
code = ErrorCode.PART_SUCCESS
Logger.warn(f"部分成功下发: {success_aiot_list}")
UserInfoTable.update_qrcode(self.table_handler, msg["params"]["user_id"], project_code, qrcode)
elif len(success_aiot_list) == 0: # 一个都没下发
code = ErrorCode.FAILURE
Logger.error("无指令完成下发")
aiot_topic = self.topic + "_resp"
return_json = json.dumps(yunfu_data_entity.EventPostResp(
messageId=msg["messageId"],
requestTime=msg["time"],
serviceCode=msg["serviceCode"],
data=yunfu_data_entity.QrCodeResp(code=code.value, message=code.message, qrcode=qrcode).__dict__
).__dict__)
client_1.publish(aiot_topic, return_json)
finally:
userdata.set_token(None)
userdata.set_status_remove("response")
class AddFaceService(BaseService):
def handle(self, msg: dict):
Logger.debug("Execute Add Face ...")
code = ErrorCode.SUCCESS
device_id = DeviceTable.get_device_id(self.table_handler, self.topic.split("/tml")[0].split("link/")[1])
project_code = DeviceTable.get_project_code(self.table_handler, device_id)
gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
success_aiot_list = []
aiot_ids = json.loads(msg["params"]["device_ids"])
user_id = msg["params"]["user_id"]
face_url = f"{msg['params']['face_url']}?x-oss-process=image/resize,m_pad,h_960,w_540,color_FFFFFF"
try:
# 将人脸下置给指定设备
s = time.time()
for aiot_id in aiot_ids:
# 查询对应aiot_id实际的美安设备id和订阅主题
device_id = DeviceTable.get_device_id(self.table_handler, aiot_id)
if device_id is None:
continue
# 将接受的用户信息记录追加或更新到本地DB
user_info = UserInfo(
user_id=user_id,
device_id=device_id,
name=msg["params"]["name"],
user_type=0
)
UserInfoTable.update(self.table_handler, user_info)
topic, topic_resp = DeviceTable.get_device_topic(self.table_handler, device_id)
# 获取客户端
client_0 = self.clients["center"][0]
userdata_0 = self.clients["center"][1]
userdata_0.set_status_add("status", False)
userdata_0.set_status_add("start_timestamp", time.time())
try:
Logger.debug(f"subscribe topics: {topic_resp}")
# 构建消息体
token = generate_token() # 正式运行应使用 generate_token()
userdata_0.set_token(token)
userdata_0.set_code("faceDownState")
device_position_desc = RegisterTable.get_device_position_desc(self.table_handler, device_id)
if device_position_desc is None:
floor = 1
else:
floor = get_owner_house_floor(user_id, project_code, device_position_desc)
face_json = json.dumps(meian_data_entity.FaceInfo(
dataType="faceInfo",
deviceId=device_id,
token=token,
userId=user_id,
faceUrl=face_url,
floor=floor
).__dict__)
# 发布事件
client_0.publish(topic, face_json)
Logger.debug(f"{face_json} ==> {topic}")
# 等待回传
while True:
if userdata_0.status["status"]:
if "errorCode" in userdata_0.status["response"].keys():
if userdata_0.status["response"]["errorCode"] != 0:
error_code = userdata_0.status['response']['errorCode']
Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
f"{meian_data_entity.error_code[error_code]}")
else:
Logger.info(f"设备 - {device_id} 用户({user_id}) 人脸完成下置")
UserInfoTable.update_face_url(self.table_handler, user_id, device_id, face_url)
success_aiot_list.append(aiot_id)
break
if time.time() - userdata_0.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
Logger.error("等待回复超时")
break
# Logger.debug(f"{userdata_0.status} waiting for {topic_resp} ... ")
time.sleep(SLEEP_TIME)
finally:
userdata_0.set_token(None)
userdata_0.set_code(None)
userdata_0.set_status_remove("response")
Logger.debug(f"{speed_ms(s)}ms")
except Exception as e:
Logger.error(f"{type(e).__name__}, {e}")
if type(e).__name__ == "TypeError":
code = ErrorCode.INPUT_TYPE_ERROR
else:
code = ErrorCode.UNKNOWN
# 将下置结果返回给平台
# 获取客户端
client_1 = self.clients[gateway_id][0]
userdata = self.clients[gateway_id][1]
userdata.set_status_add("status", False)
userdata.set_status_add("start_timestamp", time.time())
try:
if code == ErrorCode.SUCCESS:
if set(success_aiot_list) == set(aiot_ids): # 全部完成下发
Logger.info("指令完成下发")
elif success_aiot_list: # 完成一部分下发
code = ErrorCode.PART_SUCCESS
Logger.warn(f"部分成功下发: {success_aiot_list}")
else: # 一个都没下发
code = ErrorCode.FAILURE
Logger.error("无指令完成下发")
aiot_topic = self.topic + "_resp"
return_json = json.dumps(yunfu_data_entity.EventPostResp(
messageId=msg["messageId"],
requestTime=msg["time"],
serviceCode=msg["serviceCode"],
data=yunfu_data_entity.AddFaceResp(code=code.value, message=code.message).__dict__
).__dict__)
client_1.publish(aiot_topic, return_json)
finally:
userdata.set_token(None)
userdata.set_status_remove("response")
class DeleteFaceService(BaseService):
def handle(self, msg: dict):
Logger.debug("Execute Delete Face ...")
code = ErrorCode.SUCCESS
device_id = DeviceTable.get_device_id(self.table_handler, self.topic.split("/tml")[0].split("link/")[1])
project_code = DeviceTable.get_project_code(self.table_handler, device_id)
gateway_id, _ = GatewayTable.get_gateway(self.table_handler, project_code)
success_aiot_list = []
aiot_ids = json.loads(msg["params"]["device_ids"])
user_id = msg["params"]["user_id"]
try:
s = time.time()
# 将人脸删除指令下发指定设备
for aiot_id in aiot_ids:
# 查询对应aiot_id实际的美安设备id和订阅主题
device_id = DeviceTable.get_device_id(self.table_handler, aiot_id)
if device_id is None:
continue
# 本地DB判断人脸信息是否存在
if UserInfoTable.exists_face_url(self.table_handler, user_id, device_id):
topic, topic_resp = DeviceTable.get_device_topic(self.table_handler, device_id)
# 获取客户端
client_0 = self.clients["center"][0]
userdata_0 = self.clients["center"][1]
userdata_0.set_status_add("status", False)
userdata_0.set_status_add("start_timestamp", time.time())
try:
Logger.debug(f"subscribe topics: {topic_resp}")
# 构建消息体
token = generate_token() # 正式运行应使用 generate_token()
userdata_0.set_token(token)
userdata_0.set_code("deleteUserState")
face_json = json.dumps(meian_data_entity.DeleteUser(
dataType="deleteUser",
deviceId=device_id,
token=token,
userId=user_id
).__dict__)
# 发布事件
client_0.publish(topic, face_json)
# 等待回传
while True:
if userdata_0.status["status"]:
if "errorCode" in userdata_0.status["response"].keys():
if userdata_0.status["response"]["errorCode"] != 0:
error_code = userdata_0.status['response']['errorCode']
Logger.error(f"{topic_resp} 返回错误码: {error_code}, "
f"{meian_data_entity.error_code[error_code]}")
else:
Logger.info(f"设备 - {device_id} 移除用户({user_id})人脸")
UserInfoTable.update_face_url(self.table_handler, user_id, device_id, None)
success_aiot_list.append(aiot_id)
break
if time.time() - userdata_0.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
Logger.error("等待回复超时")
break
# Logger.debug(f"{userdata_0.status} waiting for {topic_resp} ... ")
time.sleep(SLEEP_TIME)
finally:
userdata_0.set_token(None)
userdata_0.set_code(None)
userdata_0.set_status_remove("response")
else:
code = ErrorCode.NEVER_POST_FACE
Logger.warn(f"本地数据库中未发现下发过人脸信息,默认无操作")
Logger.debug(f"{speed_ms(s)}ms")
except Exception as e:
Logger.error(f"{type(e).__name__}, {e}")
if type(e).__name__ == "TypeError":
code = ErrorCode.INPUT_TYPE_ERROR
else:
code = ErrorCode.UNKNOWN
# 将下置结果返回给平台
# 获取客户端
client_1 = self.clients[gateway_id][0]
userdata = self.clients[gateway_id][1]
userdata.set_status_add("status", False)
userdata.set_status_add("start_timestamp", time.time())
try:
if code == ErrorCode.SUCCESS:
if set(success_aiot_list) == set(aiot_ids): # 全部完成下发
Logger.info("指令完成下发")
elif success_aiot_list: # 完成一部分下发
code = ErrorCode.PART_SUCCESS
Logger.warn(f"部分成功下发: {success_aiot_list}")
else: # 一个都没下发
code = ErrorCode.FAILURE
Logger.error("无指令完成下发")
aiot_topic = self.topic + "_resp"
return_json = json.dumps(yunfu_data_entity.EventPostResp(
messageId=msg["messageId"],
requestTime=msg["time"],
serviceCode=msg["serviceCode"],
data=yunfu_data_entity.DelFaceResp(code=code.value, message=code.message).__dict__
).__dict__)
client_1.publish(aiot_topic, return_json)
finally:
userdata.set_token(None)
userdata.set_status_remove("response")
def auto_service(msg: dict, userdata: UserData):
"""跟据dataType自动化加载不同的服务层"""
if "dataType" in msg.keys():
entity_name = msg["dataType"][0].upper() + msg["dataType"][1:]
if entity_name in meian_data_entity.__dict__.keys():
Logger.debug(f"Target service name: {entity_name}")
# 转为服务层数据实体
entity_type = meian_data_entity.__dict__[entity_name]
msg_obj = to_obj(msg, entity_type)
# 构建服务层实体并进行调用
service_name = entity_name + "Service"
if service_name in Services.__dict__.keys():
servicer = Services.__dict__[service_name]()
servicer.set_meta(userdata)
servicer.handle(msg_obj)
elif "serviceCode" in msg.keys() and "params" in msg.keys(): # 校验是aiot平台服务调用下发
service_map = {
"get_owner_qrcode": "GetQrCodeService",
"get_visitor_qrcode": "GetQrCodeService",
"add_face": "AddFaceService",
"del_face": "DeleteFaceService"
}
# 构建服务层实体并进行调用
if msg["serviceCode"] in service_map.keys():
service_name = service_map[msg["serviceCode"]]
servicer = Services.__dict__[service_name]()
servicer.set_meta(userdata)
servicer.handle(msg)
else:
pass

702
meian/devices/meian_db.py Normal file
View File

@@ -0,0 +1,702 @@
# -*- 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))),
)

View File

@@ -0,0 +1,98 @@
# -*- coding:utf-8 -*-
"""
@File : meian_model
@Author : xuxingchen
@Version : 1.0
@Contact : xuxingchen@sinochem.com
@Desc : Data Entity
"""
from pydantic import BaseModel
# 错误码
error_code = {
0: "成功",
-1: "图片下载超时",
-2: "图片下载失败",
-3: "用户ID不存在",
-4: "参数无效",
-5: "注册失败",
-6: "用户ID已存在",
-7: "无效人脸",
-8: "内部错误",
-9: "图片解码失败图像尺寸宽高像素应为540*960",
-10: "添加人脸失败",
-11: "人脸图像无特征",
-12: "查询离线通行记录失败",
-13: "设备忙",
-14: "rf_id_not_exist",
-15: "rf_id_existed",
-16: "add_rf_id_failed or rm_rf_id_failed",
-17: "get_rf_id_reader_mode_failed",
-18: "set_rf_id_reader_mode_failed",
-19: "添加人脸操作不支持",
}
class BaseInfo(BaseModel):
data_type: str = None
def check(self):
for attr in self.__dict__.keys():
# if property can be null, default value should not be set to None
if self.__dict__[attr] is None:
raise ValueError(f"{attr} not allowed to be set to None")
class BaseRequest(BaseInfo):
device_id: str = None
token: str = None
class BaseResponse(BaseInfo):
factory_id: str = None
error_code: int = None
token: str = None
class HeartBeat(BaseInfo):
device_id: str = None
factory_id: str = None
class PushRtAccessRecord(HeartBeat):
time: str = None
user_id: str = None
access_mode: str = None
class Register(BaseRequest):
factory_id: str = None # 厂商唯一标识
device_type: int = None # 0面板机1梯控机
device_position_code: str = None
device_position_desc: str = None
class FaceInfo(BaseModel):
dataType: str
deviceId: str
token: str
userId: str
faceUrl: str
floor: int
class QrCodeInfo(BaseModel):
dataType: str
deviceId: str
token: str
userId: str
qrCode: str
floor: int
class DeleteUser(BaseModel):
dataType: str
deviceId: str
token: str
userId: str

View File

@@ -0,0 +1,137 @@
# -*- coding:utf-8 -*-
"""
@File : yunfu_model
@Author : xuxingchen
@Version : 1.0.1
@Contact : xuxingchen@sinochem.com
@Desc : 云服的数据模型
"""
import os
import time
from typing import Optional
from pydantic import BaseModel, field_validator, Field
from enum import Enum
from utils import get_sign
error_code = {
0: "成功",
401: "参数错误,原因可能是:必填参数缺失或格式错误",
402: "云端错误。请稍后再试",
403: "mq解析失败",
404: "发送kafka异常",
602: "通过deviceUuid获取redis中DeviceInfoCacheVO缓存失败",
40014: "设备不存在"
}
class MeianProduct:
if int(os.environ.get("ENV_TYPE", 0)) != 2:
id: str = "4bb9a56bfe9*********2ee99bac6d"
secret: str = "9b0159********abe57c10ea665ddd"
else:
id: str = "a2ce31a40b********230ccc860e"
secret: str = "03970a**********28b84e8437"
class OpenEventType(Enum):
# 此处的取名应当与美安设备的accessMode能够一一对应
FACE = 8
QRCODE = 10
OFFLINE = 11
class OpenEventCertificateType(Enum):
# 此处的取名应当与美安设备的accessMode能够一一对应
FACE = 2
PASSWORD = 3
QRCODE = 4
class BaseInfo(BaseModel):
def check(self):
for attr in self.__dict__.keys():
# if property can be null, default value should not be set to None
if self.__dict__[attr] is None:
raise ValueError(f"{attr} not allowed to be set to None")
class CommRegister(BaseInfo):
messageId: str = None
version: str = "1.0"
time: int = int(time.time() * 1000)
params: dict = None
class EventPost(BaseInfo):
messageId: str = None
version: str = "1.0"
time: int = int(time.time() * 1000)
ext: dict = {
"ack": 1
}
eventCode: str = None
params: dict = None
class EventPostResp(BaseInfo):
messageId: str
requestTime: int
time: int = int(time.time() * 1000)
code: int = 0
message: str = "success"
serviceCode: str
data: dict = None
class OpenEvent(BaseInfo):
type: OpenEventType | int = None
user_picture: str = "default"
code: str = "default"
name: str = None
certificate_type: OpenEventCertificateType | int = None
certificate: str = None
result: int = 1
error_code: int = 1
enter_type: int = 1
datetime: str = None
class RegisterParam(BaseInfo):
productId: str = MeianProduct.id
deviceName: str = None
displayName: str = None
sign: Optional[str] = Field(default=None, validate_default=True)
@field_validator("sign")
def gen_sign(cls, value, values):
productId = values.data.get("productId")
deviceName = values.data.get("deviceName")
return get_sign(f"deviceName{deviceName}productId{productId}", MeianProduct.secret)
class Online(BaseInfo):
messageId: str
version: str = "1.0"
time: int = int(time.time() * 1000)
cleanSession: str = "true"
productModel: str = "default"
chipModel: str = "default"
otaVersion: str = ""
class QrCodeResp(BaseInfo):
code: int
message: str
qrcode: str
class AddFaceResp(BaseInfo):
code: int
message: str
class DelFaceResp(BaseInfo):
code: int
message: str