# -*- 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