导入swagger

This commit is contained in:
2025-02-19 11:02:19 +08:00
parent a7029118f2
commit 2ea58667e0
15 changed files with 245 additions and 34 deletions

View File

@@ -61,9 +61,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version> <version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置devtools不会生效 -->
</configuration>
<executions> <executions>
<execution> <execution>
<goals> <goals>

View File

@@ -14,7 +14,6 @@ import com.test.common.enums.BusinessType;
import com.test.test.domain.TestApi; import com.test.test.domain.TestApi;
import com.test.test.service.ITestApiService; import com.test.test.service.ITestApiService;
import com.test.common.core.page.TableDataInfo; import com.test.common.core.page.TableDataInfo;
import org.springframework.web.multipart.MultipartFile;
/** /**
* 接口Controller * 接口Controller

View File

@@ -20,7 +20,7 @@ public class TestGroupController extends BaseController {
private ITestGroupService testGroupService; private ITestGroupService testGroupService;
/** /**
* 查询接口节点列表 * 查询节点列表
*/ */
@GetMapping("/list") @GetMapping("/list")
public AjaxResult list(String type) { public AjaxResult list(String type) {
@@ -29,27 +29,27 @@ public class TestGroupController extends BaseController {
} }
/** /**
* 新增接口节点 * 新增节点
*/ */
@Log(title = "接口节点", businessType = BusinessType.INSERT) @Log(title = "节点", businessType = BusinessType.INSERT)
@PostMapping("/add") @PostMapping("/add")
public AjaxResult add(@RequestBody TestGroup testGroup) { public AjaxResult add(@RequestBody TestGroup testGroup) {
return success(testGroupService.insertTestGroup(testGroup)); return success(testGroupService.insertTestGroup(testGroup));
} }
/** /**
* 修改接口节点 * 修改节点
*/ */
@Log(title = "接口节点", businessType = BusinessType.UPDATE) @Log(title = "节点", businessType = BusinessType.UPDATE)
@PostMapping("/edit") @PostMapping("/edit")
public AjaxResult edit(@RequestBody TestGroup testGroup) { public AjaxResult edit(@RequestBody TestGroup testGroup) {
return toAjax(testGroupService.updateTestGroup(testGroup)); return toAjax(testGroupService.updateTestGroup(testGroup));
} }
/** /**
* 删除接口节点 * 删除节点
*/ */
@Log(title = "接口节点", businessType = BusinessType.DELETE) @Log(title = "节点", businessType = BusinessType.DELETE)
@PostMapping("/del") @PostMapping("/del")
public AjaxResult remove(@RequestBody @Validated GroupDelectQO qo) throws Exception { public AjaxResult remove(@RequestBody @Validated GroupDelectQO qo) throws Exception {
return toAjax(testGroupService.deleteTestGroupById(qo)); return toAjax(testGroupService.deleteTestGroupById(qo));

View File

@@ -1,4 +0,0 @@
package com.test.test.domain.dto;
public class SwaggerInfo {
}

View File

@@ -33,4 +33,5 @@ public interface TestGroupMapper
*/ */
int deleteTestGroupByIds(List<Long> ids); int deleteTestGroupByIds(List<Long> ids);
TestGroup selectGroup(String name, Long parentId, String type);
} }

View File

@@ -1,6 +1,7 @@
package com.test.test.service; package com.test.test.service;
import java.util.List; import java.util.List;
import com.test.test.domain.TestGroup; import com.test.test.domain.TestGroup;
import com.test.test.domain.qo.GroupDelectQO; import com.test.test.domain.qo.GroupDelectQO;
@@ -9,8 +10,7 @@ import com.test.test.domain.qo.GroupDelectQO;
* *
* @author xiaoe * @author xiaoe
*/ */
public interface ITestGroupService public interface ITestGroupService {
{
/** /**
* 查询节点(文件夹)列表 * 查询节点(文件夹)列表
*/ */
@@ -21,6 +21,11 @@ public interface ITestGroupService
*/ */
TestGroup insertTestGroup(TestGroup testGroup); TestGroup insertTestGroup(TestGroup testGroup);
/**
* 查询节点(文件夹)
*/
TestGroup selectGroup(String name, Long parentId, String type);
/** /**
* 修改节点(文件夹) * 修改节点(文件夹)
*/ */

View File

@@ -1,16 +1,20 @@
package com.test.test.service.impl; package com.test.test.service.impl;
import java.util.List; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.test.common.utils.DateUtils; import com.test.common.utils.DateUtils;
import com.test.common.utils.http.HttpUtils; import com.test.common.utils.http.HttpUtils;
import com.test.test.domain.TestGroup;
import com.test.test.domain.qo.TestApiListQO; import com.test.test.domain.qo.TestApiListQO;
import com.test.test.service.ITestGroupService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.test.test.mapper.TestApiMapper; import com.test.test.mapper.TestApiMapper;
import com.test.test.domain.TestApi; import com.test.test.domain.TestApi;
import com.test.test.service.ITestApiService; import com.test.test.service.ITestApiService;
import org.springframework.transaction.annotation.Transactional;
/** /**
* 接口Service业务层处理 * 接口Service业务层处理
@@ -22,6 +26,9 @@ public class TestApiServiceImpl implements ITestApiService {
@Resource @Resource
private TestApiMapper testApiMapper; private TestApiMapper testApiMapper;
@Resource
private ITestGroupService testGroupService;
/** /**
* 查询接口 * 查询接口
* *
@@ -57,9 +64,159 @@ public class TestApiServiceImpl implements ITestApiService {
} }
@Override @Override
@Transactional
public int importSwaggerApi(String url) { public int importSwaggerApi(String url) {
JSONObject json = JSONObject.parse(HttpUtils.sendGet(url)); AtomicInteger count = new AtomicInteger();
return 0; JSONObject jsonObject = JSONObject.parseObject(HttpUtils.sendGet(url));
String title = jsonObject.getJSONObject("info").getString("title");
// 设置顶级节点
Long parentId = getGroupId(title, 0L);
// 获取Schemas
JSONObject schemas = jsonObject.getJSONObject("components").getJSONObject("schemas");
// 获取所有接口
jsonObject.getJSONObject("paths").forEach((uri, pathJson) -> {
((JSONObject) pathJson).forEach((method, v) -> {
JSONObject json = (JSONObject) v;
// 获取接口名
String name = json.getString("summary");
if (name == null) name = uri;
// 获取接口分组
String groupName = title;
List<String> tags = json.getList("tags", String.class);
if (!tags.isEmpty()) {
groupName = tags.get(0);
}
// 获取body
String body = "";
String contentType = "";
if (json.getJSONObject("requestBody") != null) {
JSONObject bodyJson = json.getJSONObject("requestBody").getJSONObject("content");
if (bodyJson != null) {
Set<String> keys = bodyJson.keySet();
if (!keys.isEmpty()) {
contentType = keys.iterator().next();
JSONObject schema = bodyJson.getJSONObject(contentType).getJSONObject("schema");
body = JSONObject.toJSONString(parseSchema(schema, schemas));
}
}
}
// 获取Param
String param = "";
JSONObject parameters = json.getJSONObject("parameters");
if (parameters!=null) {
JSONObject paramsJson = parseParams(parameters, schemas);
param = JSONObject.toJSONString(paramsJson);
}
TestApi testApi = new TestApi();
testApi.setUri(uri);
testApi.setMethod(method);
testApi.setName(name);
testApi.setGroupId(getGroupId(groupName, parentId));
testApi.setBody(body);
testApi.setParam(param);
count.addAndGet(testApiMapper.insertTestApi(testApi));
});
});
return count.get();
}
private static JSONObject parseSchema(JSONObject schema, JSONObject schemas) {
JSONObject result = new JSONObject();
// 如果 schema 包含 $ref则解析引用
if (schema.containsKey("$ref")) {
String ref = schema.getString("$ref");
// 去掉 #/components/schemas/ 前缀,获取引用的 schema 名称
String schemaName = ref.replace("#/components/schemas/", "");
JSONObject referencedSchema = schemas.getJSONObject(schemaName);
// 递归解析引用的 schema
return parseSchema(referencedSchema, schemas);
}
// 如果 schema 包含 type则直接根据类型构建结果
if (schema.containsKey("type")) {
String type = schema.getString("type");
switch (type) {
case "integer":
result.put("value", 0);
break;
case "string":
result.put("value", "string");
break;
case "boolean":
result.put("value", false);
break;
default:
result.put("value", type);
}
}
// 如果 schema 有 properties则遍历 properties 并递归解析
if (schema.containsKey("properties")) {
JSONObject properties = schema.getJSONObject("properties");
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
JSONObject propertySchema = (JSONObject) entry.getValue();
JSONObject propertyResult = parseSchema(propertySchema, schemas); // 递归解析每个字段
result.put(key, propertyResult.get("value")); // 获取解析结果的值
}
}
return result;
}
private static JSONObject parseParams(JSONObject params, JSONObject schemas) {
JSONObject result = new JSONObject();
// 遍历 params 中的每个参数
for (String key : params.keySet()) {
// 获取当前参数的 schema 定义
Object value = params.get(key);
// 获取 schema
JSONObject schema = schemas.getJSONObject(key);
// 如果 schema 是基础类型,则直接赋值
if (schema != null && schema.containsKey("type")) {
String type = schema.getString("type");
if ("string".equals(type)) {
result.put(key, value != null ? value : "string"); // 默认值为 "string"
} else if ("integer".equals(type)) {
result.put(key, value != null ? value : 0); // 默认值为 0
} else if ("boolean".equals(type)) {
result.put(key, value != null ? value : false); // 默认值为 false
}
}
// 如果 schema 是引用类型,则递归解析该引用
else if (schema != null && schema.containsKey("$ref")) {
String ref = schema.getString("$ref");
String schemaName = ref.replace("#/components/schemas/", "");
JSONObject referencedSchema = schemas.getJSONObject(schemaName);
// 如果引用的 schema 存在,递归解析该 schema
if (referencedSchema != null) {
// 递归调用,解析引用的 schema
JSONObject nestedResult = parseParams(new JSONObject(), schemas);
result.putAll(nestedResult);
}
}
}
return result;
}
private Long getGroupId(String groupName, Long parentId) {
TestGroup group = testGroupService.selectGroup(groupName, parentId, "api");
if (group == null) {
group = new TestGroup();
group.setParentId(parentId);
group.setName(groupName);
group.setType("api");
group.setCreateTime(DateUtils.getNowDate());
testGroupService.insertTestGroup(group);
}
return group.getId();
} }
/** /**

View File

@@ -8,6 +8,7 @@ import com.test.common.utils.DateUtils;
import com.test.common.utils.StringUtils; import com.test.common.utils.StringUtils;
import com.test.test.domain.qo.GroupDelectQO; import com.test.test.domain.qo.GroupDelectQO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.test.test.mapper.TestGroupMapper; import com.test.test.mapper.TestGroupMapper;
import com.test.test.domain.TestGroup; import com.test.test.domain.TestGroup;
@@ -26,6 +27,7 @@ public class TestGroupServiceImpl implements ITestGroupService {
private TestGroupMapper testGroupMapper; private TestGroupMapper testGroupMapper;
@Resource @Resource
@Lazy
private TestApiServiceImpl testApiServiceImpl; private TestApiServiceImpl testApiServiceImpl;
/** /**
@@ -46,6 +48,11 @@ public class TestGroupServiceImpl implements ITestGroupService {
return testGroup; return testGroup;
} }
@Override
public TestGroup selectGroup(String name, Long parentId, String type) {
return testGroupMapper.selectGroup(name, parentId, type);
}
/** /**
* 修改节点(文件夹) * 修改节点(文件夹)
*/ */

View File

@@ -33,6 +33,7 @@
<if test="method != null and method != ''"> and method = #{method}</if> <if test="method != null and method != ''"> and method = #{method}</if>
<if test="uri != null and uri != ''"> and uri like concat('%', #{uri}, '%')</if> <if test="uri != null and uri != ''"> and uri like concat('%', #{uri}, '%')</if>
</where> </where>
order by create_time desc
</select> </select>
<select id="selectTestApiById" parameterType="Long" resultMap="TestApiResult"> <select id="selectTestApiById" parameterType="Long" resultMap="TestApiResult">

View File

@@ -37,6 +37,13 @@
</where> </where>
</select> </select>
<select id="selectGroup" parameterType="String" resultMap="TestGroupResult">
<include refid="selectTestGroupVo"/>
<where>
name = #{name} and type = #{type} and parent_id = #{parentId}
</where>
</select>
<insert id="insertTestGroup" parameterType="TestGroup" useGeneratedKeys="true" keyProperty="id"> <insert id="insertTestGroup" parameterType="TestGroup" useGeneratedKeys="true" keyProperty="id">
insert into test_group insert into test_group
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">

View File

@@ -39,8 +39,16 @@ export function updateApi(data) {
// 删除接口 // 删除接口
export function delApi(id) { export function delApi(id) {
return request({ return request({
url: '/test/api/del/', url: '/test/api/del',
method: 'post', method: 'post',
params: {id} data: {id}
})
}
export function importApi(url) {
return request({
url: '/test/api/import/swagger',
method: 'post',
params: url
}) })
} }

View File

@@ -143,7 +143,7 @@ export default {
this.form.header.splice(scope.$index, 1) this.form.header.splice(scope.$index, 1)
}, },
cancel() { cancel() {
this.$tab.closeOpenPage({ path: "/api" }); this.$tab.closeOpenPage({path: "/api"});
} }
} }
} }
@@ -158,9 +158,11 @@ export default {
::v-deep .el-collapse-item__wrap { ::v-deep .el-collapse-item__wrap {
padding: 0 16px; padding: 0 16px;
} }
::v-deep.el-select { ::v-deep.el-select {
width: 130px; width: 130px;
} }
.input-with-select ::v-deep .el-input-group__prepend { .input-with-select ::v-deep .el-input-group__prepend {
background-color: #fff; background-color: #fff;
} }

View File

@@ -139,7 +139,7 @@ export default {
contentType: this.form.contentType, contentType: this.form.contentType,
body: this.form.body, body: this.form.body,
}).then(res => { }).then(res => {
this.$message.success("新增成功"); this.$message.success("修改成功");
this.cancel(); this.cancel();
}) })
}, },
@@ -165,7 +165,7 @@ export default {
this.form.header.splice(scope.$index, 1) this.form.header.splice(scope.$index, 1)
}, },
cancel() { cancel() {
this.$tab.closeOpenPage({ path: "/api" }); this.$tab.closeOpenPage({path: "/api"});
} }
} }
} }
@@ -180,9 +180,11 @@ export default {
::v-deep .el-collapse-item__wrap { ::v-deep .el-collapse-item__wrap {
padding: 0 16px; padding: 0 16px;
} }
::v-deep.el-select { ::v-deep.el-select {
width: 130px; width: 130px;
} }
.input-with-select ::v-deep .el-input-group__prepend { .input-with-select ::v-deep .el-input-group__prepend {
background-color: #fff; background-color: #fff;
} }

View File

@@ -1,22 +1,46 @@
<template> <template>
<div class="app-container"> <div>
import <el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="swagger" prop="url">
<el-input v-model="form.url" placeholder="请输入swagger文档链接"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</div> </div>
</template> </template>
<script> <script>
import {importApi} from "@/api/test/api";
export default { export default {
name: "ApiImport", name: "ApiImport",
dicts: ['http_method'], dicts: ['http_method'],
created () {
console.log(this.$route.query.groupId)
},
data() { data() {
return { return {
// 表单参数 // 表单参数
form: {}, form: {},
// 表单校验 // 表单校验
rules: {} rules: {
url: [{required: true, message: "swagger文档链接不能为空", trigger: "blur"}]
}
}
},
methods: {
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
importApi({url: this.form.url}).then((res) => {
this.$message.success("导入成功")
this.cancel();
})
}
})
},
cancel() {
this.$emit('close')
} }
} }
} }

