mirror of
https://gitee.com/mao-peng/MangoTestingPlatform.git
synced 2025-12-06 11:59:15 +08:00
增加接口的批量导入
This commit is contained in:
@@ -20,6 +20,7 @@ urlpatterns = [
|
|||||||
path("info/type", ApiInfoViews.as_view({'put': 'put_api_info_type'})),
|
path("info/type", ApiInfoViews.as_view({'put': 'put_api_info_type'})),
|
||||||
path("info/copy", ApiInfoViews.as_view({'post': 'copy_api_info'})),
|
path("info/copy", ApiInfoViews.as_view({'post': 'copy_api_info'})),
|
||||||
path("info/import/api", ApiInfoViews.as_view({'post': 'import_api'})),
|
path("info/import/api", ApiInfoViews.as_view({'post': 'import_api'})),
|
||||||
|
path("upload/api", ApiInfoViews.as_view({'post': 'post_upload_api'})),
|
||||||
#
|
#
|
||||||
path("case", ApiCaseCRUD.as_view()),
|
path("case", ApiCaseCRUD.as_view()),
|
||||||
path("case/test", ApiCaseViews.as_view({'get': 'api_test_case'})),
|
path("case/test", ApiCaseViews.as_view({'get': 'api_test_case'})),
|
||||||
@@ -33,7 +34,8 @@ urlpatterns = [
|
|||||||
path("case/detailed/refresh", ApiCaseDetailedViews.as_view({'put': 'put_refresh_api_info'})),
|
path("case/detailed/refresh", ApiCaseDetailedViews.as_view({'put': 'put_refresh_api_info'})),
|
||||||
#
|
#
|
||||||
path("case/detailed/parameter", ApiCaseDetailedParameterCRUD.as_view()),
|
path("case/detailed/parameter", ApiCaseDetailedParameterCRUD.as_view()),
|
||||||
path("case/detailed/parameter/test/jsonpath", ApiCaseDetailedParameterViews.as_view({'post': 'post_test_jsonpath'})),
|
path("case/detailed/parameter/test/jsonpath",
|
||||||
|
ApiCaseDetailedParameterViews.as_view({'post': 'post_test_jsonpath'})),
|
||||||
#
|
#
|
||||||
path("public", ApiPublicCRUD.as_view()),
|
path("public", ApiPublicCRUD.as_view()),
|
||||||
path("public/status", ApiPublicViews.as_view({'put': 'put_status'})),
|
path("public/status", ApiPublicViews.as_view({'put': 'put_status'})),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import json
|
import json
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
from curlparser import parse
|
from curlparser import parse
|
||||||
from django.forms import model_to_dict
|
from django.forms import model_to_dict
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@@ -15,6 +16,7 @@ from rest_framework.viewsets import ViewSet
|
|||||||
|
|
||||||
from src.auto_test.auto_api.models import ApiInfo
|
from src.auto_test.auto_api.models import ApiInfo
|
||||||
from src.auto_test.auto_api.service.test_case.test_api_info import TestApiInfo
|
from src.auto_test.auto_api.service.test_case.test_api_info import TestApiInfo
|
||||||
|
from src.auto_test.auto_system.models import ProjectProduct, ProductModule
|
||||||
from src.auto_test.auto_system.views.product_module import ProductModuleSerializers
|
from src.auto_test.auto_system.views.product_module import ProductModuleSerializers
|
||||||
from src.auto_test.auto_system.views.project_product import ProjectProductSerializersC
|
from src.auto_test.auto_system.views.project_product import ProjectProductSerializersC
|
||||||
from src.enums.api_enum import MethodEnum
|
from src.enums.api_enum import MethodEnum
|
||||||
@@ -148,3 +150,46 @@ class ApiInfoViews(ViewSet):
|
|||||||
result['json'] = json.dumps(parsed.json, indent=4, ensure_ascii=False)
|
result['json'] = json.dumps(parsed.json, indent=4, ensure_ascii=False)
|
||||||
data = ApiInfoCRUD.inside_post(result)
|
data = ApiInfoCRUD.inside_post(result)
|
||||||
return ResponseData.success(RESPONSE_MSG_0069, data=data)
|
return ResponseData.success(RESPONSE_MSG_0069, data=data)
|
||||||
|
|
||||||
|
@action(methods=['POST'], detail=False)
|
||||||
|
@error_response('ui')
|
||||||
|
def post_upload_api(self, request):
|
||||||
|
uploaded_file = request.FILES['file']
|
||||||
|
df = pd.read_excel(uploaded_file, keep_default_na=False)
|
||||||
|
df = df.where(df.notna(), None)
|
||||||
|
df = df.replace('', None)
|
||||||
|
df['*请求方法'] = df['*请求方法'].map(MethodEnum.reversal_obj())
|
||||||
|
|
||||||
|
df = df.rename(columns={
|
||||||
|
'*产品名称': 'project_product',
|
||||||
|
'*模块名称': 'module',
|
||||||
|
'*接口名称': 'name',
|
||||||
|
'*请求方法': 'method',
|
||||||
|
'*url': 'url',
|
||||||
|
'请求头': 'headers',
|
||||||
|
'参数': 'params',
|
||||||
|
'表单': 'data',
|
||||||
|
'JSON': 'json',
|
||||||
|
'文件': 'file',
|
||||||
|
})
|
||||||
|
for index, row in df.iterrows():
|
||||||
|
record = row.to_dict()
|
||||||
|
record['type'] = request.data.get("type")
|
||||||
|
try:
|
||||||
|
record['project_product'] = ProjectProduct.objects.get(name=record['project_product']).id
|
||||||
|
record['module'] = ProductModule.objects.get(name=record['module'],
|
||||||
|
project_product=record['project_product']).id
|
||||||
|
except (
|
||||||
|
ProductModule.MultipleObjectsReturned, ProjectProduct.MultipleObjectsReturned, ProductModule.DoesNotExist,
|
||||||
|
ProjectProduct.DoesNotExist):
|
||||||
|
return ResponseData.fail(RESPONSE_MSG_0138)
|
||||||
|
for i in ['headers', 'params', 'data', 'json', 'file']:
|
||||||
|
if record[i] == '' or record[i] is None:
|
||||||
|
record[i] = None
|
||||||
|
elif i == 'file':
|
||||||
|
try:
|
||||||
|
record[i] = json.loads(record[i])
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
return ResponseData.fail(RESPONSE_MSG_0137)
|
||||||
|
ApiInfoCRUD.inside_post(record)
|
||||||
|
return ResponseData.success(RESPONSE_MSG_0083)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.5 on 2025-10-20 07:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auto_system', '0010_testsuitedetails_case_sum_testsuitedetails_fail_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='projectproduct',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=64, unique=True, verbose_name='产品名称'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -31,7 +31,7 @@ class ProjectProduct(models.Model):
|
|||||||
create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
|
create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
|
||||||
update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True)
|
update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True)
|
||||||
project = models.ForeignKey(to=Project, to_field="id", on_delete=models.PROTECT)
|
project = models.ForeignKey(to=Project, to_field="id", on_delete=models.PROTECT)
|
||||||
name = models.CharField(verbose_name="产品名称", max_length=64)
|
name = models.CharField(verbose_name="产品名称", max_length=64, unique=True)
|
||||||
ui_client_type = models.SmallIntegerField(verbose_name="UI客户端类型", default=0)
|
ui_client_type = models.SmallIntegerField(verbose_name="UI客户端类型", default=0)
|
||||||
api_client_type = models.SmallIntegerField(verbose_name="API客户端类型", default=0)
|
api_client_type = models.SmallIntegerField(verbose_name="API客户端类型", default=0)
|
||||||
|
|
||||||
|
|||||||
@@ -139,3 +139,5 @@ RESPONSE_MSG_0133 = (200, '测试子任务成功')
|
|||||||
RESPONSE_MSG_0134 = (300, '只支持cURL(base)的格式')
|
RESPONSE_MSG_0134 = (300, '只支持cURL(base)的格式')
|
||||||
RESPONSE_MSG_0135 = (200, '提取jsonpath预发成功')
|
RESPONSE_MSG_0135 = (200, '提取jsonpath预发成功')
|
||||||
RESPONSE_MSG_0136 = (200, '设置日志状态成功')
|
RESPONSE_MSG_0136 = (200, '设置日志状态成功')
|
||||||
|
RESPONSE_MSG_0137 = (300, 'file必须上传json格式,请参照webAPI新增模版')
|
||||||
|
RESPONSE_MSG_0138 = (300, '请确保上传的产品和模块没有重名,并且存在')
|
||||||
|
|||||||
BIN
MangoServer/upload_template/接口批量上传模版.xlsx
Normal file
BIN
MangoServer/upload_template/接口批量上传模版.xlsx
Normal file
Binary file not shown.
@@ -92,3 +92,19 @@ export function postApiCopyInfo(id: number) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postApiUploadApi(type: number, file?: File) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('type', type.toString())
|
||||||
|
if (file) {
|
||||||
|
formData.append('file', file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return post({
|
||||||
|
url: '/api/upload/api',
|
||||||
|
data: () => formData,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<a-form :model="{}" layout="inline" @keyup.enter="doRefresh">
|
<a-form :model="{}" layout="inline" @keyup.enter="doRefresh">
|
||||||
<a-form-item v-for="item of conditionItems" :key="item.key" :label="item.label">
|
<a-form-item v-for="item of conditionItems" :key="item.key" :label="item.label">
|
||||||
<template v-if="item.type === 'input'">
|
<template v-if="item.type === 'input'">
|
||||||
<a-input v-model="item.value" :placeholder="item.placeholder" @blur="doRefresh" />
|
<a-input v-model="item.value" :placeholder="item.placeholder" @blur="doRefresh()" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'cascader' && item.key === 'project_product'">
|
<template v-else-if="item.type === 'cascader' && item.key === 'project_product'">
|
||||||
<a-cascader
|
<a-cascader
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<template #extra>
|
<template #extra>
|
||||||
<a-space v-if="data.apiType === '0'">
|
<a-space v-if="data.apiType === '0'">
|
||||||
<a-button size="small" type="primary" @click="onBatchUpload">录制</a-button>
|
<a-button size="small" type="primary" @click="onBatchUpload">录制</a-button>
|
||||||
<!-- <a-button type="primary" size="small" @click="onSynchronization">同步</a-button>-->
|
<a-button size="small" type="primary" @click="showBatchImportModal">批量导入</a-button>
|
||||||
<a-button size="small" status="success" :loading="caseRunning" @click="onConcurrency"
|
<a-button size="small" status="success" :loading="caseRunning" @click="onConcurrency"
|
||||||
>批量执行
|
>批量执行
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -228,6 +228,39 @@
|
|||||||
<TableFooter :pagination="pagination" />
|
<TableFooter :pagination="pagination" />
|
||||||
</template>
|
</template>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="batchImportVisible"
|
||||||
|
title="批量导入"
|
||||||
|
@ok="handleBatchImportOk"
|
||||||
|
@cancel="handleBatchImportCancel"
|
||||||
|
>
|
||||||
|
<a-space direction="vertical" style="width: 100%">
|
||||||
|
<a-button type="primary" @click="onDownload">下载模板</a-button>
|
||||||
|
<a-space>
|
||||||
|
<a-switch
|
||||||
|
v-model="debugInterface"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
checked-text="调试接口"
|
||||||
|
unchecked-text="批量生成"
|
||||||
|
/>
|
||||||
|
<span>tips:如果需要直接上传到调试接口Tab里面,请点击开关打开</span></a-space
|
||||||
|
>
|
||||||
|
<div style="margin-top: 10px">
|
||||||
|
<a-button type="primary" @click="fileInputRef?.click()">选择文件</a-button>
|
||||||
|
<span v-if="selectedFile" style="margin-left: 10px">已选择: {{ selectedFile.name }}</span>
|
||||||
|
<input
|
||||||
|
ref="fileInputRef"
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
style="display: none"
|
||||||
|
@change="handleFileSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-space>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
<ModalDialog ref="modalDialogRef" :title="data.actionTitle" @confirm="onDataForm">
|
<ModalDialog ref="modalDialogRef" :title="data.actionTitle" @confirm="onDataForm">
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-form :model="formModel">
|
<a-form :model="formModel">
|
||||||
@@ -305,6 +338,7 @@
|
|||||||
postApiCopyInfo,
|
postApiCopyInfo,
|
||||||
postApiImportUrl,
|
postApiImportUrl,
|
||||||
postApiInfo,
|
postApiInfo,
|
||||||
|
postApiUploadApi,
|
||||||
putApiInfo,
|
putApiInfo,
|
||||||
putApiPutApiInfoType,
|
putApiPutApiInfoType,
|
||||||
} from '@/api/apitest/info'
|
} from '@/api/apitest/info'
|
||||||
@@ -312,6 +346,7 @@
|
|||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from '@/store/modules/user'
|
||||||
import { strJson } from '@/utils/tools'
|
import { strJson } from '@/utils/tools'
|
||||||
import { getSystemSocketNewBrowser } from '@/api/system/socket_api'
|
import { getSystemSocketNewBrowser } from '@/api/system/socket_api'
|
||||||
|
import { baseURL } from '@/api/axios.config'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const enumStore = useEnum()
|
const enumStore = useEnum()
|
||||||
@@ -340,6 +375,10 @@
|
|||||||
const caseRunning = ref(false)
|
const caseRunning = ref(false)
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
const batchImportVisible = ref(false)
|
||||||
|
const debugInterface = ref(0) // 调试接口开关,默认为0
|
||||||
|
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
const selectedFile = ref<File | null>(null) // 存储选中的文件
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
@@ -426,11 +465,77 @@
|
|||||||
data.formItem = formItems
|
data.formItem = formItems
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBatchUpload() {
|
function onDownload() {
|
||||||
if (userStore.selected_environment == null) {
|
const file_name = '接口批量上传模版.xlsx'
|
||||||
Message.error('请先选择用例执行的环境并进行录制')
|
const file_path = `${baseURL}/download?file_name=${encodeURIComponent(file_name)}`
|
||||||
|
let aLink = document.createElement('a')
|
||||||
|
aLink.href = file_path
|
||||||
|
aLink.download = file_name
|
||||||
|
Message.loading('文件下载中~')
|
||||||
|
document.body.appendChild(aLink)
|
||||||
|
aLink.click()
|
||||||
|
document.body.removeChild(aLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增的批量导入弹窗功能
|
||||||
|
function showBatchImportModal() {
|
||||||
|
batchImportVisible.value = true
|
||||||
|
// 重置文件和开关状态
|
||||||
|
if (fileInputRef.value) {
|
||||||
|
fileInputRef.value.value = ''
|
||||||
|
}
|
||||||
|
debugInterface.value = 0
|
||||||
|
selectedFile.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBatchImportOk() {
|
||||||
|
// 点击确定时才进行上传
|
||||||
|
if (!selectedFile.value) {
|
||||||
|
Message.warning('请先选择要上传的文件')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postApiUploadApi(debugInterface.value, selectedFile.value)
|
||||||
|
.then((res) => {
|
||||||
|
Message.success(res.msg)
|
||||||
|
batchImportVisible.value = false
|
||||||
|
doRefresh()
|
||||||
|
// 重置状态
|
||||||
|
if (fileInputRef.value) {
|
||||||
|
fileInputRef.value.value = ''
|
||||||
|
}
|
||||||
|
selectedFile.value = null
|
||||||
|
debugInterface.value = 0
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
Message.error('上传失败')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBatchImportCancel() {
|
||||||
|
batchImportVisible.value = false
|
||||||
|
// 重置状态
|
||||||
|
if (fileInputRef.value) {
|
||||||
|
fileInputRef.value.value = ''
|
||||||
|
}
|
||||||
|
selectedFile.value = null
|
||||||
|
debugInterface.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFileSelect(event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
const files = target.files
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
selectedFile.value = files[0]
|
||||||
|
// 仅选择文件,不立即上传
|
||||||
|
Message.info(`已选择文件: ${selectedFile.value.name}`)
|
||||||
|
} else {
|
||||||
|
selectedFile.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBatchUpload() {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '注意事项',
|
title: '注意事项',
|
||||||
content:
|
content:
|
||||||
|
|||||||
Reference in New Issue
Block a user