diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql index ee78509d49..55f9cb378c 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql @@ -156,3 +156,17 @@ CREATE INDEX idx_type ON operation_history (`type`); -- set innodb lock wait timeout to default SET SESSION innodb_lock_wait_timeout = DEFAULT; + +CREATE TABLE IF NOT EXISTS share_info +( + `id` VARCHAR(50) NOT NULL COMMENT '分享ID' , + `create_time` BIGINT NOT NULL COMMENT '创建时间' , + `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , + `update_time` BIGINT NOT NULL COMMENT '更新时间' , + `share_type` VARCHAR(64) COMMENT '分享类型single batch' , + `custom_data` LONGBLOB COMMENT '分享扩展数据' , + `lang` VARCHAR(10) COMMENT '语言' , + PRIMARY KEY (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT = '分享'; diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java index 651c104459..ca102ff387 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java @@ -44,6 +44,7 @@ public class FilterChainUtils { filterChainDefinitionMap.put("/anonymous/**", "anon"); //分享相关接口 + filterChainDefinitionMap.put("/api/share/doc/view/**", "anon"); filterChainDefinitionMap.put("/system/theme", "anon"); filterChainDefinitionMap.put("/system/parameter/save/base-url/**", "anon"); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/constants/ShareInfoType.java b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ShareInfoType.java new file mode 100644 index 0000000000..7a19e55d87 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ShareInfoType.java @@ -0,0 +1,10 @@ +package io.metersphere.api.constants; + +/** + * @author: LAN + * @date: 2023/12/27 15:32 + * @version: 1.0 + */ +public enum ShareInfoType { + Single, Batch +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java index 76a234ed5c..480ac78468 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java @@ -187,7 +187,7 @@ public class ApiDefinitionController { @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") public ApiDefinitionDocDTO getDocInfo(@Validated @RequestBody ApiDefinitionDocRequest request) { - return apiDefinitionService.getDocInfo(request, SessionUtils.getUserId()); + return apiDefinitionService.getDocInfo(request); } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiShareController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiShareController.java new file mode 100644 index 0000000000..170ba8af4d --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiShareController.java @@ -0,0 +1,46 @@ +package io.metersphere.api.controller.definition; + +import io.metersphere.api.dto.definition.ApiDefinitionDocDTO; +import io.metersphere.api.dto.definition.ApiDefinitionDocRequest; +import io.metersphere.api.dto.share.ShareInfoDTO; +import io.metersphere.api.service.ApiShareService; +import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.system.security.CheckOwner; +import io.metersphere.system.utils.SessionUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.*; + +import java.util.Objects; + +/** + * @author: LAN + * @date: 2023/12/27 14:30 + * @version: 1.0 + */ +@RestController +@RequestMapping(value = "/api/share") +@Tag(name = "接口测试-接口管理-接口分享") +public class ApiShareController { + + @Resource + private ApiShareService apiShareService; + + @PostMapping("/doc/gen") + @Operation(summary = "接口测试-接口管理-接口文档分享") + @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) + @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") + public ShareInfoDTO genApiDocShareInfo(@RequestBody ApiDefinitionDocRequest request) { + return apiShareService.genApiDocShareInfo(request, Objects.requireNonNull(SessionUtils.getUser())); + } + + @GetMapping("/doc/view/{shareId}") + @Operation(summary = "接口测试-接口管理-接口文档分享查看") + @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) + @CheckOwner(resourceId = "#shareId", resourceType = "share_info") + public ApiDefinitionDocDTO shareDocView(@PathVariable String shareId) { + return apiShareService.shareDocView(shareId); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/share/ShareInfoDTO.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/share/ShareInfoDTO.java new file mode 100644 index 0000000000..f991ae60d4 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/share/ShareInfoDTO.java @@ -0,0 +1,14 @@ +package io.metersphere.api.dto.share; + +import lombok.Data; + +/** + * @author: LAN + * @date: 2023/12/27 14:43 + * @version: 1.0 + */ +@Data +public class ShareInfoDTO { + private String id; + private String shareUrl; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/share/request/ApiDocShareRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/share/request/ApiDocShareRequest.java new file mode 100644 index 0000000000..9c12c6916c --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/share/request/ApiDocShareRequest.java @@ -0,0 +1,29 @@ +package io.metersphere.api.dto.share.request; + +import io.metersphere.api.dto.definition.ApiDefinitionDocRequest; +import io.metersphere.sdk.domain.ShareInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * @author: LAN + * @date: 2023/12/27 14:46 + * @version: 1.0 + */ +@Data +public class ApiDocShareRequest extends ShareInfo { + + @Schema(description = "接口pk") + @Size(min = 1, max = 50, message = "{api_definition.id.length_range}") + private String apiId; + + @Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{api_definition.project_id.not_blank}") + @Size(min = 1, max = 50, message = "{api_definition.project_id.length_range}") + private String projectId; + + @Schema(description = "接口文档分享选择") + private ApiDefinitionDocRequest selectRequest; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtShareInfoMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtShareInfoMapper.java new file mode 100644 index 0000000000..ad3988636b --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtShareInfoMapper.java @@ -0,0 +1,11 @@ +package io.metersphere.api.mapper; + +import io.metersphere.sdk.domain.ShareInfo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ExtShareInfoMapper { + + List selectByShareTypeAndShareApiIdWithBLOBs(@Param("shareType") String shareType, @Param("customData") byte[] customData, @Param("lang") String lang); +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtShareInfoMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtShareInfoMapper.xml new file mode 100644 index 0000000000..8d6128f085 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtShareInfoMapper.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiShareService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiShareService.java new file mode 100644 index 0000000000..ab95a95d62 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiShareService.java @@ -0,0 +1,117 @@ +package io.metersphere.api.service; + +import io.metersphere.api.constants.ApiDefinitionDocType; +import io.metersphere.api.constants.ShareInfoType; +import io.metersphere.api.dto.definition.ApiDefinitionDocDTO; +import io.metersphere.api.dto.definition.ApiDefinitionDocRequest; +import io.metersphere.api.dto.share.ShareInfoDTO; +import io.metersphere.api.mapper.ExtShareInfoMapper; +import io.metersphere.api.service.definition.ApiDefinitionService; +import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.sdk.domain.ShareInfo; +import io.metersphere.sdk.mapper.ShareInfoMapper; +import io.metersphere.sdk.util.BeanUtils; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.dto.sdk.SessionUser; +import jakarta.annotation.Resource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * @author: LAN + * @date: 2023/12/27 14:47 + * @version: 1.0 + */ +@Service +public class ApiShareService { + + @Resource + ShareInfoMapper shareInfoMapper; + + @Resource + ExtShareInfoMapper extShareInfoMapper; + + @Resource + ApiDefinitionService apiDefinitionService; + + /** + * 生成 api 接口文档分享信息 + * 根据要分享的类型来进行完全匹配搜索。 + * 搜索的到就返回那条数据,搜索不到就新增一条信息 + */ + public ShareInfoDTO genApiDocShareInfo(ApiDefinitionDocRequest request, SessionUser user) { + ShareInfo shareInfoRequest = new ShareInfo(); + String customData = genCustomData(request, shareInfoRequest); + String lang = user.getLanguage() == null ? LocaleContextHolder.getLocale().toString() : user.getLanguage(); + + Optional.ofNullable(customData) + .ifPresent(data -> { + BeanUtils.copyBean(shareInfoRequest, request); + shareInfoRequest.setCreateUser(user.getId()); + shareInfoRequest.setLang(lang); + shareInfoRequest.setCustomData(data.getBytes()); + }); + ShareInfo shareInfo = Optional.ofNullable(customData) + .map(data -> genShareInfo(shareInfoRequest)) + .orElse(new ShareInfo()); + + return conversionShareInfoToDTO(shareInfo); + } + + private String genCustomData(ApiDefinitionDocRequest request, ShareInfo shareInfoRequest) { + String customData = null; + if (ApiDefinitionDocType.ALL.name().equals(request.getType()) || ApiDefinitionDocType.MODULE.name().equals(request.getType())) { + customData = JSON.toJSONString(request); + shareInfoRequest.setShareType(ShareInfoType.Batch.name()); + } else if (ApiDefinitionDocType.API.name().equals(request.getType())) { + apiDefinitionService.checkApiDefinition(request.getApiId()); + customData = JSON.toJSONString(request); + shareInfoRequest.setShareType(ShareInfoType.Single.name()); + } + + return customData; + } + + + + /** + * 生成分享连接 + * 如果该数据有连接则返回已有的连接,不做有效期判断, 反之创建链接 + * + * @param request 分享请求内容 + * @return 分享数据 + */ + public ShareInfo genShareInfo(ShareInfo request) { + List shareInfos = extShareInfoMapper.selectByShareTypeAndShareApiIdWithBLOBs(request.getShareType(), request.getCustomData(), request.getLang()); + return shareInfos.isEmpty() ? createShareInfo(request) : shareInfos.get(0); + } + + public ShareInfo createShareInfo(ShareInfo shareInfo) { + long createTime = System.currentTimeMillis(); + shareInfo.setId(UUID.randomUUID().toString()); + shareInfo.setCreateTime(createTime); + shareInfo.setUpdateTime(createTime); + shareInfoMapper.insert(shareInfo); + return shareInfo; + } + + public ShareInfoDTO conversionShareInfoToDTO(ShareInfo shareInfo) { + ShareInfoDTO returnDTO = new ShareInfoDTO(); + if (null != shareInfo.getCustomData()) { + String url = "?shareId=" + shareInfo.getId(); + returnDTO.setId(shareInfo.getId()); + returnDTO.setShareUrl(url); + } + return returnDTO; + } + + public ApiDefinitionDocDTO shareDocView(String shareId) { + ShareInfo shareInfo = shareInfoMapper.selectByPrimaryKey(shareId); + ApiDefinitionDocRequest apiDefinitionDocRequest = ApiDataUtils.parseObject(new String(shareInfo.getCustomData()), ApiDefinitionDocRequest.class); + return apiDefinitionService.getDocInfo(apiDefinitionDocRequest); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java index c707152609..96a90f1add 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java @@ -2,10 +2,7 @@ package io.metersphere.api.service.definition; import io.metersphere.api.constants.ApiResourceType; import io.metersphere.api.controller.result.ApiResultCode; -import io.metersphere.api.domain.ApiDefinition; -import io.metersphere.api.domain.ApiDefinitionMock; -import io.metersphere.api.domain.ApiDefinitionMockConfig; -import io.metersphere.api.domain.ApiDefinitionMockExample; +import io.metersphere.api.domain.*; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; import io.metersphere.api.dto.definition.ApiDefinitionMockDTO; import io.metersphere.api.dto.definition.HttpResponse; @@ -198,7 +195,7 @@ public class ApiDefinitionMockService { public void delete(ApiDefinitionMockRequest request, String userId) { checkApiDefinitionMock(request.getId()); String apiDefinitionMockDir = DefaultRepositoryDir.getApiDefinitionDir(request.getProjectId(), request.getId()); - apiFileResourceService.deleteByResourceId(apiDefinitionMockDir, request.getId(), request.getProjectId(), userId, OperationLogModule.API_DEFINITION); + apiFileResourceService.deleteByResourceId(apiDefinitionMockDir, request.getId(), request.getProjectId(), userId, OperationLogModule.API_DEFINITION_MOCK); apiDefinitionMockConfigMapper.deleteByPrimaryKey(request.getId()); apiDefinitionMockMapper.deleteByPrimaryKey(request.getId()); } @@ -251,4 +248,26 @@ public class ApiDefinitionMockService { public String uploadTempFile(MultipartFile file) { return apiFileResourceService.uploadTempFile(file); } + + public void deleteByApiIds(List apiIds, String userId) { + ApiDefinitionMockExample apiDefinitionMockExample = new ApiDefinitionMockExample(); + apiDefinitionMockExample.createCriteria().andApiDefinitionIdIn(apiIds); + + List apiDefinitionMocks = apiDefinitionMockMapper.selectByExample(apiDefinitionMockExample); + + if(!apiDefinitionMocks.isEmpty()){ + apiDefinitionMocks.forEach(item -> { + String apiDefinitionMockDir = DefaultRepositoryDir.getApiDefinitionDir(item.getProjectId(), item.getId()); + apiFileResourceService.deleteByResourceId(apiDefinitionMockDir, item.getId(), item.getProjectId(), userId, OperationLogModule.API_DEFINITION_MOCK); + }); + + List mockIds = apiDefinitionMocks.stream().map(ApiDefinitionMock::getId).toList(); + + ApiDefinitionMockConfigExample apiDefinitionMockConfigExample = new ApiDefinitionMockConfigExample(); + apiDefinitionMockConfigExample.createCriteria().andIdIn(mockIds); + apiDefinitionMockConfigMapper.deleteByExample(apiDefinitionMockConfigExample); + + apiDefinitionMockMapper.deleteByExample(apiDefinitionMockExample); + } + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java index 7a94e247fe..9df7d0c070 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java @@ -94,6 +94,9 @@ public class ApiDefinitionService { @Resource private ApiDefinitionLogService apiDefinitionLogService; + @Resource + private ApiDefinitionMockService apiDefinitionMockService; + public List getApiDefinitionPage(ApiDefinitionPageRequest request, String userId){ CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId); List list = extApiDefinitionMapper.list(request); @@ -446,7 +449,7 @@ public class ApiDefinitionService { * * @param apiId 接口id */ - private ApiDefinition checkApiDefinition(String apiId) { + public ApiDefinition checkApiDefinition(String apiId) { ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(apiId); if (apiDefinition == null) { throw new MSException(ApiResultCode.API_DEFINITION_NOT_EXIST); @@ -777,8 +780,9 @@ public class ApiDefinitionService { List caseIds = caseLists.stream().map(ApiTestCase::getId).distinct().toList(); // case 批量删除回收站 apiTestCaseService.deleteResourceByIds(caseIds, projectId, userId); - } + // 删除 mock + apiDefinitionMockService.deleteByApiIds(apiIds, userId); } // 获取批量操作选中的ID @@ -821,7 +825,7 @@ public class ApiDefinitionService { apiDefinitionDTO.setCustomFields(customFieldMap.get(id)); } - public ApiDefinitionDocDTO getDocInfo(ApiDefinitionDocRequest request, String userId) { + public ApiDefinitionDocDTO getDocInfo(ApiDefinitionDocRequest request) { ApiDefinitionDocDTO apiDefinitionDocDTO = new ApiDefinitionDocDTO(); apiDefinitionDocDTO.setType(request.getType()); // 下载所有/一个模块接口文档时,不做分页数据量大的时候会不会有性能问题,单独做接口 @@ -830,20 +834,21 @@ public class ApiDefinitionService { if (!list.isEmpty()) { ApiDefinitionDTO first = list.get(0); apiDefinitionLogService.handleBlob(first.getId(), first); - if(ApiDefinitionDocType.ALL.name().equals(request.getType())){ - apiDefinitionDocDTO.setDocTitle(Translator.get(ALL_API)); + String docTitle; + if (ApiDefinitionDocType.ALL.name().equals(request.getType())) { + docTitle = Translator.get(ALL_API); } else { ApiDefinitionModule apiDefinitionModule = apiDefinitionModuleMapper.selectByPrimaryKey(first.getModuleId()); - if (apiDefinitionModule != null) { - apiDefinitionDocDTO.setDocTitle(apiDefinitionModule.getName()); - } else { - apiDefinitionDocDTO.setDocTitle(Translator.get(UNPLANNED_API)); - } + docTitle = (apiDefinitionModule != null) ? apiDefinitionModule.getName() : Translator.get(UNPLANNED_API); } + apiDefinitionDocDTO.setDocTitle(docTitle); apiDefinitionDocDTO.setDocInfo(first); } } else if (ApiDefinitionDocType.API.name().equals(request.getType())) { - ApiDefinitionDTO apiDefinitionDTO = get(request.getApiId(), userId); + ApiDefinition apiDefinition = checkApiDefinition(request.getApiId()); + ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO(); + BeanUtils.copyBean(apiDefinitionDTO, apiDefinition); + apiDefinitionLogService.handleBlob(apiDefinition.getId(), apiDefinitionDTO); apiDefinitionDocDTO.setDocTitle(apiDefinitionDTO.getName()); apiDefinitionDocDTO.setDocInfo(apiDefinitionDTO); } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiShareControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiShareControllerTests.java new file mode 100644 index 0000000000..46b41f3e1b --- /dev/null +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiShareControllerTests.java @@ -0,0 +1,159 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.constants.ApiDefinitionDocType; +import io.metersphere.api.constants.ShareInfoType; +import io.metersphere.api.controller.result.ApiResultCode; +import io.metersphere.api.domain.ApiDefinition; +import io.metersphere.api.domain.ApiDefinitionBlob; +import io.metersphere.api.dto.definition.ApiDefinitionDTO; +import io.metersphere.api.dto.definition.ApiDefinitionDocDTO; +import io.metersphere.api.dto.definition.ApiDefinitionDocRequest; +import io.metersphere.api.dto.definition.HttpResponse; +import io.metersphere.api.dto.request.http.MsHTTPElement; +import io.metersphere.api.dto.share.ShareInfoDTO; +import io.metersphere.api.mapper.ApiDefinitionBlobMapper; +import io.metersphere.api.mapper.ApiDefinitionMapper; +import io.metersphere.api.mapper.ExtApiDefinitionMapper; +import io.metersphere.api.mapper.ExtShareInfoMapper; +import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.sdk.domain.ShareInfo; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.mapper.ShareInfoMapper; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.base.BaseTest; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.*; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.List; + +@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@AutoConfigureMockMvc +public class ApiShareControllerTests extends BaseTest { + + private static final String BASE_PATH = "/api/share/"; + + private static final String SHARE_DOC = BASE_PATH + "/doc/gen"; + private static final String SHARE_VIEW = BASE_PATH + "/doc/view/"; + + private static final String ALL_API = "api_definition_module.api.all"; + + @Resource + private ApiDefinitionMapper apiDefinitionMapper; + + @Resource + private ApiDefinitionBlobMapper apiDefinitionBlobMapper; + + @Resource + private ExtApiDefinitionMapper extApiDefinitionMapper; + + @Resource + private ExtShareInfoMapper extShareInfoMapper; + + @Resource + private ShareInfoMapper shareInfoMapper; + + + @Test + @Order(1) + @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void testShareDoc() throws Exception { + ApiDefinitionDocRequest request = new ApiDefinitionDocRequest(); + ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + request.setApiId(apiDefinition.getId()); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setType(ApiDefinitionDocType.API.name()); + // @@请求成功 + this.requestPostWithOkAndReturn(SHARE_DOC, request); + MvcResult mvcResult = this.requestPostWithOkAndReturn(SHARE_DOC, request); + ApiDataUtils.setResolver(MsHTTPElement.class); + ShareInfoDTO shareInfoDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResult).get("data")), ShareInfoDTO.class); + // 校验数据是否正确 + List shareInfos = extShareInfoMapper.selectByShareTypeAndShareApiIdWithBLOBs(ShareInfoType.Single.name(), JSON.toJSONString(request).getBytes(), "zh_CN"); + Assertions.assertNotNull(shareInfos); + Assertions.assertEquals(1, shareInfos.size()); + Assertions.assertEquals(shareInfoDTO.getId(), shareInfos.get(0).getId()); + Assertions.assertTrue(shareInfoDTO.getShareUrl().contains("?shareId=")); + + request.setApiId("111"); + assertErrorCode(this.requestPost(SHARE_DOC, request), ApiResultCode.API_DEFINITION_NOT_EXIST); + + // @@分享模块文档 + request.setApiId(null); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setType(ApiDefinitionDocType.MODULE.name()); + request.setModuleIds(List.of("10001")); + MvcResult mvcResultModule = this.requestPostWithOkAndReturn(SHARE_DOC, request); + ApiDataUtils.setResolver(MsHTTPElement.class); + ShareInfoDTO shareInfoDTOModule = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResultModule).get("data")), ShareInfoDTO.class); + // 校验数据是否正确 + List shareInfosModule = extShareInfoMapper.selectByShareTypeAndShareApiIdWithBLOBs(ShareInfoType.Batch.name(), JSON.toJSONString(request).getBytes(), "zh_CN"); + Assertions.assertNotNull(shareInfosModule); + Assertions.assertEquals(1, shareInfosModule.size()); + Assertions.assertEquals(shareInfoDTOModule.getId(), shareInfosModule.get(0).getId()); + Assertions.assertTrue(shareInfoDTOModule.getShareUrl().contains("?shareId=")); + + // @@分享全部文档 + request.setApiId(null); + request.setModuleIds(null); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setType(ApiDefinitionDocType.ALL.name()); + MvcResult mvcResultAll = this.requestPostWithOkAndReturn(SHARE_DOC, request); + ApiDataUtils.setResolver(MsHTTPElement.class); + ShareInfoDTO allShareInfoDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResultAll).get("data")), ShareInfoDTO.class); + + // 校验数据是否正确 + List allShareInfos = extShareInfoMapper.selectByShareTypeAndShareApiIdWithBLOBs(ShareInfoType.Batch.name(), JSON.toJSONString(request).getBytes(), "zh_CN"); + Assertions.assertNotNull(allShareInfos); + Assertions.assertEquals(1, allShareInfos.size()); + Assertions.assertEquals(allShareInfoDTO.getId(), allShareInfos.get(0).getId()); + Assertions.assertTrue(allShareInfoDTO.getShareUrl().contains("?shareId=")); + assertTestShareView(allShareInfoDTO.getId()); + + // @@校验权限 + requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_READ, SHARE_DOC, request); + } + + + public void assertTestShareView(String shareId) throws Exception { + // @@请求成功 + ShareInfo shareInfo = shareInfoMapper.selectByPrimaryKey(shareId); + ApiDefinitionDocRequest apiDefinitionDocRequest = ApiDataUtils.parseObject(new String(shareInfo.getCustomData()), ApiDefinitionDocRequest.class); + MvcResult mvcResultAll = this.requestGetWithOkAndReturn(SHARE_VIEW + shareId); + ApiDataUtils.setResolver(MsHTTPElement.class); + ApiDefinitionDocDTO allApiDefinitionDocDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResultAll).get("data")), ApiDefinitionDocDTO.class); + // 校验数据是否正确 + ApiDefinitionDocDTO copyAllApiDefinitionDocDTO = new ApiDefinitionDocDTO(); + + List allList = extApiDefinitionMapper.listDoc(apiDefinitionDocRequest); + if(null != allList){ + ApiDefinitionDTO info = allList.stream().findFirst().orElseThrow(() -> new MSException(ApiResultCode.API_DEFINITION_NOT_EXIST)); + ApiDefinitionBlob allApiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(info.getId()); + if(allApiDefinitionBlob != null){ + info.setRequest(ApiDataUtils.parseObject(new String(allApiDefinitionBlob.getRequest()), AbstractMsTestElement.class)); + info.setResponse(ApiDataUtils.parseArray(new String(allApiDefinitionBlob.getResponse()), HttpResponse.class)); + } + if(StringUtils.isBlank(copyAllApiDefinitionDocDTO.getDocTitle())){ + copyAllApiDefinitionDocDTO.setDocTitle(Translator.get(ALL_API)); + } + copyAllApiDefinitionDocDTO.setType(ApiDefinitionDocType.ALL.name()); + copyAllApiDefinitionDocDTO.setDocInfo(info); + } + + Assertions.assertEquals(allApiDefinitionDocDTO.getType(), copyAllApiDefinitionDocDTO.getType()); + Assertions.assertEquals(allApiDefinitionDocDTO.getDocTitle(), copyAllApiDefinitionDocDTO.getDocTitle()); + Assertions.assertEquals(allApiDefinitionDocDTO.getDocInfo().getId(), copyAllApiDefinitionDocDTO.getDocInfo().getId()); + // @@校验权限 + requestGetPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_READ, SHARE_VIEW + shareId); + } + +} diff --git a/backend/services/api-test/src/test/resources/dml/init_api_definition.sql b/backend/services/api-test/src/test/resources/dml/init_api_definition.sql index e227736791..4a3470290f 100644 --- a/backend/services/api-test/src/test/resources/dml/init_api_definition.sql +++ b/backend/services/api-test/src/test/resources/dml/init_api_definition.sql @@ -57,3 +57,21 @@ DELETE FROM `template` WHERE `id` in ('api-template-id', 'default-api-template-i INSERT INTO template (id, name, remark, internal, update_time, create_time, create_user, scope_type, scope_id, enable_third_part, scene) VALUES ('api-template-id', 'api-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'API'), ('default-api-template-id', 'api-default-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'API'); + + +DELETE FROM `api_definition_mock` WHERE `id` in ('mock_1', 'mock_2', 'mock_3', 'mock_4','mock_5'); +INSERT INTO `api_definition_mock` VALUES + ('mock_1', 1641120000000, 1641120000000, 'user1', 'Mock 1', '[\"tag1\",\"tag2\"]', 1, 'EXPECT001', '100001100001', '1001'), + ('mock_2', 1641121000000, 1641121000000, 'user2', 'Mock 2', '[\"tag2\",\"tag3\"]', 1, 'EXPECT002', '100001100001', '1002'), + ('mock_3', 1641122000000, 1641122000000, 'user3', 'Mock 3', '[\"tag3\",\"tag4\"]', 1, 'EXPECT003', '100001100001', '1003'), + ('mock_4', 1641123000000, 1641123000000, 'user1', 'Mock 4', '[\"tag4\",\"tag5\"]', 1, 'EXPECT004', '100001100001', '1005'), + ('mock_5', 1641124000000, 1641124000000, 'user2', 'Mock 5', '[\"tag5\",\"tag1\"]', 1, 'EXPECT005', '100001100001', '1005'); + +DELETE FROM `api_definition_mock_config` WHERE `id` in ('mock_1', 'mock_2', 'mock_3', 'mock_4','mock_5'); +INSERT INTO `api_definition_mock_config` VALUES + ('mock_1', '{"type": "exact", "value": "request_value"}', '{"status": 200, "body": {"message": "Mock Response 1"}}'), + ('mock_2', '{"type": "regex", "value": "\\d{3}"}', '{"status": 404, "body": {"error": "Not Found"}}'), + ('mock_3', '{"type": "contains", "value": "partial_value"}', '{"status": 500, "body": {"error": "Internal Server Error"}}'), + ('mock_4', '{"type": "exact", "value": "another_exact_value"}', '{"status": 200, "body": {"data": "Another Mock Response"}}'), + ('mock_5', '{"type": "jsonpath", "value": "$.items[0].name"}', '{"status": 200, "body": {"items": [{"name": "Item 1"}]}}'); + diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/ApiTestInterceptor.java b/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/ApiTestInterceptor.java index fd38938284..70f963839b 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/ApiTestInterceptor.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/ApiTestInterceptor.java @@ -18,6 +18,9 @@ public class ApiTestInterceptor { configList.add(new MybatisInterceptorConfig(ApiDefinitionBlob.class, "request", CompressUtils.class, "zip", "unzip")); configList.add(new MybatisInterceptorConfig(ApiDefinitionBlob.class, "response", CompressUtils.class, "zip", "unzip")); configList.add(new MybatisInterceptorConfig(ApiDefinitionBlob.class, "remark", CompressUtils.class, "zip", "unzip")); + // ApiDefinitionMockConfig + configList.add(new MybatisInterceptorConfig(ApiDefinitionMockConfig.class, "matching", CompressUtils.class, "zip", "unzip")); + configList.add(new MybatisInterceptorConfig(ApiDefinitionMockConfig.class, "response", CompressUtils.class, "zip", "unzip")); // ApiTestCaseBlob configList.add(new MybatisInterceptorConfig(ApiTestCaseBlob.class, "request", CompressUtils.class, "zip", "unzip")); // ApiReportBlob diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SdkInterceptor.java b/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SdkInterceptor.java index dd62a00079..894f8e2114 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SdkInterceptor.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SdkInterceptor.java @@ -1,6 +1,7 @@ package io.metersphere.system.config.interceptor; import io.metersphere.sdk.domain.OperationLogBlob; +import io.metersphere.sdk.domain.ShareInfo; import io.metersphere.sdk.util.CompressUtils; import io.metersphere.system.utils.MybatisInterceptorConfig; import org.springframework.context.annotation.Bean; @@ -17,6 +18,8 @@ public class SdkInterceptor { configList.add(new MybatisInterceptorConfig(OperationLogBlob.class, "originalValue", CompressUtils.class, "zip", "unzip")); configList.add(new MybatisInterceptorConfig(OperationLogBlob.class, "modifiedValue", CompressUtils.class, "zip", "unzip")); + // ShareInfo + configList.add(new MybatisInterceptorConfig(ShareInfo.class, "customData", CompressUtils.class, "zipString", "unzipString")); return configList; }