views.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. from django.db.models import F
  2. from django.shortcuts import render
  3. from rest_framework.exceptions import ValidationError
  4. from rest_framework.views import APIView
  5. from rest_framework.response import Response
  6. from rest_framework import serializers, status
  7. from django_redis import get_redis_connection
  8. import random
  9. # Create your views here.
  10. import uuid
  11. import re
  12. def phone_validator(value):
  13. if not re.match(r'^(1[3|4|5|6|7|8|9])\d{9}$', value):
  14. raise ValidationError('手机格式错误')
  15. class LoginSerializer(serializers.Serializer):
  16. phone = serializers.CharField(label='手机号', validators=[phone_validator, ])
  17. code = serializers.CharField(label='短信验证码')
  18. def validate_code(self, value):
  19. if len(value) !=4:
  20. raise ValidationError('短信格式错误')
  21. if not value.isdecimal():
  22. raise ValidationError("短信格式错误")
  23. phone=self.initial_data.get('phone')
  24. conn=get_redis_connection()
  25. code=conn.get(phone)
  26. if not code:
  27. raise ValidationError("验证码过期")
  28. if value!=code.decode('utf-8'):
  29. raise ValidationError("验证码错误")
  30. return value
  31. from rest_framework.views import APIView
  32. from rest_framework.response import Response
  33. from .models import UserInfo, UserBehavior
  34. import uuid
  35. from datetime import datetime, timedelta
  36. from rest_framework.views import APIView
  37. from rest_framework.response import Response
  38. from django.utils import timezone
  39. from datetime import timedelta
  40. import uuid
  41. class LoginView(APIView):
  42. def post(self, request, *args, **kwargs):
  43. # 验证登录数据
  44. ser = LoginSerializer(data=request.data)
  45. if not ser.is_valid():
  46. return Response({'status': False, 'message': '验证码错误'}, status=400)
  47. phone = ser.validated_data.get('phone')
  48. # 获取或创建用户
  49. user_object, created = UserInfo.objects.get_or_create(phone=phone)
  50. user_object.token = str(uuid.uuid4())
  51. user_object.save()
  52. # 如果是新用户,初始化三个核心功能的行为记录
  53. if created:
  54. self.initialize_user_features(user_object)
  55. return Response({
  56. 'status': True,
  57. 'data': {
  58. 'token': user_object.token,
  59. 'phone': phone,
  60. 'id': user_object.id,
  61. 'is_new_user': created
  62. }
  63. })
  64. def initialize_user_features(self, user):
  65. """为新用户初始化功能映射"""
  66. CORE_FEATURES = [
  67. {
  68. 'feature_name': 'ai-plan',
  69. 'page_path': '/pages/ai-plan/ai-plan',
  70. 'priority': 1.0
  71. },
  72. {
  73. 'feature_name': 'quiz-main',
  74. 'page_path': '/pages/quiz/quiz',
  75. 'priority': 1.0
  76. },
  77. {
  78. 'feature_name': 'search-all',
  79. 'page_path': '/pages/gongjiao/gongjiao',
  80. 'priority': 1.0
  81. }
  82. ]
  83. # 批量创建更高效
  84. FeatureMapping.objects.bulk_create([
  85. FeatureMapping(
  86. user_info=user,
  87. feature_name=feature['feature_name'],
  88. page_path=feature['page_path'],
  89. priority=feature['priority']
  90. ) for feature in CORE_FEATURES
  91. ])
  92. class MessageSerializer(serializers.Serializer):
  93. phone = serializers.CharField(label='手机号', validators=[phone_validator, ])
  94. class MessageView(APIView):
  95. def get(self, request, *args, **kwargs):
  96. ser=MessageSerializer(data=request.query_params)
  97. if not ser.is_valid():
  98. return Response({'status':False,'message':'手机号错误'})
  99. phone = ser.validated_data.get('phone')
  100. print(phone)
  101. random_code = random.randint(1000, 9999)
  102. print(random_code)
  103. import redis
  104. conn=get_redis_connection()
  105. conn.set(phone,random_code,ex=60)
  106. print(conn.keys())
  107. return Response({'status': True,'message':'发送成功'})
  108. from rest_framework.views import APIView
  109. from rest_framework.response import Response
  110. from rest_framework import status
  111. from django.utils import timezone
  112. from django.db import models, transaction
  113. from .models import UserBehavior, FeatureMapping, UserInfo
  114. from .serializers import UserBehaviorSerializer, FeatureMappingSerializer
  115. import math
  116. from datetime import timedelta
  117. class RecommendLayoutView(APIView):
  118. """
  119. 推荐个性化布局API
  120. 请求方式:GET
  121. 请求头:Authorization: Token <user_token>
  122. 返回:布局类型和推荐功能排序
  123. """
  124. def update_feature_priorities(self, feature_weights,user_info):
  125. """更新功能优先级到数据库"""
  126. with transaction.atomic():
  127. for feature_name, weight in feature_weights.items():
  128. # 将权重转换为1-10的优先级(保留2位小数)
  129. priority = round(weight * 10, 2)
  130. FeatureMapping.objects.filter(
  131. feature_name=feature_name,
  132. user_info_id = user_info,
  133. ).update(priority=priority)
  134. def get_user_by_token(self, token):
  135. try:
  136. return UserInfo.objects.get(token=token)
  137. except UserInfo.DoesNotExist:
  138. return None
  139. def calculate_feature_weights(self, behaviors, user_info):
  140. """计算功能权重(现在考虑用户特定的优先级)"""
  141. feature_weights = {}
  142. total_usage = sum(b.usage_count for b in behaviors) or 1
  143. for behavior in behaviors:
  144. # 1. 使用频率权重 (40%)
  145. freq_weight = behavior.usage_count / total_usage
  146. # 2. 最近使用权重 (30%)
  147. days_ago = (timezone.now() - behavior.last_used).days
  148. recency_weight = max(0, (30 - days_ago) / 30)
  149. # 3. 获取业务优先级 (30%) —— 现在查询用户特定的优先级
  150. try:
  151. mapping = FeatureMapping.objects.get(
  152. feature_name=behavior.feature_name,
  153. user_info=user_info # 关键修改:关联用户
  154. )
  155. priority = mapping.priority / 10.0
  156. except FeatureMapping.DoesNotExist:
  157. priority = 0.3
  158. # 综合权重(保持不变)
  159. feature_weights[behavior.feature_name] = (
  160. 0.4 * freq_weight +
  161. 0.3 * recency_weight +
  162. 0.3 * priority
  163. )
  164. return feature_weights
  165. def determine_layout_type(self, behaviors, feature_weights):
  166. """确定布局类型"""
  167. if not behaviors:
  168. return 'default'
  169. sorted_features = sorted(feature_weights.items(), key=lambda x: x[1], reverse=True)
  170. top_feature = sorted_features[0][0]
  171. # 根据最高权重的功能决定布局
  172. if top_feature in ['map', 'scenic_spot']:
  173. return 'map_focused'
  174. elif top_feature in ['ai-plan', 'gongjiao']:
  175. return 'feature_focused'
  176. elif top_feature in ['vr', 'panorama']:
  177. return 'vr_focused'
  178. else:
  179. return 'default'
  180. def get(self, request):
  181. # 通过Token获取用户
  182. token = request.META.get('HTTP_AUTHORIZATION', '').split(' ')[-1]
  183. user_info = self.get_user_by_token(token)
  184. if not user_info:
  185. default_response = {
  186. 'layout_type': 'default',
  187. 'feature_weights': {},
  188. 'features': FeatureMappingSerializer(FeatureMapping.objects.none(), many=True).data,
  189. 'recommended_order': [],
  190. 'is_default': True
  191. }
  192. return Response(default_response)
  193. # 获取用户行为数据
  194. behaviors = UserBehavior.objects.filter(user_info=user_info)
  195. # 计算功能权重
  196. feature_weights = self.calculate_feature_weights(behaviors, user_info)
  197. self.update_feature_priorities(feature_weights, user_info)
  198. # 确定布局类型
  199. layout_type = self.determine_layout_type(behaviors, feature_weights)
  200. # 获取所有功能映射
  201. features = FeatureMapping.objects.filter(user_info_id=user_info)
  202. # 构建响应数据
  203. response_data = {
  204. 'layout_type': layout_type,
  205. 'feature_weights': feature_weights,
  206. 'features': FeatureMappingSerializer(features, many=True).data,
  207. 'recommended_order': sorted(feature_weights.keys(),
  208. key=lambda x: -feature_weights[x]),
  209. 'is_default': not behaviors.exists()
  210. }
  211. return Response(response_data)
  212. class RecordUsageView(APIView):
  213. """
  214. 记录用户行为API
  215. 请求方式:POST
  216. 请求参数:feature_name (功能名称)
  217. 请求头:Authorization: Token <user_token>
  218. """
  219. def post(self, request):
  220. # 增强Token提取逻辑
  221. print("接收到的请求数据:", request.data) # 调试日志
  222. print("请求头:", request.headers)
  223. auth_header = request.META.get('HTTP_AUTHORIZATION', '')
  224. if not auth_header.startswith('Token '):
  225. return Response(
  226. {'error': '授权头格式错误,应为 Token <your_token>'},
  227. status=status.HTTP_401_UNAUTHORIZED
  228. )
  229. token = auth_header.split(' ')[-1]
  230. if not token:
  231. return Response(
  232. {'error': 'Token不能为空'},
  233. status=status.HTTP_401_UNAUTHORIZED
  234. )
  235. try:
  236. user_info = UserInfo.objects.get(token=token)
  237. except UserInfo.DoesNotExist:
  238. return Response(
  239. {
  240. 'error': '用户不存在或未授权',
  241. 'debug': f'提供的Token: {token}',
  242. 'solution': '请检查用户是否完成登录流程'
  243. },
  244. status=status.HTTP_401_UNAUTHORIZED
  245. )
  246. # 获取功能名称
  247. feature_name = request.data.get('feature_name')
  248. if not feature_name:
  249. return Response({'error': '缺少feature_name参数'},
  250. status=status.HTTP_400_BAD_REQUEST)
  251. # 验证功能是否存在
  252. if not FeatureMapping.objects.filter(feature_name=feature_name).exists():
  253. return Response({'error': '无效的功能名称'},
  254. status=status.HTTP_400_BAD_REQUEST)
  255. # 更新或创建行为记录
  256. behavior, created = UserBehavior.objects.get_or_create(
  257. user_info=user_info,
  258. feature_name=feature_name,
  259. defaults={
  260. 'usage_count': 0, # 新记录初始化为0
  261. 'last_used': timezone.now()
  262. }
  263. )
  264. # 然后更新(此时无论新旧记录都能用F())
  265. UserBehavior.objects.filter(
  266. id=behavior.id
  267. ).update(
  268. usage_count=F('usage_count') + 1
  269. )
  270. # 重新获取对象(此时usage_count是计算后的值)
  271. behavior.refresh_from_db() # 关键步骤
  272. return Response({
  273. 'status': 'success',
  274. 'feature': feature_name,
  275. 'usage_count': behavior.usage_count,
  276. 'last_used': behavior.last_used
  277. }, status=status.HTTP_200_OK)