diff --git a/backend/.gitignore b/backend/.gitignore index 047099f..6c50cc9 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -98,5 +98,4 @@ media/ __pypackages__/ package-lock.json gunicorn.pid -plugins/* !plugins/__init__.py diff --git a/backend/application/celery.py b/backend/application/celery.py index 10bce56..c719c46 100644 --- a/backend/application/celery.py +++ b/backend/application/celery.py @@ -1,6 +1,8 @@ import functools import os +from celery.signals import task_postrun + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') from django.conf import settings @@ -38,3 +40,12 @@ def retry_base_task_error(): return wrapper return wraps + + +@task_postrun.connect +def add_periodic_task_name(sender, task_id, task, args, kwargs, **extras): + periodic_task_name = kwargs.get('periodic_task_name') + if periodic_task_name: + from django_celery_results.models import TaskResult + # 更新 TaskResult 表中的 periodic_task_name 字段 + TaskResult.objects.filter(task_id=task_id).update(periodic_task_name=periodic_task_name) diff --git a/backend/application/settings.py b/backend/application/settings.py index e6bdec5..1d0adf7 100644 --- a/backend/application/settings.py +++ b/backend/application/settings.py @@ -404,7 +404,7 @@ PLUGINS_URL_PATTERNS = [] # ********** 一键导入插件配置开始 ********** # 例如: # from dvadmin_upgrade_center.settings import * # 升级中心 -# from dvadmin3_celery.settings import * # celery 异步任务 +from dvadmin3_celery.settings import * # celery 异步任务 # from dvadmin_third.settings import * # 第三方用户管理 # from dvadmin_ak_sk.settings import * # 秘钥管理管理 # from dvadmin_tenants.settings import * # 租户管理 diff --git a/backend/dvadmin/system/urls.py b/backend/dvadmin/system/urls.py index c9c12e9..0ee2cb8 100644 --- a/backend/dvadmin/system/urls.py +++ b/backend/dvadmin/system/urls.py @@ -49,7 +49,7 @@ urlpatterns = [ path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})), # path('login_log/', LoginLogViewSet.as_view({'get': 'list'})), # path('login_log//', LoginLogViewSet.as_view({'get': 'retrieve'})), - path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})), + # path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})), path('clause/privacy.html', PrivacyView.as_view()), path('clause/terms_service.html', TermsServiceView.as_view()), ] diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index 07a1637..f396860 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -10,16 +10,17 @@ from rest_framework import serializers from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated -from dvadmin.system.models import Role, Menu, MenuButton, Dept +from dvadmin.system.models import Role, Menu, MenuButton, Dept, Users from dvadmin.system.views.dept import DeptSerializer from dvadmin.system.views.menu import MenuSerializer from dvadmin.system.views.menu_button import MenuButtonSerializer from dvadmin.utils.crud_mixin import FastCrudMixin from dvadmin.utils.field_permission import FieldPermissionMixin -from dvadmin.utils.json_response import SuccessResponse, DetailResponse +from dvadmin.utils.json_response import SuccessResponse, DetailResponse, ErrorResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.validator import CustomUniqueValidator from dvadmin.utils.viewset import CustomModelViewSet +from dvadmin.utils.permission import CustomPermission class RoleSerializer(CustomModelSerializer): @@ -107,7 +108,6 @@ class MenuButtonPermissionSerializer(CustomModelSerializer): fields = '__all__' - class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin): """ 角色管理接口 @@ -141,4 +141,63 @@ class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin): # right : 添加用户权限 role.users_set.add(*movedKeys) serializer = RoleSerializer(role) - return DetailResponse(data=serializer.data, msg="更新成功") \ No newline at end of file + return DetailResponse(data=serializer.data, msg="更新成功") + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated, CustomPermission]) + def get_role_users(self, request): + """ + 获取角色已授权、未授权的用户 + 已授权的用户:1 + 未授权的用户:0 + """ + role_id = request.query_params.get('role_id', None) + + if not role_id: + return ErrorResponse(msg="请选择角色") + + if request.query_params.get('authorized', 0) == "1": + queryset = Users.objects.filter(role__id=role_id).exclude(is_superuser=True) + else: + queryset = Users.objects.exclude(role__id=role_id).exclude(is_superuser=True) + + if name := request.query_params.get('name', None): + queryset = queryset.filter(name__icontains=name) + + if dept := request.query_params.get('dept', None): + queryset = queryset.filter(dept=dept) + + page = self.paginate_queryset(queryset.values('id', 'name', 'dept__name')) + if page is not None: + return self.get_paginated_response(page) + + return SuccessResponse(data=page) + + @action(methods=['DELETE'], detail=True, permission_classes=[IsAuthenticated, CustomPermission]) + def remove_role_user(self, request, pk): + """ + 角色-删除用户 + """ + user_id = request.data.get('user_id', None) + + if not user_id: + return ErrorResponse(msg="请选择用户") + + role = self.get_object() + role.users_set.remove(*user_id) + + return SuccessResponse(msg="删除成功") + + @action(methods=['POST'], detail=True, permission_classes=[IsAuthenticated, CustomPermission]) + def add_role_users(self, request, pk): + """ + 角色-添加用户 + """ + users_id = request.data.get('users_id', None) + + if not users_id: + return ErrorResponse(msg="请选择用户") + + role = self.get_object() + role.users_set.add(*users_id) + + return DetailResponse(msg="添加成功") diff --git a/backend/dvadmin/system/views/role_menu_button_permission.py b/backend/dvadmin/system/views/role_menu_button_permission.py index 723cd2a..c041887 100644 --- a/backend/dvadmin/system/views/role_menu_button_permission.py +++ b/backend/dvadmin/system/views/role_menu_button_permission.py @@ -231,9 +231,17 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet): isCheck = data.get('isCheck', None) roleId = data.get('roleId', None) btnId = data.get('btnId', None) + data_range = data.get('data_range', None) or 0 # 默认仅本人权限 + dept = data.get('dept', None) or [] # 默认空部门 + if isCheck: # 添加权限:创建关联记录 - RoleMenuButtonPermission.objects.create(role_id=roleId, menu_button_id=btnId) + instance = RoleMenuButtonPermission.objects.create(role_id=roleId, + menu_button_id=btnId, + data_range=data_range) + # 自定义部门权限 + if data_range == 4 and dept: + instance.dept.set(dept) else: # 删除权限:移除关联记录 RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete() diff --git a/backend/dvadmin/utils/filters.py b/backend/dvadmin/utils/filters.py index da808ac..4db96db 100644 --- a/backend/dvadmin/utils/filters.py +++ b/backend/dvadmin/utils/filters.py @@ -33,7 +33,7 @@ class CoreModelFilterBankend(BaseFilterBackend): create_datetime_after = request.query_params.get('create_datetime_after', None) create_datetime_before = request.query_params.get('create_datetime_before', None) update_datetime_after = request.query_params.get('update_datetime_after', None) - update_datetime_before = request.query_params.get('update_datetime_after', None) + update_datetime_before = request.query_params.get('update_datetime_before', None) if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]): create_filter = Q() if create_datetime_after and create_datetime_before: diff --git a/backend/dvadmin/utils/viewset.py b/backend/dvadmin/utils/viewset.py index b85007a..6b5cfcb 100644 --- a/backend/dvadmin/utils/viewset.py +++ b/backend/dvadmin/utils/viewset.py @@ -6,6 +6,8 @@ @Created on: 2021/6/1 001 22:57 @Remark: 自定义视图集 """ +import copy + from django.db import transaction from django_filters import DateTimeFromToRangeFilter from django_filters.rest_framework import FilterSet @@ -67,12 +69,14 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi kwargs.setdefault('context', self.get_serializer_context()) # 全部以可见字段为准 can_see = self.get_menu_field(serializer_class) - # 排除掉序列化器级的字段 - # sub_set = set(serializer_class._declared_fields.keys()) - set(can_see) - # for field in sub_set: - # serializer_class._declared_fields.pop(field) - # if not self.request.user.is_superuser: - # serializer_class.Meta.fields = can_see + # 排除掉序列化器级的字段(排除字段权限中未授权的字段) + if not self.request.user.is_superuser: + exclude_set = set(serializer_class._declared_fields.keys()) - set(can_see) + for field in exclude_set: + serializer_class._declared_fields.pop(field) + meta = copy.deepcopy(serializer_class.Meta) + meta.fields = list(can_see) + serializer_class.Meta = meta # 在分页器中使用 self.request.permission_fields = can_see if isinstance(self.request.data, list): @@ -90,8 +94,9 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi break if finded is False: return [] - return MenuField.objects.filter(model=model['model'] - ).values('field_name', 'title') + roles = self.request.user.role.values_list('id', flat=True) + return FieldPermission.objects.filter(is_query=True, role__in=roles, field__model=model['model']).values_list( + 'field__field_name', flat=True) def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data, request=request) @@ -131,8 +136,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi instance.delete() return DetailResponse(data=[], msg="删除成功") - keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.TYPE_STRING) - + keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)) @swagger_auto_schema(request_body=openapi.Schema( type=openapi.TYPE_OBJECT, required=['keys'], diff --git a/backend/requirements.txt b/backend/requirements.txt index 3395719..9cae2cc 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -28,4 +28,5 @@ uvicorn==0.30.3 gunicorn==22.0.0 gevent==24.2.1 Pillow==10.4.0 -pyinstaller==6.9.0 \ No newline at end of file +pyinstaller==6.9.0 +dvadmin3-celery==3.1.6 \ No newline at end of file diff --git a/init.sh b/init.sh index 8c377ed..ab828cb 100644 --- a/init.sh +++ b/init.sh @@ -1,5 +1,6 @@ #!/bin/bash ENV_FILE=".env" +HOST="177.10.0.13" # 检查 .env 文件是否存在 if [ -f "$ENV_FILE" ]; then echo "$ENV_FILE 文件已存在。" @@ -15,17 +16,60 @@ else echo "REDIS随机密码已生成并写入 $ENV_FILE 文件。" awk 'BEGIN { cmd="cp -i ./backend/conf/env.example.py ./backend/conf/env.py "; print "n" |cmd; }' - sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.13'|g" ./backend/conf/env.py + sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '$HOST'|g" ./backend/conf/env.py sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.15'|g" ./backend/conf/env.py sed -i "s|DATABASE_PASSWORD = 'DVADMIN3'|DATABASE_PASSWORD = '$MYSQL_PASSWORD'|g" ./backend/conf/env.py sed -i "s|REDIS_PASSWORD = 'DVADMIN3'|REDIS_PASSWORD = '$REDIS_PASSWORD'|g" ./backend/conf/env.py echo "初始化密码创建成功" fi +echo "正在启动容器..." docker-compose up -d -docker exec dvadmin3-django python manage.py makemigrations -docker exec dvadmin3-django python manage.py migrate -docker exec dvadmin3-django python manage.py init -echo "欢迎使用dvadmin3项目" -echo "登录地址:http://ip:8080" -echo "如访问不到,请检查防火墙配置" + +if [ $? -ne 0 ]; then + echo "docker-compose up -d 执行失败!" + exit 1 +fi + +MYSQL_PORT=3306 +REDIS_PORT=6379 + +check_mysql() { + if nc -z "$HOST" "$MYSQL_PORT" >/dev/null 2>&1; then + echo "MySQL 服务正在运行在 $HOST:$MYSQL_PORT" + return 0 + else + return 1 + fi +} + +check_redis() { + if nc -z "$HOST" "$REDIS_PORT" >/dev/null 2>&1; then + echo "Redis 服务正在运行在 $HOST:$REDIS_PORT" + return 0 + else + return 1 + fi +} + +i=1 +while [ $i -le 8 ]; do + if check_mysql || check_redis; then + echo "正在迁移数据..." + docker exec dvadmin3-django python3 manage.py makemigrations + docker exec dvadmin3-django python3 manage.py migrate + echo "正在初始化数据..." + docker exec dvadmin3-django python3 manage.py init + echo "欢迎使用dvadmin3项目" + echo "登录地址:http://ip:8080" + echo "如访问不到,请检查防火墙配置" + exit 0 + else + echo "第 $i 次尝试:MySQL 或 REDIS服务未运行,等待 2 秒后重试..." + sleep 2 + fi + i=$((i+1)) +done + +echo "尝试 5 次后,MySQL 或 REDIS服务仍未运行" +exit 1 diff --git a/web/package.json b/web/package.json index 7022283..448d1fb 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "django-vue3-admin", - "version": "3.0.4", + "version": "3.1.0", "description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus", "license": "MIT", "scripts": { @@ -15,6 +15,7 @@ "@fast-crud/fast-extends": "^1.21.2", "@fast-crud/ui-element": "^1.21.2", "@fast-crud/ui-interface": "^1.21.2", + "@great-dream/dvadmin3-celery-web": "^3.1.3", "@iconify/vue": "^4.1.2", "@types/lodash": "^4.17.7", "@vitejs/plugin-vue-jsx": "^4.0.1", diff --git a/web/src/components/avatarSelector/index.vue b/web/src/components/avatarSelector/index.vue index f979cb0..2464554 100644 --- a/web/src/components/avatarSelector/index.vue +++ b/web/src/components/avatarSelector/index.vue @@ -1,6 +1,6 @@