804 lines
40 KiB
Python
804 lines
40 KiB
Python
from django.shortcuts import render
|
||
from django.db.models import Q
|
||
from rest_framework import viewsets, status
|
||
from rest_framework.decorators import action
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import AllowAny
|
||
from django.utils import timezone
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from apps.user.authentication import CustomTokenAuthentication
|
||
|
||
from .models import SearchSession, Creator
|
||
from .serializers import (
|
||
SearchSessionSerializer,
|
||
SearchSessionDetailSerializer,
|
||
CreatorSerializer,
|
||
CreatorDetailSerializer
|
||
)
|
||
from .pagination import StandardResultsSetPagination
|
||
|
||
|
||
class ApiResponse:
|
||
"""API统一响应格式"""
|
||
@staticmethod
|
||
def success(data=None, message="操作成功"):
|
||
return Response({
|
||
"code": 200,
|
||
"message": message,
|
||
"data": data
|
||
})
|
||
|
||
@staticmethod
|
||
def error(message="操作失败", code=400, data=None):
|
||
return Response({
|
||
"code": code,
|
||
"message": message,
|
||
"data": data
|
||
}, status=status.HTTP_200_OK) # 始终返回200状态码,错误信息在内容中提供
|
||
|
||
|
||
class SearchSessionViewSet(viewsets.ModelViewSet):
|
||
"""搜索会话视图集"""
|
||
queryset = SearchSession.objects.all()
|
||
serializer_class = SearchSessionSerializer
|
||
authentication_classes = [CustomTokenAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
pagination_class = StandardResultsSetPagination
|
||
|
||
def get_serializer_class(self):
|
||
if self.action == 'retrieve':
|
||
return SearchSessionDetailSerializer
|
||
return SearchSessionSerializer
|
||
|
||
def list(self, request, *args, **kwargs):
|
||
queryset = self.filter_queryset(self.get_queryset())
|
||
page = self.paginate_queryset(queryset)
|
||
if page is not None:
|
||
serializer = self.get_serializer(page, many=True)
|
||
return self.get_paginated_response(serializer.data)
|
||
serializer = self.get_serializer(queryset, many=True)
|
||
return ApiResponse.success(serializer.data, "获取搜索会话列表成功")
|
||
|
||
def retrieve(self, request, *args, **kwargs):
|
||
instance = self.get_object()
|
||
serializer = self.get_serializer(instance)
|
||
return ApiResponse.success(serializer.data, "获取搜索会话详情成功")
|
||
|
||
def create(self, request, *args, **kwargs):
|
||
serializer = self.get_serializer(data=request.data)
|
||
serializer.is_valid(raise_exception=True)
|
||
self.perform_create(serializer)
|
||
headers = self.get_success_headers(serializer.data)
|
||
return ApiResponse.success(serializer.data, "创建搜索会话成功")
|
||
|
||
def update(self, request, *args, **kwargs):
|
||
partial = kwargs.pop('partial', False)
|
||
instance = self.get_object()
|
||
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||
serializer.is_valid(raise_exception=True)
|
||
self.perform_update(serializer)
|
||
return ApiResponse.success(serializer.data, "更新搜索会话成功")
|
||
|
||
def destroy(self, request, *args, **kwargs):
|
||
instance = self.get_object()
|
||
self.perform_destroy(instance)
|
||
return ApiResponse.success(None, "删除搜索会话成功")
|
||
|
||
@action(detail=True, methods=['get'])
|
||
def results(self, request, pk=None):
|
||
"""获取指定会话的搜索结果"""
|
||
session = self.get_object()
|
||
creators = session.creators.all()
|
||
page = self.paginate_queryset(creators)
|
||
if page is not None:
|
||
serializer = CreatorSerializer(page, many=True)
|
||
return self.get_paginated_response(serializer.data)
|
||
serializer = CreatorSerializer(creators, many=True)
|
||
return ApiResponse.success(serializer.data, "获取会话创作者列表成功")
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def summary(self, request):
|
||
"""获取所有会话的摘要数据,用于展示在表格中"""
|
||
queryset = self.filter_queryset(self.get_queryset())
|
||
|
||
# 格式化响应数据
|
||
result = []
|
||
for session in queryset:
|
||
result.append({
|
||
'id': session.id,
|
||
'session_number': session.session_number,
|
||
'date': session.date_created,
|
||
'creator_count': session.creator_count,
|
||
'shoppable_creators': session.shoppable_creators,
|
||
'avg_followers': session.avg_followers,
|
||
'avg_gmv': session.avg_gmv,
|
||
'avg_video_views': session.avg_video_views
|
||
})
|
||
|
||
return ApiResponse.success(result, "获取会话摘要列表成功")
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def search(self, request):
|
||
"""搜索会话"""
|
||
keyword = request.query_params.get('keyword', '')
|
||
if not keyword:
|
||
return ApiResponse.error("缺少关键字参数", code=400)
|
||
|
||
queryset = self.get_queryset()
|
||
|
||
# 尝试判断关键字是否是日期格式
|
||
is_date = False
|
||
parsed_date = None
|
||
|
||
# 支持多种日期格式
|
||
date_formats = [
|
||
'%Y-%m-%d',
|
||
'%Y/%m/%d',
|
||
'%d-%m-%Y',
|
||
'%d/%m/%Y',
|
||
'%m-%d-%Y',
|
||
'%m/%d/%Y'
|
||
]
|
||
|
||
for fmt in date_formats:
|
||
try:
|
||
from datetime import datetime
|
||
parsed_date = datetime.strptime(keyword, fmt).date()
|
||
is_date = True
|
||
break
|
||
except ValueError:
|
||
continue
|
||
|
||
# 构建查询条件
|
||
if is_date:
|
||
# 如果是完整日期格式,按日期搜索
|
||
queryset = queryset.filter(date_created=parsed_date)
|
||
else:
|
||
# 检查是否可能是年份
|
||
is_year = False
|
||
year_value = None
|
||
try:
|
||
# 尝试将关键字作为年份处理
|
||
if len(keyword) == 4 and keyword.isdigit():
|
||
year_value = int(keyword)
|
||
if 1900 <= year_value <= 2100: # 合理的年份范围
|
||
is_year = True
|
||
# 按年份搜索
|
||
from django.db.models.functions import Extract
|
||
queryset = queryset.annotate(year=Extract('date_created', 'year')).filter(year=year_value)
|
||
except Exception:
|
||
is_year = False
|
||
|
||
# 如果不是年份,继续其他搜索逻辑
|
||
if not is_year:
|
||
try:
|
||
# 尝试将关键字转换为整数(用于ID或会话编号)
|
||
keyword_int = int(keyword)
|
||
queryset = queryset.filter(
|
||
Q(id=keyword_int) |
|
||
Q(session_number=keyword_int)
|
||
)
|
||
except ValueError:
|
||
# 如果转换失败,则按其他可能的字符串字段搜索
|
||
queryset = queryset.filter(
|
||
Q(id__icontains=keyword) |
|
||
Q(session_number__icontains=keyword)
|
||
)
|
||
|
||
# 额外尝试对日期的部分进行模糊匹配
|
||
try:
|
||
# 如果关键字可能是月份名称或数字
|
||
month_names = {
|
||
'一月': 1, '二月': 2, '三月': 3, '四月': 4, '五月': 5, '六月': 6,
|
||
'七月': 7, '八月': 8, '九月': 9, '十月': 10, '十一月': 11, '十二月': 12,
|
||
'january': 1, 'february': 2, 'march': 3, 'april': 4, 'may': 5, 'june': 6,
|
||
'july': 7, 'august': 8, 'september': 9, 'october': 10, 'november': 11, 'december': 12,
|
||
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'jun': 6, 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12
|
||
}
|
||
|
||
month_value = None
|
||
# 检查是否是月份名称
|
||
lower_keyword = keyword.lower()
|
||
if lower_keyword in month_names:
|
||
month_value = month_names[lower_keyword]
|
||
# 检查是否是数字月份
|
||
elif keyword.isdigit() and 1 <= int(keyword) <= 12:
|
||
month_value = int(keyword)
|
||
|
||
if month_value:
|
||
# 按月份搜索
|
||
from django.db.models.functions import Extract
|
||
month_queryset = self.get_queryset().annotate(month=Extract('date_created', 'month')).filter(month=month_value)
|
||
# 合并查询结果
|
||
queryset = queryset | month_queryset
|
||
except Exception:
|
||
# 忽略任何错误,继续使用之前的查询集
|
||
pass
|
||
|
||
# 如果没有找到结果
|
||
if not queryset.exists():
|
||
return ApiResponse.error(f"未找到匹配关键词 '{keyword}' 的会话", code=404)
|
||
|
||
# 格式化响应数据
|
||
result = []
|
||
for session in queryset:
|
||
result.append({
|
||
'id': session.id,
|
||
'session_number': session.session_number,
|
||
'date': session.date_created,
|
||
'creator_count': session.creator_count,
|
||
'shoppable_creators': session.shoppable_creators,
|
||
'avg_followers': session.avg_followers,
|
||
'avg_gmv': session.avg_gmv,
|
||
'avg_video_views': session.avg_video_views
|
||
})
|
||
|
||
return ApiResponse.success(result, f"搜索会话成功,关键词: {keyword}")
|
||
|
||
|
||
class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet):
|
||
"""创作者发现视图集"""
|
||
queryset = Creator.objects.all()
|
||
serializer_class = CreatorSerializer
|
||
authentication_classes = [CustomTokenAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
pagination_class = StandardResultsSetPagination
|
||
|
||
def get_serializer_class(self):
|
||
if self.action == 'retrieve':
|
||
return CreatorDetailSerializer
|
||
return CreatorSerializer
|
||
|
||
def list(self, request, *args, **kwargs):
|
||
queryset = self.filter_queryset(self.get_queryset())
|
||
page = self.paginate_queryset(queryset)
|
||
if page is not None:
|
||
serializer = self.get_serializer(page, many=True)
|
||
return self.get_paginated_response(serializer.data)
|
||
serializer = self.get_serializer(queryset, many=True)
|
||
return ApiResponse.success(serializer.data, "获取创作者列表成功")
|
||
|
||
def retrieve(self, request, *args, **kwargs):
|
||
instance = self.get_object()
|
||
serializer = self.get_serializer(instance)
|
||
return ApiResponse.success(serializer.data, "获取创作者详情成功")
|
||
|
||
@action(detail=False, methods=['post'])
|
||
def search(self, request):
|
||
"""搜索创作者"""
|
||
query = request.data.get('query', '')
|
||
category = request.data.get('category', None)
|
||
ecommerce_level = request.data.get('ecommerce_level', None)
|
||
exposure_level = request.data.get('exposure_level', None)
|
||
hashtag = request.data.get('hashtag', None)
|
||
trend = request.data.get('trend', None)
|
||
profile = request.data.get('profile', None) # 新增profile参数
|
||
|
||
try:
|
||
# 导入CreatorProfile模型
|
||
from apps.daren_detail.models import CreatorProfile
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 获取或创建当天的session
|
||
today = timezone.now().date()
|
||
today_session = SearchSession.objects.filter(date_created=today).first()
|
||
|
||
if not today_session:
|
||
# 如果当天没有session,创建一个
|
||
max_session_number = SearchSession.objects.all().order_by('-session_number').first()
|
||
session_number = 1
|
||
if max_session_number:
|
||
session_number = max_session_number.session_number + 1
|
||
|
||
today_session = SearchSession.objects.create(
|
||
session_number=session_number,
|
||
creator_count=0,
|
||
shoppable_creators=0,
|
||
avg_followers=0,
|
||
avg_gmv=0,
|
||
avg_video_views=0,
|
||
date_created=today
|
||
)
|
||
|
||
# 构建查询条件
|
||
query_filters = {}
|
||
if query:
|
||
query_filters['name__icontains'] = query
|
||
if category:
|
||
query_filters['category'] = category
|
||
if ecommerce_level:
|
||
if isinstance(ecommerce_level, str) and ecommerce_level.startswith('L') and len(ecommerce_level) > 1:
|
||
try:
|
||
ecommerce_level_num = int(ecommerce_level[1:])
|
||
query_filters['e_commerce_level'] = ecommerce_level_num
|
||
except ValueError:
|
||
pass
|
||
else:
|
||
query_filters['e_commerce_level'] = ecommerce_level
|
||
if exposure_level:
|
||
query_filters['exposure_level'] = exposure_level
|
||
if profile: # 增加对profile的过滤
|
||
query_filters['profile'] = profile
|
||
|
||
# 处理hashtag和trend的查询
|
||
creator_profiles = CreatorProfile.objects.filter(**query_filters)
|
||
|
||
# 如果指定了hashtag,进行精确过滤
|
||
if hashtag:
|
||
creator_profiles = creator_profiles.filter(hashtags__contains=f"#{hashtag}")
|
||
# 如果没有找到匹配的数据,尝试创建一个演示数据
|
||
if not creator_profiles.exists():
|
||
# 创建一个演示数据,实际生产中应删除这段逻辑
|
||
demo_profile = CreatorProfile.objects.create(
|
||
name=f"Demo Creator with #{hashtag}",
|
||
followers=1000,
|
||
gmv=500.0,
|
||
items_sold=20,
|
||
avg_video_views=2000,
|
||
category="Fashion Accessories" if category is None else category,
|
||
e_commerce_level=3 if ecommerce_level is None else ecommerce_level,
|
||
exposure_level="KOL-2" if exposure_level is None else exposure_level,
|
||
hashtags=f"#{hashtag}#style#trending",
|
||
trends=f"summer {hashtag}",
|
||
profile=profile if profile else "tiktok"
|
||
)
|
||
creator_profiles = CreatorProfile.objects.filter(pk=demo_profile.pk)
|
||
logger.info(f"创建了测试数据: {demo_profile.name}")
|
||
|
||
# 如果指定了trend,进行精确过滤
|
||
if trend:
|
||
creator_profiles = creator_profiles.filter(trends__contains=trend)
|
||
# 如果没有找到匹配的数据,尝试创建一个演示数据
|
||
if not creator_profiles.exists():
|
||
# 创建一个演示数据,实际生产中应删除这段逻辑
|
||
demo_profile = CreatorProfile.objects.create(
|
||
name=f"Demo Creator with {trend} trend",
|
||
followers=1200,
|
||
gmv=600.0,
|
||
items_sold=25,
|
||
avg_video_views=2500,
|
||
category="Beauty & Personal Care" if category is None else category,
|
||
e_commerce_level=4 if ecommerce_level is None else ecommerce_level,
|
||
exposure_level="KOL-1" if exposure_level is None else exposure_level,
|
||
hashtags=f"#beauty#{trend}#trending",
|
||
trends=f"{trend} season",
|
||
profile=profile if profile else "tiktok"
|
||
)
|
||
creator_profiles = CreatorProfile.objects.filter(pk=demo_profile.pk)
|
||
logger.info(f"创建了测试数据: {demo_profile.name}")
|
||
|
||
# 存储匹配的Creator ID列表
|
||
matched_creator_ids = []
|
||
|
||
# 对每个找到的CreatorProfile创建Creator记录
|
||
for profile_obj in creator_profiles:
|
||
# 检查是否已经在当前session中存在相同creator
|
||
existing_creator = Creator.objects.filter(
|
||
session=today_session,
|
||
name=profile_obj.name
|
||
).first()
|
||
|
||
if existing_creator:
|
||
# 如果已存在,记录ID用于过滤
|
||
matched_creator_ids.append(existing_creator.id)
|
||
else:
|
||
# 确定电商等级字符串
|
||
ecommerce_level_str = "New tag"
|
||
if profile_obj.e_commerce_level is not None:
|
||
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
|
||
|
||
# 创建Creator记录
|
||
new_creator = Creator.objects.create(
|
||
session=today_session,
|
||
name=profile_obj.name,
|
||
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
|
||
category=profile_obj.category if profile_obj.category else "Other",
|
||
ecommerce_level=ecommerce_level_str,
|
||
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
|
||
followers=float(profile_obj.followers) if profile_obj.followers else 0,
|
||
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
|
||
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
|
||
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
|
||
has_ecommerce=profile_obj.e_commerce_level is not None,
|
||
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
|
||
hashtags=profile_obj.hashtags, # 确保复制hashtags
|
||
trends=profile_obj.trends, # 确保复制trends
|
||
profile=profile_obj.profile # 复制profile字段
|
||
)
|
||
matched_creator_ids.append(new_creator.id)
|
||
|
||
# 更新session统计信息
|
||
all_creators = today_session.creators.all()
|
||
total_creators = all_creators.count()
|
||
|
||
if total_creators > 0:
|
||
# 计算平均值
|
||
total_followers = sum(creator.followers for creator in all_creators)
|
||
total_gmv = sum(creator.gmv for creator in all_creators)
|
||
total_video_views = sum(creator.avg_video_views for creator in all_creators)
|
||
|
||
today_session.creator_count = total_creators
|
||
today_session.shoppable_creators = all_creators.filter(has_ecommerce=True).count()
|
||
today_session.avg_followers = round(total_followers / total_creators, 1)
|
||
today_session.avg_gmv = round(total_gmv / total_creators, 1)
|
||
today_session.avg_video_views = round(total_video_views / total_creators, 1)
|
||
today_session.save()
|
||
|
||
# 只获取与搜索匹配的结果,而不是session中的所有creators
|
||
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
|
||
|
||
# 创建一个自定义响应
|
||
search_type = "搜索创作者"
|
||
if hashtag:
|
||
search_type = f"搜索标签 #{hashtag}"
|
||
elif trend:
|
||
search_type = f"搜索趋势 {trend}"
|
||
|
||
response_data = {
|
||
"id": today_session.id,
|
||
"session_number": today_session.session_number,
|
||
"creator_count": matched_creators.count(),
|
||
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
|
||
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"date_created": today_session.date_created,
|
||
"creators": CreatorSerializer(matched_creators, many=True).data
|
||
}
|
||
|
||
return ApiResponse.success(response_data, f"{search_type}成功")
|
||
|
||
except Exception as e:
|
||
import traceback
|
||
logger.error(f"搜索创作者时发生错误: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return ApiResponse.error(f"搜索创作者失败: {str(e)}")
|
||
|
||
@action(detail=False, methods=['post'])
|
||
def search_tags(self, request):
|
||
"""专门用于搜索标签和趋势的API"""
|
||
mode = request.data.get('mode', '') # 'hashtag' 或 'trend'
|
||
keyword = request.data.get('keyword', '')
|
||
profile = request.data.get('profile', None) # 新增profile参数
|
||
|
||
if not mode or not keyword:
|
||
return ApiResponse.error("缺少必要参数: mode和keyword", code=400)
|
||
|
||
try:
|
||
# 导入CreatorProfile模型
|
||
from apps.daren_detail.models import CreatorProfile
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 获取或创建当天的session
|
||
today = timezone.now().date()
|
||
today_session = SearchSession.objects.filter(date_created=today).first()
|
||
|
||
if not today_session:
|
||
# 如果当天没有session,创建一个
|
||
max_session_number = SearchSession.objects.all().order_by('-session_number').first()
|
||
session_number = 1
|
||
if max_session_number:
|
||
session_number = max_session_number.session_number + 1
|
||
|
||
today_session = SearchSession.objects.create(
|
||
session_number=session_number,
|
||
creator_count=0,
|
||
shoppable_creators=0,
|
||
avg_followers=0,
|
||
avg_gmv=0,
|
||
avg_video_views=0,
|
||
date_created=today
|
||
)
|
||
|
||
# 构建查询条件
|
||
query_filters = {}
|
||
if profile: # 添加平台过滤
|
||
query_filters['profile'] = profile
|
||
|
||
|
||
# 根据模式选择搜索字段
|
||
if mode.lower() == 'hashtag':
|
||
creator_profiles = CreatorProfile.objects.filter(hashtags__contains=f"#{keyword}", **query_filters)
|
||
elif mode.lower() == 'trend':
|
||
creator_profiles = CreatorProfile.objects.filter(trends__contains=keyword, **query_filters)
|
||
else:
|
||
return ApiResponse.error(f"不支持的搜索模式: {mode},请使用 'hashtag' 或 'trend'", code=400)
|
||
|
||
# 日志记录查询结果
|
||
logger.info(f"搜索{mode.lower()}'{keyword}'找到{creator_profiles.count()}个结果")
|
||
for profile_obj in creator_profiles:
|
||
if mode.lower() == 'hashtag':
|
||
logger.info(f"Profile: {profile_obj.name}, Hashtags: {profile_obj.hashtags}")
|
||
else:
|
||
logger.info(f"Profile: {profile_obj.name}, Trends: {profile_obj.trends}")
|
||
|
||
# 存储匹配的Creator ID列表
|
||
matched_creator_ids = []
|
||
|
||
# 对每个找到的CreatorProfile创建Creator记录
|
||
created_count = 0
|
||
for profile_obj in creator_profiles:
|
||
# 检查是否已经在当前session中存在相同creator
|
||
existing_creator = Creator.objects.filter(
|
||
session=today_session,
|
||
name=profile_obj.name
|
||
).first()
|
||
|
||
if existing_creator:
|
||
# 如果已存在,记录ID用于过滤
|
||
matched_creator_ids.append(existing_creator.id)
|
||
else:
|
||
# 确定电商等级字符串
|
||
ecommerce_level_str = "New tag"
|
||
if profile_obj.e_commerce_level is not None:
|
||
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
|
||
|
||
# 创建Creator记录
|
||
new_creator = Creator.objects.create(
|
||
session=today_session,
|
||
name=profile_obj.name,
|
||
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
|
||
category=profile_obj.category if profile_obj.category else "Other",
|
||
ecommerce_level=ecommerce_level_str,
|
||
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
|
||
followers=float(profile_obj.followers) if profile_obj.followers else 0,
|
||
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
|
||
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
|
||
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
|
||
has_ecommerce=profile_obj.e_commerce_level is not None,
|
||
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
|
||
hashtags=profile_obj.hashtags, # 确保复制hashtags
|
||
trends=profile_obj.trends, # 确保复制trends
|
||
profile=profile_obj.profile # 复制profile字段
|
||
)
|
||
matched_creator_ids.append(new_creator.id)
|
||
created_count += 1
|
||
|
||
# 如果没有找到匹配的结果,返回提示
|
||
if creator_profiles.count() == 0:
|
||
return ApiResponse.error(f"未找到匹配的{mode.lower()}:{keyword}", code=404)
|
||
|
||
# 如果没有创建新的Creator记录,也要提示用户
|
||
if created_count == 0 and creator_profiles.count() > 0:
|
||
logger.info(f"找到了{creator_profiles.count()}个匹配的创作者,但都已经在当前session中")
|
||
|
||
# 更新session统计信息
|
||
all_creators = today_session.creators.all()
|
||
total_creators = all_creators.count()
|
||
|
||
if total_creators > 0:
|
||
# 计算平均值
|
||
total_followers = sum(creator.followers for creator in all_creators)
|
||
total_gmv = sum(creator.gmv for creator in all_creators)
|
||
total_video_views = sum(creator.avg_video_views for creator in all_creators)
|
||
|
||
today_session.creator_count = total_creators
|
||
today_session.shoppable_creators = all_creators.filter(has_ecommerce=True).count()
|
||
today_session.avg_followers = round(total_followers / total_creators, 1)
|
||
today_session.avg_gmv = round(total_gmv / total_creators, 1)
|
||
today_session.avg_video_views = round(total_video_views / total_creators, 1)
|
||
today_session.save()
|
||
|
||
# 只获取与搜索匹配的结果,而不是返回session中的所有creators
|
||
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
|
||
|
||
# 创建一个自定义响应
|
||
response_data = {
|
||
"id": today_session.id,
|
||
"session_number": today_session.session_number,
|
||
"creator_count": matched_creators.count(),
|
||
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
|
||
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"date_created": today_session.date_created,
|
||
"creators": CreatorSerializer(matched_creators, many=True).data
|
||
}
|
||
|
||
return ApiResponse.success(response_data, f"搜索{mode.lower()}成功,关键词: {keyword}")
|
||
|
||
except Exception as e:
|
||
import traceback
|
||
logger.error(f"搜索标签时发生错误: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return ApiResponse.error(f"搜索失败: {str(e)}")
|
||
|
||
@action(detail=False, methods=['post'])
|
||
def search_individual(self, request):
|
||
"""根据用户输入的自然语言文本搜索达人"""
|
||
criteria = request.data.get('criteria', '')
|
||
top_n = request.data.get('top_n', 10)
|
||
|
||
if not criteria:
|
||
return ApiResponse.error("缺少必要参数: criteria", code=400)
|
||
|
||
try:
|
||
import requests
|
||
import logging
|
||
import json
|
||
from apps.daren_detail.models import CreatorProfile
|
||
from apps.user.models import User
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 调用外部API
|
||
api_url = 'http://81.69.223.133:58099/api/operation/sql_search/'
|
||
headers = {
|
||
'Authorization': 'Token 3cf5348d3a59e2446a80d3d629c4b9cd6d9ff8ab',
|
||
'Content-Type': 'application/json'
|
||
}
|
||
payload = {
|
||
'criteria': criteria,
|
||
'top_n': top_n
|
||
}
|
||
|
||
logger.info(f"调用外部API,参数: {payload}")
|
||
|
||
# 发送请求
|
||
response = requests.post(api_url, headers=headers, json=payload)
|
||
|
||
# 检查请求是否成功
|
||
if response.status_code != 200:
|
||
logger.error(f"外部API请求失败,状态码: {response.status_code}, 响应: {response.text}")
|
||
return ApiResponse.error(f"外部API请求失败,状态码: {response.status_code}", code=500)
|
||
|
||
# 解析响应
|
||
resp_data = response.json()
|
||
logger.info(f"外部API响应成功: {resp_data}")
|
||
|
||
# 检查响应数据格式
|
||
if 'data' not in resp_data or 'results' not in resp_data['data']:
|
||
logger.error(f"外部API响应格式错误: {resp_data}")
|
||
return ApiResponse.error("外部API响应格式错误", code=500)
|
||
|
||
# 获取搜索结果
|
||
api_results = resp_data['data']['results']
|
||
|
||
# 获取或创建当天的session
|
||
today = timezone.now().date()
|
||
today_session = SearchSession.objects.filter(date_created=today).first()
|
||
|
||
if not today_session:
|
||
# 如果当天没有session,创建一个
|
||
max_session_number = SearchSession.objects.all().order_by('-session_number').first()
|
||
session_number = 1
|
||
if max_session_number:
|
||
session_number = max_session_number.session_number + 1
|
||
|
||
today_session = SearchSession.objects.create(
|
||
session_number=session_number,
|
||
creator_count=0,
|
||
shoppable_creators=0,
|
||
avg_followers=0,
|
||
avg_gmv=0,
|
||
avg_video_views=0,
|
||
date_created=today
|
||
)
|
||
|
||
# 将API结果转换为Creator记录
|
||
matched_creator_ids = []
|
||
|
||
for creator_data in api_results:
|
||
try:
|
||
# 安全获取创作者名称,确保有一个默认值
|
||
creator_name = creator_data.get('name', 'Unknown Creator')
|
||
|
||
# 按名称找到第一个匹配的CreatorProfile或创建新的
|
||
profile_obj = None
|
||
try:
|
||
profile_obj = CreatorProfile.objects.filter(name=creator_name).first()
|
||
except Exception as e:
|
||
logger.error(f"查询CreatorProfile时出错: {str(e)}")
|
||
|
||
if not profile_obj:
|
||
# 如果没有找到,创建一个新的CreatorProfile
|
||
try:
|
||
profile_obj = CreatorProfile.objects.create(
|
||
name=creator_name,
|
||
avatar_url=creator_data.get('avatar_url'),
|
||
email=creator_data.get('email'),
|
||
instagram=creator_data.get('instagram'),
|
||
tiktok_link=creator_data.get('tiktok_link'),
|
||
location=creator_data.get('location'),
|
||
live_schedule=creator_data.get('live_schedule'),
|
||
category=creator_data.get('category'),
|
||
e_commerce_level=creator_data.get('e_commerce_level'),
|
||
exposure_level=creator_data.get('exposure_level'),
|
||
followers=creator_data.get('followers', 0),
|
||
gmv=creator_data.get('gmv', 0),
|
||
items_sold=creator_data.get('items_sold', 0),
|
||
avg_video_views=creator_data.get('avg_video_views', 0),
|
||
pricing=creator_data.get('pricing'),
|
||
pricing_package=creator_data.get('pricing_package'),
|
||
collab_count=creator_data.get('collab_count'),
|
||
latest_collab=creator_data.get('latest_collab'),
|
||
profile=creator_data.get('profile'),
|
||
hashtags=creator_data.get('hashtags', ''),
|
||
trends=creator_data.get('trends', '')
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"创建CreatorProfile时出错: {str(e)}")
|
||
continue
|
||
|
||
# 检查是否已经在当前session中存在相同creator
|
||
existing_creator = Creator.objects.filter(
|
||
session=today_session,
|
||
name=profile_obj.name
|
||
).first()
|
||
|
||
if existing_creator:
|
||
# 如果已存在,记录ID用于过滤
|
||
matched_creator_ids.append(existing_creator.id)
|
||
else:
|
||
# 确定电商等级字符串
|
||
ecommerce_level_str = "New tag"
|
||
if profile_obj.e_commerce_level is not None:
|
||
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
|
||
|
||
# 创建Creator记录
|
||
new_creator = Creator.objects.create(
|
||
session=today_session,
|
||
name=profile_obj.name,
|
||
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
|
||
category=profile_obj.category if profile_obj.category else "Other",
|
||
ecommerce_level=ecommerce_level_str,
|
||
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
|
||
followers=float(profile_obj.followers) if profile_obj.followers else 0,
|
||
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
|
||
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
|
||
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
|
||
has_ecommerce=profile_obj.e_commerce_level is not None,
|
||
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
|
||
hashtags=profile_obj.hashtags,
|
||
trends=profile_obj.trends,
|
||
profile=profile_obj.profile
|
||
)
|
||
matched_creator_ids.append(new_creator.id)
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理创作者数据时发生错误: {str(e)}, 数据: {creator_data}")
|
||
continue
|
||
|
||
# 更新session统计信息
|
||
all_creators = today_session.creators.all()
|
||
total_creators = all_creators.count()
|
||
|
||
if total_creators > 0:
|
||
# 计算平均值
|
||
total_followers = sum(creator.followers for creator in all_creators)
|
||
total_gmv = sum(creator.gmv for creator in all_creators)
|
||
total_video_views = sum(creator.avg_video_views for creator in all_creators)
|
||
|
||
today_session.creator_count = total_creators
|
||
today_session.shoppable_creators = all_creators.filter(has_ecommerce=True).count()
|
||
today_session.avg_followers = round(total_followers / total_creators, 1)
|
||
today_session.avg_gmv = round(total_gmv / total_creators, 1)
|
||
today_session.avg_video_views = round(total_video_views / total_creators, 1)
|
||
today_session.save()
|
||
|
||
# 只获取与搜索匹配的结果,而不是session中的所有creators
|
||
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
|
||
|
||
# 创建统一的响应格式,与其他搜索接口保持一致
|
||
response_data = {
|
||
"id": today_session.id,
|
||
"session_number": today_session.session_number,
|
||
"creator_count": matched_creators.count(),
|
||
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
|
||
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||
"date_created": today_session.date_created,
|
||
"creators": CreatorSerializer(matched_creators, many=True).data
|
||
}
|
||
|
||
return ApiResponse.success(response_data, f"搜索创作者成功")
|
||
|
||
except Exception as e:
|
||
import traceback
|
||
logger.error(f"搜索时发生错误: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return ApiResponse.error(f"搜索失败: {str(e)}", code=500)
|