Procházet zdrojové kódy

优化查询电量/休眠唤醒接口

zhangdongming před 4 měsíci
rodič
revize
5193483f08

+ 3 - 0
Ansjer/server_urls/algorithm_shop_url.py

@@ -12,13 +12,16 @@ from AdminController import AlgorithmShopManageController
 from Controller.AlgorithmShop import AlgorithmShopController
 from Controller.CloudPhoto import CloudPhotoController
 from Controller.Cron import CronCloudPhotoController
+from Controller.UserDevice import DeviceReportController
 
 urlpatterns = [
     re_path(r'^api/(?P<operation>.*)$', AlgorithmShopController.AlgorithmShopView.as_view()),
     re_path(r'^cron/(?P<operation>.*)$', CronCloudPhotoController.CronCloudPhotoView.as_view()),
     re_path(r'^photo/(?P<operation>.*)$', CloudPhotoController.CronCloudPhotoView.as_view()),
     re_path(r'^open/(?P<operation>.*)$', AlgorithmShopController.AlgorithmShopView.as_view()),
+    re_path(r'^report/(?P<operation>.*)$', DeviceReportController.DeviceReportView.as_view()),
     re_path(r'^(?P<apiVersion>[a-zA-Z0-9]+)/manage/(?P<operation>.*)$',
             AlgorithmShopManageController.AlgorithmShopManageView.as_view()),
 
+
 ]

+ 176 - 0
Controller/UserDevice/DeviceReportController.py

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

+ 1 - 0
Model/models.py

@@ -5714,6 +5714,7 @@ class DeviceDailyReport(models.Model):
     report_date = models.DateTimeField(blank=True, null=True, verbose_name=u'统计日期时间')
     report_time = models.IntegerField(default=0, verbose_name='设备上报时间戳')
     human_detection = models.IntegerField(default=0, verbose_name='人形检测次数')
+    working_hours = models.IntegerField(default=0, verbose_name='工作时长/秒')
     wake_sleep = models.IntegerField(default=0, verbose_name='唤醒休眠次数')
     channel = models.IntegerField(default=1, blank=True, verbose_name=u'设备通道')
     created_time = models.IntegerField(default=0, verbose_name='创建时间')