ActivityCenterController.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : ActivityCenterController.py
  4. @Time : 2025/3/26 14:23
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import json
  10. import time
  11. from django.views import View
  12. from Model.models import FreeEvaluationActivity, ActivityTime
  13. from Object.Enums.RedisKeyConstant import RedisKeyConstant
  14. from Object.RedisObject import RedisObject
  15. from Object.ResponseObject import ResponseObject
  16. from Object.TokenObject import TokenObject
  17. from Ansjer.config import LOGGER
  18. class ActivityCenterView(View):
  19. def get(self, request, *args, **kwargs):
  20. request.encoding = 'utf-8'
  21. operation = kwargs.get('operation')
  22. return self.validation(request.GET, request, operation)
  23. def post(self, request, *args, **kwargs):
  24. request.encoding = 'utf-8'
  25. operation = kwargs.get('operation')
  26. return self.validation(request.POST, request, operation)
  27. def validation(self, request_dict, request, operation):
  28. response = ResponseObject('en')
  29. tko = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
  30. LOGGER.info(f'APNConfigView -- tko: {tko.code}, request_dict: {request_dict}')
  31. if tko.code != 0:
  32. return response.json(tko.code)
  33. response.lang = tko.lang
  34. userID = tko.userID
  35. if operation == 'updateActivityVisited':
  36. return self.update_activity_visited(request_dict, userID, response)
  37. else:
  38. return response.json(414)
  39. @staticmethod
  40. def update_activity_visited(request_data: dict, user_id: str, response) -> bool:
  41. """
  42. 标记用户访问特定活动,并更新访问次数
  43. Args:
  44. request_data: 请求参数字典,需包含activityId
  45. user_id: 用户唯一标识字符串
  46. response: 响应对象,用于返回标准HTTP状态
  47. Returns:
  48. bool: 操作是否成功,或通过response返回错误状态
  49. 逻辑说明:
  50. 1. 校验请求中是否包含有效活动ID
  51. 2. 获取当前有效活动列表
  52. 3. 仅当活动未过期时更新访问次数
  53. 4. 访问次数存储在Redis并设置活动过期时间
  54. """
  55. try:
  56. # 参数有效性校验
  57. activity_id = request_data.get('activityId')
  58. if not activity_id:
  59. return response.json(10, "Missing activityId") # 更规范的HTTP状态
  60. # 获取有效活动列表
  61. redis_client = RedisObject(4)
  62. activity_info = ActivityCenterView.query_product_activity()
  63. if not activity_info:
  64. return response.json(10, "No available activities")
  65. # 查找目标活动并验证有效期
  66. current_time = int(time.time())
  67. target_activity = None
  68. # 遍历查找匹配活动
  69. for activity in activity_info['activity_list']:
  70. if str(activity.get('id')) == str(activity_id): # 兼容字符串和数字ID
  71. if int(activity.get('end_time', 0)) > current_time:
  72. target_activity = activity
  73. break # 找到即终止循环
  74. if not target_activity:
  75. return response.json(10, "Invalid or expired activity")
  76. # 构造Redis键并更新访问次数
  77. base_key = RedisKeyConstant.ACTIVITY_INFO.value
  78. redis_key = f"{base_key}:{user_id}:{activity_id}"
  79. # 原子性增加计数并设置过期时间
  80. expire_seconds = target_activity['end_time'] - current_time
  81. if expire_seconds <= 0:
  82. return response.json(10, "Activity already expired")
  83. # 使用incr命令保证原子操作
  84. redis_client.incr(redis_key, 1, expire_seconds)
  85. return response.json(0)
  86. except Exception as e:
  87. LOGGER.error(
  88. f"活动访问标记异常,用户:{user_id},"
  89. f"错误行号:{e.__traceback__.tb_lineno},"
  90. f"详情:{repr(e)}"
  91. )
  92. return response.json(500)
  93. @staticmethod
  94. def get_activity_badge_status(user_id: str) -> bool:
  95. """
  96. 检查用户是否有未读活动红点(Redis无记录时视为未读)
  97. Args:
  98. user_id: 用户唯一标识字符串
  99. Returns:
  100. bool: True-存在未读活动,False-无未读或异常降级
  101. """
  102. try:
  103. activity_info = ActivityCenterView.query_product_activity()
  104. if not activity_info:
  105. return False # 无活动不显示红点
  106. redis_client = RedisObject(4)
  107. base_key = RedisKeyConstant.ACTIVITY_INFO.value
  108. current_time = int(time.time())
  109. for activity in activity_info['activity_list']:
  110. activity_id = activity.get('id')
  111. if not activity_id: # 防御空ID
  112. continue
  113. if int(activity['end_time']) < current_time:
  114. continue
  115. redis_key = f"{base_key}:{user_id}:{activity_id}"
  116. raw_value = redis_client.get_data(redis_key)
  117. # 关键判断逻辑:空值或值为0时触发红点
  118. if raw_value is None: # Redis无记录
  119. return True
  120. try:
  121. if int(raw_value) == 0: # 明确标记为未读
  122. return True
  123. except (TypeError, ValueError):
  124. LOGGER.warning(
  125. f"活动状态值非法,用户:{user_id},"
  126. f"活动ID:{activity_id},值:{raw_value}"
  127. )
  128. return True # 异常值保守返回红点
  129. return False
  130. except Exception as e:
  131. LOGGER.error(
  132. f"红点查询异常,用户:{user_id},"
  133. f"错误行号:{e.__traceback__.tb_lineno},"
  134. f"详情:{repr(e)}"
  135. )
  136. return False
  137. @staticmethod
  138. def query_product_activity():
  139. """
  140. 查询当前有效的产品活动信息,优先从Redis读取,不存在时从数据库加载并缓存
  141. Returns:
  142. dict: 包含活动数量及详细信息的字典,异常时返回None
  143. """
  144. try:
  145. # 初始化Redis客户端,使用4号数据库
  146. redis_client = RedisObject(4)
  147. redis_key = RedisKeyConstant.ACTIVITY_INFO.value
  148. # 尝试从Redis获取缓存数据
  149. cached_data = redis_client.get_data(redis_key)
  150. if cached_data:
  151. return json.loads(cached_data)
  152. # 数据库查询:获取所有可见的活动
  153. activity_queryset = FreeEvaluationActivity.objects.filter(is_show=1).values('id')
  154. if not activity_queryset.exists(): # 无数据时提前返回
  155. return None
  156. activity_list = []
  157. # 遍历每个活动,获取其时间范围
  158. for activity in activity_queryset:
  159. activity_id = activity['id']
  160. time_queryset = ActivityTime.objects.filter(activity_id=activity_id).order_by('start_time')
  161. # 处理存在的时间记录
  162. if time_queryset.exists():
  163. first_time = time_queryset.first()
  164. activity_dict = {
  165. 'id': activity_id,
  166. 'start_time': first_time.start_time,
  167. 'end_time': time_queryset.last().end_time if time_queryset.count() > 1 else first_time.end_time
  168. }
  169. activity_list.append(activity_dict)
  170. # 构建返回数据结构
  171. result = {
  172. 'count': len(activity_list),
  173. 'activity_list': activity_list
  174. }
  175. # 写入Redis缓存(24小时有效期)
  176. redis_client.set_data(
  177. redis_key,
  178. json.dumps(result),
  179. RedisKeyConstant.EXPIRE_TIME_24_HOURS.value
  180. )
  181. return result
  182. except Exception as e:
  183. # 异常处理(包含错误行号打印)
  184. LOGGER.error(f'活动查询异常,行号:{e.__traceback__.tb_lineno},错误信息:{repr(e)}')
  185. return None