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/copy", ApiInfoViews.as_view({'post': 'copy_api_info'})),
|
||||
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/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/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/status", ApiPublicViews.as_view({'put': 'put_status'})),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import json
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
import pandas as pd
|
||||
from curlparser import parse
|
||||
from django.forms import model_to_dict
|
||||
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.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.project_product import ProjectProductSerializersC
|
||||
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)
|
||||
data = ApiInfoCRUD.inside_post(result)
|
||||
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)
|
||||
update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True)
|
||||
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)
|
||||
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_0135 = (200, '提取jsonpath预发成功')
|
||||
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-item v-for="item of conditionItems" :key="item.key" :label="item.label">
|
||||
<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 v-else-if="item.type === 'cascader' && item.key === 'project_product'">
|
||||
<a-cascader
|
||||
@@ -75,7 +75,7 @@
|
||||
<template #extra>
|
||||
<a-space v-if="data.apiType === '0'">
|
||||
<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>
|
||||
@@ -228,6 +228,39 @@
|
||||
<TableFooter :pagination="pagination" />
|
||||
</template>
|
||||
</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">
|
||||
<template #content>
|
||||
<a-form :model="formModel">
|
||||
@@ -305,6 +338,7 @@
|
||||
postApiCopyInfo,
|
||||
postApiImportUrl,
|
||||
postApiInfo,
|
||||
postApiUploadApi,
|
||||
putApiInfo,
|
||||
putApiPutApiInfoType,
|
||||
} from '@/api/apitest/info'
|
||||
@@ -312,6 +346,7 @@
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { strJson } from '@/utils/tools'
|
||||
import { getSystemSocketNewBrowser } from '@/api/system/socket_api'
|
||||
import { baseURL } from '@/api/axios.config'
|
||||
|
||||
const router = useRouter()
|
||||
const enumStore = useEnum()
|
||||
@@ -340,6 +375,10 @@
|
||||
const caseRunning = 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 = () => {
|
||||
visible.value = false
|
||||
@@ -426,11 +465,77 @@
|
||||
data.formItem = formItems
|
||||
}
|
||||
|
||||
function onBatchUpload() {
|
||||
if (userStore.selected_environment == null) {
|
||||
Message.error('请先选择用例执行的环境并进行录制')
|
||||
function onDownload() {
|
||||
const file_name = '接口批量上传模版.xlsx'
|
||||
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
|
||||
}
|
||||
|
||||
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({
|
||||
title: '注意事项',
|
||||
content:
|
||||
|
||||
Reference in New Issue
Block a user