View File

@@ -28,6 +28,7 @@
<el-table-column label="接口名称" align="center" prop="name"/> <el-table-column label="接口名称" align="center" prop="name"/>
<el-table-column label="接口请求类型" align="center" prop="method"/> <el-table-column label="接口请求类型" align="center" prop="method"/>
<el-table-column label="接口路径" align="center" prop="uri"/> <el-table-column label="接口路径" align="center" prop="uri"/>
<el-table-column label="创建时间" align="center" prop="createTime"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
@@ -35,18 +36,21 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/> <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<el-dialog title="导入接口" :visible.sync="dialogVisible" :close-on-click-modal="false" destroy-on-close>
<api-import @close="() => dialogVisible = false"/>
</el-dialog>
</folder-page> </folder-page>
</template> </template>
<script> <script>
import {listApi, delApi} from "@/api/test/api"; import {listApi, delApi} from "@/api/test/api";
import FolderPage from "@/components/FolderPage/index.vue"; import FolderPage from "@/components/FolderPage/index.vue";
import ApiImport from "@/views/test/api/import.vue";
export default { export default {
name: "Api", name: "Api",
components: {FolderPage}, components: {ApiImport, FolderPage},
dicts: ['http_method'], dicts: ['http_method'],
data() { data() {
return { return {
@@ -62,6 +66,7 @@ export default {
method: null, method: null,
uri: null, uri: null,
}, },
dialogVisible: false,
}; };
}, },
methods: { methods: {
@@ -97,7 +102,7 @@ export default {
this.$tab.openPage("添加接口", "/api/add", {groupId: this.queryParams.groupId}); this.$tab.openPage("添加接口", "/api/add", {groupId: this.queryParams.groupId});
}, },
handleImport() { handleImport() {
this.$tab.openPage("导入接口", "/api/import", {groupId: this.queryParams.groupId}); this.dialogVisible = true;
}, },
/** 修改按钮操作 */ /** 修改按钮操作 */
handleUpdate(row) { handleUpdate(row) {