diff --git a/apps/application/serializers/application_statistics_serializers.py b/apps/application/serializers/application_statistics_serializers.py new file mode 100644 index 000000000..3378210a8 --- /dev/null +++ b/apps/application/serializers/application_statistics_serializers.py @@ -0,0 +1,76 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: application_statistics_serializers.py + @date:2024/3/27 10:55 + @desc: +""" +import datetime +import os + +from django.db import models +from django.db.models.query import QuerySet +from rest_framework import serializers + +from application.models.api_key_model import ApplicationPublicAccessClient +from common.db.search import native_search, get_dynamics_model +from common.util.field_message import ErrMessage +from common.util.file_util import get_file_content +from smartdoc.conf import PROJECT_DIR + + +class ApplicationStatisticsSerializer(serializers.Serializer): + application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char("应用id")) + history_day = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("历史天数")) + + def get_end_time(self): + history_day = self.data.get('history_day') + return datetime.datetime.now() - datetime.timedelta(days=history_day) + + def get_customer_count(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + return native_search( + QuerySet(ApplicationPublicAccessClient).filter(application_id=self.data.get('application_id'), + create_time__gte=self.get_end_time()), + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count.sql')), + with_search_one=True) + + def get_customer_count_trend(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + return native_search( + {'default_sql': QuerySet(ApplicationPublicAccessClient).filter( + application_id=self.data.get('application_id'), + create_time__gte=self.get_end_time())}, + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count_trend.sql'))) + + def get_chat_record_aggregate(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + return native_search( + QuerySet(model=get_dynamics_model( + {'application_chat.application_id': models.UUIDField(), + 'application_chat_record.create_time': models.DateTimeField()})).filter( + **{'application_chat.application_id': self.data.get('application_id'), + 'application_chat_record.create_time__gte': self.get_end_time()} + ), + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count.sql')), + with_search_one=True) + + def get_chat_record_aggregate_trend(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + return native_search( + {'default_sql': QuerySet(model=get_dynamics_model( + {'application_chat.application_id': models.UUIDField(), + 'application_chat_record.create_time': models.DateTimeField()})).filter( + **{'application_chat.application_id': self.data.get('application_id'), + 'application_chat_record.create_time__gte': self.get_end_time()} + )}, + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count_trend.sql'))) diff --git a/apps/application/sql/chat_record_count.sql b/apps/application/sql/chat_record_count.sql new file mode 100644 index 000000000..4acbfa76c --- /dev/null +++ b/apps/application/sql/chat_record_count.sql @@ -0,0 +1,8 @@ +SELECT SUM + ( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS "star_num", + SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num", + SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_sum", + "count"(application_chat_record."id") as chat_record_count +FROM + application_chat_record application_chat_record + LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id \ No newline at end of file diff --git a/apps/application/sql/chat_record_count_trend.sql b/apps/application/sql/chat_record_count_trend.sql new file mode 100644 index 000000000..ac8df3c2a --- /dev/null +++ b/apps/application/sql/chat_record_count_trend.sql @@ -0,0 +1,11 @@ +SELECT SUM + ( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS "star_num", + SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num", + SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_sum", + "count"(application_chat_record."id") as chat_record_count, + application_chat_record.create_time :: DATE as "day" +FROM + application_chat_record application_chat_record + LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id +${default_sql} +GROUP BY "day" \ No newline at end of file diff --git a/apps/application/sql/customer_count.sql b/apps/application/sql/customer_count.sql new file mode 100644 index 000000000..b20b5274f --- /dev/null +++ b/apps/application/sql/customer_count.sql @@ -0,0 +1,5 @@ +SELECT + ( SUM ( CASE WHEN create_time :: DATE = CURRENT_DATE THEN 1 ELSE 0 END ) ) AS "today_added_count", + COUNT ( "application_public_access_client"."id" ) AS "added_count" +FROM + "application_public_access_client" \ No newline at end of file diff --git a/apps/application/sql/customer_count_trend.sql b/apps/application/sql/customer_count_trend.sql new file mode 100644 index 000000000..e0a006235 --- /dev/null +++ b/apps/application/sql/customer_count_trend.sql @@ -0,0 +1,7 @@ +SELECT + COUNT ( "application_public_access_client"."id" ) AS "added_count", + create_time :: DATE as "day" +FROM + "application_public_access_client" +${default_sql} +GROUP BY "day" \ No newline at end of file diff --git a/apps/application/swagger_api/application_statistics_api.py b/apps/application/swagger_api/application_statistics_api.py new file mode 100644 index 000000000..4803cc639 --- /dev/null +++ b/apps/application/swagger_api/application_statistics_api.py @@ -0,0 +1,78 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: application_statistics_api.py + @date:2024/3/27 15:09 + @desc: +""" +from drf_yasg import openapi + +from common.mixins.api_mixin import ApiMixin + + +class ApplicationStatisticsApi(ApiMixin): + @staticmethod + def get_request_params_api(): + return [openapi.Parameter(name='application_id', + in_=openapi.IN_PATH, + type=openapi.TYPE_STRING, + required=True, + description='应用id'), + openapi.Parameter(name='history_day', + in_=openapi.IN_QUERY, + type=openapi.TYPE_NUMBER, + required=True, + description='历史天数'), + + ] + + class ChatRecordAggregate(ApiMixin): + @staticmethod + def get_response_body_api(): + return openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['star_num', 'trample_num', 'tokens_sum', 'chat_record_count'], + properties={ + 'star_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="点赞数量", + description="点赞数量"), + + 'trample_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="点踩数量", description="点踩数量"), + 'tokens_sum': openapi.Schema(type=openapi.TYPE_NUMBER, title="token使用数量", + description="token使用数量"), + 'chat_record_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="对话次数", + description="对话次数"), + 'day': openapi.Schema(type=openapi.TYPE_STRING, + title="日期", + description="日期,只有查询趋势的时候才有该字段"), + } + ) + + class CustomerCountTrend(ApiMixin): + @staticmethod + def get_response_body_api(): + return openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['added_count'], + properties={ + 'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="新增数量", description="新增数量"), + + 'day': openapi.Schema(type=openapi.TYPE_STRING, + title="时间", + description="时间"), + } + ) + + class CustomerCount(ApiMixin): + @staticmethod + def get_response_body_api(): + return openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['added_count'], + properties={ + 'today_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="今日新增数量", + description="今日新增数量"), + 'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="新增数量", description="新增数量"), + + } + ) diff --git a/apps/application/urls.py b/apps/application/urls.py index 9c70def5b..648583198 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -8,6 +8,14 @@ urlpatterns = [ path('application/profile', views.Application.Profile.as_view()), path('application/embed', views.Application.Embed.as_view()), path('application/authentication', views.Application.Authentication.as_view()), + path('application//statistics/customer_count', + views.ApplicationStatistics.CustomerCount.as_view()), + path('application//statistics/customer_count_trend', + views.ApplicationStatistics.CustomerCountTrend.as_view()), + path('application//statistics/chat_record_aggregate', + views.ApplicationStatistics.ChatRecordAggregate.as_view()), + path('application//statistics/chat_record_aggregate_trend', + views.ApplicationStatistics.ChatRecordAggregateTrend.as_view()), path('application//model', views.Application.Model.as_view()), path('application//hit_test', views.Application.HitTest.as_view()), path('application//api_key', views.Application.ApplicationKey.as_view()), diff --git a/apps/application/views/application_views.py b/apps/application/views/application_views.py index d121611b3..0f4d9bf8a 100644 --- a/apps/application/views/application_views.py +++ b/apps/application/views/application_views.py @@ -14,7 +14,9 @@ from rest_framework.request import Request from rest_framework.views import APIView from application.serializers.application_serializers import ApplicationSerializer +from application.serializers.application_statistics_serializers import ApplicationStatisticsSerializer from application.swagger_api.application_api import ApplicationApi +from application.swagger_api.application_statistics_api import ApplicationStatisticsApi from common.auth import TokenAuth, has_permissions from common.constants.permission_constants import CompareConstants, PermissionConstants, Permission, Group, Operate, \ ViewPermission, RoleConstants @@ -25,6 +27,95 @@ from common.util.common import query_params_to_single_dict from dataset.serializers.dataset_serializers import DataSetSerializers +class ApplicationStatistics(APIView): + class CustomerCount(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="用户统计", + operation_id="用户统计", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_response( + ApplicationStatisticsApi.CustomerCount.get_response_body_api()) + ) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'history_day': request.query_params.get( + 'history_day')}).get_customer_count()) + + class CustomerCountTrend(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="用户统计趋势", + operation_id="用户统计趋势", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_array_response( + ApplicationStatisticsApi.CustomerCountTrend.get_response_body_api())) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'history_day': request.query_params.get( + 'history_day')}).get_customer_count_trend()) + + class ChatRecordAggregate(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="对话相关统计", + operation_id="对话相关统计", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_response( + ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api()) + ) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'history_day': request.query_params.get( + 'history_day')}).get_chat_record_aggregate()) + + class ChatRecordAggregateTrend(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="对话相关统计趋势", + operation_id="对话相关统计趋势", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_array_response( + ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api()) + ) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'history_day': request.query_params.get( + 'history_day')}).get_chat_record_aggregate_trend()) + + class Application(APIView): authentication_classes = [TokenAuth]