From 3c748eacd6e8011ddf09efbb3c2635eb15d17d43 Mon Sep 17 00:00:00 2001 From: xie7654 <765462425@qq.com> Date: Sun, 6 Jul 2025 21:40:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=9F=A5=E8=AF=A2=E3=80=81n+1=E9=97=AE=E9=A2=98,=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/settings.py | 24 ++++++++- backend/system/views/dept.py | 50 ++++++++++++++++--- backend/system/views/menu.py | 2 +- backend/system/views/role.py | 2 +- backend/system/views/user.py | 3 +- backend/utils/filters_logs.py | 19 +++++++ web/apps/web-antd/src/api/system/dept.ts | 9 +++- .../web-antd/src/views/system/dept/list.vue | 4 +- 8 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 backend/utils/filters_logs.py diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 1fb84f2..f628620 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -23,7 +23,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = 'django-insecure-m4@pv814c_m^pgpyhz^i96a@mcqh_@m9ccu(17*895t!79e!nb' # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.getenv('DEBUG', 'False') == 'True' +DEBUG = os.getenv('DEBUG', 'True') == 'True' # 演示环境配置 DEMO_MODE = os.getenv('DEMO_MODE', 'False') == 'False' @@ -208,5 +208,27 @@ CELERY_BEAT_SCHEDULE = { } # celery 配置结束 +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'ignore_auth_user': { + '()': 'utils.filters_logs.IgnoreSQLFilter', + } + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'filters': ['ignore_auth_user'], + }, + }, + 'loggers': { + 'django.db.backends': { + 'handlers': ['console'], + 'level': 'DEBUG', # 只在 DEBUG 模式时生效 + }, + } +} + if os.path.exists(os.path.join(BASE_DIR, 'backend/local_settings.py')): from backend.local_settings import * \ No newline at end of file diff --git a/backend/system/views/dept.py b/backend/system/views/dept.py index 9d46f5c..cd4a452 100644 --- a/backend/system/views/dept.py +++ b/backend/system/views/dept.py @@ -1,10 +1,11 @@ +from collections import defaultdict from datetime import timezone, datetime +from django.db import models from django_filters.rest_framework import DjangoFilterBackend from rest_framework import status, serializers from rest_framework.decorators import action from rest_framework.filters import SearchFilter, OrderingFilter -from rest_framework.response import Response from system.models import Dept from utils.custom_model_viewSet import CustomModelViewSet @@ -23,9 +24,9 @@ class DeptSerializer(CustomModelSerializer): def get_children(self, obj): """获取子部门""" - children = obj.children.all().order_by('id') + children = getattr(obj, 'children', []) if children: - return DeptSerializer(children, many=True).data + return DeptSerializer(children.all(), many=True).data return [] def get_status_text(self, obj): @@ -35,7 +36,9 @@ class DeptSerializer(CustomModelSerializer): class DeptViewSet(CustomModelViewSet): """部门管理视图集""" - queryset = Dept.objects.filter(pid__isnull=True).order_by('id', 'status') + queryset = Dept.objects.filter(pid__isnull=True).order_by('id', 'status').select_related('pid').prefetch_related( + models.Prefetch('children', queryset=Dept.objects.order_by('id')) + ) serializer_class = DeptSerializer filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] filterset_fields = ['status', 'pid'] @@ -78,8 +81,39 @@ class DeptViewSet(CustomModelViewSet): @action(detail=False, methods=['get']) def tree(self, request): - """获取部门树形结构""" - queryset = self.get_queryset().filter(pid__isnull=True) - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) + """一次性查出所有部门,构建树形结构""" + all_depts = Dept.objects.all().order_by('sort', 'id') + dept_dict = {} + children_map = defaultdict(list) + # 先序列化为 dict,不递归 + for dept in all_depts: + item = { + 'id': dept.id, + 'pid': dept.pid_id, + 'name': dept.name, + 'status': dept.status, + 'status_text': dept.get_status_display(), + 'create_time': dept.create_time, + 'sort': dept.sort, + 'leader': dept.leader, + 'phone': dept.phone, + 'email': dept.email, + 'remark': dept.remark, + 'children': [], + } + dept_dict[dept.id] = item + if dept.pid_id: + children_map[dept.pid_id].append(item) + + # 构建树 + for dept_id, dept in dept_dict.items(): + dept['children'] = children_map.get(dept_id, []) + + # 返回 pid=None(根部门) + tree = [dept for dept in dept_dict.values() if dept['pid'] is None] + return self._build_response( + data=tree, + message="ok", + status=status.HTTP_200_OK, + ) \ No newline at end of file diff --git a/backend/system/views/menu.py b/backend/system/views/menu.py index 60e25bf..dbc40da 100644 --- a/backend/system/views/menu.py +++ b/backend/system/views/menu.py @@ -91,7 +91,7 @@ class MenuMetaViewSet(viewsets.ModelViewSet): class MenuViewSet(CustomModelViewSet): """菜单管理视图集""" - queryset = Menu.objects.filter(pid__isnull=True).order_by('sort', 'id', 'status') + queryset = Menu.objects.filter(pid__isnull=True).order_by('sort', 'id', 'status').prefetch_related('children') serializer_class = MenuSerializer filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] filterset_fields = ['status', 'type', 'pid', 'name'] diff --git a/backend/system/views/role.py b/backend/system/views/role.py index 058468d..e14a7e1 100644 --- a/backend/system/views/role.py +++ b/backend/system/views/role.py @@ -42,7 +42,7 @@ class RoleFilter(filters.FilterSet): class RoleViewSet(CustomModelViewSet): """角色管理视图集""" - queryset = Role.objects.all() + queryset = Role.objects.all().prefetch_related('permissions') serializer_class = RoleSerializer filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] filterset_class = RoleFilter diff --git a/backend/system/views/user.py b/backend/system/views/user.py index 2b0abde..7b8bbbf 100644 --- a/backend/system/views/user.py +++ b/backend/system/views/user.py @@ -1,3 +1,4 @@ +from django.db.models import Prefetch, F from django.utils import timezone from rest_framework import serializers from rest_framework.authtoken.models import Token @@ -152,7 +153,7 @@ class UserViewSet(CustomModelViewSet): """ 用户数据 视图集 """ - queryset = User.objects.filter(is_deleted=False).order_by('-id') + queryset = User.objects.filter(is_deleted=False).order_by('-id').prefetch_related('role', 'dept', 'post') serializer_class = UserSerializer read_only_fields = ['id', 'create_time', 'update_time', 'login_ip'] filterset_class = UserFilter diff --git a/backend/utils/filters_logs.py b/backend/utils/filters_logs.py new file mode 100644 index 0000000..eb197f0 --- /dev/null +++ b/backend/utils/filters_logs.py @@ -0,0 +1,19 @@ +import logging + +class IgnoreSQLFilter(logging.Filter): + def __init__(self, name=''): + super().__init__(name) + # 后续只要在这里添加关键字即可 + self.ignore_keywords = [ + 'authtoken_token', + '@@lower_case_table_names', + 'SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', + ] + + def filter(self, record): + sql = record.getMessage().strip().lower() + # 如果包含任何一个关键字,则返回 False(不打印) + for keyword in self.ignore_keywords: + if keyword.lower() in sql: + return False + return True \ No newline at end of file diff --git a/web/apps/web-antd/src/api/system/dept.ts b/web/apps/web-antd/src/api/system/dept.ts index 950c920..4863353 100644 --- a/web/apps/web-antd/src/api/system/dept.ts +++ b/web/apps/web-antd/src/api/system/dept.ts @@ -53,4 +53,11 @@ async function deleteDept(id: string) { return requestClient.delete(`/system/dept/${id}/`); } -export { createDept, deleteDept, getDeptList, updateDept }; +/** + * 查询tree + */ +async function treeDept() { + return requestClient.get(`/system/dept/tree/`); +} + +export { createDept, deleteDept, getDeptList, treeDept, updateDept }; diff --git a/web/apps/web-antd/src/views/system/dept/list.vue b/web/apps/web-antd/src/views/system/dept/list.vue index 95744cc..c7358f0 100644 --- a/web/apps/web-antd/src/views/system/dept/list.vue +++ b/web/apps/web-antd/src/views/system/dept/list.vue @@ -11,7 +11,7 @@ import { Plus } from '@vben/icons'; import { Button, message } from 'ant-design-vue'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; -import { deleteDept, getDeptList } from '#/api/system/dept'; +import { deleteDept, treeDept } from '#/api/system/dept'; import { $t } from '#/locales'; import { useColumns } from './data'; @@ -108,7 +108,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ proxyConfig: { ajax: { query: async (_params) => { - return await getDeptList(); + return await treeDept(); }, }, },