feat(系统设置): 部分接口添加文件上传大小限制

This commit is contained in:
song-cc-rock 2024-09-05 14:31:30 +08:00 committed by Craftsman
parent 748b4e93e4
commit bb2a6ab074
19 changed files with 159 additions and 12 deletions

View File

@ -68,8 +68,8 @@ spring.flyway.validate-on-migrate=false
# jmeter
jmeter.home=/opt/jmeter
# file upload
spring.servlet.multipart.max-file-size=500MB
spring.servlet.multipart.max-request-size=500MB
spring.servlet.multipart.max-file-size=1024MB
spring.servlet.multipart.max-request-size=1024MB
# i18n
spring.messages.basename=i18n/commons,i18n/api,i18n/bug,i18n/case,i18n/plan,i18n/project,i18n/system
# actuator

View File

@ -5,3 +5,6 @@ SET SESSION innodb_lock_wait_timeout = 7200;
-- 组织管理员增加权限
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'org_admin', 'ORGANIZATION_PROJECT_MEMBER_UPDATE');
-- 初始化默认参数文件上传大小
INSERT INTO system_parameter (param_key, param_value, type) VALUES ('upload.file.size', '50', 'text');

View File

@ -328,6 +328,8 @@ operation_history.version_id.length_range=变更记录版本 ID 长度必须在{
user_open_source_max=系统用户数超额({num}人),继续添加用户可申请企业版适用
user_dept_max=系统用户数超额({num}人),继续添加用户可申请企业版扩容
# file_upload
file_upload.size_limit=上传文件大小超过系统限制

View File

@ -332,3 +332,5 @@ permission.organization_task_center.stop=Stop task
user_open_source_max=There are too many users({num}),please apply for the enterprise version to use
user_dept_max=There are too many users({num}),please apply for enterprise version expansion
# file_upload
file_upload.size_limit=Upload file size exceeds system limit

View File

@ -333,3 +333,6 @@ permission.organization_task_center.name=任务中心
permission.organization_task_center.stop=停止
user_open_source_max=系统用户数超额({0}人),继续添加用户可申请企业版适用
user_dept_max=系统用户数超额({0}人),继续添加用户可申请企业版扩容
# file_upload
file_upload.size_limit=上传文件大小超过系统限制

View File

@ -331,4 +331,7 @@ operation_history.version_id.length_range=變更記錄版本 ID 長度必須在{
permission.organization_task_center.name=任務中心
permission.organization_task_center.stop=停止
user_open_source_max=系統用戶數超額({num}人),繼續添加用戶可申請企業版適用
user_dept_max=系統用戶數超額({num}人),繼續添加用戶可申請企業版擴容
user_dept_max=系統用戶數超額({num}人),繼續添加用戶可申請企業版擴容
# file_upload
file_upload.size_limit=上傳文件大小超過系統限制

View File

@ -14,8 +14,8 @@ import io.metersphere.project.service.FileModuleService;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.security.CheckOwner;
@ -69,6 +69,7 @@ public class ApiDebugController {
return apiDebugService.add(request, SessionUtils.getUserId());
}
@FileLimit
@PostMapping("/upload/temp/file")
@Operation(summary = "上传接口调试所需的文件资源并返回文件ID")
@RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_API_DEBUG_ADD, PermissionConstants.PROJECT_API_DEBUG_UPDATE})

View File

@ -21,6 +21,7 @@ import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.request.OperationHistoryVersionRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
@ -203,6 +204,7 @@ public class ApiDefinitionController {
return PageUtils.setPageInfo(page, apiDefinitionService.getDocPage(request, SessionUtils.getUserId()));
}
@FileLimit
@PostMapping("/upload/temp/file")
@Operation(summary = "上传接口定义所需的文件资源并返回文件ID")
@RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_API_DEFINITION_ADD, PermissionConstants.PROJECT_API_DEFINITION_UPDATE})

View File

@ -24,6 +24,7 @@ import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
@ -136,6 +137,7 @@ public class ApiDefinitionMockController {
return apiDefinitionMockService.copy(request, SessionUtils.getUserId());
}
@FileLimit
@PostMapping("/upload/temp/file")
@Operation(summary = "上传接口 Mock 所需的文件资源并返回文件ID")
@RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_API_DEFINITION_MOCK_ADD, PermissionConstants.PROJECT_API_DEFINITION_MOCK_UPDATE})

View File

