增加接口的批量导入

This commit is contained in:
毛鹏
2025-10-20 15:31:11 +08:00
parent 276144a1ac
commit ee91aeb24c
8 changed files with 195 additions and 7 deletions

View File

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

View File

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

View File

@@ -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='产品名称'),
),
]

View File

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

View File

@@ -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, '请确保上传的产品和模块没有重名,并且存在')

View File

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

View File

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