# -*- encoding: utf-8 -*- """ @File : DeviceReportController.py @Time : 2025/4/3 16:20 @Author : stephen @Email : zhangdongming@asj6.wecom.work @Software: PyCharm """ import time from datetime import timedelta, datetime import pytz from django.db.models import Sum, Value from django.db.models.functions import Coalesce from django.http import QueryDict from django.views import View from Ansjer.config import LOGGER from Model.models import DeviceDailyReport from Object.ResponseObject import ResponseObject from Object.TokenObject import TokenObject class DeviceReportView(View): def get(self, request, *args, **kwargs): request.encoding = 'utf-8' operation = kwargs.get('operation') return self.validation(request.GET, request, operation) def post(self, request, *args, **kwargs): request.encoding = 'utf-8' operation = kwargs.get('operation') return self.validation(request.POST, request, operation) def delete(self, request, *args, **kwargs): request.encoding = 'utf-8' operation = kwargs.get('operation') delete = QueryDict(request.body) if not delete: delete = request.GET return self.validation(delete, request, operation) def put(self, request, *args, **kwargs): request.encoding = 'utf-8' operation = kwargs.get('operation') put = QueryDict(request.body) return self.validation(put, request, operation) def validation(self, request_dict, request, operation): response = ResponseObject('cn') tko = TokenObject(request.META.get('HTTP_AUTHORIZATION')) if tko.code != 0: return response.json(tko.code) response.lang = tko.lang userID = tko.userID if operation == 'getBatteryCapacityList': # 获取电池电量列表 return self.get_battery_capacity_list(userID, request, request_dict, response) elif operation == 'getWakeSleepData': return self @classmethod def get_wake_sleep_data(cls, response): """ 获取设备睡眠唤醒统计数据 :param response: 响应对象 :return: 包含7天汇总统计和30天明细数据的JSON响应 """ try: # region 时间参数计算 current_time = int(time.time()) SECONDS_PER_DAY = 24 * 60 * 60 # 单日秒数常量 # 计算时间范围(秒级时间戳) seven_days_ago = current_time - 7 * SECONDS_PER_DAY # 7天前 thirty_days_ago = current_time - 30 * SECONDS_PER_DAY # 30天前 # endregion # region 7天汇总统计 (使用Coalesce避免空值) seven_days_stats = DeviceDailyReport.objects.filter( report_time__range=(seven_days_ago, current_time) # 时间范围查询 ).aggregate( total_human_detection=Coalesce(Sum('human_detection'), Value(0)), total_working_hours=Coalesce(Sum('working_hours'), Value(0)), total_wake_sleep=Coalesce(Sum('wake_sleep'), Value(0)) ) # endregion # region 30天明细数据(按时间倒序) thirty_days_details = DeviceDailyReport.objects.filter( report_time__gte=thirty_days_ago, report_time__lte=current_time ).order_by('-report_time').values_list( 'human_detection', 'working_hours', 'wake_sleep', 'battery_level', named=True # 使用命名元组方便访问 ) # endregion # 组合返回数据结构 result_data = { **seven_days_stats, 'detail_report': list(thirty_days_details) # 转换查询集为列表 } return response.json(0, result_data) except Exception as e: error_msg = f"查询异常: {str(e)}" error_line = e.__traceback__.tb_lineno LOGGER.error(f"{error_msg} 行号: {error_line}") # 返回安全空数据 return response.json(500, { 'total_human_detection': 0, 'total_working_hours': 0, 'total_wake_sleep': 0, 'detail_report': []}) @classmethod def get_battery_capacity_list(cls, userID, request, request_dict, response): try: # 参数校验 device_id = request_dict.get('device_id') if not device_id: return response.json(444, "设备ID不能为空") tz_offset = float(request_dict.get('tz', 0)) # 允许小数时区如5.5 if not (-12 <= tz_offset <= 14): raise ValueError tz = pytz.FixedOffset(int(tz_offset * 60)) # 转换为分钟偏移 # 计算日期范围(关键修改:end_time取昨日23:59:59) query_days = int(request_dict.get('days', 7)) client_now = datetime.now(tz) end_time = client_now.replace( hour=23, minute=59, second=59 ) - timedelta(days=1) # 截止到客户端昨日 start_time = end_time - timedelta(days=query_days - 1) start_time = start_time.replace(hour=0, minute=0, second=0) # 转换时间范围到UTC时间戳 start_utc_timestamp = int(start_time.astimezone(pytz.utc).timestamp()) end_utc_timestamp = int(end_time.astimezone(pytz.utc).timestamp()) # 查询数据库 records = DeviceDailyReport.objects.filter( device_id=device_id, type=1, report_time__gte=start_utc_timestamp, report_time__lte=end_utc_timestamp ).order_by('report_time') # 构建日期-电量映射(按客户端时区) date_battery_map = {} for record in records: local_date = datetime.fromtimestamp(record.report_time, tz).date() date_str = local_date.strftime("%Y-%m-%d") date_battery_map[date_str] = record.battery_level # 生成完整日期序列 report_data = [] for day_offset in range(query_days): current_date = (end_time.date() - timedelta(days=query_days - 1 - day_offset)) report_data.append({ "index": current_date.day - 1, # 日期下标从0开始 "battery": date_battery_map.get(current_date.strftime("%Y-%m-%d"), 0), "time": current_date.strftime("%Y-%m-%d") }) return response.json(0, report_data) except Exception as e: LOGGER.error('查询设备电量上报列表异常error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e))) return response.json(0, {})