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