瀏覽代碼

"以旧换新"红点标记

zhangdongming 5 月之前
父節點
當前提交
d842357998
共有 3 個文件被更改,包括 227 次插入1 次删除
  1. 3 1
      Ansjer/urls.py
  2. 5 0
      Controller/AppSetController.py
  3. 219 0
      Controller/UserDevice/ActivityCenterController.py

+ 3 - 1
Ansjer/urls.py

@@ -33,7 +33,7 @@ from Controller.MessagePush import EquipmentMessagePush
 from Controller.SensorGateway import SensorGatewayController, EquipmentFamilyController
 from Controller.Surveys import CloudStorageController
 from Controller.UserDevice import UserDeviceShareController, UserSubscriptionController, DeviceVersionInfoController, \
-    APNConfigController
+    APNConfigController, ActivityCenterController
 from Controller.CampaignController import AppCampaignController, AdDepartmentController
 from Controller.UserDevice import SerialNumberCheckController
 from Model import views  # 定时任务,不要删除该行代码
@@ -339,6 +339,8 @@ urlpatterns = [
     re_path('messageManagement/(?P<operation>.*)', MessageMangementController.MassageView.as_view()),
     # 新品体验官
     re_path('activityManagement/(?P<operation>.*)', EvaluationActivityController.EvaluationActivityView.as_view()),
+    # 活动中心
+    re_path('activityCenter/(?P<operation>.*)', ActivityCenterController.ActivityCenterView.as_view()),
     # 后台界面接口 -------------------------------------------------------------------------------------------------------
 
     # 定时任务接口

+ 5 - 0
Controller/AppSetController.py

@@ -6,6 +6,7 @@ from django.db import transaction
 from django.views.generic.base import View
 
 from Ansjer.config import SERVER_TYPE, CONFIG_INFO, CONFIG_TEST
+from Controller.UserDevice.ActivityCenterController import ActivityCenterView
 from Model.models import AppSetModel, PromotionRuleModel, PopupsConfig, RedDotsConfig, Device_Info, UidSetModel, \
     UserOperationLog, Order_Model, IPAddr, RegionRestriction, UserSetStatus
 from Object.Enums.ConstantEnum import ConstantEnum
@@ -190,6 +191,10 @@ class AppSetView(View):
                         'module': red_dots['module'],
                         'status': red_dots_status,
                     })
+                dict_json['red_dots'].append({
+                    'module': 'pressureTemp',
+                    'status': 1 if ActivityCenterView.get_activity_badge_status(userID) else 0,
+                })
                 dict_json['red_dots'] = list(dict_json['red_dots'])
                 return response.json(0, dict_json)
         except Exception as e:

+ 219 - 0
Controller/UserDevice/ActivityCenterController.py

