DeviceReportController.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : DeviceReportController.py
  4. @Time : 2025/4/3 16:20
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import time
  10. from datetime import timedelta, datetime
  11. import pytz
  12. from django.db.models import Sum, Value
  13. from django.db.models.functions import Coalesce
  14. from django.http import QueryDict
  15. from django.views import View
  16. from Ansjer.config import LOGGER
  17. from Model.models import DeviceDailyReport, UidChannelSetModel, UidSetModel
  18. from Object.ResponseObject import ResponseObject
  19. from Object.TokenObject import TokenObject
  20. from Service.CommonService import CommonService
  21. class DeviceReportView(View):
  22. def get(self, request, *args, **kwargs):
  23. request.encoding = 'utf-8'
  24. operation = kwargs.get('operation')
  25. return self.validation(request.GET, request, operation)
  26. def post(self, request, *args, **kwargs):
  27. request.encoding = 'utf-8'
  28. operation = kwargs.get('operation')
  29. return self.validation(request.POST, request, operation)
  30. def delete(self, request, *args, **kwargs):
  31. request.encoding = 'utf-8'
  32. operation = kwargs.get('operation')
  33. delete = QueryDict(request.body)
  34. if not delete:
  35. delete = request.GET
  36. return self.validation(delete, request, operation)
  37. def put(self, request, *args, **kwargs):
  38. request.encoding = 'utf-8'
  39. operation = kwargs.get('operation')
  40. put = QueryDict(request.body)
  41. return self.validation(put, request, operation)
  42. def validation(self, request_dict, request, operation):
  43. response = ResponseObject('cn')
  44. tko = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
  45. if tko.code != 0:
  46. return response.json(tko.code)
  47. response.lang = tko.lang
  48. userID = tko.userID
  49. if operation == 'getBatteryCapacityList': # 获取电池电量列表
  50. return self.get_battery_capacity_list(userID, request, request_dict, response)
  51. elif operation == 'getWakeSleepData':
  52. return self.get_wake_sleep_data(userID, request_dict, response)
  53. elif operation == 'deviceReset':
  54. return self.device_reset(userID, request, request_dict, response)
  55. return response.json(414)
  56. @classmethod
  57. def get_wake_sleep_data(cls, userID, request_dict, response):
  58. """
  59. 获取设备睡眠唤醒统计数据
  60. :param userID: 响应对象
  61. :param request_dict: 参数对象
  62. :param response: 响应对象
  63. :return: 包含7天汇总统计和30天明细数据的JSON响应
  64. """
  65. try:
  66. # region 时间参数计算
  67. uid = request_dict.get('device_id', None)
  68. if not uid:
  69. return response.json(444)
  70. channel = int(request_dict.get('channel', 1))
  71. current_time = int(time.time())
  72. SECONDS_PER_DAY = 24 * 60 * 60 # 单日秒数常量
  73. # 计算时间范围(秒级时间戳)
  74. seven_days_ago = current_time - 7 * SECONDS_PER_DAY # 7天前
  75. thirty_days_ago = current_time - 30 * SECONDS_PER_DAY # 30天前
  76. # endregion
  77. # region 7天汇总统计 (使用Coalesce避免空值)
  78. seven_days_stats = DeviceDailyReport.objects.filter(
  79. device_id=uid,
  80. channel=channel,
  81. report_time__range=(seven_days_ago, current_time) # 时间范围查询
  82. ).aggregate(
  83. total_human_detection=Coalesce(Sum('human_detection'), Value(0)),
  84. total_working_hours=Coalesce(Sum('working_hours'), Value(0)),
  85. total_wake_sleep=Coalesce(Sum('wake_sleep'), Value(0))
  86. )
  87. # endregion
  88. # region 30天明细数据(按时间倒序)
  89. thirty_days_details = DeviceDailyReport.objects.filter(
  90. device_id=uid,
  91. channel=channel,
  92. report_time__gte=thirty_days_ago,
  93. report_time__lte=current_time
  94. ).order_by('-report_time').values_list(
  95. 'human_detection',
  96. 'working_hours',
  97. 'wake_sleep',
  98. 'battery_level',
  99. 'report_time',
  100. named=True # 使用命名元组方便访问
  101. )
  102. # endregion
  103. # 组合返回数据结构
  104. result_data = {
  105. **seven_days_stats,
  106. 'detail_report': list(thirty_days_details) # 转换查询集为列表
  107. }
  108. return response.json(0, result_data)
  109. except Exception as e:
  110. error_msg = f"查询异常: {str(e)},userID: {userID}"
  111. error_line = e.__traceback__.tb_lineno
  112. LOGGER.error(f"{error_msg} 行号: {error_line}")
  113. # 返回安全空数据
  114. return response.json(500, {
  115. 'total_human_detection': 0,
  116. 'total_working_hours': 0,
  117. 'total_wake_sleep': 0,
  118. 'detail_report': []})
  119. @classmethod
  120. def get_battery_capacity_list(cls, userID, request, request_dict, response):
  121. try:
  122. # 参数校验
  123. device_id = request_dict.get('device_id')
  124. if not device_id:
  125. return response.json(444, "设备ID不能为空")
  126. channel = int(request_dict.get('channel', 1))
  127. tz_offset = float(request_dict.get('tz', 0))
  128. if not (-12 <= tz_offset <= 14):
  129. raise ValueError("时区偏移超出范围")
  130. tz = pytz.FixedOffset(int(tz_offset * 60))
  131. # 计算日期范围
  132. query_days = int(request_dict.get('days', 7))
  133. client_now = datetime.now(tz)
  134. # 数据日期范围 = [今天 - query_days, 昨天]
  135. end_date = (client_now - timedelta(days=1)).date() # 昨天
  136. start_date = end_date - timedelta(days=query_days - 1) # 起始日期
  137. # 转换查询范围到UTC时间戳
  138. start_utc = int(tz.localize(
  139. datetime.combine(start_date, datetime.min.time())
  140. ).astimezone(pytz.utc).timestamp())
  141. end_utc = int(tz.localize(
  142. datetime.combine(end_date + timedelta(days=1), datetime.min.time())
  143. ).astimezone(pytz.utc).timestamp())
  144. # 查询数据库
  145. records = DeviceDailyReport.objects.filter(
  146. device_id=device_id,
  147. channel=channel,
  148. type=1,
  149. report_time__gte=start_utc,
  150. report_time__lt=end_utc
  151. ).order_by('report_time')
  152. # 构建日期-电量映射(直接使用上报日期)
  153. date_battery_map = {}
  154. for record in records:
  155. # 关键变更:直接使用上报日期作为数据日期
  156. record_date = datetime.fromtimestamp(record.report_time, tz).date()
  157. # 只记录查询范围内的数据
  158. if start_date <= record_date <= end_date:
  159. date_battery_map[record_date.isoformat()] = record.battery_level
  160. # 生成完整日期序列
  161. report_data = []
  162. for day_offset in range(query_days):
  163. current_date = start_date + timedelta(days=day_offset)
  164. report_data.append({
  165. "index": current_date.day - 1, # 日期下标从0开始
  166. "battery": date_battery_map.get(current_date.isoformat(), 0),
  167. "time": current_date.strftime("%Y-%m-%d")
  168. })
  169. return response.json(0, report_data)
  170. except Exception as e:
  171. LOGGER.error(f'查询设备电量上报列表异常: {e}')
  172. return response.json(0, {})
  173. def device_reset(self, userID, request, request_dict, response):
  174. """900兆基站修改通道名"""
  175. ip = CommonService.get_ip_address(request)
  176. uid = request_dict.get('uid', None)
  177. if None in [uid]:
  178. return response.json(444)
  179. LOGGER.info(f'900兆基站类型设备,uid={uid},位置={ip},开始修改通道名')
  180. try:
  181. uid_set_qs = UidSetModel.objects.filter(uid=uid).values('id').first()
  182. if not uid_set_qs:
  183. LOGGER.info(f"uid={uid},ip={ip},未查询到对应记录")
  184. return response.json(0)
  185. channel_list = UidChannelSetModel.objects.filter(uid_id=uid_set_qs['id']).values('channel')
  186. for channel_qs in channel_list:
  187. channel = channel_qs['channel']
  188. channel_name = 'Channel {}'.format(channel)
  189. UidChannelSetModel.objects.filter(uid_id=uid_set_qs['id'], channel=channel).update(channel_name=channel_name)
  190. DeviceDailyReport.objects.filter(device_id=uid).delete()
  191. LOGGER.info(f"uid={uid},ip={ip},通道名称修改成功")
  192. return response.json(0)
  193. except Exception as e:
  194. LOGGER.error(f"{uid}修改通道名时发生错误: {str(e)}")
  195. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))