Jelajahi Sumber

新增以旧换新标记已读未读

zhangdongming 5 bulan lalu
induk
melakukan
18d3cc1232

+ 91 - 42
AdminController/EvaluationActivityController.py

@@ -153,55 +153,104 @@ class EvaluationActivityView(View):
             bucket = "ansjerfilemanager"
             s3_obj = AmazonS3Util(ACCESS_KEY_ID, SECRET_ACCESS_KEY, REGION_NAME)
             s3_url = 'https://{}.s3.{}.amazonaws.com.cn/'.format(bucket, REGION_NAME)
-            if carousel_image:
-                carousel_image_path = '前端/EvaluationActivity/carousel_image_{}.jpg'.format(issue)
-                s3_obj.upload_file_obj(
-                    bucket,
-                    carousel_image_path,
-                    carousel_image,
-                    {"ContentType": carousel_image.content_type, "ACL": "public-read"},
-                )
-                carousel_image_url = s3_url + carousel_image_path
-                if activity_id:
-                    FreeEvaluationActivity.objects.filter(id=activity_id).update(carousel_image_url=carousel_image_url)
-            if details_image:
-                details_image_path = '前端/EvaluationActivity/details_image_{}.jpg'.format(issue)
-                s3_obj.upload_file_obj(
-                    bucket,
-                    details_image_path,
-                    details_image,
-                    {"ContentType": details_image.content_type, "ACL": "public-read"},
-                )
-                details_image_url = s3_url + details_image_path
-                if activity_id:
-                    FreeEvaluationActivity.objects.filter(id=activity_id).update(details_image_url=details_image_url)
+
             with transaction.atomic():
                 if activity_id:  # 编辑活动
-                    FreeEvaluationActivity.objects.filter(id=activity_id).update(issue=issue, product_name=product_name,
-                                                                                 product_number=product_number,
-                                                                                 original_price=original_price,
-                                                                                 is_show=is_show, update_time=now_time,
-                                                                                 activity_name=activity_name)
+                    activity = FreeEvaluationActivity.objects.get(id=activity_id)
+                    activity.activity_name = activity_name
+                    activity.issue = issue
+                    activity.product_number = product_number
+                    activity.product_name = product_name
+                    activity.original_price = original_price
+                    activity.is_show = is_show
+                    activity.update_time = now_time
+
+                    # 上传轮播图
+                    if carousel_image:
+                        carousel_image_path = '前端/EvaluationActivity/carousel_image_{}.jpg'.format(activity_id)
+                        s3_obj.upload_file_obj(
+                            bucket,
+                            carousel_image_path,
+                            carousel_image,
+                            {"ContentType": carousel_image.content_type, "ACL": "public-read"},
+                        )
+                        activity.carousel_image_url = s3_url + carousel_image_path
+
+                    # 上传详情图
+                    if details_image:
+                        details_image_path = '前端/EvaluationActivity/details_image_{}.jpg'.format(activity_id)
+                        s3_obj.upload_file_obj(
+                            bucket,
+                            details_image_path,
+                            details_image,
+                            {"ContentType": details_image.content_type, "ACL": "public-read"},
+                        )
+                        activity.details_image_url = s3_url + details_image_path
+
+                    activity.save()
+
+                    # 处理活动时间节点
                     ActivityTime.objects.filter(activity_id=activity_id).delete()
                     for index, item in enumerate(activity_process):
-                        ActivityTime.objects.create(activity_id=activity_id, node_content=item['node_content'],
-                                                    start_time=item['start_time'], end_time=item['end_time'],
-                                                    sort=index)
-                else:  # 增加活动
+                        ActivityTime.objects.create(
+                            activity_id=activity_id,
+                            node_content=item['node_content'],
+                            start_time=item['start_time'],
+                            end_time=item['end_time'],
+                            sort=index
+                        )
+                else:  # 添加活动
                     if not all([carousel_image, details_image]):
                         return response.json(404)
-                    activity_qs = FreeEvaluationActivity.objects.create(activity_name=activity_name, issue=issue,
-                                                                        product_number=product_number,
-                                                                        carousel_image_url=carousel_image_url,
-                                                                        is_show=is_show, product_name=product_name,
-                                                                        original_price=original_price,
-                                                                        details_image_url=details_image_url,
-                                                                        created_time=now_time, update_time=now_time)
+
+                    # 创建活动对象(先不保存图片URL)
+                    activity = FreeEvaluationActivity.objects.create(
+                        activity_name=activity_name,
+                        issue=issue,
+                        product_number=product_number,
+                        product_name=product_name,
+                        original_price=original_price,
+                        is_show=is_show,
+                        created_time=now_time,
+                        update_time=now_time,
+                        carousel_image_url='',  # 初始化为空
+                        details_image_url=''
+                    )
+                    activity_id = activity.id
+
+                    # 上传轮播图并更新URL
+                    carousel_image_path = '前端/EvaluationActivity/carousel_image_{}.jpg'.format(activity_id)
+                    s3_obj.upload_file_obj(
+                        bucket,
+                        carousel_image_path,
+                        carousel_image,
+                        {"ContentType": carousel_image.content_type, "ACL": "public-read"},
+                    )
+                    activity.carousel_image_url = s3_url + carousel_image_path
+
+                    # 上传详情图并更新URL
+                    details_image_path = '前端/EvaluationActivity/details_image_{}.jpg'.format(activity_id)
+                    s3_obj.upload_file_obj(
+                        bucket,
+                        details_image_path,
+                        details_image,
+                        {"ContentType": details_image.content_type, "ACL": "public-read"},
+                    )
+                    activity.details_image_url = s3_url + details_image_path
+
+                    activity.save()  # 保存图片URL
+
+                    # 创建活动时间节点
                     for index, item in enumerate(activity_process):
-                        ActivityTime.objects.create(activity_id=activity_qs.id, node_content=item['node_content'],
-                                                    start_time=item['start_time'], end_time=item['end_time'],
-                                                    sort=index)
-            return response.json(0)
+                        ActivityTime.objects.create(
+                            activity_id=activity_id,
+                            node_content=item['node_content'],
+                            start_time=item['start_time'],
+                            end_time=item['end_time'],
+                            sort=index
+                        )
+
+                return response.json(0)
         except Exception as e:
             return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
 

+ 4 - 1
Ansjer/urls.py

@@ -29,7 +29,8 @@ from Controller.Cron import CronTaskController
 from Controller.CustomCustomer import CustomCustomerController
 from Controller.MessagePush import EquipmentMessagePush
 from Controller.Surveys import CloudStorageController
-from Controller.UserDevice import UserSubscriptionController, DeviceVersionInfoController, APNConfigController
+from Controller.UserDevice import UserSubscriptionController, DeviceVersionInfoController, APNConfigController, \
+    ActivityCenterController
 from Controller.CampaignController import AdDepartmentController
 from Controller.SensorGateway import SensorGatewayController, EquipmentFamilyController
 from django.urls import include
@@ -427,6 +428,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

+ 2 - 0
Object/Enums/RedisKeyConstant.py

@@ -29,6 +29,8 @@ class RedisKeyConstant(Enum):
     BASIC_USER = 'BASIC:USER:'
     # APP扫码工具域名
     APP_DOMAIN_NAME = "DOMAIN:NAME"
+    # 活动信息
+    ACTIVITY_INFO = 'ACTIVITY:INFO'
 
     # Redis 过期时间常量 (秒)
     EXPIRE_TIME_60_SECONDS = 60    # 60秒