From 1b2b6856c063b37eedb3ca879a5d9dee17865a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=B1=E8=8F=9C?= Date: Sat, 15 Jun 2024 21:54:36 +0800 Subject: [PATCH] feat/mock_log (#218) * feat: add mock log api, django debug mode log sql * feat: add mock log page * feat: shorter project id --------- Co-authored-by: rikasai233 --- FasterRunner/settings/base.py | 33 +-- FasterRunner/urls.py | 7 +- mock/models.py | 11 +- mock/serializers.py | 11 +- mock/views.py | 47 +++- web/src/pages/home/components/Side.vue | 3 +- web/src/pages/mock_server/mock_log/index.vue | 252 +++++++++++++++++++ web/src/router/index.js | 12 +- 8 files changed, 352 insertions(+), 24 deletions(-) create mode 100644 web/src/pages/mock_server/mock_log/index.vue diff --git a/FasterRunner/settings/base.py b/FasterRunner/settings/base.py index 0bedc36..b50836c 100644 --- a/FasterRunner/settings/base.py +++ b/FasterRunner/settings/base.py @@ -30,7 +30,6 @@ DEBUG = False - ALLOWED_HOSTS = ["*"] # Token Settings, 30天过期 @@ -130,10 +129,8 @@ USE_I18N = True - USE_TZ = False - REST_FRAMEWORK = { # 'DEFAULT_AUTHENTICATION_CLASSES': ['FasterRunner.auth.DeleteAuthenticator', 'FasterRunner.auth.Authenticator', ], "DEFAULT_AUTHENTICATION_CLASSES": [ @@ -161,8 +158,12 @@ SWAGGER_SETTINGS = { "DEFAULT_AUTO_SCHEMA_CLASS": "FasterRunner.swagger.CustomSwaggerAutoSchema", # 基础样式 - "SECURITY_DEFINITIONS": { - "basic": {"type": "basic"}, + 'SECURITY_DEFINITIONS': { + 'Bearer': { + 'type': 'apiKey', + 'name': 'Authorization', + 'in': 'header' + } }, # 如果需要登录才能够查看接口文档, 登录的链接使用restframework自带的. # 'LOGIN_URL': 'rest_framework:login', @@ -220,10 +221,10 @@ logger.add( f"logs/{level.lower()}.log", format="{time:YYYY-MM-DD HH:mm:ss.SSS}" - " [pid:{process} -> thread:{thread.name}]" - " {level}" - " [{name}:{function}:{line}]" - " {message}", + " [pid:{process} -> thread:{thread.name}]" + " {level}" + " [{name}:{function}:{line}]" + " {message}", level=level, rotation="00:00", retention="14 days", @@ -239,7 +240,7 @@ "color": { "()": "colorlog.ColoredFormatter", "format": "%(green)s%(asctime)s [%(request_id)s] %(name)s %(log_color)s%(levelname)s [pid:%(process)d] " - "[%(filename)s->%(funcName)s:%(lineno)s] %(cyan)s%(message)s", + "[%(filename)s->%(funcName)s:%(lineno)s] %(cyan)s%(message)s", "log_colors": { "DEBUG": "black", "INFO": "white", @@ -300,6 +301,13 @@ "level": "INFO", "propagate": True, }, + + 'django.db.backends': { + 'level': 'DEBUG', + 'handlers': ['console'], + "propagate": False, + }, + "fastrunner": { "handlers": ["default", "console", "error", "db"], "level": "INFO", @@ -331,11 +339,9 @@ GENERATE_REQUEST_ID_IF_NOT_IN_HEADER = True REQUEST_ID_RESPONSE_HEADER = "TRACE-ID" - # https://github.com/celery/celery/issues/4796 DJANGO_CELERY_BEAT_TZ_AWARE = False - # 邮箱配置 EMAIL_USE_SSL = True EMAIL_HOST = os.environ.get("EMAIL_HOST", "smtp.qq.com") # 如果是 163 改成 smtp.163.com @@ -377,6 +383,5 @@ # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.AutoField" - # mock配置, 如果是性能测试环境,就设置为1,会关闭写日志, db -IS_PERF = os.environ.get("IS_PERF", "0") \ No newline at end of file +IS_PERF = os.environ.get("IS_PERF", "0") diff --git a/FasterRunner/urls.py b/FasterRunner/urls.py index 33d5a42..b89d6af 100644 --- a/FasterRunner/urls.py +++ b/FasterRunner/urls.py @@ -22,7 +22,7 @@ from rest_framework_jwt.views import obtain_jwt_token from fastrunner.views import run_all_auto_case -from mock.views import MockAPIView, MockAPIViewset, MockProjectViewSet +from mock.views import MockAPIView, MockAPIViewset, MockProjectViewSet, MockAPILogViewSet from system import views as system_views schema_view = get_schema_view( @@ -35,7 +35,7 @@ license=openapi.License(name="BSD License"), ), public=True, - permission_classes=(permissions.AllowAny,), + permission_classes=[permissions.AllowAny,], authentication_classes=[], ) system_router = DefaultRouter() @@ -47,6 +47,7 @@ mock_project_router = DefaultRouter() mock_project_router.register(r"mock_project", MockProjectViewSet) +mock_project_router.register(r"mock_log", MockAPILogViewSet) urlpatterns = [ path("api/mock/", include(mock_project_router.urls)), @@ -70,7 +71,7 @@ path("get_report_url/", run_all_auto_case.get_report_url, name="get_report_url"), # swagger re_path(r"^swagger(?P\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), name="schema-json"), - path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), + path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui",), path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), re_path(r'^mock/(?P\w+)(?P/.*)$', MockAPIView.as_view()) ] diff --git a/mock/models.py b/mock/models.py index 9b3d8b0..677ea16 100644 --- a/mock/models.py +++ b/mock/models.py @@ -1,3 +1,6 @@ +import random +import string +import time import uuid from django.db import models @@ -8,8 +11,14 @@ def generate_uuid(): return uuid.uuid4().hex +def generate_short_id(): + timestamp = int(time.time() * 1000) + random_string = ''.join(random.choices(string.ascii_letters + string.digits, k=5)) + return f'{timestamp}{random_string}' + + class MockProject(BaseTable): - project_id = models.CharField(max_length=100, unique=True, default=generate_uuid) + project_id = models.CharField(max_length=100, unique=True, default=generate_short_id) project_name = models.CharField(max_length=100) project_desc = models.CharField(max_length=100) is_active = models.BooleanField(default=True) diff --git a/mock/serializers.py b/mock/serializers.py index da9c8f6..b9ce96c 100644 --- a/mock/serializers.py +++ b/mock/serializers.py @@ -9,7 +9,7 @@ from rest_framework import serializers -from .models import MockAPI, MockProject +from .models import MockAPI, MockProject, MockAPILog class MockAPISerializer(serializers.ModelSerializer): @@ -109,3 +109,12 @@ class Meta: "update_time", ] read_only_fields = ["id", "creator", "updater", "create_time", "update_time", "project_id"] + + +class MockAPILogSerializer(serializers.ModelSerializer): + api = MockAPISerializer() + project = MockProjectSerializer() + + class Meta: + model = MockAPILog + fields = '__all__' diff --git a/mock/views.py b/mock/views.py index a59667a..5d6e13b 100644 --- a/mock/views.py +++ b/mock/views.py @@ -3,11 +3,14 @@ import traceback import types import uuid +from datetime import datetime + from django_filters import rest_framework as filters from django_filters.rest_framework import DjangoFilterBackend from drf_yasg.utils import swagger_auto_schema from rest_framework import status, viewsets +from rest_framework.filters import SearchFilter from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -15,7 +18,7 @@ from FasterRunner.customer_swagger import CustomSwaggerAutoSchema from .models import MockAPI, MockAPILog, MockProject -from .serializers import MockAPISerializer, MockProjectSerializer +from .serializers import MockAPISerializer, MockProjectSerializer, MockAPILogSerializer logger = logging.getLogger(__name__) @@ -147,8 +150,23 @@ def load_and_execute(module_name, code, method_name, request) -> Response: except Exception as e: raise e + +def convert_to_kv(input_dict): + """ + 将包含元组值的字典转换为键值对字典。 + + 参数: + input_dict (dict): 输入字典,其中值为包含两个元素的元组。 + + 返回: + dict: 简单的键值对字典。 + """ + return {value[0]: value[1] for key, value in input_dict.items()} + + def process(path, project_id, request: Request): try: + req_time = datetime.now() if settings.IS_PERF == '0': logger.info(f"request path: {request.get_full_path()}") @@ -157,7 +175,7 @@ def process(path, project_id, request: Request): "path": path, "mock_server_full_path": request.get_full_path(), "body": request.data, - "headers": request.headers._store, + "headers": convert_to_kv(request.headers._store), "query_params": request.query_params, } if settings.IS_PERF == '0': @@ -178,7 +196,7 @@ def process(path, project_id, request: Request): response_obj = { "status": response.status_code, "body": response.data, - "headers": response.headers._store, + "headers": convert_to_kv(response.headers._store), } if settings.IS_PERF == '0': logger.info(f"response_obj: {json.dumps(response_obj, indent=4)}") @@ -187,6 +205,7 @@ def process(path, project_id, request: Request): request_id=request_id, api_id=mock_api.api_id, project_id=mock_api.project, + create_time=req_time ) log_obj.response_obj = response_obj log_obj.save() @@ -245,3 +264,25 @@ class MockProjectViewSet(viewsets.ModelViewSet): serializer_class = MockProjectSerializer filter_backends = [DjangoFilterBackend] filterset_class = MockProjectFilter + + +class MockAPILogFilter(filters.FilterSet): + request_id = filters.CharFilter(lookup_expr="icontains") + request_path = filters.CharFilter(method="filter_by_request_path") + + class Meta: + model = MockAPILog + fields = ["request_id", "request_path"] + + def filter_by_request_path(self, queryset, name, value): + return queryset.filter(api__request_path__icontains=value) + + +class MockAPILogViewSet(viewsets.ModelViewSet): + swagger_tag = 'Mock API Log CRUD' + swagger_schema = CustomSwaggerAutoSchema + queryset = MockAPILog.objects.select_related('api', 'project').all() + serializer_class = MockAPILogSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_class = MockAPILogFilter + search_fields = ['request_id', 'api__request_path'] diff --git a/web/src/pages/home/components/Side.vue b/web/src/pages/home/components/Side.vue index 3b8eb52..c662445 100644 --- a/web/src/pages/home/components/Side.vue +++ b/web/src/pages/home/components/Side.vue @@ -39,7 +39,8 @@ export default { icon: 'el-icon-s-help', children: [ // 子菜单项 {name: "Mock 项目", url: "MockProject", icon: 'el-icon-folder-opened'}, - {name: "Mock APIs", url: "MockAPIs", icon: 'el-icon-document-copy'} + {name: "Mock APIs", url: "MockAPIs", icon: 'el-icon-document-copy'}, + {name: "Mock Log", url: "MockLog", icon: 'el-icon-data-board'} ] } ] diff --git a/web/src/pages/mock_server/mock_log/index.vue b/web/src/pages/mock_server/mock_log/index.vue new file mode 100644 index 0000000..3510ed8 --- /dev/null +++ b/web/src/pages/mock_server/mock_log/index.vue @@ -0,0 +1,252 @@ + + + + diff --git a/web/src/router/index.js b/web/src/router/index.js index 5205a77..1d50343 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -14,6 +14,7 @@ import RecordConfig from '@/pages/fastrunner/config/RecordConfig' import Tasks from '@/pages/task/Tasks' import HostAddress from '@/pages/variables/HostAddress' import MockProject from "@/pages/mock_server/mock_project/index.vue"; +import MockLog from "@/pages/mock_server/mock_log/index.vue"; import MockAPI from "@/pages/mock_server/mock_api/index.vue"; import CommonLayout from "@/pages/common/layout/CommonLayout.vue"; @@ -174,7 +175,16 @@ export default new Router({ path: 'mock_apis/:id', component: MockAPI, meta: { - title: 'Mock APIs', + title: 'Mock API', + requireAuth: true + } + }, + { + name: 'MockLog', + path: 'mock_log/:id', + component: MockLog, + meta: { + title: 'MockLog', requireAuth: true } }