@@ -0,0 +1,219 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : ActivityCenterController.py
+@Time    : 2025/3/26 14:23
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import json
+import time
+
+from django.views import View
+
+from Model.models import FreeEvaluationActivity, ActivityTime
+from Object.Enums.RedisKeyConstant import RedisKeyConstant
+from Object.RedisObject import RedisObject
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Ansjer.config import LOGGER
+
+
+class ActivityCenterView(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 validation(self, request_dict, request, operation):
+        response = ResponseObject('en')
+        tko = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
+        LOGGER.info(f'APNConfigView -- tko: {tko.code}, request_dict: {request_dict}')
+        if tko.code != 0:
+            return response.json(tko.code)
+        response.lang = tko.lang
+        userID = tko.userID
+        if operation == 'updateActivityVisited':
+            return self.update_activity_visited(request_dict, userID, response)
+        else:
+            return response.json(414)
+
+    @staticmethod
+    def update_activity_visited(request_data: dict, user_id: str, response) -> bool:
+        """
+        标记用户访问特定活动,并更新访问次数
+
+        Args:
+            request_data: 请求参数字典,需包含activityId
+            user_id: 用户唯一标识字符串
+            response: 响应对象,用于返回标准HTTP状态
+
+        Returns:
+            bool: 操作是否成功,或通过response返回错误状态
+
+        逻辑说明:
+            1. 校验请求中是否包含有效活动ID
+            2. 获取当前有效活动列表
+            3. 仅当活动未过期时更新访问次数
+            4. 访问次数存储在Redis并设置活动过期时间
+        """
+        try:
+            # 参数有效性校验
+            activity_id = request_data.get('activityId')
+            if not activity_id:
+                return response.json(10, "Missing activityId")  # 更规范的HTTP状态
+
+            # 获取有效活动列表
+            redis_client = RedisObject(4)
+            activity_info = ActivityCenterView.query_product_activity()
+            if not activity_info:
+                return response.json(10, "No available activities")
+
+            # 查找目标活动并验证有效期
+            current_time = int(time.time())
+            target_activity = None
+
+            # 遍历查找匹配活动
+            for activity in activity_info['activity_list']:
+                if str(activity.get('id')) == str(activity_id):  # 兼容字符串和数字ID
+                    if int(activity.get('end_time', 0)) > current_time:
+                        target_activity = activity
+                    break  # 找到即终止循环
+
+            if not target_activity:
+                return response.json(10, "Invalid or expired activity")
+
+            # 构造Redis键并更新访问次数
+            base_key = RedisKeyConstant.ACTIVITY_INFO.value
+            redis_key = f"{base_key}:{user_id}:{activity_id}"
+
+            # 原子性增加计数并设置过期时间
+            expire_seconds = target_activity['end_time'] - current_time
+            if expire_seconds <= 0:
+                return response.json(10, "Activity already expired")
+
+            # 使用incr命令保证原子操作
+            redis_client.incr(redis_key, 1, expire_seconds)
+
+            return response.json(0)
+
+        except Exception as e:
+            LOGGER.error(
+                f"活动访问标记异常,用户:{user_id},"
+                f"错误行号:{e.__traceback__.tb_lineno},"
+                f"详情:{repr(e)}"
+            )
+            return response.json(500)
+
+    @staticmethod
+    def get_activity_badge_status(user_id: str) -> bool:
+        """
+        检查用户是否有未读活动红点(Redis无记录时视为未读)
+
+        Args:
+            user_id: 用户唯一标识字符串
+
+        Returns:
+            bool: True-存在未读活动,False-无未读或异常降级
+        """
+        try:
+            activity_info = ActivityCenterView.query_product_activity()
+            if not activity_info:
+                return False  # 无活动不显示红点
+
+            redis_client = RedisObject(4)
+            base_key = RedisKeyConstant.ACTIVITY_INFO.value
+            current_time = int(time.time())
+            for activity in activity_info['activity_list']:
+                activity_id = activity.get('id')
+                if not activity_id:  # 防御空ID
+                    continue
+                if int(activity['end_time']) < current_time:
+                    continue
+                redis_key = f"{base_key}:{user_id}:{activity_id}"
+                raw_value = redis_client.get_data(redis_key)
+
+                # 关键判断逻辑:空值或值为0时触发红点
+                if raw_value is None:  # Redis无记录
+                    return True
+                try:
+                    if int(raw_value) == 0:  # 明确标记为未读
+                        return True
+                except (TypeError, ValueError):
+                    LOGGER.warning(
+                        f"活动状态值非法,用户:{user_id},"
+                        f"活动ID:{activity_id},值:{raw_value}"
+                    )
+                    return True  # 异常值保守返回红点
+
+            return False
+
+        except Exception as e:
+            LOGGER.error(
+                f"红点查询异常,用户:{user_id},"
+                f"错误行号:{e.__traceback__.tb_lineno},"
+                f"详情:{repr(e)}"
+            )
+            return False
+
+    @staticmethod
+    def query_product_activity():
+        """
+        查询当前有效的产品活动信息,优先从Redis读取,不存在时从数据库加载并缓存
+        Returns:
+            dict: 包含活动数量及详细信息的字典,异常时返回None
+        """
+        try:
+            # 初始化Redis客户端,使用4号数据库
+            redis_client = RedisObject(4)
+            redis_key = RedisKeyConstant.ACTIVITY_INFO.value
+
+            # 尝试从Redis获取缓存数据
+            cached_data = redis_client.get_data(redis_key)
+            if cached_data:
+                return json.loads(cached_data)
+
+            # 数据库查询:获取所有可见的活动
+            activity_queryset = FreeEvaluationActivity.objects.filter(is_show=1).values('id')
+            if not activity_queryset.exists():  # 无数据时提前返回
+                return None
+
+            activity_list = []
+            # 遍历每个活动,获取其时间范围
+            for activity in activity_queryset:
+                activity_id = activity['id']
+                time_queryset = ActivityTime.objects.filter(activity_id=activity_id).order_by('start_time')
+
+                # 处理存在的时间记录
+                if time_queryset.exists():
+                    first_time = time_queryset.first()
+                    activity_dict = {
+                        'id': activity_id,
+                        'start_time': first_time.start_time,
+                        'end_time': time_queryset.last().end_time if time_queryset.count() > 1 else first_time.end_time
+                    }
+                    activity_list.append(activity_dict)
+
+            # 构建返回数据结构
+            result = {
+                'count': len(activity_list),
+                'activity_list': activity_list
+            }
+
+            # 写入Redis缓存(24小时有效期)
+            redis_client.set_data(
+                redis_key,
+                json.dumps(result),
+                RedisKeyConstant.EXPIRE_TIME_24_HOURS.value
+            )
+            return result
+
+        except Exception as e:
+            # 异常处理(包含错误行号打印)
+            LOGGER.error(f'活动查询异常,行号:{e.__traceback__.tb_lineno},错误信息:{repr(e)}')
+            return None