DeviceVersionInfoController.py 20 KB

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