sync from local backup
This commit is contained in:
+239
@@ -0,0 +1,239 @@
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import AllowAny
|
||||
from django.db.models import Q
|
||||
from django.core.paginator import Paginator
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
from chunyu_project.common_schemas import success_response, error_response, unauthorized_response, not_found_response
|
||||
|
||||
from article.models import Article
|
||||
from tool.models import Tool
|
||||
from learn.models import Course
|
||||
from apidirectory.models import ApiItem
|
||||
|
||||
from utils.response_codes import ResponseCode, create_standardized_response
|
||||
|
||||
|
||||
SORT_MAPPING = {
|
||||
'1': 'relevance',
|
||||
'2': 'views',
|
||||
'3': 'newest',
|
||||
}
|
||||
|
||||
|
||||
def normalize_sort(sort_value):
|
||||
return SORT_MAPPING.get(sort_value, sort_value)
|
||||
|
||||
|
||||
class GlobalSearchView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=['搜索'],
|
||||
operation_summary='全局搜索',
|
||||
operation_description='根据关键词搜索文章、工具、课程和API,支持分页和排序',
|
||||
manual_parameters=[
|
||||
openapi.Parameter('q', openapi.IN_QUERY, description='搜索关键词', type=openapi.TYPE_STRING, required=True),
|
||||
openapi.Parameter('type', openapi.IN_QUERY, description='内容类型筛选: all/article/tool/course/api', type=openapi.TYPE_STRING),
|
||||
openapi.Parameter('page', openapi.IN_QUERY, description='页码', type=openapi.TYPE_INTEGER),
|
||||
openapi.Parameter('page_size', openapi.IN_QUERY, description='每页数量', type=openapi.TYPE_INTEGER),
|
||||
openapi.Parameter('sort', openapi.IN_QUERY, description='排序方式: relevance/views/newest (或数字 1/2/3)', type=openapi.TYPE_STRING),
|
||||
],
|
||||
responses={200: success_response}
|
||||
)
|
||||
def get(self, request):
|
||||
q = request.query_params.get('q', '').strip()
|
||||
content_type = request.query_params.get('type', 'all')
|
||||
page = int(request.query_params.get('page', 1))
|
||||
page_size = int(request.query_params.get('page_size', 12))
|
||||
sort = normalize_sort(request.query_params.get('sort', 'relevance'))
|
||||
|
||||
if not q:
|
||||
return create_standardized_response(data={
|
||||
'results': [],
|
||||
'total': 0,
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
}, code=ResponseCode.SUCCESS)
|
||||
|
||||
results = []
|
||||
|
||||
if content_type == 'all' or content_type == 'article':
|
||||
articles = Article.objects.filter(
|
||||
Q(title__icontains=q) | Q(excerpt__icontains=q) | Q(content__icontains=q)
|
||||
).filter(status='published').order_by('-created_at')[:100]
|
||||
|
||||
for article in articles:
|
||||
cover_url = ''
|
||||
if article.cover and hasattr(article.cover, 'url'):
|
||||
cover_url = request.build_absolute_uri(article.cover.url)
|
||||
|
||||
excerpt = article.excerpt or ''
|
||||
if not excerpt and article.content:
|
||||
excerpt = article.content[:120] + '...' if len(article.content) > 120 else article.content
|
||||
|
||||
results.append({
|
||||
'id': article.id,
|
||||
'type': '文章',
|
||||
'title': article.title,
|
||||
'desc': excerpt,
|
||||
'cover': cover_url,
|
||||
'views': article.views or 0,
|
||||
'likes': article.likes or 0,
|
||||
'url': f'/articles/{article.id}',
|
||||
})
|
||||
|
||||
if content_type == 'all' or content_type == 'tool':
|
||||
tools = Tool.objects.filter(
|
||||
Q(name__icontains=q) | Q(description__icontains=q)
|
||||
).order_by('-created_at')[:100]
|
||||
|
||||
for tool in tools:
|
||||
cover_url = ''
|
||||
if tool.icon and hasattr(tool.icon, 'url'):
|
||||
cover_url = request.build_absolute_uri(tool.icon.url)
|
||||
|
||||
results.append({
|
||||
'id': tool.id,
|
||||
'type': '工具',
|
||||
'title': tool.name,
|
||||
'desc': tool.description or '',
|
||||
'cover': cover_url,
|
||||
'views': tool.views or 0,
|
||||
'likes': tool.likes or 0,
|
||||
'url': f'/use?tool={tool.id}',
|
||||
})
|
||||
|
||||
if content_type == 'all' or content_type == 'course':
|
||||
courses = Course.objects.filter(
|
||||
Q(title__icontains=q) | Q(description__icontains=q)
|
||||
).order_by('-created_at')[:100]
|
||||
|
||||
for course in courses:
|
||||
cover_url = ''
|
||||
if course.cover and hasattr(course.cover, 'url'):
|
||||
cover_url = request.build_absolute_uri(course.cover.url)
|
||||
|
||||
results.append({
|
||||
'id': course.id,
|
||||
'type': '课程',
|
||||
'title': course.title,
|
||||
'desc': course.description or '',
|
||||
'cover': cover_url,
|
||||
'views': course.views or 0,
|
||||
'likes': course.likes or 0,
|
||||
'url': f'/courses/{course.id}',
|
||||
})
|
||||
|
||||
if content_type == 'all' or content_type == 'api':
|
||||
apis = ApiItem.objects.filter(
|
||||
Q(name__icontains=q) | Q(description__icontains=q) | Q(url_path__icontains=q)
|
||||
).filter(is_enabled=True).order_by('-created_at')[:100]
|
||||
|
||||
for api in apis:
|
||||
desc = api.description or f'{api.method} {api.url_path}'
|
||||
results.append({
|
||||
'id': api.id,
|
||||
'type': 'API',
|
||||
'title': api.name,
|
||||
'desc': desc,
|
||||
'cover': api.image_url or '',
|
||||
'views': api.views_count or 0,
|
||||
'likes': 0,
|
||||
'url': f'/api-detail/{api.id}',
|
||||
})
|
||||
|
||||
if sort == 'views':
|
||||
results.sort(key=lambda x: x['views'], reverse=True)
|
||||
elif sort == 'newest':
|
||||
pass
|
||||
else:
|
||||
results.sort(key=lambda x: x['views'] + x['likes'] * 10, reverse=True)
|
||||
|
||||
paginator = Paginator(results, page_size)
|
||||
total = len(results)
|
||||
|
||||
try:
|
||||
paginated_results = paginator.page(page)
|
||||
except Exception:
|
||||
paginated_results = []
|
||||
|
||||
return create_standardized_response(data={
|
||||
'results': list(paginated_results) if hasattr(paginated_results, '__iter__') else paginated_results,
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
}, code=ResponseCode.SUCCESS)
|
||||
|
||||
|
||||
class SearchSuggestionsView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=['搜索'],
|
||||
operation_summary='搜索建议',
|
||||
operation_description='根据输入前缀返回搜索建议列表',
|
||||
manual_parameters=[
|
||||
openapi.Parameter('q', openapi.IN_QUERY, description='搜索前缀', type=openapi.TYPE_STRING, required=True),
|
||||
openapi.Parameter('limit', openapi.IN_QUERY, description='返回数量上限', type=openapi.TYPE_INTEGER),
|
||||
],
|
||||
responses={200: success_response}
|
||||
)
|
||||
def get(self, request):
|
||||
q = request.query_params.get('q', '').strip()
|
||||
limit = int(request.query_params.get('limit', 8))
|
||||
|
||||
if not q:
|
||||
return create_standardized_response(data=[], code=ResponseCode.SUCCESS)
|
||||
|
||||
suggestions = set()
|
||||
|
||||
article_titles = Article.objects.filter(
|
||||
title__icontains=q
|
||||
).filter(status='published').values_list('title', flat=True)[:5]
|
||||
suggestions.update(article_titles)
|
||||
|
||||
tool_names = Tool.objects.filter(
|
||||
name__icontains=q
|
||||
).values_list('name', flat=True)[:5]
|
||||
suggestions.update(tool_names)
|
||||
|
||||
course_titles = Course.objects.filter(
|
||||
title__icontains=q
|
||||
).values_list('title', flat=True)[:5]
|
||||
suggestions.update(course_titles)
|
||||
|
||||
api_names = ApiItem.objects.filter(
|
||||
name__icontains=q
|
||||
).filter(is_enabled=True).values_list('name', flat=True)[:5]
|
||||
suggestions.update(api_names)
|
||||
|
||||
result = list(suggestions)[:limit]
|
||||
return create_standardized_response(data=result, code=ResponseCode.SUCCESS)
|
||||
|
||||
|
||||
class HotKeywordsView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=['搜索'],
|
||||
operation_summary='获取热门搜索关键词',
|
||||
operation_description='返回热门搜索关键词列表',
|
||||
responses={200: success_response}
|
||||
)
|
||||
def get(self, request):
|
||||
hot_keywords = [
|
||||
'React Hooks',
|
||||
'RESTful API',
|
||||
'TypeScript',
|
||||
'Docker部署',
|
||||
'GraphQL',
|
||||
'微服务架构',
|
||||
'CI/CD',
|
||||
'性能优化',
|
||||
'Vue3',
|
||||
'Python',
|
||||
]
|
||||
return create_standardized_response(data=hot_keywords, code=ResponseCode.SUCCESS)
|
||||
Reference in New Issue
Block a user