| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 | # -*- encoding: utf-8 -*-"""@File    : ActivityCenterController.py@Time    : 2025/3/26 14:23@Author  : stephen@Email   : zhangdongming@asj6.wecom.work@Software: PyCharm"""import jsonimport timefrom django.views import Viewfrom Model.models import FreeEvaluationActivity, ActivityTimefrom Object.Enums.RedisKeyConstant import RedisKeyConstantfrom Object.RedisObject import RedisObjectfrom Object.ResponseObject import ResponseObjectfrom Object.TokenObject import TokenObjectfrom Ansjer.config import LOGGERclass 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
 |