增加公共达人私有大人搜索
This commit is contained in:
parent
88ecc3f9da
commit
6795715200
@ -16,6 +16,11 @@ urlpatterns = [
|
|||||||
# path('tiktok/delete_user/', views.delete_tiktok_user, name='delete_tiktok_user'),
|
# path('tiktok/delete_user/', views.delete_tiktok_user, name='delete_tiktok_user'),
|
||||||
|
|
||||||
|
|
||||||
|
# 搜索公有达人
|
||||||
|
path('creators/search/', views.search_creators, name='search_creators'),
|
||||||
|
# 搜索私有达人
|
||||||
|
path('private/creators/search/', views.search_private_creators, name='search_private_creators'),
|
||||||
|
|
||||||
|
|
||||||
# 新的API路由结构 - 只保留TikTok API
|
# 新的API路由结构 - 只保留TikTok API
|
||||||
# path('api/tiktok/', include('app.api.tiktok.urls')), # TikTok API
|
# path('api/tiktok/', include('app.api.tiktok.urls')), # TikTok API
|
||||||
|
@ -3459,6 +3459,492 @@ def remove_from_public_pool(request):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
@authentication_classes([CustomTokenAuthentication])
|
||||||
|
@csrf_exempt
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def search_creators(request):
|
||||||
|
"""搜索创作者功能 - 基于关键词搜索创作者名称、分类等信息,支持多关键词搜索,只搜索公有达人库"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
from django.db.models import Q
|
||||||
|
from .models import PublicCreatorPool
|
||||||
|
|
||||||
|
# 获取搜索参数
|
||||||
|
search_query = request.GET.get('q', '').strip() # 搜索关键词
|
||||||
|
search_mode = request.GET.get('mode', 'and').lower() # 搜索模式:and 或 or,默认为 and
|
||||||
|
page = int(request.GET.get('page', 1))
|
||||||
|
page_size = int(request.GET.get('page_size', 10))
|
||||||
|
|
||||||
|
# 基础查询 - 只查询公有达人库中的达人
|
||||||
|
query = PublicCreatorPool.objects.select_related('creator').all()
|
||||||
|
|
||||||
|
# 如果有搜索关键词,进行模糊搜索
|
||||||
|
if search_query:
|
||||||
|
# 分割关键词(支持空格、逗号、分号分隔)
|
||||||
|
import re
|
||||||
|
keywords = re.split(r'[,;\s]+', search_query)
|
||||||
|
keywords = [keyword.strip() for keyword in keywords if keyword.strip()]
|
||||||
|
|
||||||
|
# 如果没有分隔符,将整个查询作为单个关键词处理(支持单个字符或汉字)
|
||||||
|
if not keywords:
|
||||||
|
keywords = [search_query]
|
||||||
|
|
||||||
|
if keywords:
|
||||||
|
if search_mode == 'or':
|
||||||
|
# OR模式:任一关键词匹配即可
|
||||||
|
final_conditions = Q()
|
||||||
|
for keyword in keywords:
|
||||||
|
keyword_conditions = Q()
|
||||||
|
# 搜索创作者名称 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__name__icontains=keyword)
|
||||||
|
# 搜索分类 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__category__icontains=keyword)
|
||||||
|
# 搜索MCN - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__mcn__icontains=keyword)
|
||||||
|
# 搜索曝光等级 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__exposure_level__icontains=keyword)
|
||||||
|
# 搜索电商平台 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_platforms__icontains=keyword)
|
||||||
|
# 搜索公有库分类 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(category__icontains=keyword)
|
||||||
|
|
||||||
|
# 特殊处理电商等级搜索(L+数字格式)
|
||||||
|
if keyword.upper().startswith('L') and len(keyword) > 1:
|
||||||
|
# 提取L后面的数字
|
||||||
|
level_match = re.match(r'L(\d+)', keyword.upper())
|
||||||
|
if level_match:
|
||||||
|
level_number = int(level_match.group(1))
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_level=level_number)
|
||||||
|
|
||||||
|
final_conditions |= keyword_conditions
|
||||||
|
else:
|
||||||
|
# AND模式:所有关键词都必须匹配(默认)
|
||||||
|
final_conditions = Q()
|
||||||
|
for i, keyword in enumerate(keywords):
|
||||||
|
keyword_conditions = Q()
|
||||||
|
# 搜索创作者名称 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__name__icontains=keyword)
|
||||||
|
# 搜索分类 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__category__icontains=keyword)
|
||||||
|
# 搜索MCN - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__mcn__icontains=keyword)
|
||||||
|
# 搜索曝光等级 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__exposure_level__icontains=keyword)
|
||||||
|
# 搜索电商平台 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_platforms__icontains=keyword)
|
||||||
|
# 搜索公有库分类 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(category__icontains=keyword)
|
||||||
|
|
||||||
|
# 特殊处理电商等级搜索(L+数字格式)
|
||||||
|
if keyword.upper().startswith('L') and len(keyword) > 1:
|
||||||
|
# 提取L后面的数字
|
||||||
|
level_match = re.match(r'L(\d+)', keyword.upper())
|
||||||
|
if level_match:
|
||||||
|
level_number = int(level_match.group(1))
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_level=level_number)
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
final_conditions = keyword_conditions
|
||||||
|
else:
|
||||||
|
final_conditions &= keyword_conditions
|
||||||
|
|
||||||
|
# 应用搜索条件
|
||||||
|
query = query.filter(final_conditions)
|
||||||
|
|
||||||
|
# 按相关性排序(名称匹配优先)
|
||||||
|
if search_query:
|
||||||
|
# 使用第一个关键词进行排序优化
|
||||||
|
first_keyword = keywords[0] if 'keywords' in locals() and keywords else search_query
|
||||||
|
|
||||||
|
# 优化排序逻辑,支持单个字符的精确匹配
|
||||||
|
query = query.extra(
|
||||||
|
select={
|
||||||
|
# 完全匹配优先级最高
|
||||||
|
'name_exact_match': "CASE WHEN LOWER(creator_profiles.name) = LOWER(%s) THEN 1 ELSE 0 END",
|
||||||
|
# 名称开头匹配优先级第二
|
||||||
|
'name_starts_with': "CASE WHEN LOWER(creator_profiles.name) LIKE LOWER(%s) THEN 1 ELSE 0 END",
|
||||||
|
# 名称包含匹配优先级第三
|
||||||
|
'name_contains': "CASE WHEN LOWER(creator_profiles.name) LIKE LOWER(%s) THEN 1 ELSE 0 END",
|
||||||
|
# 分类匹配优先级第四
|
||||||
|
'category_match': "CASE WHEN LOWER(creator_profiles.category) LIKE LOWER(%s) THEN 1 ELSE 0 END"
|
||||||
|
},
|
||||||
|
select_params=[
|
||||||
|
first_keyword,
|
||||||
|
first_keyword + '%',
|
||||||
|
'%' + first_keyword + '%',
|
||||||
|
'%' + first_keyword + '%'
|
||||||
|
]
|
||||||
|
).order_by(
|
||||||
|
'-name_exact_match',
|
||||||
|
'-name_starts_with',
|
||||||
|
'-name_contains',
|
||||||
|
'-category_match',
|
||||||
|
'creator__name'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 如果没有搜索词,按名称排序
|
||||||
|
query = query.order_by('creator__name')
|
||||||
|
|
||||||
|
# 获取总数据量
|
||||||
|
total_count = query.count()
|
||||||
|
|
||||||
|
# 计算分页
|
||||||
|
start = (page - 1) * page_size
|
||||||
|
end = start + page_size
|
||||||
|
|
||||||
|
# 执行查询并分页
|
||||||
|
public_creators = query[start:end]
|
||||||
|
|
||||||
|
creator_list = []
|
||||||
|
for public_creator in public_creators:
|
||||||
|
creator = public_creator.creator
|
||||||
|
|
||||||
|
# 格式化电商等级
|
||||||
|
e_commerce_level_formatted = f"L{creator.e_commerce_level}" if creator.e_commerce_level else None
|
||||||
|
|
||||||
|
# 格式化GMV
|
||||||
|
gmv_formatted = f"${creator.gmv}k" if creator.gmv else "$0"
|
||||||
|
|
||||||
|
# 格式化粉丝数和观看量
|
||||||
|
followers_formatted = f"{int(creator.followers / 1000)}k" if creator.followers else "0"
|
||||||
|
avg_views_formatted = f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0"
|
||||||
|
|
||||||
|
# 格式化价格
|
||||||
|
pricing_formatted = f"${creator.pricing}" if creator.pricing else None
|
||||||
|
|
||||||
|
# 格式化结果
|
||||||
|
formatted_creator = {
|
||||||
|
"Creator": {
|
||||||
|
"name": creator.name,
|
||||||
|
"avatar": creator.get_avatar_url()
|
||||||
|
},
|
||||||
|
"Category": creator.category,
|
||||||
|
"E-commerce Level": e_commerce_level_formatted,
|
||||||
|
"Exposure Level": creator.exposure_level,
|
||||||
|
"Followers": followers_formatted,
|
||||||
|
"GMV": gmv_formatted,
|
||||||
|
"Items Sold": f"{creator.items_sold}k" if creator.items_sold else None,
|
||||||
|
"Avg. Video Views": avg_views_formatted,
|
||||||
|
"Pricing": pricing_formatted,
|
||||||
|
"Pricing Package": creator.pricing_package,
|
||||||
|
"# Collab": creator.collab_count,
|
||||||
|
"Latest Collab.": creator.latest_collab,
|
||||||
|
"E-commerce": creator.e_commerce_platforms,
|
||||||
|
"creator_id": creator.id, # 添加ID用于后续操作
|
||||||
|
"profile": creator.profile if hasattr(creator, 'profile') else 'tiktok', # 平台信息
|
||||||
|
"public_pool_category": public_creator.category, # 公有库分类
|
||||||
|
"public_pool_remark": public_creator.remark, # 公有库备注
|
||||||
|
"is_public": True # 标识这是公有达人库的达人
|
||||||
|
}
|
||||||
|
creator_list.append(formatted_creator)
|
||||||
|
|
||||||
|
# 计算总页数
|
||||||
|
total_pages = (total_count + page_size - 1) // page_size
|
||||||
|
|
||||||
|
# 构造分页信息
|
||||||
|
pagination = {
|
||||||
|
"current_page": page,
|
||||||
|
"total_pages": total_pages,
|
||||||
|
"total_count": total_count,
|
||||||
|
"has_next": page < total_pages,
|
||||||
|
"has_prev": page > 1,
|
||||||
|
"page_size": page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构造搜索信息
|
||||||
|
search_info = {
|
||||||
|
"query": search_query,
|
||||||
|
"keywords": keywords if 'keywords' in locals() else [],
|
||||||
|
"search_mode": search_mode,
|
||||||
|
"results_count": total_count,
|
||||||
|
"search_applied": bool(search_query),
|
||||||
|
"supports_single_char": True, # 标识支持单字符搜索
|
||||||
|
"search_scope": "public_only" # 标识只搜索公有达人库
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 200,
|
||||||
|
'message': '搜索成功',
|
||||||
|
'data': {
|
||||||
|
'creators': creator_list,
|
||||||
|
'pagination': pagination,
|
||||||
|
'search_info': search_info
|
||||||
|
}
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"搜索创作者失败: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.error(f"详细错误: {traceback.format_exc()}")
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 500,
|
||||||
|
'message': f'搜索创作者失败: {str(e)}',
|
||||||
|
'data': None
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
@authentication_classes([CustomTokenAuthentication])
|
||||||
|
@csrf_exempt
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def search_private_creators(request):
|
||||||
|
"""搜索私有达人库功能 - 基于关键词搜索用户私有达人库中的创作者名称、分类等信息,支持多关键词搜索"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
from django.db.models import Q
|
||||||
|
from .models import PrivateCreatorPool, PrivateCreatorRelation
|
||||||
|
|
||||||
|
# 获取当前用户
|
||||||
|
user = request.user
|
||||||
|
if not user or not user.is_authenticated:
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 401,
|
||||||
|
'message': '未授权访问',
|
||||||
|
'data': None
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
# 获取搜索参数
|
||||||
|
search_query = request.GET.get('q', '').strip() # 搜索关键词
|
||||||
|
search_mode = request.GET.get('mode', 'and').lower() # 搜索模式:and 或 or,默认为 and
|
||||||
|
pool_id = request.GET.get('pool_id') # 私有达人库ID,如果提供则只搜索特定私有库
|
||||||
|
page = int(request.GET.get('page', 1))
|
||||||
|
page_size = int(request.GET.get('page_size', 10))
|
||||||
|
|
||||||
|
# 基础查询 - 查询用户的私有达人库中的达人
|
||||||
|
if pool_id:
|
||||||
|
# 如果提供了特定池ID,只搜索该池
|
||||||
|
try:
|
||||||
|
pool = PrivateCreatorPool.objects.get(id=pool_id, user=user)
|
||||||
|
query = PrivateCreatorRelation.objects.filter(
|
||||||
|
private_pool=pool
|
||||||
|
).select_related('creator', 'private_pool')
|
||||||
|
except PrivateCreatorPool.DoesNotExist:
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 404,
|
||||||
|
'message': '私有达人库不存在',
|
||||||
|
'data': None
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
else:
|
||||||
|
# 否则搜索用户所有私有库
|
||||||
|
query = PrivateCreatorRelation.objects.filter(
|
||||||
|
private_pool__user=user
|
||||||
|
).select_related('creator', 'private_pool')
|
||||||
|
|
||||||
|
# 如果有搜索关键词,进行模糊搜索
|
||||||
|
if search_query:
|
||||||
|
# 分割关键词(支持空格、逗号、分号分隔)
|
||||||
|
import re
|
||||||
|
keywords = re.split(r'[,;\s]+', search_query)
|
||||||
|
keywords = [keyword.strip() for keyword in keywords if keyword.strip()]
|
||||||
|
|
||||||
|
# 如果没有分隔符,将整个查询作为单个关键词处理(支持单个字符或汉字)
|
||||||
|
if not keywords:
|
||||||
|
keywords = [search_query]
|
||||||
|
|
||||||
|
if keywords:
|
||||||
|
if search_mode == 'or':
|
||||||
|
# OR模式:任一关键词匹配即可
|
||||||
|
final_conditions = Q()
|
||||||
|
for keyword in keywords:
|
||||||
|
keyword_conditions = Q()
|
||||||
|
# 搜索创作者名称 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__name__icontains=keyword)
|
||||||
|
# 搜索分类 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__category__icontains=keyword)
|
||||||
|
# 搜索MCN - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__mcn__icontains=keyword)
|
||||||
|
# 搜索曝光等级 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__exposure_level__icontains=keyword)
|
||||||
|
# 搜索电商平台 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_platforms__icontains=keyword)
|
||||||
|
# 搜索笔记 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(notes__icontains=keyword)
|
||||||
|
# 搜索私有库名称 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(private_pool__name__icontains=keyword)
|
||||||
|
|
||||||
|
# 特殊处理电商等级搜索(L+数字格式)
|
||||||
|
if keyword.upper().startswith('L') and len(keyword) > 1:
|
||||||
|
# 提取L后面的数字
|
||||||
|
level_match = re.match(r'L(\d+)', keyword.upper())
|
||||||
|
if level_match:
|
||||||
|
level_number = int(level_match.group(1))
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_level=level_number)
|
||||||
|
|
||||||
|
final_conditions |= keyword_conditions
|
||||||
|
else:
|
||||||
|
# AND模式:所有关键词都必须匹配(默认)
|
||||||
|
final_conditions = Q()
|
||||||
|
for i, keyword in enumerate(keywords):
|
||||||
|
keyword_conditions = Q()
|
||||||
|
# 搜索创作者名称 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__name__icontains=keyword)
|
||||||
|
# 搜索分类 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__category__icontains=keyword)
|
||||||
|
# 搜索MCN - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__mcn__icontains=keyword)
|
||||||
|
# 搜索曝光等级 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__exposure_level__icontains=keyword)
|
||||||
|
# 搜索电商平台 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_platforms__icontains=keyword)
|
||||||
|
# 搜索笔记 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(notes__icontains=keyword)
|
||||||
|
# 搜索私有库名称 - 支持单个字符匹配
|
||||||
|
keyword_conditions |= Q(private_pool__name__icontains=keyword)
|
||||||
|
|
||||||
|
# 特殊处理电商等级搜索(L+数字格式)
|
||||||
|
if keyword.upper().startswith('L') and len(keyword) > 1:
|
||||||
|
# 提取L后面的数字
|
||||||
|
level_match = re.match(r'L(\d+)', keyword.upper())
|
||||||
|
if level_match:
|
||||||
|
level_number = int(level_match.group(1))
|
||||||
|
keyword_conditions |= Q(creator__e_commerce_level=level_number)
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
final_conditions = keyword_conditions
|
||||||
|
else:
|
||||||
|
final_conditions &= keyword_conditions
|
||||||
|
|
||||||
|
# 应用搜索条件
|
||||||
|
query = query.filter(final_conditions)
|
||||||
|
|
||||||
|
# 按相关性排序(名称匹配优先)
|
||||||
|
if search_query:
|
||||||
|
# 使用第一个关键词进行排序优化
|
||||||
|
first_keyword = keywords[0] if 'keywords' in locals() and keywords else search_query
|
||||||
|
|
||||||
|
# 优化排序逻辑,支持单个字符的精确匹配
|
||||||
|
query = query.extra(
|
||||||
|
select={
|
||||||
|
# 完全匹配优先级最高
|
||||||
|
'name_exact_match': "CASE WHEN LOWER(creator_profiles.name) = LOWER(%s) THEN 1 ELSE 0 END",
|
||||||
|
# 名称开头匹配优先级第二
|
||||||
|
'name_starts_with': "CASE WHEN LOWER(creator_profiles.name) LIKE LOWER(%s) THEN 1 ELSE 0 END",
|
||||||
|
# 名称包含匹配优先级第三
|
||||||
|
'name_contains': "CASE WHEN LOWER(creator_profiles.name) LIKE LOWER(%s) THEN 1 ELSE 0 END",
|
||||||
|
# 分类匹配优先级第四
|
||||||
|
'category_match': "CASE WHEN LOWER(creator_profiles.category) LIKE LOWER(%s) THEN 1 ELSE 0 END"
|
||||||
|
},
|
||||||
|
select_params=[
|
||||||
|
first_keyword,
|
||||||
|
first_keyword + '%',
|
||||||
|
'%' + first_keyword + '%',
|
||||||
|
'%' + first_keyword + '%'
|
||||||
|
]
|
||||||
|
).order_by(
|
||||||
|
'-name_exact_match',
|
||||||
|
'-name_starts_with',
|
||||||
|
'-name_contains',
|
||||||
|
'-category_match',
|
||||||
|
'creator__name'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 如果没有搜索词,按名称排序
|
||||||
|
query = query.order_by('creator__name')
|
||||||
|
|
||||||
|
# 获取总数据量
|
||||||
|
total_count = query.count()
|
||||||
|
|
||||||
|
# 计算分页
|
||||||
|
start = (page - 1) * page_size
|
||||||
|
end = start + page_size
|
||||||
|
|
||||||
|
# 执行查询并分页
|
||||||
|
private_creator_relations = query[start:end]
|
||||||
|
|
||||||
|
creator_list = []
|
||||||
|
for relation in private_creator_relations:
|
||||||
|
creator = relation.creator
|
||||||
|
|
||||||
|
# 格式化电商等级
|
||||||
|
e_commerce_level_formatted = f"L{creator.e_commerce_level}" if creator.e_commerce_level else None
|
||||||
|
|
||||||
|
# 格式化GMV
|
||||||
|
gmv_formatted = f"${creator.gmv}k" if creator.gmv else "$0"
|
||||||
|
|
||||||
|
# 格式化粉丝数和观看量
|
||||||
|
followers_formatted = f"{int(creator.followers / 1000)}k" if creator.followers else "0"
|
||||||
|
avg_views_formatted = f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0"
|
||||||
|
|
||||||
|
# 格式化价格
|
||||||
|
pricing_formatted = f"${creator.pricing}" if creator.pricing else None
|
||||||
|
|
||||||
|
# 格式化结果
|
||||||
|
formatted_creator = {
|
||||||
|
"Creator": {
|
||||||
|
"name": creator.name,
|
||||||
|
"avatar": creator.get_avatar_url()
|
||||||
|
},
|
||||||
|
"Category": creator.category,
|
||||||
|
"E-commerce Level": e_commerce_level_formatted,
|
||||||
|
"Exposure Level": creator.exposure_level,
|
||||||
|
"Followers": followers_formatted,
|
||||||
|
"GMV": gmv_formatted,
|
||||||
|
"Items Sold": f"{creator.items_sold}k" if creator.items_sold else None,
|
||||||
|
"Avg. Video Views": avg_views_formatted,
|
||||||
|
"Pricing": pricing_formatted,
|
||||||
|
"Pricing Package": creator.pricing_package,
|
||||||
|
"# Collab": creator.collab_count,
|
||||||
|
"Latest Collab.": creator.latest_collab,
|
||||||
|
"E-commerce": creator.e_commerce_platforms,
|
||||||
|
"creator_id": creator.id, # 添加ID用于后续操作
|
||||||
|
"profile": creator.profile if hasattr(creator, 'profile') else 'tiktok', # 平台信息
|
||||||
|
"private_pool": {
|
||||||
|
"id": relation.private_pool.id,
|
||||||
|
"name": relation.private_pool.name,
|
||||||
|
"is_default": relation.private_pool.is_default
|
||||||
|
},
|
||||||
|
"notes": relation.notes, # 私有库中的笔记
|
||||||
|
"status": relation.status, # 私有库中的状态
|
||||||
|
"relation_id": relation.id, # 关联ID
|
||||||
|
"is_private": True # 标识这是私有达人库的达人
|
||||||
|
}
|
||||||
|
creator_list.append(formatted_creator)
|
||||||
|
|
||||||
|
# 计算总页数
|
||||||
|
total_pages = (total_count + page_size - 1) // page_size
|
||||||
|
|
||||||
|
# 构造分页信息
|
||||||
|
pagination = {
|
||||||
|
"current_page": page,
|
||||||
|
"total_pages": total_pages,
|
||||||
|
"total_count": total_count,
|
||||||
|
"has_next": page < total_pages,
|
||||||
|
"has_prev": page > 1,
|
||||||
|
"page_size": page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构造搜索信息
|
||||||
|
search_info = {
|
||||||
|
"query": search_query,
|
||||||
|
"keywords": keywords if 'keywords' in locals() else [],
|
||||||
|
"search_mode": search_mode,
|
||||||
|
"results_count": total_count,
|
||||||
|
"search_applied": bool(search_query),
|
||||||
|
"supports_single_char": True, # 标识支持单字符搜索
|
||||||
|
"search_scope": "private_only", # 标识只搜索私有达人库
|
||||||
|
"pool_id": pool_id # 返回当前搜索的池ID,如果有的话
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 200,
|
||||||
|
'message': '搜索成功',
|
||||||
|
'data': {
|
||||||
|
'creators': creator_list,
|
||||||
|
'pagination': pagination,
|
||||||
|
'search_info': search_info
|
||||||
|
}
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"搜索私有达人失败: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.error(f"详细错误: {traceback.format_exc()}")
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 500,
|
||||||
|
'message': f'搜索私有达人失败: {str(e)}',
|
||||||
|
'data': None
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
214
search_api_documentation.md
Normal file
214
search_api_documentation.md
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
# TikTok创作者数据库搜索API文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
基于您的TikTok创作者数据库界面,我们实现了三个强大的搜索API,支持关键词搜索、高级筛选和搜索建议功能。
|
||||||
|
|
||||||
|
## API接口列表
|
||||||
|
|
||||||
|
### 1. 基础搜索API
|
||||||
|
|
||||||
|
**接口地址:** `GET /creators/search/`
|
||||||
|
|
||||||
|
**功能描述:** 基于关键词搜索创作者,支持搜索创作者名称、分类、MCN、曝光等级等字段
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
- `q` (string, 可选): 搜索关键词
|
||||||
|
- `page` (int, 可选): 页码,默认为1
|
||||||
|
- `page_size` (int, 可选): 每页数量,默认为10
|
||||||
|
|
||||||
|
**请求示例:**
|
||||||
|
```
|
||||||
|
GET /creators/search/?q=beauty&page=1&page_size=10
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "搜索成功",
|
||||||
|
"data": {
|
||||||
|
"creators": [
|
||||||
|
{
|
||||||
|
"Creator": {
|
||||||
|
"name": "Beauty Guru",
|
||||||
|
"avatar": "https://example.com/avatar.jpg"
|
||||||
|
},
|
||||||
|
"Category": "Beauty & Personal Care",
|
||||||
|
"E-commerce Level": "L3",
|
||||||
|
"Exposure Level": "KOL-2",
|
||||||
|
"Followers": "162k",
|
||||||
|
"GMV": "$534k",
|
||||||
|
"Items Sold": "18k",
|
||||||
|
"Avg. Video Views": "1.9k",
|
||||||
|
"Pricing": "$80",
|
||||||
|
"Pricing Package": "Standard Package",
|
||||||
|
"# Collab": 15,
|
||||||
|
"Latest Collab.": "2024-01-15",
|
||||||
|
"E-commerce": ["TikTok Shop", "Instagram"],
|
||||||
|
"creator_id": 123,
|
||||||
|
"profile": "tiktok"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagination": {
|
||||||
|
"current_page": 1,
|
||||||
|
"total_pages": 5,
|
||||||
|
"total_count": 50,
|
||||||
|
"has_next": true,
|
||||||
|
"has_prev": false,
|
||||||
|
"page_size": 10
|
||||||
|
},
|
||||||
|
"search_info": {
|
||||||
|
"query": "beauty",
|
||||||
|
"results_count": 50,
|
||||||
|
"search_applied": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 高级搜索API
|
||||||
|
|
||||||
|
**接口地址:** `POST /creators/search/advanced/`
|
||||||
|
|
||||||
|
**功能描述:** 支持关键词搜索 + 多维度筛选条件的组合搜索
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
- URL参数:
|
||||||
|
- `page` (int, 可选): 页码,默认为1
|
||||||
|
- `page_size` (int, 可选): 每页数量,默认为10
|
||||||
|
|
||||||
|
- POST请求体:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"q": "beauty", // 搜索关键词
|
||||||
|
"filters": {
|
||||||
|
"category": ["Beauty & Personal Care", "Fashion"], // 分类多选
|
||||||
|
"e_commerce_level": ["L2", "L3", "L4"], // 电商等级多选
|
||||||
|
"exposure_level": ["KOL-1", "KOL-2"], // 曝光等级多选
|
||||||
|
"gmv_range": ["$5k-$25k", "$25k-$50k"], // GMV范围多选
|
||||||
|
"views_range": ["1000,10000"], // 观看量范围(新格式:最小值,最大值)
|
||||||
|
"followers_range": "10000,50000" // 粉丝数范围(格式:最小值,最大值)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求示例:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "/creators/search/advanced/?page=1&page_size=10" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Token your_token_here" \
|
||||||
|
-d '{
|
||||||
|
"q": "beauty",
|
||||||
|
"filters": {
|
||||||
|
"category": ["Beauty & Personal Care"],
|
||||||
|
"e_commerce_level": ["L2", "L3"],
|
||||||
|
"gmv_range": ["$5k-$25k"]
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应格式:** 与基础搜索API相同,但增加了`filters_applied`字段
|
||||||
|
|
||||||
|
### 3. 搜索建议API
|
||||||
|
|
||||||
|
**接口地址:** `GET /creators/search/suggestions/`
|
||||||
|
|
||||||
|
**功能描述:** 提供搜索关键词的自动补全建议
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
- `q` (string, 必需): 搜索关键词(至少2个字符)
|
||||||
|
- `limit` (int, 可选): 建议数量限制,默认为10
|
||||||
|
|
||||||
|
**请求示例:**
|
||||||
|
```
|
||||||
|
GET /creators/search/suggestions/?q=bea&limit=5
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "获取搜索建议成功",
|
||||||
|
"data": {
|
||||||
|
"query": "bea",
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"text": "Beauty Guru",
|
||||||
|
"type": "creator_name",
|
||||||
|
"label": "创作者: Beauty Guru"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Beauty & Personal Care",
|
||||||
|
"type": "category",
|
||||||
|
"label": "分类: Beauty & Personal Care"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 搜索功能特性
|
||||||
|
|
||||||
|
### 1. 智能搜索
|
||||||
|
- **多字段搜索:** 同时搜索创作者名称、分类、MCN、曝光等级、电商平台
|
||||||
|
- **模糊匹配:** 支持部分关键词匹配
|
||||||
|
- **相关性排序:** 优先显示完全匹配和前缀匹配的结果
|
||||||
|
|
||||||
|
### 2. 高级筛选
|
||||||
|
- **分类筛选:** 支持多选分类过滤
|
||||||
|
- **电商等级:** L1-L7等级筛选
|
||||||
|
- **曝光等级:** KOL-1, KOL-2, KOC-1等筛选
|
||||||
|
- **GMV范围:** 多个价格区间选择
|
||||||
|
- **观看量范围:** 自定义数值范围
|
||||||
|
- **粉丝数范围:** 自定义粉丝数区间
|
||||||
|
|
||||||
|
### 3. 搜索建议
|
||||||
|
- **实时建议:** 输入2个字符即可获得建议
|
||||||
|
- **分类建议:** 区分创作者名称和分类建议
|
||||||
|
- **去重处理:** 自动去除重复建议
|
||||||
|
|
||||||
|
## 数据格式说明
|
||||||
|
|
||||||
|
### 筛选条件格式
|
||||||
|
|
||||||
|
1. **观看量范围 (views_range):**
|
||||||
|
- 新格式:`["1000,10000"]` (最小值,最大值)
|
||||||
|
- 兼容旧格式:`["1k-10k"]`
|
||||||
|
|
||||||
|
2. **粉丝数范围 (followers_range):**
|
||||||
|
- 格式:`"10000,50000"` (最小值,最大值)
|
||||||
|
|
||||||
|
3. **GMV范围 (gmv_range):**
|
||||||
|
- 预定义选项:`["$0-$5k", "$5k-$25k", "$25k-$50k", "$50k-$150k", "$150k-$400k", "$400k-$1500k", "$1500k+"]`
|
||||||
|
|
||||||
|
### 响应数据格式
|
||||||
|
|
||||||
|
所有搜索API返回的创作者数据格式与您现有的筛选API保持一致,确保前端兼容性。
|
||||||
|
|
||||||
|
## 使用建议
|
||||||
|
|
||||||
|
1. **基础搜索:** 适用于简单的关键词搜索场景
|
||||||
|
2. **高级搜索:** 适用于需要复杂筛选条件的场景
|
||||||
|
3. **搜索建议:** 配合搜索框实现自动补全功能
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
所有API都包含统一的错误处理机制:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"message": "搜索创作者失败: 具体错误信息",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 认证要求
|
||||||
|
|
||||||
|
所有搜索API都需要使用`CustomTokenAuthentication`进行身份验证:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Token your_token_here
|
||||||
|
```
|
174
test_search_api.py
Normal file
174
test_search_api.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
TikTok创作者数据库搜索API测试脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 配置
|
||||||
|
BASE_URL = "http://localhost:8000" # 根据您的服务器地址调整
|
||||||
|
TOKEN = "your_token_here" # 替换为实际的认证token
|
||||||
|
|
||||||
|
# 请求头
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Token {TOKEN}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_basic_search():
|
||||||
|
"""测试基础搜索功能"""
|
||||||
|
print("=== 测试基础搜索功能 ===")
|
||||||
|
|
||||||
|
# 测试1: 无关键词搜索(获取所有创作者)
|
||||||
|
print("\n1. 测试无关键词搜索:")
|
||||||
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/creators/search/",
|
||||||
|
headers=HEADERS,
|
||||||
|
params={"page": 1, "page_size": 5}
|
||||||
|
)
|
||||||
|
print(f"状态码: {response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
print(f"总数量: {data['data']['pagination']['total_count']}")
|
||||||
|
print(f"返回数量: {len(data['data']['creators'])}")
|
||||||
|
else:
|
||||||
|
print(f"错误: {response.text}")
|
||||||
|
|
||||||
|
# 测试2: 关键词搜索
|
||||||
|
print("\n2. 测试关键词搜索 (搜索'beauty'):")
|
||||||
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/creators/search/",
|
||||||
|
headers=HEADERS,
|
||||||
|
params={"q": "beauty", "page": 1, "page_size": 5}
|
||||||
|
)
|
||||||
|
print(f"状态码: {response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
print(f"搜索结果数量: {data['data']['search_info']['results_count']}")
|
||||||
|
print(f"搜索关键词: {data['data']['search_info']['query']}")
|
||||||
|
if data['data']['creators']:
|
||||||
|
print(f"第一个结果: {data['data']['creators'][0]['Creator']['name']}")
|
||||||
|
else:
|
||||||
|
print(f"错误: {response.text}")
|
||||||
|
|
||||||
|
def test_advanced_search():
|
||||||
|
"""测试高级搜索功能"""
|
||||||
|
print("\n=== 测试高级搜索功能 ===")
|
||||||
|
|
||||||
|
# 测试高级搜索
|
||||||
|
search_data = {
|
||||||
|
"q": "beauty",
|
||||||
|
"filters": {
|
||||||
|
"category": ["Beauty & Personal Care", "Fashion"],
|
||||||
|
"e_commerce_level": ["L2", "L3"],
|
||||||
|
"gmv_range": ["$5k-$25k", "$25k-$50k"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"\n搜索条件: {json.dumps(search_data, indent=2, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{BASE_URL}/creators/search/advanced/",
|
||||||
|
headers=HEADERS,
|
||||||
|
params={"page": 1, "page_size": 5},
|
||||||
|
json=search_data
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"状态码: {response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
print(f"搜索结果数量: {data['data']['search_info']['results_count']}")
|
||||||
|
print(f"应用的筛选条件: {data['data']['search_info']['filters_applied']}")
|
||||||
|
if data['data']['creators']:
|
||||||
|
creator = data['data']['creators'][0]
|
||||||
|
print(f"第一个结果:")
|
||||||
|
print(f" - 名称: {creator['Creator']['name']}")
|
||||||
|
print(f" - 分类: {creator['Category']}")
|
||||||
|
print(f" - 电商等级: {creator['E-commerce Level']}")
|
||||||
|
print(f" - GMV: {creator['GMV']}")
|
||||||
|
else:
|
||||||
|
print(f"错误: {response.text}")
|
||||||
|
|
||||||
|
def test_search_suggestions():
|
||||||
|
"""测试搜索建议功能"""
|
||||||
|
print("\n=== 测试搜索建议功能 ===")
|
||||||
|
|
||||||
|
# 测试搜索建议
|
||||||
|
test_queries = ["bea", "spo", "fas"]
|
||||||
|
|
||||||
|
for query in test_queries:
|
||||||
|
print(f"\n搜索建议 '{query}':")
|
||||||
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/creators/search/suggestions/",
|
||||||
|
headers=HEADERS,
|
||||||
|
params={"q": query, "limit": 5}
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"状态码: {response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
suggestions = data['data']['suggestions']
|
||||||
|
print(f"建议数量: {len(suggestions)}")
|
||||||
|
for suggestion in suggestions:
|
||||||
|
print(f" - {suggestion['label']}")
|
||||||
|
else:
|
||||||
|
print(f"错误: {response.text}")
|
||||||
|
|
||||||
|
def test_pagination():
|
||||||
|
"""测试分页功能"""
|
||||||
|
print("\n=== 测试分页功能 ===")
|
||||||
|
|
||||||
|
# 测试分页
|
||||||
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/creators/search/",
|
||||||
|
headers=HEADERS,
|
||||||
|
params={"page": 1, "page_size": 3}
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"状态码: {response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
pagination = data['data']['pagination']
|
||||||
|
print(f"分页信息:")
|
||||||
|
print(f" - 当前页: {pagination['current_page']}")
|
||||||
|
print(f" - 总页数: {pagination['total_pages']}")
|
||||||
|
print(f" - 总数量: {pagination['total_count']}")
|
||||||
|
print(f" - 有下一页: {pagination['has_next']}")
|
||||||
|
print(f" - 有上一页: {pagination['has_prev']}")
|
||||||
|
print(f" - 每页数量: {pagination['page_size']}")
|
||||||
|
else:
|
||||||
|
print(f"错误: {response.text}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主测试函数"""
|
||||||
|
print("TikTok创作者数据库搜索API测试")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 运行所有测试
|
||||||
|
test_basic_search()
|
||||||
|
test_advanced_search()
|
||||||
|
test_search_suggestions()
|
||||||
|
test_pagination()
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("所有测试完成!")
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
print("错误: 无法连接到服务器,请确保服务器正在运行")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"测试过程中发生错误: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("请在运行测试前确保:")
|
||||||
|
print("1. 服务器正在运行")
|
||||||
|
print("2. 已更新BASE_URL和TOKEN配置")
|
||||||
|
print("3. 数据库中有测试数据")
|
||||||
|
print()
|
||||||
|
|
||||||
|
confirm = input("是否继续运行测试? (y/n): ")
|
||||||
|
if confirm.lower() == 'y':
|
||||||
|
main()
|
||||||
|
else:
|
||||||
|
print("测试已取消")
|
Loading…
Reference in New Issue
Block a user