DeviceVersionInfoController.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : DeviceVersionInfoController.py
  4. @Time : 2024/11/20 14:20
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import json
  10. import time
  11. from django.http import QueryDict
  12. from django.views import View
  13. from Ansjer.config import LOGGER
  14. from Model.models import DeviceVersionInfo, Device_Info
  15. from Object.Enums.RedisKeyConstant import RedisKeyConstant
  16. from Object.RedisObject import RedisObject
  17. from Object.ResponseObject import ResponseObject
  18. from Object.TokenObject import TokenObject
  19. from Service.CommonService import CommonService
  20. class DeviceVersionInfoView(View):
  21. def get(self, request, *args, **kwargs):
  22. request.encoding = 'utf-8'
  23. operation = kwargs.get('operation')
  24. return self.validation(request.GET, request, operation)
  25. def post(self, request, *args, **kwargs):
  26. request.encoding = 'utf-8'
  27. operation = kwargs.get('operation')
  28. return self.validation(request.POST, request, operation)
  29. def delete(self, request, *args, **kwargs):
  30. request.encoding = 'utf-8'
  31. operation = kwargs.get('operation')
  32. delete = QueryDict(request.body)
  33. if not delete:
  34. delete = request.GET
  35. return self.validation(delete, request, operation)
  36. def put(self, request, *args, **kwargs):
  37. request.encoding = 'utf-8'
  38. operation = kwargs.get('operation')
  39. put = QueryDict(request.body)
  40. return self.validation(put, request, operation)
  41. def validation(self, request_dict, request, operation):
  42. response = ResponseObject('cn')
  43. if operation == 'syncVerConfig':
  44. return self.sync_ver_config(request, request_dict, response)
  45. tko = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
  46. if tko.code != 0:
  47. return response.json(tko.code)
  48. response.lang = tko.lang
  49. userID = tko.userID
  50. if operation == 'getInfo':
  51. return self.get_device_version_info(userID, request, request_dict, response)
  52. elif operation == 'validateUserDevice':
  53. return self.validateUserDevice(userID, request, request_dict, response)
  54. elif operation == 'clearDeviceVersionCache':
  55. return self.clear_device_version_cache(userID, request, request_dict, response)
  56. elif operation == 'getDeviceVersion':
  57. d_code = request_dict.get('d_code')
  58. ver = request_dict.get('ver')
  59. res = self.cache_device_version_info(d_code, ver)
  60. return response.json(0, res)
  61. else:
  62. return response.json(414)
  63. @classmethod
  64. def clear_device_version_cache(cls, user_id, request, request_dict, response):
  65. """
  66. 清除设备版本缓存
  67. @param user_id: 用户ID
  68. @param request: 请求对象
  69. @param request_dict: 请求参数
  70. @param response: 响应对象
  71. @return: 响应结果
  72. """
  73. d_code = request_dict.get('d_code', None)
  74. timestamp = request_dict.get('timestamp', None)
  75. if not d_code and not timestamp:
  76. return response.json(444) # 错误代码:缺少必要参数
  77. try:
  78. LOGGER.info(f'清除缓存 user: {user_id}, d_code: {d_code}, timestamp: {timestamp}')
  79. redis = RedisObject()
  80. if d_code:
  81. # 清除指定d_code的所有版本缓存
  82. pattern = RedisKeyConstant.ZOSI_DEVICE_VERSION_INFO.value + '*' + d_code
  83. matched_keys = redis.get_keys(pattern) # 将结果赋值给 matched_keys
  84. if matched_keys:
  85. for key in matched_keys: # 使用正确的变量名
  86. redis.del_data(key)
  87. if timestamp:
  88. # 清除创建时间早于指定时间戳的缓存
  89. devices = DeviceVersionInfo.objects.filter(created_time__lt=timestamp).values('d_code', 'software_ver')
  90. for device in devices:
  91. key = RedisKeyConstant.ZOSI_DEVICE_VERSION_INFO.value + device['software_ver'] + device['d_code']
  92. redis.del_data(key)
  93. return response.json(0, "缓存清除成功")
  94. except Exception as e:
  95. error_line = e.__traceback__.tb_lineno
  96. LOGGER.error(f'清除缓存失败 user:{user_id}, error_line:{error_line}, error_msg:{repr(e)}')
  97. return response.json(500, f'error_line:{error_line}, error_msg:{repr(e)}')
  98. @classmethod
  99. def get_device_version_info(cls, user_id, request, request_dict, response):
  100. # 从请求字典中获取uid和version
  101. uid = request_dict.get('uid')
  102. version = request_dict.get('version')
  103. # 检查version是否存在
  104. if not version:
  105. return response.json(444) # 错误代码:没有提供version
  106. try:
  107. # 示例输入字符串
  108. ip = CommonService.get_ip_address(request)
  109. LOGGER.info(f'获取设备版本配置信息 user: {user_id}, uid: {uid}, version: {version}, ip: {ip} ')
  110. # 从右侧分割字符串,获取版本和设备代码
  111. ver, d_code = version.rsplit('.', 1) # 使用从右到左分割
  112. ver = ver.replace('V', '') # 移除版本前的'V'
  113. # 创建Redis对象
  114. redis = RedisObject()
  115. # 构建Redis键
  116. version_key = RedisKeyConstant.ZOSI_DEVICE_VERSION_INFO.value + ver + d_code
  117. # 尝试从Redis中获取数据
  118. version_info = redis.get_data(version_key)
  119. if version_info:
  120. # 如果在Redis中找到了数据,直接返回
  121. return response.json(0, json.loads(version_info))
  122. # 从数据库查询设备版本信息
  123. device_version_qs = DeviceVersionInfo.objects.filter(d_code=d_code, software_ver=ver)
  124. if not device_version_qs.exists():
  125. LOGGER.info(f'获取设备版本配置信息失败 user: {user_id}, uid: {uid}, version: {version}, ip: {ip} ')
  126. return response.json(173) # 错误代码:未找到设备版本信息
  127. # 从QuerySet中获取设备信息
  128. device_info = device_version_qs.first()
  129. device_dict = device_version_qs.values().first()
  130. other_features = cls.build_other_features(device_dict, d_code)
  131. device_dict["other_features"] = other_features
  132. if getattr(device_info, "other_features", {}) != other_features:
  133. device_info.other_features = other_features
  134. device_info.save(update_fields=['other_features'])
  135. DeviceVersionInfo.objects.filter(d_code=d_code, software_ver=ver).update(other_features=other_features)#更新数据库
  136. LOGGER.info(f'更新other_features成功 id={device_info.id}')
  137. device_json = json.dumps(device_dict)
  138. redis.set_data(version_key, device_json, 60 * 60 * 24) # 设置TTL为24小时
  139. # 返回设备信息
  140. return response.json(0, device_dict)
  141. except Exception as e:
  142. LOGGER.error('uid:{}返回173,error_line:{}, error_msg:{}'.format(uid, e.__traceback__.tb_lineno, repr(e)))
  143. return response.json(173)
  144. @classmethod
  145. def cache_device_version_info(cls, d_code, ver):
  146. """
  147. 缓存设备版本信息
  148. :param d_code: 设备规格码(软件方案)
  149. :param ver: 设备版本号
  150. :return: 设备版本信息字典或 None
  151. """
  152. redis = RedisObject()
  153. version_key = f"{RedisKeyConstant.ZOSI_DEVICE_VERSION_INFO.value}{ver}{d_code}"
  154. try:
  155. version_info = redis.get_data(version_key)
  156. if version_info:
  157. return json.loads(version_info)
  158. except Exception as e:
  159. LOGGER.error(f"Redis get failed for key {version_key}: {e}")
  160. return None
  161. device_info = DeviceVersionInfo.objects.filter(
  162. d_code=d_code, software_ver=ver
  163. ).values().first()
  164. if not device_info:
  165. return None
  166. other_features = cls.build_other_features(device_info, d_code)
  167. device_info["other_features"] = other_features
  168. DeviceVersionInfo.objects.filter(d_code=d_code, software_ver=ver).update(other_features=
  169. other_features) # 更新数据库
  170. try:
  171. redis.set_data(version_key, json.dumps(device_info), 60 * 60 * 24)
  172. except Exception as e:
  173. LOGGER.error(f"Redis set failed for key {version_key}: {e}")
  174. return None
  175. return device_info
  176. @staticmethod
  177. def build_other_features(device_dict, d_code):
  178. """根据设备信息构建完整的other_features"""
  179. other_features_fields = [
  180. 'supports_lighting',
  181. 'is_support_multi_speed',
  182. 'supports_algorithm_switch',
  183. 'supports_playback_filtering',
  184. 'resolution_list'
  185. ]
  186. mp_resolution_map = {
  187. '8': ["800", "50"],
  188. '5': ["500", "50"],
  189. '4': ["400", "50"],
  190. '3': ["300", "50"],
  191. '2': ["200", "50"],
  192. '1': ["100", "50"],
  193. 'A': ["1000", "50"],
  194. 'B': ["1100", "50"],
  195. 'C': ["1200", "50"],
  196. }
  197. resolution_list = mp_resolution_map.get(d_code[-5], ["400", "50"])
  198. other_features = {}
  199. try:
  200. raw = device_dict.get("other_features")
  201. if raw:
  202. other_features = json.loads(raw) if isinstance(raw, str) else raw.copy()
  203. except Exception:
  204. pass
  205. # 填充默认值
  206. for field in other_features_fields:
  207. if field not in other_features or other_features[field] in (None, ''):
  208. other_features[field] = resolution_list if field == 'resolution_list' else -1
  209. if other_features.get('resolution_list') != resolution_list:
  210. other_features['resolution_list'] = resolution_list
  211. return other_features
  212. @classmethod
  213. def validateUserDevice(cls, user_id, request, request_dict, response):
  214. """
  215. 验证用户设备
  216. @param user_id: 用户id
  217. @param request: 请求
  218. @param request_dict: 请求参数
  219. @param response: 响应参数
  220. @return: 成功 0 失败 173
  221. """
  222. uid = request_dict.get('uid')
  223. serial_number = request_dict.get('serialNumber')
  224. app_bundle_id = request_dict.get('appBundleId')
  225. m_code = request_dict.get('mCode')
  226. if not uid:
  227. return response.json(444) # 错误代码:没有提供uid
  228. try:
  229. LOGGER.info(f'直播验证用户uid:{uid},user:{user_id},m_code:{m_code}')
  230. redis = RedisObject(3)
  231. # 构建Redis键
  232. device_key = f"{RedisKeyConstant.BASIC_USER.value}{user_id}:UID:{uid}"
  233. # 尝试从Redis中获取数据
  234. device_info = redis.get_data(device_key)
  235. # 检查缓存命中和UID匹配
  236. if device_info == uid:
  237. return response.json(0, uid) # 缓存命中返回 0
  238. ip = CommonService.get_ip_address(request)
  239. LOGGER.info(f'直播验证用户uid:{uid},ip:{ip},serial:{serial_number},app{app_bundle_id}')
  240. # 从数据库查询设备版本信息
  241. try:
  242. device_qs = Device_Info.objects.filter(userID_id=user_id, UID=uid).values('UID')
  243. if not device_qs.exists() or uid != device_qs.first().get('UID'):
  244. LOGGER.error(f'验证用户uid错误,未找到设备信息 uid:{uid}')
  245. return response.json(173) # 错误代码:未找到设备信息
  246. # 将数据写入Redis,以便后续使用,设置TTL为48小时
  247. redis.set_data(device_key, uid, 60 * 60 * 24 * 2)
  248. return response.json(0, uid) # 返回设备信息
  249. except Exception as db_exception:
  250. LOGGER.error(f'数据库查询失败 user:{user_id}, uid:{uid}, error: {repr(db_exception)}')
  251. return response.json(500, '数据库查询错误')
  252. except Exception as e:
  253. error_line = e.__traceback__.tb_lineno
  254. LOGGER.error(f'验证用户uid异常 user:{user_id}, uid:{uid}, error_line:{error_line}, error_msg:{repr(e)}')
  255. return response.json(500, f'error_line:{error_line}, error_msg:{repr(e)}')
  256. @classmethod
  257. def sync_ver_config(cls, request, request_dict, response):
  258. # 定义必要字段列表
  259. required_fields = ['dCode', 'softwareVer', 'videoCode', 'deviceType', 'supportsAlarm', 'screenChannels',
  260. 'networkType']
  261. # 验证必要参数是否存在
  262. if not all(request_dict.get(field) for field in required_fields):
  263. LOGGER.error(f'缺少必要参数: {request_dict}')
  264. return response.json(444)
  265. LOGGER.info(f'同步设备版本信息 params: {request_dict}')
  266. try:
  267. d_code = request_dict.get('dCode', '')
  268. ver = request_dict.get('softwareVer', '')
  269. # 提取参数并设置默认值(添加必要字段的类型转换)
  270. params = {
  271. 'd_code': d_code, # 字符串类型,添加默认空字符串
  272. 'software_ver': ver, # 字符串类型,添加默认空字符串
  273. 'firmware_ver': request_dict.get('firmwareVer', ''), # 已有默认空字符串,保持不变
  274. 'video_code': int(request_dict.get('videoCode', 0)), # 使用get()添加默认值0,避免KeyError
  275. 'region_alexa': request_dict.get('regionAlexa', 'ALL'), # 匹配模型默认值'ALL'
  276. 'supports_human_tracking': bool(int(request_dict.get('supportsHumanTracking', 0))), # 转为布尔值
  277. 'supports_custom_voice': bool(int(request_dict.get('supportsCustomVoice', 0))), # 转为布尔值
  278. 'supports_dual_band_wifi': bool(int(request_dict.get('supportsDualBandWifi', 0))), # 转为布尔值
  279. 'supports_four_point': bool(int(request_dict.get('supportsFourPoint', 0))), # 转为布尔值
  280. 'supports_4g': bool(int(request_dict.get('supports4g', 0))), # 转为布尔值
  281. 'supports_ptz': bool(int(request_dict.get('supportsPTZ', 0))), # 转为布尔值
  282. 'supports_ai': bool(int(request_dict.get('supportsAi', 0))), # 转为布尔值
  283. 'supports_cloud_storage': bool(int(request_dict.get('supportsCloudStorage', 0))), # 转为布尔值
  284. 'supports_alexa': bool(int(request_dict.get('supportsAlexa', 0))), # 转为布尔值
  285. 'device_type': int(request_dict.get('deviceType', 0)), # 使用get()添加默认值0
  286. 'resolution': request_dict.get('resolution', ''), # 字符串类型,添加默认空字符串
  287. 'ai_type': int(request_dict.get('aiType', 0)), # 使用get()添加默认值0
  288. 'supports_alarm': int(request_dict.get('supportsAlarm', -1)), # 使用get()添加默认值-1
  289. 'supports_night_vision': int(request_dict.get('supportsNightVision', -1)), # 使用get()添加默认值-1
  290. 'screen_channels': int(request_dict.get('screenChannels', 1)), # 匹配模型默认值1
  291. 'network_type': int(request_dict.get('networkType', 1)), # 匹配模型默认值1
  292. 'other_features': json.loads(request_dict['otherFeatures']) if request_dict.get(
  293. 'otherFeatures') else None, # 保持不变
  294. 'electricity_statistics': int(request_dict.get('electricityStatistics', 0)), # 使用get()添加默认值0
  295. 'supports_pet_tracking': int(request_dict.get('supportsPetTracking', 0)), # 使用get()添加默认值0
  296. 'has_4g_cloud': int(request_dict.get('has4gCloud', -1)), # 使用get()添加默认值0
  297. }
  298. now_time = int(time.time())
  299. # 使用 update_or_create 合并更新和创建操作
  300. version_config_qs = DeviceVersionInfo.objects.filter(
  301. d_code=params['d_code'],
  302. software_ver=params['software_ver']
  303. )
  304. if version_config_qs.exists():
  305. version_config_qs.update(**params, updated_time=now_time)
  306. # 创建Redis对象
  307. redis = RedisObject()
  308. # 构建Redis键
  309. version_key = RedisKeyConstant.ZOSI_DEVICE_VERSION_INFO.value + ver + d_code
  310. redis.del_data(version_key)
  311. else:
  312. DeviceVersionInfo.objects.create(**params, created_time=now_time, updated_time=now_time)
  313. except json.JSONDecodeError as e:
  314. # 专门捕获 JSON 解析错误
  315. LOGGER.error(f"JSON 解析失败: {str(e)}")
  316. return response.json(500, f'Invalid JSON format: {str(e)}')
  317. except KeyError as e:
  318. # 处理字段缺失(理论上 required_fields 已校验,此处作为备用)
  319. LOGGER.error(f"参数缺失: {str(e)}")
  320. return response.json(444, f'Missing parameter: {str(e)}')
  321. except ValueError as e:
  322. # 处理整型转换失败
  323. LOGGER.error(f"参数类型错误: {str(e)}")
  324. return response.json(400, f'Invalid parameter type: {str(e)}')
  325. except Exception as e:
  326. # 通用异常处理(建议替换为具体异常类型)
  327. LOGGER.exception("同步设备配置失败")
  328. return response.json(500, f'Server error: {str(e)}')
  329. return response.json(0)