From 60e65a8b17714425798832c5259c7e507e3cebe9 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:44:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=8F=A3=E6=A0=A1=E9=AA=8C=20?= =?UTF-8?q?(#721)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: 接口校验 --- .../serializers/application_serializers.py | 3 ++ apps/common/util/common.py | 21 ++++++++ .../serializers/dataset_serializers.py | 6 ++- apps/setting/serializers/valid_serializers.py | 50 +++++++++++++++++++ apps/setting/swagger_api/valid_api.py | 27 ++++++++++ apps/setting/urls.py | 3 +- apps/setting/views/__init__.py | 1 + apps/setting/views/valid.py | 32 ++++++++++++ apps/users/serializers/user_serializers.py | 10 +++- ui/src/api/type/user.ts | 2 + ui/src/api/user.ts | 16 +++++- ui/src/enums/common.ts | 12 +++++ ui/src/request/index.ts | 6 ++- ui/src/stores/modules/common.ts | 16 +++++- ui/src/stores/modules/user.ts | 7 ++- ui/src/utils/message.ts | 8 +++ .../component/CreateApplicationDialog.vue | 26 +++++++++- ui/src/views/user-manage/index.vue | 24 +++++++-- 18 files changed, 256 insertions(+), 14 deletions(-) create mode 100644 apps/setting/serializers/valid_serializers.py create mode 100644 apps/setting/swagger_api/valid_api.py create mode 100644 apps/setting/views/valid.py diff --git a/apps/application/serializers/application_serializers.py b/apps/application/serializers/application_serializers.py index ab34ba489..8ac962c95 100644 --- a/apps/application/serializers/application_serializers.py +++ b/apps/application/serializers/application_serializers.py @@ -32,6 +32,7 @@ from common.db.search import get_dynamics_model, native_search, native_page_sear from common.db.sql_execute import select_list from common.exception.app_exception import AppApiException, NotFound404, AppUnauthorizedFailed from common.field.common import UploadedImageField +from common.util.common import valid_license from common.util.field_message import ErrMessage from common.util.file_util import get_file_content from dataset.models import DataSet, Document, Image @@ -329,6 +330,8 @@ class ApplicationSerializer(serializers.Serializer): class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id")) + @valid_license(model=Application, count=5, + message='社区版最多支持 5 个应用,如需拥有更多应用,请联系我们(https://fit2cloud.com/)。') @transaction.atomic def insert(self, application: Dict): application_type = application.get('type') diff --git a/apps/common/util/common.py b/apps/common/util/common.py index 9ba50c671..9a3ca0c0d 100644 --- a/apps/common/util/common.py +++ b/apps/common/util/common.py @@ -10,6 +10,11 @@ import importlib from functools import reduce from typing import Dict, List +from django.conf import settings +from django.db.models import QuerySet + +from ..exception.app_exception import AppApiException + def sub_array(array: List, item_num=10): result = [] @@ -66,3 +71,19 @@ def post(post_function): return run return inner + + +def valid_license(model=None, count=None, message=None): + def inner(func): + def run(*args, **kwargs): + if (not (settings.XPACK_LICENSE_IS_VALID if hasattr(settings, + 'XPACK_LICENSE_IS_VALID') else None) + and QuerySet( + model).count() >= count): + error = message or f'超出限制{count},请联系我们(https://fit2cloud.com/)。' + raise AppApiException(400, error) + return func(*args, **kwargs) + + return run + + return inner diff --git a/apps/dataset/serializers/dataset_serializers.py b/apps/dataset/serializers/dataset_serializers.py index 629719bff..50a6a9b99 100644 --- a/apps/dataset/serializers/dataset_serializers.py +++ b/apps/dataset/serializers/dataset_serializers.py @@ -15,7 +15,7 @@ from functools import reduce from typing import Dict, List from urllib.parse import urlparse -import xlwt +from django.conf import settings from django.contrib.postgres.fields import ArrayField from django.core import validators from django.db import transaction, models @@ -31,7 +31,7 @@ from common.db.sql_execute import select_list from common.event import ListenerManagement, SyncWebDatasetArgs from common.exception.app_exception import AppApiException from common.mixins.api_mixin import ApiMixin -from common.util.common import post, flat_map +from common.util.common import post, flat_map, valid_license from common.util.field_message import ErrMessage from common.util.file_util import get_file_content from common.util.fork import ChildLink, Fork @@ -368,6 +368,8 @@ class DataSetSerializers(serializers.ModelSerializer): dataset_instance = {'name': instance.get('name'), 'desc': instance.get('desc'), 'documents': document_list} return self.save(dataset_instance, with_valid=True) + @valid_license(model=DataSet, count=50, + message='社区版最多支持 50 个知识库,如需拥有更多知识库,请联系我们(https://fit2cloud.com/)。') @post(post_function=post_embedding_dataset) @transaction.atomic def save(self, instance: Dict, with_valid=True): diff --git a/apps/setting/serializers/valid_serializers.py b/apps/setting/serializers/valid_serializers.py new file mode 100644 index 000000000..71c7d2146 --- /dev/null +++ b/apps/setting/serializers/valid_serializers.py @@ -0,0 +1,50 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: valid_serializers.py + @date:2024/7/8 18:00 + @desc: +""" +import re + +from django.core import validators +from django.db.models import QuerySet +from rest_framework import serializers + +from application.models import Application +from common.exception.app_exception import AppApiException +from common.util.field_message import ErrMessage +from dataset.models import DataSet +from smartdoc import settings +from users.models import User + +model_message_dict = { + 'dataset': {'model': DataSet, 'count': 50, + 'message': '社区版最多支持 50 个知识库,如需拥有更多知识库,请联系我们(https://fit2cloud.com/)。'}, + 'application': {'model': Application, 'count': 5, + 'message': '社区版最多支持 5 个应用,如需拥有更多应用,请联系我们(https://fit2cloud.com/)。'}, + 'user': {'model': User, 'count': 2, + 'message': '社区版最多支持 2 个用户,如需拥有更多用户,请联系我们(https://fit2cloud.com/)。'} +} + + +class ValidSerializer(serializers.Serializer): + valid_type = serializers.CharField(required=True, error_messages=ErrMessage.char("类型"), validators=[ + validators.RegexValidator(regex=re.compile("^application|dataset|user$"), + message="类型只支持:application|dataset|user", code=500) + ]) + valid_count = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("校验数量")) + + def valid(self, is_valid=True): + if is_valid: + self.is_valid(raise_exception=True) + model_value = model_message_dict.get(self.data.get('valid_type')) + if not (settings.XPACK_LICENSE_IS_VALID if hasattr(settings, + 'XPACK_LICENSE_IS_VALID') else None): + if self.data.get('valid_count') != model_value.get('count'): + raise AppApiException(400, model_value.get('message')) + if QuerySet( + model_value.get('model')).count() >= model_value.get('count'): + raise AppApiException(400, model_value.get('message')) + return True diff --git a/apps/setting/swagger_api/valid_api.py b/apps/setting/swagger_api/valid_api.py new file mode 100644 index 000000000..4fad9e804 --- /dev/null +++ b/apps/setting/swagger_api/valid_api.py @@ -0,0 +1,27 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: valid_api.py + @date:2024/7/8 17:52 + @desc: +""" +from drf_yasg import openapi + +from common.mixins.api_mixin import ApiMixin + + +class ValidApi(ApiMixin): + @staticmethod + def get_request_params_api(): + return [openapi.Parameter(name='valid_type', + in_=openapi.IN_PATH, + type=openapi.TYPE_STRING, + required=True, + description='校验类型:application|dataset|user'), + openapi.Parameter(name='valid_count', + in_=openapi.IN_PATH, + type=openapi.TYPE_STRING, + required=True, + description='校验数量') + ] diff --git a/apps/setting/urls.py b/apps/setting/urls.py index 42fea74ec..2a7cdd68a 100644 --- a/apps/setting/urls.py +++ b/apps/setting/urls.py @@ -17,6 +17,7 @@ urlpatterns = [ path('model', views.Model.as_view(), name='model'), path('model/', views.Model.Operate.as_view(), name='model/operate'), path('model//meta', views.Model.ModelMeta.as_view(), name='model/operate/meta'), - path('email_setting', views.SystemSetting.Email.as_view(), name='email_setting') + path('email_setting', views.SystemSetting.Email.as_view(), name='email_setting'), + path('valid//', views.Valid.as_view()) ] diff --git a/apps/setting/views/__init__.py b/apps/setting/views/__init__.py index 0885ef978..d222d52aa 100644 --- a/apps/setting/views/__init__.py +++ b/apps/setting/views/__init__.py @@ -9,3 +9,4 @@ from .Team import * from .model import * from .system_setting import * +from .valid import * diff --git a/apps/setting/views/valid.py b/apps/setting/views/valid.py new file mode 100644 index 000000000..f88c58927 --- /dev/null +++ b/apps/setting/views/valid.py @@ -0,0 +1,32 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: valid.py + @date:2024/7/8 17:50 + @desc: +""" +from drf_yasg.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.views import APIView + +from common.auth import TokenAuth, has_permissions +from common.constants.permission_constants import RoleConstants +from common.response import result +from setting.serializers.valid_serializers import ValidSerializer +from setting.swagger_api.valid_api import ValidApi + + +class Valid(APIView): + authentication_classes = [TokenAuth] + + @action(methods=['GET'], detail=False) + @swagger_auto_schema(operation_summary="获取校验结果", + operation_id="获取校验结果", + manual_parameters=ValidApi.get_request_params_api(), + responses=result.get_default_response() + , tags=["校验"]) + @has_permissions(RoleConstants.ADMIN, RoleConstants.USER) + def get(self, request: Request, valid_type: str, valid_count: int): + return result.success(ValidSerializer(data={'valid_type': valid_type, 'valid_count': valid_count}).valid()) diff --git a/apps/users/serializers/user_serializers.py b/apps/users/serializers/user_serializers.py index 2d0da7f56..3aa469b15 100644 --- a/apps/users/serializers/user_serializers.py +++ b/apps/users/serializers/user_serializers.py @@ -12,6 +12,7 @@ import random import re import uuid +from django.conf import settings from django.core import validators, signing, cache from django.core.mail import send_mail from django.core.mail.backends.smtp import EmailBackend @@ -29,6 +30,7 @@ from common.event import ListenerManagement from common.exception.app_exception import AppApiException from common.mixins.api_mixin import ApiMixin from common.response.result import get_api_response +from common.util.common import valid_license from common.util.field_message import ErrMessage from common.util.lock import lock from dataset.models import DataSet, Document, Paragraph, Problem, ProblemParagraphMapping @@ -43,7 +45,9 @@ class SystemSerializer(ApiMixin, serializers.Serializer): @staticmethod def get_profile(): version = os.environ.get('MAXKB_VERSION') - return {'version': version} + return {'version': version, 'IS_XPACK': hasattr(settings, 'XPACK_LICENSE_IS_VALID'), + 'XPACK_LICENSE_IS_VALID': (settings.XPACK_LICENSE_IS_VALID if hasattr(settings, + 'XPACK_LICENSE_IS_VALID') else False)} @staticmethod def get_response_body_api(): @@ -174,6 +178,8 @@ class RegisterSerializer(ApiMixin, serializers.Serializer): return True + @valid_license(model=User, count=2, + message='社区版最多支持 2 个用户,如需拥有更多用户,请联系我们(https://fit2cloud.com/)。') @transaction.atomic def save(self, **kwargs): m = User( @@ -681,6 +687,8 @@ class UserManageSerializer(serializers.Serializer): if self.data.get('password') != self.data.get('re_password'): raise ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.to_app_api_exception() + @valid_license(model=User, count=2, + message='社区版最多支持 2 个用户,如需拥有更多用户,请联系我们(https://fit2cloud.com/)。') @transaction.atomic def save(self, instance, with_valid=True): if with_valid: diff --git a/ui/src/api/type/user.ts b/ui/src/api/type/user.ts index 6724252c9..a91a40e8b 100644 --- a/ui/src/api/type/user.ts +++ b/ui/src/api/type/user.ts @@ -23,6 +23,8 @@ interface User { * 是否需要修改密码 */ is_edit_password?: boolean + IS_XPACK?: boolean + XPACK_LICENSE_IS_VALID?: boolean } interface LoginRequest { diff --git a/ui/src/api/user.ts b/ui/src/api/user.ts index e11d70168..d162d46e6 100644 --- a/ui/src/api/user.ts +++ b/ui/src/api/user.ts @@ -131,6 +131,19 @@ const getVersion: (loading?: Ref) => Promise> = (loading) = return get('/profile', undefined, loading) } +/** + * 获取校验 + * @param valid_type 校验类型: application|dataset|user + * @param valid_count 校验数量: 5 | 50 | 2 + */ +const getValid: ( + valid_type: string, + valid_count: number, + loading?: Ref +) => Promise> = (valid_type, valid_count, loading) => { + return get(`/valid/${valid_type}/${valid_count}`, undefined, loading) +} + export default { login, register, @@ -142,5 +155,6 @@ export default { resetCurrentUserPassword, logout, getUserList, - getVersion + getVersion, + getValid } diff --git a/ui/src/enums/common.ts b/ui/src/enums/common.ts index dd655309b..3afd8fc26 100644 --- a/ui/src/enums/common.ts +++ b/ui/src/enums/common.ts @@ -2,3 +2,15 @@ export enum DeviceType { Mobile = 'Mobile', Desktop = 'Desktop' } + +export enum ValidType { + Application = 'application', + Dataset = 'dataset', + User = 'user' +} + +export enum ValidCount { + Application = 5, + Dataset = 50, + User = 2 +} diff --git a/ui/src/request/index.ts b/ui/src/request/index.ts index 354bf639f..a6039083a 100644 --- a/ui/src/request/index.ts +++ b/ui/src/request/index.ts @@ -40,8 +40,10 @@ instance.interceptors.response.use( (response: any) => { if (response.data) { if (response.data.code !== 200 && !(response.data instanceof Blob)) { - MsgError(response.data.message) - return Promise.reject(response.data) + if (!response.config.url.includes('/valid')) { + MsgError(response.data.message) + return Promise.reject(response.data) + } } } return response diff --git a/ui/src/stores/modules/common.ts b/ui/src/stores/modules/common.ts index d02841157..636a36dc2 100644 --- a/ui/src/stores/modules/common.ts +++ b/ui/src/stores/modules/common.ts @@ -1,5 +1,7 @@ import { defineStore } from 'pinia' -import { DeviceType } from '@/enums/common' +import { DeviceType, ValidType } from '@/enums/common' +import type { Ref } from 'vue' +import userApi from '@/api/user' export interface commonTypes { breadcrumb: any @@ -32,6 +34,18 @@ const useCommonStore = defineStore({ }, isMobile() { return this.device === DeviceType.Mobile + }, + async asyncGetValid(valid_type: ValidType, valid_count: number, loading?: Ref) { + return new Promise((resolve, reject) => { + userApi + .getValid(valid_type, valid_count, loading) + .then((data) => { + resolve(data) + }) + .catch((error) => { + reject(error) + }) + }) } } }) diff --git a/ui/src/stores/modules/user.ts b/ui/src/stores/modules/user.ts index fc269ec2d..69399c835 100644 --- a/ui/src/stores/modules/user.ts +++ b/ui/src/stores/modules/user.ts @@ -8,6 +8,7 @@ export interface userStateTypes { token: any version?: string accessToken?: string + isXPack: false } const useUserStore = defineStore({ @@ -16,9 +17,13 @@ const useUserStore = defineStore({ userType: 1, userInfo: null, token: '', - version: '' + version: '', + isXPack: false }), actions: { + isEnterprise() { + return this.userInfo?.IS_XPACK && this.userInfo?.XPACK_LICENSE_IS_VALID + }, getToken(): String | null { if (this.token) { return this.token diff --git a/ui/src/utils/message.ts b/ui/src/utils/message.ts index 8b1e35d37..c876575ed 100644 --- a/ui/src/utils/message.ts +++ b/ui/src/utils/message.ts @@ -36,6 +36,14 @@ export const MsgError = (message: string) => { }) } +export const MsgAlert = (title: string, description: string, options?: any) => { + const defaultOptions: Object = { + confirmButtonText: '确定', + ...options + } + return ElMessageBox.alert(description, title, defaultOptions) +} + /** * 删除知识库 * @param 参数 message: {title, description,type} diff --git a/ui/src/views/application/component/CreateApplicationDialog.vue b/ui/src/views/application/component/CreateApplicationDialog.vue index 39252b014..1b9784496 100644 --- a/ui/src/views/application/component/CreateApplicationDialog.vue +++ b/ui/src/views/application/component/CreateApplicationDialog.vue @@ -66,7 +66,7 @@ {{ $t('views.application.applicationForm.buttons.cancel') }} - + {{ $t('views.application.applicationForm.buttons.create') }} @@ -79,9 +79,13 @@ import { useRouter, useRoute } from 'vue-router' import type { ApplicationFormType } from '@/api/type/application' import type { FormInstance, FormRules } from 'element-plus' import applicationApi from '@/api/application' -import { MsgSuccess } from '@/utils/message' +import { MsgSuccess, MsgAlert } from '@/utils/message' import { isWorkFlow } from '@/utils/application' import { t } from '@/locales' +import useStore from '@/stores' +import { ValidType, ValidCount } from '@/enums/common' + +const { common, user } = useStore() const router = useRouter() const emit = defineEmits(['refresh']) @@ -169,6 +173,24 @@ const open = () => { dialogVisible.value = true } +const submitValid = (formEl: FormInstance | undefined) => { + if (user.isEnterprise()) { + submitHandle(formEl) + } else { + common + .asyncGetValid(ValidType.Application, ValidCount.Application, loading) + .then(async (res: any) => { + if (res?.data?.data) { + submitHandle(formEl) + } else { + MsgAlert( + '提示', + '社区版最多支持 5 个应用,如需拥有更多应用,请联系我们(https://fit2cloud.com/)。' + ) + } + }) + } +} const submitHandle = async (formEl: FormInstance | undefined) => { if (!formEl) return await formEl.validate((valid) => { diff --git a/ui/src/views/user-manage/index.vue b/ui/src/views/user-manage/index.vue index e33441971..af8a4be89 100644 --- a/ui/src/views/user-manage/index.vue +++ b/ui/src/views/user-manage/index.vue @@ -83,9 +83,13 @@ import { ref, onMounted, reactive } from 'vue' import UserDialog from './component/UserDialog.vue' import UserPwdDialog from './component/UserPwdDialog.vue' -import { MsgSuccess, MsgConfirm } from '@/utils/message' +import { MsgSuccess, MsgConfirm, MsgAlert } from '@/utils/message' import userApi from '@/api/user-manage' import { datetimeFormat } from '@/utils/time' +import useStore from '@/stores' +import { ValidType, ValidCount } from '@/enums/common' + +const { common, user } = useStore() const UserDialogRef = ref() const UserPwdDialogRef = ref() @@ -127,8 +131,22 @@ function editUser(row: any) { } function createUser() { - title.value = '创建用户' - UserDialogRef.value.open() + if (user.isEnterprise()) { + title.value = '创建用户' + UserDialogRef.value.open() + } else { + common.asyncGetValid(ValidType.User, ValidCount.User, loading).then((res: any) => { + if (res?.data?.data) { + title.value = '创建用户' + UserDialogRef.value.open() + } else { + MsgAlert( + '提示', + '社区版最多支持 2 个用户,如需拥有更多用户,请联系我们(https://fit2cloud.com/)。' + ) + } + }) + } } function deleteUserManage(row: any) {