feat: 接口校验 (#721)

feat: 接口校验
This commit is contained in:
shaohuzhang1 2024-07-09 13:44:27 +08:00 committed by GitHub
parent ed526c3bf3
commit 60e65a8b17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 256 additions and 14 deletions

View File

@ -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')

View File

@ -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

View File

@ -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):

View File

@ -0,0 +1,50 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file valid_serializers.py
@date2024/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

View File

@ -0,0 +1,27 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file valid_api.py
@date2024/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='校验数量')
]

View File

@ -17,6 +17,7 @@ urlpatterns = [
path('model', views.Model.as_view(), name='model'),
path('model/<str:model_id>', views.Model.Operate.as_view(), name='model/operate'),
path('model/<str:model_id>/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/<str:valid_type>/<int:valid_count>', views.Valid.as_view())
]

View File

@ -9,3 +9,4 @@
from .Team import *
from .model import *
from .system_setting import *
from .valid import *

View File

@ -0,0 +1,32 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file valid.py
@date2024/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())

View File

@ -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:

View File

@ -23,6 +23,8 @@ interface User {
*
*/
is_edit_password?: boolean
IS_XPACK?: boolean
XPACK_LICENSE_IS_VALID?: boolean
}
interface LoginRequest {

View File

@ -131,6 +131,19 @@ const getVersion: (loading?: Ref<boolean>) => Promise<Result<any>> = (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<boolean>
) => Promise<Result<any>> = (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
}

View File

@ -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
}

View File

@ -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

View File

@ -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<boolean>) {
return new Promise((resolve, reject) => {
userApi
.getValid(valid_type, valid_count, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
}
}
})

View File

@ -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

View File

@ -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}

View File

@ -66,7 +66,7 @@
<el-button @click.prevent="dialogVisible = false">
{{ $t('views.application.applicationForm.buttons.cancel') }}
</el-button>
<el-button type="primary" @click="submitHandle(applicationFormRef)">
<el-button type="primary" @click="submitValid(applicationFormRef)">
{{ $t('views.application.applicationForm.buttons.create') }}
</el-button>
</span>
@ -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) => {

View File

@ -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) {