from django.http import JsonResponse from rest_framework.views import APIView from rest_framework import status from rest_framework.permissions import AllowAny, IsAuthenticated from django.db.models import Count, Q from ..models import ToolCategory, Tool, ToolFavorite from ..serializers import ToolCategorySerializer, ToolSerializer 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 class ToolCategoryListView(APIView): permission_classes = [AllowAny] @swagger_auto_schema( operation_summary='获取工具分类列表', operation_description='获取所有工具分类及其关联的工具列表', tags=['工具'], responses={200: success_response} ) def get(self, request): categories = ToolCategory.objects.prefetch_related('tools').all() serializer = ToolCategorySerializer(categories, many=True) return JsonResponse({ 'success': True, 'data': serializer.data }) class ToolListView(APIView): permission_classes = [AllowAny] @swagger_auto_schema( operation_summary='获取工具列表', operation_description='按分类获取工具列表,支持启用状态过滤', tags=['工具'], manual_parameters=[ openapi.Parameter('category_id', openapi.IN_QUERY, description='分类ID', type=openapi.TYPE_INTEGER), openapi.Parameter('enabled_only', openapi.IN_QUERY, description='是否只显示启用的工具', type=openapi.TYPE_STRING), openapi.Parameter('ordering', openapi.IN_QUERY, description='排序字段', type=openapi.TYPE_STRING), ], responses={200: success_response} ) def get(self, request): category_id = request.query_params.get('category_id') enabled_only = request.query_params.get('enabled_only', 'true') ordering = request.query_params.get('ordering', '') tools = Tool.objects.select_related('category').annotate( annotated_favorites_count=Count('tool_favorites') ) if enabled_only == 'true': tools = tools.filter(is_enabled=True) if category_id: tools = tools.filter(category_id=category_id) if ordering: # 支持按 favorites_count 排序 if ordering == '-favorites_count': tools = tools.order_by('-annotated_favorites_count') elif ordering == 'favorites_count': tools = tools.order_by('annotated_favorites_count') else: tools = tools.order_by(ordering) else: tools = tools.order_by('sort_order', '-created_at') serializer = ToolSerializer(tools, many=True) data = serializer.data # 构建 favorites_count 映射 favorites_count_map = { t.id: t.annotated_favorites_count for t in tools } if request.user.is_authenticated: favorite_tool_ids = set( ToolFavorite.objects.filter(user=request.user).values_list('tool_id', flat=True) ) for item in data: item['is_favorited'] = item['id'] in favorite_tool_ids item['favorites_count'] = favorites_count_map.get(item['id'], 0) else: for item in data: item['is_favorited'] = False item['favorites_count'] = favorites_count_map.get(item['id'], 0) return JsonResponse({ 'success': True, 'data': data }) class ToolDetailView(APIView): permission_classes = [AllowAny] @swagger_auto_schema( operation_summary='获取工具详情', operation_description='获取单个工具的详细信息', tags=['工具'], manual_parameters=[ openapi.Parameter('pk', openapi.IN_PATH, description='工具ID', type=openapi.TYPE_INTEGER, required=True) ], responses={200: success_response, 404: not_found_response} ) def get(self, request, pk): try: tool = Tool.objects.select_related('category').get(pk=pk, is_enabled=True) serializer = ToolSerializer(tool) data = serializer.data if request.user.is_authenticated: data['is_favorited'] = ToolFavorite.objects.filter( user=request.user, tool=tool ).exists() else: data['is_favorited'] = False data['favorites_count'] = ToolFavorite.objects.filter(tool=tool).count() return JsonResponse({ 'success': True, 'data': data }) except Tool.DoesNotExist: return JsonResponse( {'success': False, 'error': '工具不存在'}, status=status.HTTP_404_NOT_FOUND ) class ToolFavoriteToggleView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( operation_summary='切换工具收藏状态', operation_description='添加或取消工具收藏', tags=['工具'], manual_parameters=[ openapi.Parameter('pk', openapi.IN_PATH, description='工具ID', type=openapi.TYPE_INTEGER, required=True) ], responses={200: success_response, 401: unauthorized_response, 404: not_found_response} ) def post(self, request, pk): tool = Tool.objects.filter(pk=pk, is_enabled=True).first() if not tool: return JsonResponse( {'success': False, 'error': '工具不存在'}, status=status.HTTP_404_NOT_FOUND ) fav, created = ToolFavorite.objects.get_or_create(user=request.user, tool=tool) if not created: fav.delete() is_favorited = False else: is_favorited = True favorites_count = ToolFavorite.objects.filter(tool=tool).count() return JsonResponse({ 'success': True, 'data': { 'id': tool.id, 'is_favorited': is_favorited, 'favorites_count': favorites_count, } }) class ToolFavoriteListView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( operation_summary='获取我的收藏工具列表', operation_description='获取当前用户收藏的所有工具', tags=['工具'], responses={200: success_response, 401: unauthorized_response} ) def get(self, request): favorites = ToolFavorite.objects.select_related('tool', 'tool__category').filter( user=request.user, tool__is_enabled=True ).order_by('-created_at') result = [] for fav in favorites: tool_data = ToolSerializer(fav.tool).data tool_data['is_favorited'] = True tool_data['favorites_count'] = ToolFavorite.objects.filter(tool=fav.tool).count() result.append(tool_data) return JsonResponse({ 'success': True, 'data': result }) class ToolUsageIncrementView(APIView): """工具使用次数递增接口""" permission_classes = [AllowAny] @swagger_auto_schema( operation_summary='递增工具使用次数', operation_description='用户点击工具时调用,递增该工具的使用次数(基于会话去重,同一会话5分钟内不重复计数)', tags=['工具'], manual_parameters=[ openapi.Parameter('pk', openapi.IN_PATH, description='工具ID', type=openapi.TYPE_INTEGER, required=True) ], responses={200: success_response, 404: not_found_response} ) def post(self, request, pk): try: tool = Tool.objects.get(pk=pk, is_enabled=True) # 使用F()表达式避免竞态条件 from django.db.models import F Tool.objects.filter(pk=pk).update(usage_count=F('usage_count') + 1) tool.refresh_from_db() return JsonResponse({ 'success': True, 'data': { 'id': tool.id, 'usage_count': tool.usage_count, } }) except Tool.DoesNotExist: return JsonResponse( {'success': False, 'error': '工具不存在'}, status=status.HTTP_404_NOT_FOUND )