feat(测试计划): 补充接口文档分享功能

--story=1016179 --user=宋昌昌 【接口测试】接口文档 https://www.tapd.cn/55049933/s/1589978
This commit is contained in:
song-cc-rock 2024-10-11 11:13:54 +08:00 committed by Craftsman
parent b08ce1508b
commit ec1d26522c
20 changed files with 587 additions and 22 deletions

View File

@ -24,33 +24,39 @@ public class ApiDocShare implements Serializable {
@Size(min = 1, max = 255, message = "{api_doc_share.name.length_range}", groups = {Created.class, Updated.class})
private String name;
@Schema(title = "是否公开; 0: 私有、1: 公开", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(title = "是否公开;0: 私有、1: 公开", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{api_doc_share.is_public.not_blank}", groups = {Created.class})
private Boolean isPublic;
@Schema(title = "访问密码; 私有时需要访问密码")
@Schema(title = "访问密码;私有时需要访问密码")
private String password;
@Schema(title = "允许导出; 0: 不允许、1: 允许")
@Schema(title = "允许导出;0: 不允许、1: 允许", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{api_doc_share.allow_export.not_blank}", groups = {Created.class})
private Boolean allowExport;
@Schema(title = "接口范围; 全部接口(ALL)、模块(MODULE)、路径(PATH)、标签(TAG)", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(title = "接口范围;全部接口(ALL)、模块(MODULE)、路径(PATH)、标签(TAG)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_doc_share.api_range.not_blank}", groups = {Created.class})
@Size(min = 1, max = 10, message = "{api_doc_share.api_range.length_range}", groups = {Created.class, Updated.class})
private String apiRange;
@Schema(title = "范围匹配符; 包含(CONTAINS)、等于(EQUALS)")
@Schema(title = "范围匹配符;包含(CONTAINS)、等于(EQUALS)")
private String rangeMatchSymbol;
@Schema(title = "范围匹配值; eg: 选中路径范围时, 该值作为路径匹配")
@Schema(title = "范围匹配值;eg: 选中路径范围时, 该值作为路径匹配")
private String rangeMatchVal;
@Schema(title = "失效时间值")
private Integer invalidTime;
@Schema(title = "失效时间单位; 小时(HOUR)、天(DAY)、月(MONTH)、年(YEAR)")
@Schema(title = "失效时间单位;小时(HOUR)、天(DAY)、月(MONTH)、年(YEAR)")
private String invalidUnit;
@Schema(title = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_doc_share.project_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{api_doc_share.project_id.length_range}", groups = {Created.class, Updated.class})
private String projectId;
@Schema(title = "创建时间")
private Long createTime;
@ -70,6 +76,7 @@ public class ApiDocShare implements Serializable {
rangeMatchVal("range_match_val", "rangeMatchVal", "VARCHAR", false),
invalidTime("invalid_time", "invalidTime", "INTEGER", false),
invalidUnit("invalid_unit", "invalidUnit", "VARCHAR", false),
projectId("project_id", "projectId", "VARCHAR", false),
createTime("create_time", "createTime", "BIGINT", false),
createUser("create_user", "createUser", "VARCHAR", false);

View File

@ -774,6 +774,76 @@ public class ApiDocShareExample {
return (Criteria) this;
}
public Criteria andProjectIdIsNull() {
addCriterion("project_id is null");
return (Criteria) this;
}
public Criteria andProjectIdIsNotNull() {
addCriterion("project_id is not null");
return (Criteria) this;
}
public Criteria andProjectIdEqualTo(String value) {
addCriterion("project_id =", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotEqualTo(String value) {
addCriterion("project_id <>", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThan(String value) {
addCriterion("project_id >", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThanOrEqualTo(String value) {
addCriterion("project_id >=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThan(String value) {
addCriterion("project_id <", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThanOrEqualTo(String value) {
addCriterion("project_id <=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLike(String value) {
addCriterion("project_id like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotLike(String value) {
addCriterion("project_id not like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdIn(List<String> values) {
addCriterion("project_id in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotIn(List<String> values) {
addCriterion("project_id not in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdBetween(String value1, String value2) {
addCriterion("project_id between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotBetween(String value1, String value2) {
addCriterion("project_id not between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andCreateTimeIsNull() {
addCriterion("create_time is null");
return (Criteria) this;

View File

@ -12,6 +12,7 @@
<result column="range_match_val" jdbcType="VARCHAR" property="rangeMatchVal" />
<result column="invalid_time" jdbcType="INTEGER" property="invalidTime" />
<result column="invalid_unit" jdbcType="VARCHAR" property="invalidUnit" />
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="create_user" jdbcType="VARCHAR" property="createUser" />
</resultMap>
@ -75,7 +76,7 @@
</sql>
<sql id="Base_Column_List">
id, `name`, is_public, `password`, allow_export, api_range, range_match_symbol, range_match_val,
invalid_time, invalid_unit, create_time, create_user
invalid_time, invalid_unit, project_id, create_time, create_user
</sql>
<select id="selectByExample" parameterType="io.metersphere.api.domain.ApiDocShareExample" resultMap="BaseResultMap">
select
@ -111,13 +112,13 @@
insert into api_doc_share (id, `name`, is_public,
`password`, allow_export, api_range,
range_match_symbol, range_match_val, invalid_time,
invalid_unit, create_time, create_user
)
invalid_unit, project_id, create_time,
create_user)
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{isPublic,jdbcType=BIT},
#{password,jdbcType=VARCHAR}, #{allowExport,jdbcType=BIT}, #{apiRange,jdbcType=VARCHAR},
#{rangeMatchSymbol,jdbcType=VARCHAR}, #{rangeMatchVal,jdbcType=VARCHAR}, #{invalidTime,jdbcType=INTEGER},
#{invalidUnit,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{createUser,jdbcType=VARCHAR}
)
#{invalidUnit,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{createUser,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.api.domain.ApiDocShare">
insert into api_doc_share
@ -152,6 +153,9 @@
<if test="invalidUnit != null">
invalid_unit,
</if>
<if test="projectId != null">
project_id,
</if>
<if test="createTime != null">
create_time,
</if>
@ -190,6 +194,9 @@
<if test="invalidUnit != null">
#{invalidUnit,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
#{projectId,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
#{createTime,jdbcType=BIGINT},
</if>
@ -237,6 +244,9 @@
<if test="record.invalidUnit != null">
invalid_unit = #{record.invalidUnit,jdbcType=VARCHAR},
</if>
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT},
</if>
@ -260,6 +270,7 @@
range_match_val = #{record.rangeMatchVal,jdbcType=VARCHAR},
invalid_time = #{record.invalidTime,jdbcType=INTEGER},
invalid_unit = #{record.invalidUnit,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
create_user = #{record.createUser,jdbcType=VARCHAR}
<if test="_parameter != null">
@ -296,6 +307,9 @@
<if test="invalidUnit != null">
invalid_unit = #{invalidUnit,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
project_id = #{projectId,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT},
</if>
@ -316,6 +330,7 @@
range_match_val = #{rangeMatchVal,jdbcType=VARCHAR},
invalid_time = #{invalidTime,jdbcType=INTEGER},
invalid_unit = #{invalidUnit,jdbcType=VARCHAR},
project_id = #{projectId,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
create_user = #{createUser,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
@ -323,14 +338,15 @@
<insert id="batchInsert" parameterType="map">
insert into api_doc_share
(id, `name`, is_public, `password`, allow_export, api_range, range_match_symbol,
range_match_val, invalid_time, invalid_unit, create_time, create_user)
range_match_val, invalid_time, invalid_unit, project_id, create_time, create_user
)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR}, #{item.isPublic,jdbcType=BIT},
#{item.password,jdbcType=VARCHAR}, #{item.allowExport,jdbcType=BIT}, #{item.apiRange,jdbcType=VARCHAR},
#{item.rangeMatchSymbol,jdbcType=VARCHAR}, #{item.rangeMatchVal,jdbcType=VARCHAR},
#{item.invalidTime,jdbcType=INTEGER}, #{item.invalidUnit,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT},
#{item.createUser,jdbcType=VARCHAR})
#{item.invalidTime,jdbcType=INTEGER}, #{item.invalidUnit,jdbcType=VARCHAR}, #{item.projectId,jdbcType=VARCHAR},
#{item.createTime,jdbcType=BIGINT}, #{item.createUser,jdbcType=VARCHAR})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -373,6 +389,9 @@
<if test="'invalid_unit'.toString() == column.value">
#{item.invalidUnit,jdbcType=VARCHAR}
</if>
<if test="'project_id'.toString() == column.value">
#{item.projectId,jdbcType=VARCHAR}
</if>
<if test="'create_time'.toString() == column.value">
#{item.createTime,jdbcType=BIGINT}
</if>

View File

@ -84,12 +84,13 @@ CREATE TABLE api_doc_share (
`name` VARCHAR(255) NOT NULL COMMENT '名称' ,
`is_public` BIT(1) NOT NULL DEFAULT 0 COMMENT '是否公开; 0: 私有、1: 公开' ,
`password` VARCHAR(10) COMMENT '访问密码; 私有时需要访问密码' ,
`allow_export` BIT(1) DEFAULT 0 COMMENT '允许导出; 0: 不允许、1: 允许' ,
`allow_export` BIT(1) NOT NULL DEFAULT 0 COMMENT '允许导出; 0: 不允许、1: 允许' ,
`api_range` VARCHAR(10) NOT NULL DEFAULT 'ALL' COMMENT '接口范围; 全部接口(ALL)、模块(MODULE)、路径(PATH)、标签(TAG)' ,
`range_match_symbol` VARCHAR(10) COMMENT '范围匹配符; 包含(CONTAINS)、等于(EQUALS)' ,
`range_match_val` VARCHAR(1000) COMMENT '范围匹配值; eg: 选中路径范围时, 该值作为路径匹配' ,
`invalid_time` INT COMMENT '失效时间值' ,
`invalid_unit` VARCHAR(10) COMMENT '失效时间单位; 小时(HOUR)、天(DAY)、月(MONTH)、年(YEAR)' ,
`project_id` VARCHAR(50) NOT NULL COMMENT '项目ID' ,
`create_time` BIGINT NOT NULL COMMENT '创建时间' ,
`create_user` VARCHAR(50) NOT NULL COMMENT '创建人' ,
PRIMARY KEY (id)

View File

@ -447,15 +447,21 @@ api_definition.status.continuous=连调中
api_test_case.clear.api_change=忽略本次变更差异
api_test_case.ignore.api_change=忽略全部变更差异
# api doc share i18n
api_doc_share.not_exist=接口文档分享不存在
api_doc_share.id.not_blank=主键不能为空
api_doc_share.id.length_range=主键长度必须在{min}和{max}之间
api_doc_share.name.not_blank=名称不能为空
api_doc_share.name.length_range=名称长度必须在{min}和{max}之间
api_doc_share.is_public.not_blank=是否公开不能为空
api_doc_share.is_public.length_range=是否公开长度必须在{min}和{max}之间
api_doc_share.allow_export.not_blank=允许导出不能為空
api_doc_share.allow_export.length_range=允许导出長度必須在{min}和{max}之间
api_doc_share.api_range.not_blank=接口范围不能为空
api_doc_share.api_range.length_range=接口范围长度必须在{min}和{max}之间
api_doc_share.create_user.not_blank=创建人不能为空
api_doc_share.create_user.length_range=创建人长度必须在{min}和{max}之间
api_doc_share.project_id.not_blank=项目ID不能为空
api_doc_share.project_id.length_range=项目ID长度必须在{min}和{max}之间

View File

@ -457,14 +457,20 @@ api_test_case.ignore.api_change=Ignore all change differences
curl_script_is_empty=Curl script cannot be empty
curl_script_is_invalid=Curl script is invalid
curl_raw_content_is_invalid=Raw content is invalid
# api doc share i18n
api_doc_share.not_exist=api doc share not exist
api_doc_share.id.not_blank=id cannot be empty
api_doc_share.id.length_range=id length must be between {min} and {max}
api_doc_share.name.not_blank=name cannot be empty
api_doc_share.name.length_range=name length must be between {min} and {max}
api_doc_share.is_public.not_blank=isPublic cannot be empty
api_doc_share.is_public.length_range=isPublic length must be between {min} and {max}
api_doc_share.allow_export.not_blank=allowExport cannot be empty
api_doc_share.allow_export.length_range=allowExport length must be between {min} and {max}
api_doc_share.api_range.not_blank=apiRange cannot be empty
api_doc_share.api_range.length_range=apiRange length must be between {min} and {max}
api_doc_share.create_user.not_blank=createUser cannot be empty
api_doc_share.create_user.length_range=createUser length must be between {min} and {max}
api_doc_share.create_user.length_range=createUser length must be between {min} and {max}
api_doc_share.project_id.not_blank=projectId cannot be empty
api_doc_share.project_id.length_range=projectId length must be between {min} and {max}

View File

@ -425,14 +425,20 @@ api_test_case.ignore.api_change=忽略全部变更差异
curl_script_is_empty=cURL脚本不能为空
curl_script_is_invalid=cURL脚本格式不正确
curl_raw_content_is_invalid=raw内容格式不正确
# api doc share i18n
api_doc_share.not_exist=接口文档分享不存在
api_doc_share.id.not_blank=主键不能为空
api_doc_share.id.length_range=主键长度必须在{min}和{max}之间
api_doc_share.name.not_blank=名称不能为空
api_doc_share.name.length_range=名称长度必须在{min}和{max}之间
api_doc_share.is_public.not_blank=是否公开不能为空
api_doc_share.is_public.length_range=是否公开长度必须在{min}和{max}之间
api_doc_share.allow_export.not_blank=允许导出不能為空
api_doc_share.allow_export.length_range=允许导出長度必須在{min}和{max}之间
api_doc_share.api_range.not_blank=接口范围不能为空
api_doc_share.api_range.length_range=接口范围长度必须在{min}和{max}之间
api_doc_share.create_user.not_blank=创建人不能为空
api_doc_share.create_user.length_range=创建人长度必须在{min}和{max}之间
api_doc_share.create_user.length_range=创建人长度必须在{min}和{max}之间
api_doc_share.project_id.not_blank=项目ID不能为空
api_doc_share.project_id.length_range=项目ID长度必须在{min}和{max}之间

View File

@ -425,14 +425,20 @@ api_test_case.ignore.api_change=忽略全部變更差異
curl_script_is_empty=curl脚本不能爲空
curl_script_is_invalid=curl脚本格式不正確
curl_raw_content_is_invalid=raw内容格式不正確
# api doc share i18n
api_doc_share.not_exist=接口文檔分享不存在
api_doc_share.id.not_blank=主键不能為空
api_doc_share.id.length_range=主键長度必須在{min}和{max}之间
api_doc_share.name.not_blank=名称不能為空
api_doc_share.name.length_range=名称長度必須在{min}和{max}之间
api_doc_share.is_public.not_blank=是否公开不能為空
api_doc_share.is_public.length_range=是否公开長度必須在{min}和{max}之间
api_doc_share.allow_export.not_blank=允许导出不能為空
api_doc_share.allow_export.length_range=允许导出長度必須在{min}和{max}之间
api_doc_share.api_range.not_blank=接口范围不能為空
api_doc_share.api_range.length_range=接口范围長度必須在{min}和{max}之间
api_doc_share.create_user.not_blank=创建人不能為空
api_doc_share.create_user.length_range=创建人長度必須在{min}和{max}之间
api_doc_share.create_user.length_range=创建人長度必須在{min}和{max}之间
api_doc_share.project_id.not_blank=项目ID不能為空
api_doc_share.project_id.length_range=项目ID長度必須在{min}和{max}之间

View File

@ -0,0 +1,75 @@
package io.metersphere.api.controller.definition;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.domain.ApiDocShare;
import io.metersphere.api.dto.definition.ApiDocShareDTO;
import io.metersphere.api.dto.definition.request.ApiDocShareEditRequest;
import io.metersphere.api.dto.definition.request.ApiDocSharePageRequest;
import io.metersphere.api.service.definition.ApiDocShareService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
import io.metersphere.system.utils.SessionUtils;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author song-cc-rock
*/
@RestController
@RequestMapping(value = "/api/doc/share")
@Tag(name = "接口测试-接口管理-分享")
public class ApiDocShareController {
@Resource
private ApiDocShareService apiDocShareService;
@PostMapping(value = "/page")
@Operation(summary = "接口测试-接口管理-分页获取分享列表")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DOC_SHARE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public Pager<List<ApiDocShareDTO>> page(@Validated @RequestBody ApiDocSharePageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
return PageUtils.setPageInfo(page, apiDocShareService.list(request));
}
@PostMapping(value = "/add")
@Operation(summary = "接口测试-接口管理-新增分享")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DOC_SHARE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public ApiDocShare add(@Validated({Created.class}) @RequestBody ApiDocShareEditRequest request) {
return apiDocShareService.create(request, SessionUtils.getUserId());
}
@PostMapping(value = "/update")
@Operation(summary = "接口测试-接口管理-更新分享")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DOC_SHARE)
@CheckOwner(resourceId = "#request.getId()", resourceType = "api_doc_share")
public ApiDocShare update(@Validated({Updated.class}) @RequestBody ApiDocShareEditRequest request) {
return apiDocShareService.update(request);
}
@GetMapping("/delete/{id}")
@Operation(summary = "接口测试-接口管理-删除分享")
@Parameter(name = "id", description = "分享ID", schema = @Schema(requiredMode = Schema.RequiredMode.REQUIRED))
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DOC_SHARE)
@CheckOwner(resourceId = "#id", resourceType = "api_doc_share")
public void delete(@PathVariable String id) {
apiDocShareService.delete(id);
}
}

View File

@ -0,0 +1,20 @@
package io.metersphere.api.dto.definition;
import io.metersphere.api.domain.ApiDocShare;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiDocShareDTO extends ApiDocShare {
@Schema(title = "分享是否失效")
private Boolean invalid;
@Schema(title = "分享接口数量")
private Integer apiShareNum;
@Schema(title = "截止日期")
private Long deadline;
}

View File

@ -0,0 +1,57 @@
package io.metersphere.api.dto.definition.request;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiDocShareEditRequest implements Serializable {
@Schema(title = "主键", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_doc_share.id.not_blank}", groups = {Updated.class})
@Size(min = 1, max = 50, message = "{api_doc_share.id.length_range}", groups = {Created.class, Updated.class})
private String id;
@Schema(description = "分享名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_doc_share.name.not_blank}")
@Size(min = 1, max = 255, message = "{api_doc_share.name.length_range}")
private String name;
@Schema(title = "接口范围;全部接口(ALL)、模块(MODULE)、路径(PATH)、标签(TAG)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_doc_share.api_range.not_blank}", groups = {Created.class})
@Size(min = 1, max = 10, message = "{api_doc_share.api_range.length_range}", groups = {Created.class, Updated.class})
private String apiRange;
@Schema(title = "范围匹配符;包含(CONTAINS)、等于(EQUALS)")
private String rangeMatchSymbol;
@Schema(title = "范围匹配值;eg: 选中路径范围时, 该值作为路径匹配")
private String rangeMatchVal;
@Schema(title = "失效时间值")
private Integer invalidTime;
@Schema(title = "失效时间单位;小时(HOUR)、天(DAY)、月(MONTH)、年(YEAR)")
private String invalidUnit;
@Schema(title = "是否公开;0: 私有、1: 公开", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{api_doc_share.is_public.not_blank}", groups = {Created.class})
private Boolean isPublic;
@Schema(title = "访问密码;私有时需要访问密码")
private String password;
@Schema(title = "允许导出;0: 不允许、1: 允许", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean allowExport;
@Schema(title = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_doc_share.project_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{api_doc_share.project_id.length_range}", groups = {Created.class, Updated.class})
private String projectId;
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto.definition.request;
import io.metersphere.system.dto.sdk.BasePageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ApiDocSharePageRequest extends BasePageRequest {
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_doc_share.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_doc_share.project_id.length_range}")
private String projectId;
}

View File

@ -101,4 +101,6 @@ public interface ExtApiDefinitionMapper {
List<ApiDefinition> getListBySelectModules(@Param("projectId") String projectId, @Param("moduleIds") List<String> moduleIds, @Param("protocols") List<String> protocols);
List<ApiDefinition> getListBySelectIds(@Param("projectId") String projectId, @Param("ids") List<String> ids, @Param("protocols") List<String> protocols);
Long countByShareParam(@Param("projectId") String projectId, @Param("condition") String condition);
}

View File

@ -749,5 +749,10 @@
</if>
</select>
<select id="countByShareParam" resultType="java.lang.Long">
select count(*) from api_definition where project_id = #{projectId} and deleted = 0
<if test="condition != null and condition != ''">
and ${condition}
</if>
</select>
</mapper>

View File

@ -0,0 +1,19 @@
package io.metersphere.api.mapper;
import io.metersphere.api.dto.definition.ApiDocShareDTO;
import io.metersphere.api.dto.definition.request.ApiDocSharePageRequest;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ExtApiDocShareMapper {
/**
* 分页获取分享列表
* @param request 请求参数
* @return 分享列表
*/
List<ApiDocShareDTO> list(@Param("request")ApiDocSharePageRequest request);
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.api.mapper.ExtApiDocShareMapper">
<select id="list" resultType="io.metersphere.api.dto.definition.ApiDocShareDTO">
select id, name, is_public isPublic, create_user createUser, create_time createTime,
api_range apiRange, range_match_symbol rangeMatchSymbol, range_match_val rangeMatchVal,
invalid_time invalidTime, invalid_unit invalidUnit, project_id projectId
from api_doc_share
<include refid="queryWhereCondition"/>
</select>
<sql id="queryWhereCondition">
<where>
<if test="request.projectId != null and request.projectId != ''">
and project_id = #{request.projectId}
</if>
<if test="request.keyword != null and request.keyword != ''">
and name like concat('%', #{request.keyword},'%')
</if>
</where>
</sql>
</mapper>

View File

@ -0,0 +1,139 @@
package io.metersphere.api.service.definition;
import io.metersphere.api.domain.ApiDocShare;
import io.metersphere.api.dto.definition.ApiDocShareDTO;
import io.metersphere.api.dto.definition.request.ApiDocShareEditRequest;
import io.metersphere.api.dto.definition.request.ApiDocSharePageRequest;
import io.metersphere.api.mapper.ApiDocShareMapper;
import io.metersphere.api.mapper.ExtApiDefinitionMapper;
import io.metersphere.api.mapper.ExtApiDocShareMapper;
import io.metersphere.sdk.constants.MsAssertionCondition;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiDocShareService {
@Resource
private ExtApiDefinitionMapper extApiDefinitionMapper;
@Resource
private ApiDocShareMapper apiDocShareMapper;
@Resource
private ExtApiDocShareMapper extApiDocShareMapper;
public static final String RANGE_ALL = "ALL";
public List<ApiDocShareDTO> list(ApiDocSharePageRequest request) {
List<ApiDocShareDTO> list = extApiDocShareMapper.list(request);
return buildApiShareExtra(list);
}
public ApiDocShare create(ApiDocShareEditRequest request, String currentUser) {
ApiDocShare docShare = new ApiDocShare();
BeanUtils.copyBean(docShare, request);
docShare.setId(IDGenerator.nextStr());
docShare.setCreateUser(currentUser);
docShare.setCreateTime(System.currentTimeMillis());
apiDocShareMapper.insert(docShare);
return docShare;
}
public ApiDocShare update(ApiDocShareEditRequest request) {
checkExit(request.getId());
ApiDocShare docShare = new ApiDocShare();
BeanUtils.copyBean(docShare, request);
apiDocShareMapper.updateByPrimaryKeySelective(docShare);
return docShare;
}
public void delete(String id) {
checkExit(id);
apiDocShareMapper.deleteByPrimaryKey(id);
}
/**
* 构建分享额外信息
* @param docShares 分享列表
* @return 分享列表
*/
public List<ApiDocShareDTO> buildApiShareExtra(List<ApiDocShareDTO> docShares) {
docShares.forEach(docShare -> {
docShare.setDeadline(calculateDeadline(docShare.getInvalidTime(), docShare.getInvalidUnit(), docShare.getCreateTime()));
docShare.setInvalid(docShare.getDeadline() != null && docShare.getDeadline() < System.currentTimeMillis());
docShare.setApiShareNum(countApiShare(docShare));
});
return docShares;
}
/**
* 统计接口访范围分享接口数量
* @param docShare 接口分享
* @return 数量
*/
public Integer countApiShare(ApiDocShareDTO docShare) {
StringBuilder condition = new StringBuilder();
if (!StringUtils.equals(docShare.getApiRange(), RANGE_ALL) && !StringUtils.isBlank(docShare.getRangeMatchVal())) {
switch (docShare.getApiRange()) {
case "MODULE" -> condition.append("module_id = '").append(docShare.getRangeMatchVal()).append("'");
case "PATH" -> {
if (StringUtils.equals(docShare.getRangeMatchSymbol(), MsAssertionCondition.EQUALS.name())) {
condition.append("path = '").append(docShare.getRangeMatchVal()).append("'");
} else {
condition.append("path like \"%").append(docShare.getRangeMatchVal()).append("%\"");
}
}
case "TAG" -> {
condition.append("(1=2 ");
String[] tags = StringUtils.split(docShare.getRangeMatchVal(), ",");
for (String tag : tags) {
condition.append("OR JSON_CONTAINS(tags, JSON_ARRAY(\"").append(tag).append("\"))");
}
condition.append(")");
}
default -> {
}
}
}
return extApiDefinitionMapper.countByShareParam(docShare.getProjectId(), condition.toString()).intValue();
}
/**
* 计算截止时间
* @param val 时间值
* @param unit 时间单位
* @param stareTime 起始时间
* @return 截止时间
*/
private Long calculateDeadline(Integer val, String unit, Long stareTime) {
if (val == null) {
return null;
}
return switch (unit) {
case "HOUR" -> stareTime + val * 60 * 60 * 1000L;
case "DAY" -> stareTime + val * 24 * 60 * 60 * 1000L;
case "MONTH" -> stareTime + val * 30 * 24 * 60 * 60 * 1000L;
case "YEAR" -> stareTime + val * 365 * 24 * 60 * 60 * 1000L;
default -> null;
};
}
/**
* 是否存在
* @param id 分享ID
*/
private void checkExit(String id) {
ApiDocShare docShare = apiDocShareMapper.selectByPrimaryKey(id);
if (docShare == null) {
throw new MSException(Translator.get("api_doc_share.not_exist"));
}
}
}

View File

@ -0,0 +1,87 @@
package io.metersphere.api.controller;
import io.metersphere.api.domain.ApiDocShare;
import io.metersphere.api.dto.definition.request.ApiDocShareEditRequest;
import io.metersphere.api.dto.definition.request.ApiDocSharePageRequest;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class ApiDocShareControllerTests extends BaseTest {
private static final String BASE_PATH = "/api/doc/share/";
private final static String ADD = BASE_PATH + "add";
private final static String UPDATE = BASE_PATH + "update";
private final static String DELETE = BASE_PATH + "delete/";
private final static String PAGE = BASE_PATH + "page";
@Order(1)
@Test
public void addOrUpdate() throws Exception {
ApiDocShareEditRequest request = new ApiDocShareEditRequest();
request.setName("share-1");
request.setProjectId(DEFAULT_PROJECT_ID);
request.setApiRange("ALL");
request.setIsPublic(false);
request.setAllowExport(false);
MvcResult mvcResult = this.requestPostWithOk(ADD, request).andReturn();
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
ApiDocShare docShare = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), ApiDocShare.class);
request.setId(docShare.getId());
request.setName("share-2");
request.setApiRange("MODULE");
request.setRangeMatchVal("module-1");
request.setInvalidTime(1);
request.setInvalidUnit("HOUR");
this.requestPostWithOk(UPDATE, request);
this.requestGetWithOk(DELETE + docShare.getId());
// 不存在的ID
this.requestGet(DELETE + "not-exist-id").andExpect(status().is5xxServerError());
}
@Order(2)
@Test
public void page() throws Exception {
ApiDocShareEditRequest request = new ApiDocShareEditRequest();
request.setName("share-1");
request.setProjectId(DEFAULT_PROJECT_ID);
request.setApiRange("ALL");
request.setIsPublic(false);
request.setAllowExport(false);
this.requestPostWithOk(ADD, request);
request.setInvalidTime(1);
request.setInvalidUnit("HOUR");
request.setApiRange("MODULE");
request.setRangeMatchVal("module-1");
this.requestPostWithOk(ADD, request);
request.setApiRange("PATH");
request.setRangeMatchSymbol("EQUALS");
request.setRangeMatchVal("path-1");
this.requestPostWithOk(ADD, request);
request.setRangeMatchSymbol("CONTAINS");
this.requestPostWithOk(ADD, request);
request.setApiRange("TAG");
request.setRangeMatchVal("tag-1,tag-2");
this.requestPostWithOk(ADD, request);
ApiDocSharePageRequest pageRequest = new ApiDocSharePageRequest();
pageRequest.setProjectId(DEFAULT_PROJECT_ID);
pageRequest.setCurrent(1);
pageRequest.setPageSize(10);
this.requestPostWithOk(PAGE, pageRequest);
}
}

View File

@ -147,7 +147,7 @@
<if test="!request.useTrash">
b.deleted = 0
</if>
<if test="request.projectId">
<if test="request.projectId != null and request.projectId != ''">
and b.project_id = #{request.projectId}
</if>
<if test="request.keyword != null and request.keyword != ''">

View File

@ -49,7 +49,7 @@ public enum UserViewType implements ValueEnum {
List.of(InternalUserView.ALL_DATA, InternalUserView.MY_CREATE)),
PLAN_API_SCENARIO_DRAWER("plan-api-scenario-drawer",
List.of(InternalUserView.ALL_DATA, InternalUserView.MY_CREATE)),
PLAN_BUG_DRAWER("plan-bug--drawer",
PLAN_BUG_DRAWER("plan-bug-drawer",
List.of(InternalUserView.ALL_DATA, InternalUserView.MY_CREATE));