@ -20,6 +20,7 @@ import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
@ -225,6 +226,7 @@ public class ApiTestCaseController {
apiTestCaseService.moveNode(request);
}
@FileLimit
@PostMapping("/upload/temp/file")
@Operation(summary = "上传接口调试所需的文件资源并返回文件ID")
@RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_API_DEFINITION_CASE_ADD, PermissionConstants.PROJECT_API_DEFINITION_CASE_UPDATE})

View File

@ -23,6 +23,7 @@ import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
@ -96,6 +97,7 @@ public class ApiScenarioController {
return apiScenarioService.add(request, SessionUtils.getUserId());
}
@FileLimit
@PostMapping("/upload/temp/file")
@Operation(summary = "接口测试-接口场景管理-上传场景所需的文件资源并返回文件ID")
@RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_API_SCENARIO_ADD, PermissionConstants.PROJECT_API_SCENARIO_UPDATE})

View File

@ -22,6 +22,7 @@ import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
@ -97,6 +98,7 @@ public class BugController {
return PageUtils.setPageInfo(page, bugService.list(request));
}
@FileLimit
@PostMapping("/add")
@Operation(summary = "缺陷管理-列表-创建缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_ADD)
@ -108,6 +110,7 @@ public class BugController {
return bugService.addOrUpdate(request, files, SessionUtils.getUserId(), SessionUtils.getCurrentOrganizationId(), false);
}
@FileLimit
@PostMapping("/update")
@Operation(summary = "缺陷管理-列表-编辑缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)

View File

@ -21,6 +21,7 @@ import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.SessionUser;
import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
@ -74,6 +75,7 @@ public class FunctionalCaseController {
}
@FileLimit
@PostMapping("/add")
@Operation(summary = "用例管理-功能用例-新增用例")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_ADD)
@ -95,7 +97,7 @@ public class FunctionalCaseController {
return functionalCaseService.getFunctionalCaseDetail(id, userId, true);
}
@FileLimit
@PostMapping("/update")
@Operation(summary = "用例管理-功能用例-更新用例")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_UPDATE)

View File

@ -7,6 +7,7 @@ import io.metersphere.project.service.FileManagementService;
import io.metersphere.project.service.FileMetadataService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.Pager;
import io.metersphere.system.utils.SessionUtils;
@ -66,6 +67,7 @@ public class FileManagementController {
return fileMetadataService.moduleCount(request, SessionUtils.getUserId());
}
@FileLimit
@PostMapping("/upload")
@Operation(summary = "项目管理-文件管理-上传文件")
@RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ_ADD)
@ -74,6 +76,7 @@ public class FileManagementController {
return fileMetadataService.upload(request, SessionUtils.getUserId(), uploadFile);
}
@FileLimit
@PostMapping("/re-upload")
@Operation(summary = "项目管理-文件管理-重新上传文件")
@RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ_UPDATE)

View File

@ -7,6 +7,7 @@ import io.metersphere.system.domain.Plugin;
import io.metersphere.system.dto.PluginDTO;
import io.metersphere.system.dto.request.PlatformOptionRequest;
import io.metersphere.system.dto.request.PluginUpdateRequest;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.service.PluginLogService;
@ -46,6 +47,7 @@ public class PluginController {
return pluginService.list();
}
@FileLimit
@PostMapping("/add")
@Operation(summary = "系统设置-系统-插件管理-创建插件")
@RequiresPermissions(PermissionConstants.SYSTEM_PLUGIN_ADD)

View File

@ -0,0 +1,14 @@
package io.metersphere.system.file.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FileLimit {
/**
* 文件大小限制 (单位: MB)
*/
long maxSize() default 0;
}

View File

@ -0,0 +1,75 @@
package io.metersphere.system.file.aspect;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.SystemParameter;
import io.metersphere.system.domain.SystemParameterExample;
import io.metersphere.system.file.annotation.FileLimit;
import io.metersphere.system.mapper.SystemParameterMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Method;
import java.util.List;
@Aspect
@Component
public class FileLimitAspect {
@Resource
private SystemParameterMapper systemParameterMapper;
@Pointcut("@annotation(io.metersphere.system.file.annotation.FileLimit)")
public void limitPointCut() {
}
@Before("limitPointCut()")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
FileLimit limit = method.getAnnotation(FileLimit.class);
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 获取文件大小限制 (默认使用系统参数中的文件限制大小, 如果注解中有配置则使用注解中的配置)
long fileMaxSize = limit.maxSize() != 0 ? limit.maxSize() : getFileMaxSize();
for (Object arg : args) {
// 单个文件
if (arg instanceof MultipartFile file) {
// 判断文件大小是否超过限制
if (file.getSize() > fileMaxSize * 1024 * 1024) {
throw new MSException(Translator.get("file_upload.size_limit"));
}
}
// 多个文件
if (arg instanceof List<?> files) {
// List<MultipartFile> 类型参数跳过
if (CollectionUtils.isEmpty(files) || !(files.getFirst() instanceof MultipartFile)) {
continue;
}
files.forEach(f -> {
if (f instanceof MultipartFile file) {
// 判断文件大小是否超过限制
if (file.getSize() > fileMaxSize * 1024 * 1024) {
throw new MSException(Translator.get("file_upload.size_limit"));
}
}
});
}
}
}
private Long getFileMaxSize() {
SystemParameterExample example = new SystemParameterExample();
example.createCriteria().andParamKeyEqualTo("upload.file.size");
List<SystemParameter> systemParameters = systemParameterMapper.selectByExample(example);
return Long.parseLong(systemParameters.getFirst().getParamValue());
}
}

View File

@ -4,6 +4,8 @@ import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.PluginScenarioType;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.LocalFileRepository;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.base.BasePluginTestService;
@ -12,15 +14,14 @@ import io.metersphere.system.controller.param.PluginUpdateRequestDefinition;
import io.metersphere.system.domain.*;
import io.metersphere.system.dto.OrganizationDTO;
import io.metersphere.system.dto.PluginDTO;
import io.metersphere.system.dto.request.PlatformOptionRequest;
import io.metersphere.system.dto.request.PluginUpdateRequest;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.LocalFileRepository;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.PluginMapper;
import io.metersphere.system.mapper.PluginOrganizationMapper;
import io.metersphere.system.mapper.PluginScriptMapper;
import io.metersphere.system.dto.request.PlatformOptionRequest;
import io.metersphere.system.dto.request.PluginUpdateRequest;
import io.metersphere.system.mapper.SystemParameterMapper;
import io.metersphere.system.service.*;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
@ -75,6 +76,8 @@ public class PluginControllerTests extends BaseTest {
private PluginScriptService pluginScriptService;
@Resource
private LocalFileRepository localFileRepository;
@Resource
private SystemParameterMapper systemParameterMapper;
private static Plugin addPlugin;
private static Plugin anotherAddPlugin;
@ -186,7 +189,6 @@ public class PluginControllerTests extends BaseTest {
getDefaultMultiPartParam(request, myDriver));
Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(DEFAULT_ORGANIZATION_ID), Arrays.asList("io.jianxing.MyDriver", "com.mysql.cj.jdbc.Driver"));
// 校验QUOTA动上传成功
request.setName("cloud-quota-plugin");
request.setOrganizationIds(Arrays.asList(org.getId()));
@ -196,6 +198,9 @@ public class PluginControllerTests extends BaseTest {
);
this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
getDefaultMultiPartParam(request, quota));
// 设置文件限制为1M
setMaxFileSizeTo1M();
this.requestMultipart(DEFAULT_ADD, getDefaultMultiPartParam(request, quota), status().is5xxServerError());
// 清理掉
this.requestGetWithOk(DEFAULT_DELETE, "cloud-quota-plugin");
@ -458,4 +463,15 @@ public class PluginControllerTests extends BaseTest {
.content(JSON.toJSONString(param))
.contentType(MediaType.APPLICATION_JSON));
}
private void setMaxFileSizeTo1M() {
// 设置文件限制为1M
SystemParameterExample example = new SystemParameterExample();
example.createCriteria().andParamKeyEqualTo("upload.file.size");
SystemParameter systemParameter = new SystemParameter();
systemParameter.setParamKey("upload.file.size");
systemParameter.setParamValue("1");
systemParameter.setType("text");
systemParameterMapper.updateByExample(systemParameter, example);
}
}

View File

@ -45,11 +45,21 @@
select brc.case_id as id, fc.num as num, 'FUNCTIONAL' as type, brc.bug_id as bugId, fc.name as name
from bug_relation_case brc
join functional_case fc on brc.case_id = fc.id
union
where brc.test_plan_id = #{planId}
and brc.bug_id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
union all
select brc.case_id as id, atc.num as num, 'API' as type, brc.bug_id as bugId, atc.name as name
from bug_relation_case brc
join api_test_case atc on brc.case_id = atc.id
union
where brc.test_plan_id = #{planId}
and brc.bug_id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
union all
select brc.case_id as id, asi.num as num, 'SCENARIO' as type, brc.bug_id as bugId, asi.name as name
from bug_relation_case brc
join api_scenario asi on brc.case_id = asi.id