refactor(系统设置): 配额管理开源

--task=1010908 --user=李玉号 【开源计划】租户配额管理
https://www.tapd.cn/55049933/s/1327251
This commit is contained in:
shiziyuan9527 2022-12-30 11:47:09 +08:00 committed by 刘瑞斌
parent d4b0d51df4
commit 49e384231d
27 changed files with 1660 additions and 250 deletions

View File

@ -41,6 +41,7 @@ import io.metersphere.log.vo.api.DefinitionReference;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.plugin.core.MsTestElement;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.RelationshipEdgeRequest;
import io.metersphere.request.ResetOrderRequest;
import io.metersphere.request.SyncApiDefinitionRequest;
@ -52,7 +53,6 @@ import io.metersphere.service.plan.TestPlanApiCaseService;
import io.metersphere.service.scenario.ApiScenarioService;
import io.metersphere.xpack.api.service.ApiCaseBatchSyncService;
import io.metersphere.xpack.api.service.ApiDefinitionSyncService;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.collections.CollectionUtils;
@ -160,6 +160,8 @@ public class ApiDefinitionService {
@Resource
private ApiDefinitionImportUtilService apiDefinitionImportUtilService;
@Resource
private BaseQuotaService baseQuotaService;
private static final String COPY = "Copy";
@ -432,10 +434,7 @@ public class ApiDefinitionService {
public void checkQuota(String projectId) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
quotaService.checkAPIDefinitionQuota(projectId);
}
baseQuotaService.checkAPIDefinitionQuota(projectId);
}
public void delete(String apiId) {

View File

@ -48,6 +48,7 @@ import io.metersphere.log.vo.schedule.ScheduleReference;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.plugin.core.MsTestElement;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.ResetOrderRequest;
import io.metersphere.sechedule.ApiScenarioTestJob;
import io.metersphere.sechedule.SwaggerUrlImportJob;
@ -57,7 +58,6 @@ import io.metersphere.service.ext.ExtApiScheduleService;
import io.metersphere.service.ext.ExtFileAssociationService;
import io.metersphere.service.plan.TestPlanScenarioCaseService;
import io.metersphere.xpack.api.service.ApiAutomationRelationshipEdgeService;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
@ -157,6 +157,8 @@ public class ApiScenarioService {
private ExtApiScenarioReferenceIdMapper extApiScenarioReferenceIdMapper;
@Resource
private ExtTestPlanApiScenarioMapper extTestPlanApiScenarioMapper;
@Resource
private BaseQuotaService baseQuotaService;
private ThreadLocal<Long> currentScenarioOrder = new ThreadLocal<>();
@ -297,10 +299,7 @@ public class ApiScenarioService {
}
private void checkQuota(String projectId) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
quotaService.checkAPIAutomationQuota(projectId);
}
baseQuotaService.checkAPIAutomationQuota(projectId);
}
private void uploadFiles(SaveApiScenarioRequest request, List<MultipartFile> bodyFiles, List<MultipartFile> scenarioFiles) {

View File

@ -0,0 +1,15 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.LoadTestReportResult;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ExtLoadTestReportResultMapper {
@Select("SELECT * FROM load_test_report_result WHERE report_id = #{id} AND report_key = #{key}")
List<LoadTestReportResult> selectByIdAndKey(@Param("id") String id, @Param("key") String key);
}

View File

@ -0,0 +1,45 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.Quota;
import io.metersphere.quota.dto.CountDto;
import io.metersphere.quota.dto.QuotaResult;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtQuotaMapper {
List<QuotaResult> listWorkspaceQuota(@Param("name") String name);
Quota getWorkspaceQuota(@Param("workspaceId") String workspaceId);
List<Quota> listUseDefaultOrgQuota();
List<Quota> listUseDefaultWsQuota();
List<Quota> listUseDefaultProjectQuota(String workspaceId);
long countAPITest(@Param("workspaceIds") List<String> workspaceIds);
long countLoadTest(@Param("projectIds") List<String> workspaceIds);
long countAPIDefinition(@Param("projectIds") List<String> ids);
long countAPIAutomation(@Param("projectIds") List<String> ids);
List<QuotaResult> listProjectQuota(@Param("wsId") String workspaceId, @Param("name") String name);
Quota getProjectQuota(String projectId);
Long countMember(@Param("sourceId") String sourceId);
Long countWorkspaceProject(@Param("workspaceId") String workspaceId);
List<Quota> listQuotaBySourceIds(@Param("sourceIds") List<String> sourceIds);
List<CountDto> listUserBySourceIds(@Param("sourceIds") List<String> sourceIds);
long listUserByWorkspaceAndProjectIds(@Param("sourceIds") List<String> sourceIds, @Param("memberIds") List<String> memberIds);
Quota getProjectQuotaSum(String workspaceId);
}

View File

@ -0,0 +1,195 @@
<?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.base.mapper.ext.ExtQuotaMapper">
<resultMap id="BaseResultMap" type="io.metersphere.quota.dto.QuotaResult"
extends="io.metersphere.base.mapper.QuotaMapper.BaseResultMap">
<result column="project_name" property="projectName"/>
<result column="workspace_name" property="workspaceName"/>
</resultMap>
<select id="listWorkspaceQuota" resultMap="BaseResultMap">
select w.name as workspace_name, w.id as workspace_id, q.id, q.api, q.performance,
q.max_threads, q.duration, q.resource_pool, q.workspace_id, q.use_default, q.update_time,
q.member, q.project, q.project_id, q.vum_total, temp.vum_used, temp.project_used
from workspace as w
left join quota as q on w.id = q.workspace_id
inner join (select sum(vum_used) as vum_used, workspace.id, count(p.id) as project_used
from workspace
left join project p on p.workspace_id = workspace.id
left join quota on quota.project_id = p.id
group by workspace.id) as temp
on temp.id = w.id
<where>
<if test="name != null">
and w.name like CONCAT('%', #{name},'%')
</if>
</where>
order by q.update_time desc, workspace_name
</select>
<select id="getWorkspaceQuota" resultMap="io.metersphere.base.mapper.QuotaMapper.BaseResultMap">
SELECT *
FROM quota
<where>
workspace_id = #{workspaceId}
</where>
</select>
<select id="listUseDefaultOrgQuota" resultMap="io.metersphere.base.mapper.QuotaMapper.BaseResultMap">
SELECT *
FROM quota
<where>
organization_id IS NOT NULL
AND workspace_id IS NULL
AND use_default = '1'
</where>
</select>
<select id="listUseDefaultWsQuota" resultMap="io.metersphere.base.mapper.QuotaMapper.BaseResultMap">
SELECT *
FROM quota
<where>
workspace_id IS NOT NULL
AND use_default = '1'
</where>
</select>
<select id="countAPITest" resultType="java.lang.Long">
SELECT count(*)
FROM api_test LEFT JOIN project ON api_test.project_id = project.id
WHERE project.workspace_id in
<foreach collection="workspaceIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<select id="countLoadTest" resultType="java.lang.Long">
SELECT count(*)
FROM load_test LEFT JOIN project ON load_test.project_id = project.id
WHERE project.id in
<foreach collection="projectIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<select id="countAPIDefinition" resultType="long">
SELECT count(*)
FROM api_definition
WHERE api_definition.status != 'Trash' and api_definition.latest = 1 and api_definition.project_id in
<foreach collection="projectIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<select id="countAPIAutomation" resultType="long">
SELECT count(*)
FROM api_scenario
WHERE api_scenario.status != 'Trash' and api_scenario.latest = 1 and api_scenario.project_id in
<foreach collection="projectIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<select id="listUseDefaultProjectQuota" resultType="io.metersphere.base.domain.Quota">
select *
from quota join project on quota.project_id = project.id
<where>
project_id is not null
and project.workspace_id = #{workspaceId}
and quota.workspace_id is null
and use_default = '1'
</where>
</select>
<select id="listProjectQuota" resultMap="BaseResultMap">
SELECT p.name AS project_name, p.id AS project_id, p.workspace_id AS workspace_id, q.*
FROM project AS p LEFT JOIN quota AS q
ON p.id = q.project_id
<where>
<if test="wsId != null">
and p.workspace_id = #{wsId}
</if>
<if test="name != null">
and p.name like CONCAT('%', #{name},'%')
</if>
</where>
ORDER BY q.update_time DESC, project_name
</select>
<select id="getProjectQuota" resultMap="io.metersphere.base.mapper.QuotaMapper.BaseResultMap">
SELECT *
FROM quota
<where>
project_id = #{projectId}
and workspace_id IS NULL
</where>
</select>
<select id="countMember" resultType="java.lang.Long">
SELECT count(*)
FROM user_group
WHERE user_group.source_id = #{sourceId}
</select>
<select id="countWorkspaceProject" resultType="java.lang.Long">
SELECT count(*)
FROM project
WHERE project.workspace_id = #{workspaceId}
</select>
<select id="listQuotaBySourceIds" resultMap="io.metersphere.base.mapper.QuotaMapper.BaseResultMap">
SELECT *
FROM quota
<where>
project_id in
<foreach collection="sourceIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
or
workspace_id in
<foreach collection="sourceIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</where>
</select>
<select id="listUserBySourceIds" resultType="io.metersphere.quota.dto.CountDto">
SELECT source_id, count(distinct(user.id)) as count
FROM user_group join user on user_group.user_id = user.id
<where>
source_id in
<foreach collection="sourceIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
group by source_id
</where>
</select>
<select id="listUserByWorkspaceAndProjectIds" resultType="java.lang.Long">
SELECT count(distinct(user.id)) as count
FROM user_group join user on user_group.user_id = user.id
<where>
source_id in
<foreach collection="sourceIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
<if test="memberIds != null and memberIds.size() != 0">
and user_id not in
<foreach collection="memberIds" item="memberId" separator="," open="(" close=")">
#{memberId}
</foreach>
</if>
</where>
</select>
<select id="getProjectQuotaSum" resultMap="io.metersphere.base.mapper.QuotaMapper.BaseResultMap">
select sum(vum_used) as vum_used
from quota
<where>
quota.project_id
in (select id from project where project.workspace_id = #{workspaceId})
</where>
</select>
</mapper>

View File

@ -0,0 +1,81 @@
package io.metersphere.quota.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.Quota;
import io.metersphere.commons.constants.OperLogConstants;
import io.metersphere.commons.constants.OperLogModule;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.log.annotation.MsAuditLog;
import io.metersphere.quota.dto.QuotaConstants;
import io.metersphere.quota.dto.QuotaResult;
import io.metersphere.quota.service.QuotaManagementService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping(value = "/quota")
public class QuotaController {
@Resource
private QuotaManagementService quotaManagementService;
@GetMapping("/default/workspace")
public Quota getWsDefaultQuota() {
return quotaManagementService.getDefaultQuota(QuotaConstants.DefaultType.workspace);
}
@GetMapping("/default/project/{workspaceId}")
public Quota getProjectDefaultQuota(@PathVariable String workspaceId) {
return quotaManagementService.getProjectDefaultQuota(workspaceId);
}
@PostMapping("/save/default/workspace")
@MsAuditLog(module = OperLogModule.SYSTEM_QUOTA_MANAGEMENT, type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#quota.id)", content = "#msClass.getLogDetails(#quota.id)", msClass = QuotaManagementService.class)
public void saveWsDefaultQuota(@RequestBody Quota quota) {
quota.setId(QuotaConstants.DefaultType.workspace.name());
quotaManagementService.saveQuota(quota);
}
@PostMapping("/save/default/project")
@MsAuditLog(module = OperLogModule.SYSTEM_QUOTA_MANAGEMENT, type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#quota.id)", content = "#msClass.getLogDetails(#quota.id)", msClass = QuotaManagementService.class)
public void saveProjectDefaultQuota(@RequestBody Quota quota) {
if (StringUtils.isBlank(quota.getId())) {
quota.setId(SessionUtils.getCurrentWorkspaceId());
}
quota.setId(QuotaConstants.prefix + quota.getId());
quotaManagementService.saveQuota(quota);
}
@PostMapping("/list/workspace/{goPage}/{pageSize}")
public Pager<List<QuotaResult>> listWsQuota(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody Map<String, String> param) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, quotaManagementService.listWorkspaceQuota(param.get("name")));
}
@PostMapping("/list/project/{goPage}/{pageSize}")
public Pager<List<QuotaResult>> listProjectQuota(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody Map<String, String> param) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, quotaManagementService.listProjectQuota(param.get("workspaceId"), param.get("name")));
}
@PostMapping("/save")
@MsAuditLog(module = OperLogModule.SYSTEM_QUOTA_MANAGEMENT, type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#quota.id)", content = "#msClass.getLogDetails(#quota.id)", msClass = QuotaManagementService.class)
public void saveQuota(@RequestBody Quota quota) {
quotaManagementService.saveQuota(quota);
}
@PostMapping("/delete")
@MsAuditLog(module = OperLogModule.SYSTEM_QUOTA_MANAGEMENT, type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#quota.id)", msClass = QuotaManagementService.class)
public void delete(@RequestBody Quota quota) {
quotaManagementService.deleteQuota(quota.getId());
}
}

View File

@ -1,4 +1,4 @@
package io.metersphere.xpack.quota.dto;
package io.metersphere.quota.dto;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package io.metersphere.xpack.quota.dto;
package io.metersphere.quota.dto;
public class QuotaConstants {

View File

@ -1,4 +1,4 @@
package io.metersphere.xpack.quota.dto;
package io.metersphere.quota.dto;
import io.metersphere.base.domain.Quota;
import lombok.Getter;

View File

@ -0,0 +1,10 @@
package io.metersphere.quota.dto;
import lombok.Data;
@Data
public class ReportTimeInfo {
private long duration;
private long startTime;
private long endTime;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.quota.dto;
import lombok.Data;
@Data
public class TestOverview {
private String maxUsers;
private String avgThroughput;
private String errors;
private String avgResponseTime;
private String responseTime90;
private String avgBandwidth;
private String avgTransactions;
}

View File

@ -0,0 +1,757 @@
package io.metersphere.quota.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.QuotaMapper;
import io.metersphere.base.mapper.WorkspaceMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportResultMapper;
import io.metersphere.base.mapper.ext.ExtQuotaMapper;
import io.metersphere.commons.constants.ReportKeys;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.quota.dto.*;
import io.metersphere.request.TestPlanRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author lyh
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class BaseQuotaService {
@Resource
private QuotaManagementService quotaManagementService;
@Resource
private ExtQuotaMapper extQuotaMapper;
@Resource
private QuotaMapper quotaMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private WorkspaceMapper workspaceMapper;
@Resource
private ExtLoadTestReportResultMapper extLoadTestReportResultMapper;
private static final String API = "API";
private static final String LOAD = "LOAD";
private static final String DURATION = "DURATION";
private static final String MAX_THREAD = "MAX_THREAD";
private static final String MEMBER = "MEMBER";
private static final String PROJECT = "PROJECT";
private static final String CHECK_PROJECT = "PROJECT";
private static final String CHECK_WORKSPACE = "WORKSPACE";
private boolean isValid(Quota quota, Object obj) {
boolean sign = quota != null;
if (obj instanceof Integer) {
return sign && quotaManagementService.isValid((Integer) obj);
} else if (obj instanceof String) {
return sign && quotaManagementService.isValid((String) obj);
} else if (obj instanceof BigDecimal) {
return sign && quotaManagementService.isValid((BigDecimal) obj);
}
return false;
}
private List<String> queryProjectIdsByWorkspaceId(String workspaceId) {
ProjectExample example = new ProjectExample();
example.createCriteria().andWorkspaceIdEqualTo(workspaceId);
List<Project> projects = projectMapper.selectByExample(example);
return projects.stream().map(Project::getId).collect(Collectors.toList());
}
private String queryWorkspaceId(String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
if (project == null) {
return null;
}
return project.getWorkspaceId();
}
/**
* 接口定义配额数量检查
*/
public void checkAPIDefinitionQuota(String projectId) {
this.checkQuota(extQuotaMapper::countAPIDefinition, API, projectId,
Translator.get("quota_api_excess_project"),
Translator.get("quota_api_excess_workspace"));
}
/**
* 接口自动化配额数量检查
*/
public void checkAPIAutomationQuota(String projectId) {
this.checkQuota(extQuotaMapper::countAPIAutomation, API, projectId,
Translator.get("quota_api_excess_project"),
Translator.get("quota_api_excess_workspace"));
}
/**
* 增量为1的配额检查方法
*
* @param queryFunc 查询已存在数量的方法
* @param checkType 检查配额类型
* @param projectId 项目ID
* @param projectMessage 项目超出配额警告信息
* @param workspaceMessage 工作空间超出配额警告信息
*/
public void checkQuota(Function<List<String>, Long> queryFunc, String checkType, String projectId, String projectMessage, String workspaceMessage) {
if (queryFunc == null) {
LogUtil.info("param warning. function is null");
return;
}
if (StringUtils.isBlank(projectId)) {
return;
}
// 检查项目配额
Quota qt = quotaManagementService.getProjectQuota(projectId);
boolean isContinue = true;
long count;
if (qt != null) {
count = queryFunc.apply(Collections.singletonList(projectId));
// 数量+1后检查
isContinue = this.doCheckQuota(qt, checkType, projectMessage, count + 1);
}
// 检查是否有工作空间限额
if (isContinue) {
String workspaceId = this.queryWorkspaceId(projectId);
if (StringUtils.isBlank(workspaceId)) {
return;
}
Quota quota = quotaManagementService.getWorkspaceQuota(workspaceId);
if (quota == null) {
return;
}
count = queryFunc.apply(this.queryProjectIdsByWorkspaceId(workspaceId));
this.doCheckQuota(quota, checkType, workspaceMessage, count + 1);
}
}
private boolean doCheckQuota(Quota quota, String checkType, String errorMsg, long queryCount) {
if (quota == null) {
return true;
}
Object quotaCount = getQuotaCount(quota, checkType);
if (isValid(quota, quotaCount)) {
long count = Long.parseLong(String.valueOf(quotaCount));
if (queryCount > count) {
MSException.throwException(errorMsg);
}
return false;
}
return true;
}
private Object getQuotaCount(Quota quota, String type) {
Object count = null;
switch (type) {
case API:
count = quota.getApi();
break;
case LOAD:
count = quota.getPerformance();
break;
case DURATION:
count = quota.getDuration();
break;
case MAX_THREAD:
count = quota.getMaxThreads();
break;
case MEMBER:
count = quota.getMember();
break;
case PROJECT:
count = quota.getProject();
break;
default:
MSException.throwException("get quota count fail, don't have type: " + type);
}
return count;
}
/**
* 性能测试配额检查
* @param request 压力配置
* @param checkPerformance 检查创建数量配额 / 检查并发数和时间
*/
@Transactional(noRollbackFor = MSException.class, rollbackFor = Exception.class)
public void checkLoadTestQuota(TestPlanRequest request, boolean checkPerformance) {
String loadConfig = request.getLoadConfiguration();
int threadNum = 0;
long duration = 0;
if (loadConfig != null) {
threadNum = getIntegerValue(loadConfig, "TargetLevel");
duration = getIntegerValue(loadConfig, "duration");
}
String projectId = request.getProjectId();
if (checkPerformance) {
this.checkPerformance(projectId);
} else {
checkMaxThread(projectId, threadNum);
checkDuration(projectId, duration);
}
}
private void checkPerformance(String projectId) {
this.checkQuota(extQuotaMapper::countLoadTest, LOAD, projectId,
Translator.get("quota_performance_excess_project"),
Translator.get("quota_performance_excess_workspace"));
}
private void checkMaxThread(String projectId, int threadNum) {
// 增量为0的检查
this.checkQuota(p -> (long) (threadNum - 1), MAX_THREAD, projectId,
Translator.get("quota_max_threads_excess_project"),
Translator.get("quota_max_threads_excess_workspace"));
}
private void checkDuration(String projectId, long duration) {
// 增量为0的检查
this.checkQuota(p -> duration - 1, DURATION, projectId,
Translator.get("quota_duration_excess_project"),
Translator.get("quota_duration_excess_workspace"));
}
/**
* 获取可用资源池集合
* @return 资源池名称Set集合
*/
public Set<String> getQuotaResourcePools() {
Set<String> pools = new HashSet<>();
String projectId = SessionUtils.getCurrentProjectId();
Quota pjQuota = quotaManagementService.getProjectQuota(projectId);
if (pjQuota != null) {
if (isValid(pjQuota, pjQuota.getResourcePool())) {
pools.addAll(Arrays.asList(pjQuota.getResourcePool().split(",")));
return pools;
}
}
String workspaceId = this.queryWorkspaceId(projectId);
if (StringUtils.isBlank(workspaceId)) {
return pools;
}
Quota wsQuota = quotaManagementService.getWorkspaceQuota(workspaceId);
if (wsQuota != null) {
if (isValid(wsQuota, wsQuota.getResourcePool())) {
pools.addAll(Arrays.asList(wsQuota.getResourcePool().split(",")));
}
}
return pools;
}
/**
* 工作空间下被限制使用的资源池
* @param workspaceId 工作空间ID
* @return 资源池名称Set
*/
public Set<String> getQuotaWsResourcePools(String workspaceId) {
Set<String> pools = new HashSet<>();
Quota wsQuota = quotaManagementService.getWorkspaceQuota(workspaceId);
if (wsQuota != null) {
if (isValid(wsQuota, wsQuota.getResourcePool())) {
pools.addAll(Arrays.asList(wsQuota.getResourcePool().split(",")));
}
}
return pools;
}
/**
* 检查工作空间项目数量配额
* @param workspaceId 工作空间ID
*/
public void checkWorkspaceProject(String workspaceId) {
this.doCheckQuota(quotaManagementService.getWorkspaceQuota(workspaceId),
PROJECT, Translator.get("quota_project_excess_project"),
extQuotaMapper.countWorkspaceProject(workspaceId) + 1);
}
/**
* 检查vumUsed配额
* 未超过返回本次执行预计消耗的配额
* 超过抛出异常
* @param request 压力配置
* @param projectId 性能测试所属项目ID
* @return 本次执行预计消耗的配额
*/
@Transactional(noRollbackFor = MSException.class, rollbackFor = Exception.class)
public BigDecimal checkVumUsed(TestPlanRequest request, String projectId) {
BigDecimal toVumUsed = this.calcVum(request.getLoadConfiguration());
if (toVumUsed.compareTo(BigDecimal.ZERO) == 0) {
return toVumUsed;
}
Quota pjQuota = quotaManagementService.getProjectQuota(projectId);
String pjWarningMsg = Translator.get("quota_vum_used_excess_project");
boolean isContinue = this.doCheckVumUsed(pjQuota, toVumUsed, pjWarningMsg);
if (isContinue) {
String workspaceId = this.queryWorkspaceId(projectId);
if (StringUtils.isBlank(workspaceId)) {
return toVumUsed;
}
String wsWarningMsg = Translator.get("quota_vum_used_excess_workspace");
Quota wsQuota = quotaManagementService.getWorkspaceQuota(workspaceId);
if (wsQuota == null) {
return toVumUsed;
}
// 获取工作空间下已经消耗的vum
Quota qt = extQuotaMapper.getProjectQuotaSum(workspaceId);
if (qt == null || qt.getVumUsed() == null) {
wsQuota.setVumUsed(BigDecimal.ZERO);
} else {
wsQuota.setVumUsed(qt.getVumUsed());
}
this.doCheckVumUsed(wsQuota, toVumUsed, wsWarningMsg);
}
return toVumUsed;
}
private BigDecimal calcVum(String loadConfig) {
BigDecimal vum = BigDecimal.ZERO;
try {
List jsonArray = JSON.parseArray(loadConfig);
this.filterDeleteAndEnabled(jsonArray);
for (Object value : jsonArray) {
if (value instanceof List) {
List o = (List) value;
int thread = 0;
long duration = 0;
for (Object item : o) {
Map b = (Map) item;
if (StringUtils.equals((String) b.get("key"), "TargetLevel")) {
thread += (int) b.get("value");
break;
}
}
for (int j = 0; j < o.size(); j++) {
Map b = (Map) o.get(j);
if (StringUtils.equals((String) b.get("key"), "duration")) {
duration += (int) b.get("value");
break;
}
}
// 每个ThreadGroup消耗的vum单独计算
vum = vum.add(this.calcVum(thread, duration));
}
}
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
return vum;
}
private BigDecimal calcVum(int thread, long duration) {
double used = thread * duration * 1.00 / 60;
DecimalFormat df = new DecimalFormat("#.00000");
return new BigDecimal(df.format(used));
}
private boolean doCheckVumUsed(Quota quota, BigDecimal toVumUsed, String errorMsg) {
if (quota == null) {
return true;
}
// 如果vumTotal为NULL是不限制
if (isValid(quota, quota.getVumTotal())) {
BigDecimal vumTotal = quota.getVumTotal();
BigDecimal used = quota.getVumUsed();
if (used == null) {
used = BigDecimal.ZERO;
}
if (used.add(toVumUsed).compareTo(vumTotal) > 0) {
MSException.throwException(errorMsg);
}
return false;
}
return true;
}
/**
* 检查向某资源添加人员时是否超额
* @param addMemberMap 资源ID:添加用户ID列表
* @param type 检查类型 PROJECT/WORKSPACE
*/
public void checkMemberCount(Map<String, List<String>> addMemberMap, String type) {
if (addMemberMap == null || addMemberMap.keySet().size() == 0) {
return;
}
if (!StringUtils.equals(type, CHECK_PROJECT) && !StringUtils.equals(type, CHECK_WORKSPACE)) {
return;
}
List<String> sourceIds = new ArrayList<>(addMemberMap.keySet());
List<Quota> quotas = extQuotaMapper.listQuotaBySourceIds(sourceIds);
Quota defaultQuota = null;
if (StringUtils.equals(CHECK_WORKSPACE, type)) {
defaultQuota = quotaManagementService.getDefaultQuota(QuotaConstants.DefaultType.workspace);
}
Map<String, Integer> quotaMap = new HashMap<>();
for (Quota quota : quotas) {
String key;
if (StringUtils.equals(CHECK_PROJECT, type)) {
key = quota.getProjectId();
} else {
key = quota.getWorkspaceId();
}
if (StringUtils.isBlank(key)) {
continue;
}
if (BooleanUtils.isTrue(quota.getUseDefault())) {
if (StringUtils.equals(CHECK_PROJECT, type)) {
Project project = projectMapper.selectByPrimaryKey(key);
if (project == null || StringUtils.isBlank(project.getWorkspaceId())) {
continue;
}
defaultQuota = quotaManagementService.getProjectDefaultQuota(project.getWorkspaceId());
}
if (defaultQuota == null) {
continue;
}
quota = defaultQuota;
}
if (quota.getMember() == null || quota.getMember() == 0) {
continue;
}
quotaMap.put(key, quota.getMember());
}
Set<String> set = quotaMap.keySet();
if (set.isEmpty()) {
return;
}
Map<String, Integer> memberCountMap = this.getDBMemberCountMap(addMemberMap, type);
Map<String, String> sourceNameMap = this.getNameMap(sourceIds, type);
this.doCheckMemberCount(quotaMap, memberCountMap, sourceNameMap, addMemberMap, type);
}
private void doCheckMemberCount(Map<String, Integer> quotaMap, Map<String, Integer> memberCountMap,
Map<String, String> sourceNameMap, Map<String, List<String>> addMemberMap, String checkType) {
Set<String> set = quotaMap.keySet();
StringBuilder builder = new StringBuilder();
for (String sourceId : set) {
// 没有配额限制的跳过
if (!addMemberMap.containsKey(sourceId)) {
continue;
}
// 当前已存在人员数量
Integer dbCount = memberCountMap.getOrDefault(sourceId, 0);
int toAddCount = addMemberMap.get(sourceId) == null ? 0 : addMemberMap.get(sourceId).size();
// 添加人员之后判断配额
if (dbCount + toAddCount > quotaMap.get(sourceId)) {
builder.append(sourceNameMap.get(sourceId));
builder.append(" ");
}
}
if (builder.length() > 0) {
builder.append("超出成员数量配额");
if (StringUtils.equals(CHECK_WORKSPACE, checkType)) {
builder.append("(工作空间成员配额将其下所有项目成员也计算在内)");
}
MSException.throwException(builder.toString());
}
}
private Map<String, Integer> getDBMemberCountMap(Map<String, List<String>> addMemberMap, String type) {
List<String> sourceIds = new ArrayList<>(addMemberMap.keySet());
Map<String, Integer> memberCountMap = new HashMap<>();
if (StringUtils.equals(CHECK_WORKSPACE, type)) {
// 检查工作空间配额时将其下所有项目成员也计算在其中
ProjectExample projectExample = new ProjectExample();
for (String sourceId : sourceIds) {
projectExample.clear();
projectExample.createCriteria().andWorkspaceIdEqualTo(sourceId);
List<Project> projects = projectMapper.selectByExample(projectExample);
List<String> ids = projects.stream().map(Project::getId).collect(Collectors.toList());
ids.add(sourceId);
List<String> memberIds = addMemberMap.getOrDefault(sourceId, new ArrayList<>());
long count = extQuotaMapper.listUserByWorkspaceAndProjectIds(ids, memberIds);
memberCountMap.put(sourceId, (int) count);
}
} else if (StringUtils.equals(CHECK_PROJECT, type)) {
List<CountDto> list = extQuotaMapper.listUserBySourceIds(sourceIds);
memberCountMap = list.stream().collect(Collectors.toMap(CountDto::getSourceId, CountDto::getCount));
}
return memberCountMap;
}
/**
* 更新VumUsed配额
* @param projectId 项目ID
* @param vumUsed 预计使用数量
*/
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void updateVumUsed(String projectId, BigDecimal vumUsed) {
if (vumUsed == null) {
LogUtil.info("update vum count fail. vum count is null.");
return;
}
Quota dbPjQuota = extQuotaMapper.getProjectQuota(projectId);
Quota newPjQuota = this.newPjQuota(projectId, vumUsed);
this.doUpdateVumUsed(dbPjQuota, newPjQuota);
}
/**
* 获取需要回退的vum数量
* @param report 性能测试报告
* @return 需要回退的vum数量
*/
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public BigDecimal getReduceVumUsed(LoadTestReportWithBLOBs report) {
String reportId = report.getId();
List<LoadTestReportResult> timeInfos = queryReportResult(reportId, ReportKeys.TimeInfo.toString());
List<LoadTestReportResult> overviews = queryReportResult(reportId, ReportKeys.Overview.toString());
// 预计使用的数量
String loadConfig = report.getLoadConfiguration();
BigDecimal toUsed = calcVum(loadConfig);
if (CollectionUtils.isEmpty(timeInfos) || CollectionUtils.isEmpty(overviews)) {
LogUtil.error("reduce vum used error. load test report time info is null.");
return toUsed;
}
ReportTimeInfo timeInfo = parseReportTimeInfo(timeInfos.get(0));
TestOverview overview = parseOverview(overviews.get(0));
long duration = timeInfo.getDuration();
String maxUserStr = overview.getMaxUsers();
int maxUsers = 0;
try {
maxUsers = Integer.parseInt(maxUserStr);
} catch (Exception e) {
//
}
if (duration == 0 || maxUsers == 0) {
return toUsed;
}
// 已经使用的数量
BigDecimal used = calcVum(maxUsers, duration);
// 实际使用值比预计值大不回退否则回退差值
return used.compareTo(toUsed) >= 0 ? BigDecimal.ZERO : toUsed.subtract(used);
}
/**
* 如果有该项目配额修改为使用工作空间下项目默认配额
* 无该配额新建配额并默认使用工作空间下项目默认配额
* @return 操作后的配额
*/
public Quota projectUseDefaultQuota(String projectId) {
Quota pjQuota = extQuotaMapper.getProjectQuota(projectId);
if (pjQuota != null) {
pjQuota.setUseDefault(true);
quotaMapper.updateByPrimaryKeySelective(pjQuota);
return pjQuota;
} else {
Quota quota = new Quota();
quota.setId(UUID.randomUUID().toString());
quota.setUseDefault(true);
quota.setProjectId(projectId);
quota.setWorkspaceId(null);
quota.setUpdateTime(System.currentTimeMillis());
quotaMapper.insert(quota);
return quota;
}
}
/**
* 如果有该工作空间配额修改为使用系统默认配额
* 无该配额新建配额并默认使用系统配额
* @param workspaceId 工作空间ID
* @return 操作后的配额
*/
public Quota workspaceUseDefaultQuota(String workspaceId) {
Quota wsQuota = extQuotaMapper.getWorkspaceQuota(workspaceId);
if (wsQuota != null) {
wsQuota.setUseDefault(true);
quotaMapper.updateByPrimaryKeySelective(wsQuota);
return wsQuota;
} else {
Quota quota = new Quota();
quota.setId(UUID.randomUUID().toString());
quota.setUseDefault(true);
quota.setProjectId(null);
quota.setWorkspaceId(workspaceId);
quota.setUpdateTime(System.currentTimeMillis());
quotaMapper.insert(quota);
return quota;
}
}
// todo
private List<LoadTestReportResult> queryReportResult(String id, String key) {
return extLoadTestReportResultMapper.selectByIdAndKey(id, key);
}
private ReportTimeInfo parseReportTimeInfo(LoadTestReportResult reportResult) {
String content = reportResult.getReportValue();
ReportTimeInfo timeInfo = new ReportTimeInfo();
try {
timeInfo = JSON.parseObject(content, ReportTimeInfo.class);
} catch (Exception e) {
// 兼容字符串和数字
Map jsonObject = JSON.parseObject(content, Map.class);
String startTime = (String) jsonObject.get("startTime");
String endTime = (String) jsonObject.get("endTime");
String duration = (String) jsonObject.get("duration");
SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
try {
timeInfo.setStartTime(df.parse(startTime).getTime());
timeInfo.setEndTime(df.parse(endTime).getTime());
timeInfo.setDuration(Long.parseLong(duration));
} catch (Exception parseException) {
LogUtil.error("reduce vum error. parse time info error. " + parseException.getMessage());
}
}
return timeInfo;
}
private TestOverview parseOverview(LoadTestReportResult reportResult) {
String content = reportResult.getReportValue();
TestOverview overview = new TestOverview();
try {
overview = JSON.parseObject(content, TestOverview.class);
} catch (Exception e) {
LogUtil.error("parse test overview error.");
LogUtil.error(e.getMessage(), e);
}
return overview;
}
private void doUpdateVumUsed(Quota dbQuota, Quota newQuota) {
if (dbQuota == null || StringUtils.isBlank(dbQuota.getId())) {
quotaMapper.insert(newQuota);
} else {
BigDecimal vumUsed = dbQuota.getVumUsed();
if (vumUsed == null) {
vumUsed = BigDecimal.ZERO;
}
if (newQuota == null || newQuota.getVumUsed() == null) {
return;
}
BigDecimal toSetVum = vumUsed.add(newQuota.getVumUsed());
if (toSetVum.compareTo(BigDecimal.ZERO) < 0) {
LogUtil.info("update vum used warning. vum value: " + toSetVum);
toSetVum = BigDecimal.ZERO;
}
LogUtil.info("update vum used add value: " + newQuota.getVumUsed());
dbQuota.setVumUsed(toSetVum);
quotaMapper.updateByPrimaryKeySelective(dbQuota);
}
}
private Quota newPjQuota(String projectId, BigDecimal vumUsed) {
Quota quota = new Quota();
quota.setId(UUID.randomUUID().toString());
quota.setUpdateTime(System.currentTimeMillis());
quota.setUseDefault(false);
quota.setVumUsed(vumUsed);
quota.setProjectId(projectId);
quota.setWorkspaceId(null);
return quota;
}
private Map<String, String> getNameMap(List<String> sourceIds, String type) {
Map<String, String> nameMap = new HashMap<>(16);
if (CollectionUtils.isEmpty(sourceIds)) {
return nameMap;
}
if (StringUtils.equals(CHECK_PROJECT, type)) {
ProjectExample projectExample = new ProjectExample();
projectExample.createCriteria().andIdIn(sourceIds);
List<Project> projects = projectMapper.selectByExample(projectExample);
nameMap = projects.stream().collect(Collectors.toMap(Project::getId, Project::getName));
} else if (StringUtils.equals(CHECK_WORKSPACE, type)) {
WorkspaceExample workspaceExample = new WorkspaceExample();
workspaceExample.createCriteria().andIdIn(sourceIds);
List<Workspace> workspaces = workspaceMapper.selectByExample(workspaceExample);
nameMap = workspaces.stream().collect(Collectors.toMap(Workspace::getId, Workspace::getName));
}
return nameMap;
}
private void filterDeleteAndEnabled(List jsonArray) {
Iterator<Object> iterator = jsonArray.iterator();
outer:
while (iterator.hasNext()) {
Object next = iterator.next();
if (next instanceof List<?>) {
List<?> o = (List<?>) next;
for (Object o1 : o) {
Map jsonObject = (Map) o1;
if (StringUtils.equals((String) jsonObject.get("key"), "deleted")) {
String value = (String) jsonObject.get("value");
if (StringUtils.equals(value, "true")) {
iterator.remove();
continue outer;
}
}
}
for (Object o1 : o) {
Map jsonObject = (Map) o1;
if (StringUtils.equals((String) jsonObject.get("key"), "enabled")) {
String value = (String) jsonObject.get("value");
if (StringUtils.equals(value, "false")) {
iterator.remove();
continue outer;
}
}
}
}
}
}
private int getIntegerValue(String loadConfiguration, String key) {
int s = 0;
try {
List jsonArray = JSON.parseArray(loadConfiguration);
this.filterDeleteAndEnabled(jsonArray);
for (int i = 0; i < jsonArray.size(); i++) {
if (jsonArray.get(i) instanceof List) {
List o = (List) jsonArray.get(i);
for (int j = 0; j < o.size(); j++) {
Map b = (Map) o.get(j);
if (StringUtils.equals((String) b.get("key"), key)) {
s += (int) b.get("value");
break;
}
}
}
}
} catch (Exception e) {
LogUtil.error("get load configuration integer value error.");
LogUtil.error(e.getMessage(), e);
}
return s;
}
}

View File

@ -0,0 +1,388 @@
package io.metersphere.quota.service;
import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.Quota;
import io.metersphere.base.domain.QuotaExample;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.QuotaMapper;
import io.metersphere.base.mapper.ext.ExtQuotaMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.i18n.Translator;
import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.quota.dto.QuotaConstants;
import io.metersphere.quota.dto.QuotaResult;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* 工作空间默认配额(全局唯一)QuotaConstants.DefaultType.workspace
* 工作空间下项目的默认配额(工作空间下唯一)QuotaConstants.prefix + workspace_id
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class QuotaManagementService {
@Resource
private QuotaMapper quotaMapper;
@Resource
private ExtQuotaMapper extQuotaMapper;
@Resource
private ProjectMapper projectMapper;
public Quota getDefaultQuota(QuotaConstants.DefaultType type) {
Quota quota = quotaMapper.selectByPrimaryKey(type.name());
if (quota == null) {
quota = new Quota();
quota.setId(type.name());
}
return quota;
}
public Quota getProjectDefaultQuota(String workspaceId) {
if (StringUtils.isBlank(workspaceId)) {
return new Quota();
}
Quota quota = quotaMapper.selectByPrimaryKey(QuotaConstants.prefix + workspaceId);
if (quota == null) {
quota = new Quota();
quota.setId(QuotaConstants.prefix + workspaceId);
}
return quota;
}
public void saveQuota(Quota quota) {
if (!isDefaultQuota(quota)) {
// 保存项目配额时检查工作空间下所有项目配额总和是否大于工作空间配额
if (StringUtils.isNotBlank(quota.getProjectId())) {
// 使用默认配额
if (BooleanUtils.isTrue(quota.getUseDefault())) {
Project project = projectMapper.selectByPrimaryKey(quota.getProjectId());
if (project == null || StringUtils.isBlank(project.getWorkspaceId())) {
MSException.throwException("save project quota fail. project is null");
}
useDefaultQuota(quota, getProjectDefaultQuota(project.getWorkspaceId()));
}
vumCompare(quota.getVumTotal(), quota.getVumUsed());
checkProjectQuota(quota);
quota.setWorkspaceId(null);
}
// 保存工作空间配额时检查是否小于项目配额总和
if (StringUtils.isBlank(quota.getProjectId()) && StringUtils.isNotBlank(quota.getWorkspaceId())) {
// 使用默认配额
if (BooleanUtils.isTrue(quota.getUseDefault())) {
useDefaultQuota(quota, getDefaultQuota(QuotaConstants.DefaultType.workspace));
}
wsVumCompare(quota);
checkWorkspaceQuota(quota);
}
} else {
checkDefaultQuota(quota);
}
quota.setUpdateTime(System.currentTimeMillis());
if (StringUtils.isNotBlank(quota.getId())) {
Quota qt = quotaMapper.selectByPrimaryKey(quota.getId());
if (qt != null) {
quota.setVumUsed(qt.getVumUsed());
}
}
quotaMapper.deleteByPrimaryKey(quota.getId());
if (StringUtils.isBlank(quota.getId())) {
quota.setId(UUID.randomUUID().toString());
}
if (StringUtils.isNotBlank(quota.getWorkspaceId()) && StringUtils.isNotBlank(quota.getProjectId())) {
LogUtil.error("save quota error, illegal parameter, workspace id and project id cannot exist at the same time");
return;
} else if (StringUtils.isNotBlank(quota.getWorkspaceId()) && StringUtils.isBlank(quota.getProjectId())) {
QuotaExample quotaExample = new QuotaExample();
quotaExample.createCriteria().andWorkspaceIdEqualTo(quota.getWorkspaceId())
.andProjectIdIsNull();
if (quotaMapper.countByExample(quotaExample) > 0) {
LogUtil.error("save quota error, repeat insert workspace quota, id is: " + quota.getWorkspaceId());
return;
}
} else if (StringUtils.isNotBlank(quota.getProjectId()) && StringUtils.isBlank(quota.getWorkspaceId())) {
QuotaExample quotaExample = new QuotaExample();
quotaExample.createCriteria().andProjectIdEqualTo(quota.getProjectId())
.andWorkspaceIdIsNull();
if (quotaMapper.countByExample(quotaExample) > 0) {
LogUtil.error("save quota error, repeat insert project quota, id is: " + quota.getProjectId());
return;
}
} else if (this.isDefaultQuota(quota)) {
QuotaExample quotaExample = new QuotaExample();
quotaExample.createCriteria().andIdEqualTo(quota.getId());
if (quotaMapper.countByExample(quotaExample) > 0) {
LogUtil.error("save quota error, repeat insert default quota, id is: " + quota.getId());
return;
}
}
BigDecimal vumTotal = quota.getVumTotal();
BigDecimal max = BigDecimal.valueOf(99999999.00);
if (vumTotal.compareTo(max) > 0) {
MSException.throwException("总vum数量不能超过99999999");
}
quotaMapper.insert(quota);
}
public void wsVumCompare(Quota quota) {
if (quota == null) {
return;
}
if (StringUtils.isNotBlank(quota.getWorkspaceId())) {
// 工作空间消耗的vum数量
Quota vumUsedSum = extQuotaMapper.getProjectQuotaSum(quota.getWorkspaceId());
if (vumUsedSum != null && vumUsedSum.getVumUsed() != null) {
vumCompare(quota.getVumTotal(), vumUsedSum.getVumUsed());
}
}
}
public void vumCompare(BigDecimal vumTotal, BigDecimal vumUsed) {
if (isValid(vumTotal) && isValid(vumUsed)) {
if (vumUsed.compareTo(vumTotal) > 0) {
MSException.throwException(Translator.get("quota_vum_used_gt_vum_total"));
}
}
}
public List<QuotaResult> listWorkspaceQuota(String name) {
return extQuotaMapper.listWorkspaceQuota(name);
}
public List<QuotaResult> listProjectQuota(String workspaceId, String name) {
return extQuotaMapper.listProjectQuota(workspaceId, name);
}
public void deleteQuota(String id) {
Quota quota = quotaMapper.selectByPrimaryKey(id);
// 保留vum使用数量
Quota qt = new Quota();
qt.setId(UUID.randomUUID().toString());
qt.setVumUsed(quota.getVumUsed());
qt.setWorkspaceId(quota.getWorkspaceId());
qt.setProjectId(quota.getProjectId());
qt.setUseDefault(false);
qt.setUpdateTime(System.currentTimeMillis());
quotaMapper.insert(qt);
quotaMapper.deleteByPrimaryKey(id);
}
public void checkDefaultQuota(Quota quota) {
if (StringUtils.equals(quota.getId(), QuotaConstants.DefaultType.workspace.name())) {
List<Quota> wsQuotas = extQuotaMapper.listUseDefaultWsQuota();
for (Quota q : wsQuotas) {
useDefaultQuota(q, quota);
wsVumCompare(q);
checkWorkspaceQuota(q);
}
}
if (StringUtils.startsWith(quota.getId(), QuotaConstants.prefix)) {
String workspaceId = quota.getId().substring(QuotaConstants.prefix.length());
Quota wsQuota = getWorkspaceQuota(workspaceId);
checkQuota(quota, wsQuota);
List<Quota> pjQuotas = extQuotaMapper.listUseDefaultProjectQuota(workspaceId);
for (Quota q : pjQuotas) {
useDefaultQuota(q, quota);
vumCompare(q.getVumTotal(), q.getVumUsed());
checkProjectQuota(q);
}
}
}
private boolean isDefaultQuota(Quota quota) {
return StringUtils.startsWith(quota.getId(), QuotaConstants.prefix)
|| StringUtils.equals(quota.getId(), QuotaConstants.DefaultType.workspace.name());
}
private void checkProjectQuota(Quota quota) {
// 获取工作空间下所有项目配额总和(接口和性能测试数量)
Quota sumQuota = getProjectSumQuota(quota.getWorkspaceId(), quota.getProjectId());
if (quota.getApi() != null) {
sumQuota.setApi(sumQuota.getApi() + quota.getApi());
}
if (quota.getPerformance() != null) {
sumQuota.setPerformance(sumQuota.getPerformance() + quota.getPerformance());
}
if (quota.getVumTotal() != null) {
sumQuota.setVumTotal(sumQuota.getVumTotal().add(quota.getVumTotal()));
}
sumQuota.setMaxThreads(quota.getMaxThreads());
sumQuota.setDuration(quota.getDuration());
sumQuota.setResourcePool(quota.getResourcePool());
// 工作空间配额
Quota wsQuota = getWorkspaceQuota(quota.getWorkspaceId());
// 检查是否超过工作空间配额
checkQuota(sumQuota, wsQuota);
}
private void checkWorkspaceQuota(Quota quota) {
// 获取工作空间下所有项目配额总和(接口和性能测试数量)
Quota sumQuota = getProjectSumQuota(quota.getWorkspaceId(), null);
sumQuota.setResourcePool(quota.getResourcePool());
// 检查是否超过工作空间配额
checkQuota(sumQuota, quota);
}
private void checkQuota(Quota sumQuota, Quota wsQuota) {
if (wsQuota == null) {
return;
}
if (isValid(wsQuota.getApi()) && isValid(sumQuota.getApi())) {
if (sumQuota.getApi() > wsQuota.getApi()) {
MSException.throwException(Translator.get("quota_project_excess_ws_api"));
}
}
if (isValid(wsQuota.getPerformance()) && isValid(sumQuota.getPerformance())) {
if (sumQuota.getPerformance() > wsQuota.getPerformance()) {
MSException.throwException(Translator.get("quota_project_excess_ws_performance"));
}
}
if (isValid(wsQuota.getMaxThreads()) && isValid(sumQuota.getMaxThreads())) {
if (sumQuota.getMaxThreads() > wsQuota.getMaxThreads()) {
MSException.throwException(Translator.get("quota_project_excess_ws_max_threads"));
}
}
if (isValid(wsQuota.getDuration()) && isValid(sumQuota.getDuration())) {
if (sumQuota.getDuration() > wsQuota.getDuration()) {
MSException.throwException(Translator.get("quota_project_excess_ws_max_duration"));
}
}
if (isValid(wsQuota.getVumTotal()) && isValid(sumQuota.getVumTotal())) {
if (sumQuota.getVumTotal().compareTo(wsQuota.getVumTotal()) > 0) {
MSException.throwException(Translator.get("quota_project_excess_ws_vum_total"));
}
}
if (isValid(wsQuota.getResourcePool()) && isValid(sumQuota.getResourcePool())) {
for (String id : sumQuota.getResourcePool().split(",")) {
if (!wsQuota.getResourcePool().contains(id)) {
MSException.throwException(Translator.get("quota_project_excess_ws_resource_pool"));
}
}
}
}
private Quota getProjectSumQuota(String workspaceId, String currentProjectId) {
List<QuotaResult> quotaResults = listProjectQuota(workspaceId, null);
Quota projectDefaultQuota = this.getProjectDefaultQuota(workspaceId);
AtomicInteger api = new AtomicInteger();
AtomicInteger performance = new AtomicInteger();
AtomicReference<BigDecimal> vumTotal = new AtomicReference<>(new BigDecimal("0.00"));
int thread = 0;
int duration = 0;
for (QuotaResult quotaResult : quotaResults) {
if (BooleanUtils.isTrue(quotaResult.getUseDefault())) {
useDefaultQuota(quotaResult, projectDefaultQuota);
}
// 不计算当前project
if (StringUtils.isNotBlank(currentProjectId) && StringUtils.equals(currentProjectId, quotaResult.getProjectId())) {
continue;
}
if (quotaResult.getApi() != null) {
api.addAndGet(quotaResult.getApi());
}
if (quotaResult.getPerformance() != null) {
performance.addAndGet(quotaResult.getPerformance());
}
if (quotaResult.getVumTotal() != null) {
vumTotal.updateAndGet(v -> v.add(quotaResult.getVumTotal()));
}
if (quotaResult.getMaxThreads() != null) {
if (quotaResult.getMaxThreads() > thread) {
thread = quotaResult.getMaxThreads();
}
}
if (quotaResult.getDuration() != null) {
if (quotaResult.getDuration() > duration) {
duration = quotaResult.getDuration();
}
}
}
Quota quota = new Quota();
quota.setApi(api.get());
quota.setPerformance(performance.get());
quota.setVumTotal(vumTotal.get());
quota.setMaxThreads(thread);
quota.setDuration(duration);
return quota;
}
public boolean isValid(Integer value) {
return value != null && value > 0;
}
public boolean isValid(String value) {
return StringUtils.isNotBlank(value);
}
public boolean isValid(BigDecimal value) {
return value != null && value.compareTo(BigDecimal.ZERO) != 0;
}
private void useDefaultQuota(Quota quota, Quota defaultQuota) {
quota.setApi(defaultQuota.getApi());
quota.setPerformance(defaultQuota.getPerformance());
quota.setMaxThreads(defaultQuota.getMaxThreads());
quota.setDuration(defaultQuota.getDuration());
quota.setResourcePool(defaultQuota.getResourcePool());
quota.setVumTotal(defaultQuota.getVumTotal());
}
public Quota getWorkspaceQuota(String workspaceId) {
Quota quota = extQuotaMapper.getWorkspaceQuota(workspaceId);
if (quota != null && BooleanUtils.isTrue(quota.getUseDefault())) {
return getDefaultQuota(QuotaConstants.DefaultType.workspace);
}
return quota;
}
public Quota getProjectQuota(String projectId) {
Quota quota = extQuotaMapper.getProjectQuota(projectId);
if (quota != null && BooleanUtils.isTrue(quota.getUseDefault())) {
Project project = projectMapper.selectByPrimaryKey(projectId);
if (project == null || StringUtils.isBlank(project.getWorkspaceId())) {
MSException.throwException("project is null or workspace_id of project is null");
}
return getProjectDefaultQuota(project.getWorkspaceId());
}
return quota;
}
public String getLogDetails(String id) {
Quota pool = quotaMapper.selectByPrimaryKey(id);
if (pool != null) {
List<DetailColumn> columns = ReflexObjectUtil.getColumns(pool, SystemReference.quotaColumns);
OperatingLogDetails details = new OperatingLogDetails(JSON.toJSONString(pool.getId()), null, "默认配额", null, columns);
return JSON.toJSONString(details);
}
return null;
}
}

View File

@ -0,0 +1,43 @@
package io.metersphere.request;
import io.metersphere.base.domain.Schedule;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestPlanRequest {
private String id;
private String projectId;
private String name;
private String description;
private Long createTime;
private Long updateTime;
private String loadConfiguration;
private String advancedConfiguration;
private String runtimeConfiguration;
private Schedule schedule;
private String testResourcePoolId;
private String refId;
private String versionId;
private List<String> follows;
private static final long serialVersionUID = 1L;
}

View File

@ -1,100 +0,0 @@
package io.metersphere.xpack.quota.service;
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.base.domain.Quota;
import io.metersphere.request.TestPlanRequest;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author lyh
*/
public interface QuotaService {
/**
* api配额检查
*/
void checkAPIDefinitionQuota(String projectId);
/**
* api配额检查
*/
void checkAPIAutomationQuota(String projectId);
/**
* 性能测试配额检查
* @param request 压力配置
* @param checkPerformance 检查创建数量配额 / 检查并发数和时间
*/
void checkLoadTestQuota(TestPlanRequest request, boolean checkPerformance);
/**
* 检查资源池
* @return 资源池名称
*/
Set<String> getQuotaResourcePools();
/**
* 工作空间下被限制使用的资源池
* @param workspaceId 工作空间ID
* @return 资源池名称Set
*/
Set<String> getQuotaWsResourcePools(String workspaceId);
/**
* 检查工作空间项目数量配额
* @param workspaceId 工作空间ID
*/
void checkWorkspaceProject(String workspaceId);
/**
* 检查vumUsed配额
* 未超过返回本次执行预计消耗的配额
* 超过抛出异常
* @param request 压力配置
* @param projectId 性能测试所属项目ID
* @return 本次执行预计消耗的配额
*/
BigDecimal checkVumUsed(TestPlanRequest request, String projectId);
/**
* 检查向某资源添加人员时是否超额
* @param map 资源ID:添加用户ID列表
* @param type 检查类型 PROJECT/WORKSPACE
*/
void checkMemberCount(Map<String, List<String>> map, String type);
/**
* 更新VumUsed配额
* @param projectId 项目ID
* @param used 预计使用数量
*/
void updateVumUsed(String projectId, BigDecimal used);
/**
* 回退因主动停止或者测试启动后中途执行异常扣除的VumUsed配额
* @param report 性能测试报告
* @return 预计回退数量
*/
BigDecimal getReduceVumUsed(LoadTestReportWithBLOBs report);
/**
* 如果有该项目配额修改为使用工作空间下项目默认配额
* 无该配额新建配额并默认使用工作空间下项目默认配额
* @return 操作后的配额
*/
Quota projectUseDefaultQuota(String projectId);
/**
* 如果有该工作空间配额修改为使用系统默认配额
* 无该配额新建配额并默认使用系统配额
* @param workspaceId 工作空间ID
* @return 操作后的配额
*/
Quota workspaceUseDefaultQuota(String workspaceId);
}

View File

@ -5,10 +5,10 @@ import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.TestResourcePoolDTO;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.resourcepool.QueryResourcePoolRequest;
import io.metersphere.service.BaseTestResourcePoolService;
import io.metersphere.service.NodeResourcePoolService;
import io.metersphere.xpack.quota.service.QuotaService;
import io.metersphere.xpack.resourcepool.engine.KubernetesResourcePoolService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@ -29,17 +29,15 @@ public class ValidQuotaResourcePoolService {
private NodeResourcePoolService nodeResourcePoolService;
@Resource
private TestResourcePoolMapper testResourcePoolMapper;
@Resource
private BaseQuotaService baseQuotaService;
public List<TestResourcePoolDTO> listValidQuotaResourcePools() {
return filterQuota(listValidResourcePools());
}
private List<TestResourcePoolDTO> filterQuota(List<TestResourcePoolDTO> list) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService == null) {
return list;
}
Set<String> pools = quotaService.getQuotaResourcePools();
Set<String> pools = baseQuotaService.getQuotaResourcePools();
if (!pools.isEmpty()) {
return list.stream().filter(pool -> pools.contains(pool.getId())).collect(Collectors.toList());
}

View File

@ -12,7 +12,7 @@ import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.consumer.LoadTestFinishEvent;
import io.metersphere.dto.VumProcessedStatus;
import io.metersphere.xpack.quota.service.QuotaService;
import io.metersphere.quota.service.BaseQuotaService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
@ -35,6 +35,8 @@ public class LoadTestVumEvent implements LoadTestFinishEvent {
private RedissonClient redissonClient;
@Resource
private ProjectMapper projectMapper;
@Resource
private BaseQuotaService baseQuotaService;
private void handleVum(LoadTestReport report) {
if (report == null) {
@ -63,20 +65,15 @@ public class LoadTestVumEvent implements LoadTestFinishEvent {
MSException.throwException("project is null or workspace_id of project is null. project id: " + projectId);
}
RLock lock = redissonClient.getLock(project.getWorkspaceId());
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
try {
lock.lock();
BigDecimal toReduceVum = quotaService.getReduceVumUsed(testReport);
BigDecimal toReduceVum = baseQuotaService.getReduceVumUsed(testReport);
if (toReduceVum.compareTo(BigDecimal.ZERO) != 0) {
quotaService.updateVumUsed(projectId, toReduceVum.negate());
baseQuotaService.updateVumUsed(projectId, toReduceVum.negate());
}
} finally {
lock.unlock();
}
} else {
LogUtil.error("handle vum event get quota service bean is null. load test report id: " + report.getId());
}
}
public void execute(LoadTestReport report) {

View File

@ -22,11 +22,11 @@ import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.performance.PerformanceReference;
import io.metersphere.metadata.service.FileMetadataService;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.DeleteReportRequest;
import io.metersphere.request.OrderRequest;
import io.metersphere.request.RenameReportRequest;
import io.metersphere.request.ReportRequest;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
@ -85,6 +85,8 @@ public class PerformanceReportService {
private ExtTestPlanLoadCaseMapper extTestPlanLoadCaseMapper;
@Resource
private BaseEnvironmentService baseEnvironmentService;
@Resource
private BaseQuotaService baseQuotaService;
public List<ReportDTO> getRecentReportList(ReportRequest request) {
List<OrderRequest> orders = new ArrayList<>();
@ -191,14 +193,12 @@ public class PerformanceReportService {
if (project == null || StringUtils.isBlank(project.getWorkspaceId())) {
MSException.throwException("project is null or workspace_id of project is null. project id: " + projectId);
}
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
RLock lock = redissonClient.getLock(project.getWorkspaceId());
if (quotaService != null) {
try {
lock.lock();
BigDecimal toReduceVum = quotaService.getReduceVumUsed(report);
BigDecimal toReduceVum = baseQuotaService.getReduceVumUsed(report);
if (toReduceVum.compareTo(BigDecimal.ZERO) != 0) {
quotaService.updateVumUsed(projectId, toReduceVum.negate());
baseQuotaService.updateVumUsed(projectId, toReduceVum.negate());
}
engine.stop();
loadTest.setStatus(PerformanceTestStatus.Saved.name());
@ -207,7 +207,6 @@ public class PerformanceReportService {
lock.unlock();
}
}
}
public ReportDTO getReportTestAndProInfo(String reportId) {
ReportDTO reportDTO = extLoadTestReportMapper.getReportTestAndProInfo(reportId);

View File

@ -24,9 +24,9 @@ import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.performance.PerformanceReference;
import io.metersphere.metadata.service.FileMetadataService;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.*;
import io.metersphere.task.dto.TaskRequestDTO;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
@ -92,6 +92,8 @@ public class PerformanceTestService {
private TestPlanLoadCaseMapper testPlanLoadCaseMapper;
@Resource
private TestCaseTestMapper testCaseTestMapper;
@Resource
private BaseQuotaService baseQuotaService;
public List<LoadTestDTO> list(QueryTestPlanRequest request) {
request.setOrders(ServiceUtils.getDefaultSortOrder(request.getOrders()));
@ -509,11 +511,9 @@ public class PerformanceTestService {
private void checkLoadQuota(LoadTestReportWithBLOBs testReport, Engine engine) {
RunTestPlanRequest checkRequest = new RunTestPlanRequest();
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
checkRequest.setLoadConfiguration(testReport.getLoadConfiguration());
checkRequest.setProjectId(testReport.getProjectId());
if (quotaService != null) {
quotaService.checkLoadTestQuota(checkRequest, false);
baseQuotaService.checkLoadTestQuota(checkRequest, false);
String projectId = testReport.getProjectId();
Project project = projectMapper.selectByPrimaryKey(projectId);
if (project == null || StringUtils.isBlank(project.getWorkspaceId())) {
@ -522,18 +522,14 @@ public class PerformanceTestService {
RLock lock = redissonClient.getLock(project.getWorkspaceId());
try {
lock.lock();
BigDecimal toUsed = quotaService.checkVumUsed(checkRequest, projectId);
BigDecimal toUsed = baseQuotaService.checkVumUsed(checkRequest, projectId);
engine.start();
if (toUsed.compareTo(BigDecimal.ZERO) != 0) {
quotaService.updateVumUsed(projectId, toUsed);
baseQuotaService.updateVumUsed(projectId, toUsed);
}
} finally {
lock.unlock();
}
} else {
engine.start();
LogUtil.error("check load test quota fail, quotaService is null.");
}
}
public List<LoadTestDTO> recentTestPlans(QueryTestPlanRequest request) {
@ -710,10 +706,8 @@ public class PerformanceTestService {
}
private void checkQuota(TestPlanRequest request, boolean create) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
quotaService.checkLoadTestQuota(request, create);
}
baseQuotaService.checkLoadTestQuota(request, create);
}
public List<LoadTestDTO> getLoadTestListByIds(List<String> ids) {

View File

@ -20,10 +20,10 @@ import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.GroupRequest;
import io.metersphere.request.group.EditGroupRequest;
import io.metersphere.request.group.EditGroupUserRequest;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@ -69,6 +69,8 @@ public class GroupService {
private MicroService microService;
@Resource
private BaseUserService baseUserService;
@Resource
private BaseQuotaService baseQuotaService;
private static final String GLOBAL = "global";
// 服务权限拼装顺序
@ -526,13 +528,12 @@ public class GroupService {
}
private void addNotSystemGroupUser(Group group, List<String> userIds, List<String> sourceIds) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
for (String userId : userIds) {
User user = userMapper.selectByPrimaryKey(userId);
if (user == null) {
continue;
}
checkQuota(quotaService, group.getType(), sourceIds, Collections.singletonList(userId));
checkQuota(group.getType(), sourceIds, Collections.singletonList(userId));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserGroupMapper mapper = sqlSession.getMapper(UserGroupMapper.class);
UserGroupExample userGroupExample = new UserGroupExample();
@ -555,11 +556,9 @@ public class GroupService {
}
private void checkQuota(QuotaService quotaService, String type, List<String> sourceIds, List<String> userIds) {
if (quotaService != null) {
private void checkQuota(String type, List<String> sourceIds, List<String> userIds) {
Map<String, List<String>> addMemberMap = sourceIds.stream().collect(Collectors.toMap( id -> id, id -> userIds));
quotaService.checkMemberCount(addMemberMap, type);
}
baseQuotaService.checkMemberCount(addMemberMap, type);
}
public void editGroupUser(EditGroupUserRequest request) {

View File

@ -20,10 +20,10 @@ import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.GroupRequest;
import io.metersphere.request.group.EditGroupRequest;
import io.metersphere.request.group.EditGroupUserRequest;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@ -66,6 +66,8 @@ public class GroupService {
private StringRedisTemplate stringRedisTemplate;
@Resource
private UserMapper userMapper;
@Resource
private BaseQuotaService baseQuotaService;
private static final String GLOBAL = "global";
private static final String PERSONAL_PREFIX = "PERSONAL";
@ -511,13 +513,12 @@ public class GroupService {
}
private void addNotSystemGroupUser(Group group, List<String> userIds, List<String> sourceIds) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
for (String userId : userIds) {
User user = userMapper.selectByPrimaryKey(userId);
if (user == null) {
continue;
}
checkQuota(quotaService, group.getType(), sourceIds, Collections.singletonList(userId));
checkQuota(group.getType(), sourceIds, Collections.singletonList(userId));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserGroupMapper mapper = sqlSession.getMapper(UserGroupMapper.class);
UserGroupExample userGroupExample = new UserGroupExample();
@ -539,11 +540,9 @@ public class GroupService {
}
}
private void checkQuota(QuotaService quotaService, String type, List<String> sourceIds, List<String> userIds) {
if (quotaService != null) {
private void checkQuota(String type, List<String> sourceIds, List<String> userIds) {
Map<String, List<String>> addMemberMap = sourceIds.stream().collect(Collectors.toMap(id -> id, id -> userIds));
quotaService.checkMemberCount(addMemberMap, type);
}
baseQuotaService.checkMemberCount(addMemberMap, type);
}
public void editGroupUser(EditGroupUserRequest request) {

View File

@ -24,10 +24,10 @@ import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.metadata.service.FileMetadataService;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.AddProjectRequest;
import io.metersphere.request.ProjectRequest;
import io.metersphere.xpack.api.service.ProjectApplicationSyncService;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
@ -71,6 +71,8 @@ public class SystemProjectService {
private EnvironmentGroupProjectService environmentGroupProjectService;
@Resource
private BaseScheduleService baseScheduleService;
@Resource
private BaseQuotaService baseQuotaService;
public Project addProject(AddProjectRequest project) {
if (StringUtils.isBlank(project.getName())) {
@ -81,10 +83,7 @@ public class SystemProjectService {
if (projectMapper.countByExample(example) > 0) {
MSException.throwException(Translator.get("project_name_already_exists"));
}
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
quotaService.checkWorkspaceProject(project.getWorkspaceId());
}
baseQuotaService.checkWorkspaceProject(project.getWorkspaceId());
if (project.getMockTcpPort() != null && project.getMockTcpPort() > 0) {
this.checkMockTcpPort(project.getMockTcpPort());
@ -123,9 +122,7 @@ public class SystemProjectService {
// 设置默认的通知
baseProjectMapper.setDefaultMessageTask(project.getId());
if (quotaService != null) {
quotaService.projectUseDefaultQuota(pjId);
}
baseQuotaService.projectUseDefaultQuota(pjId);
// 创建默认版本
addProjectVersion(project);

View File

@ -19,8 +19,8 @@ import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.resourcepool.QueryResourcePoolRequest;
import io.metersphere.xpack.quota.service.QuotaService;
import io.metersphere.xpack.resourcepool.engine.KubernetesResourcePoolService;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
@ -52,6 +52,8 @@ public class TestResourcePoolService {
private BaseTaskMapper baseTaskMapper;
@Resource
private MicroService microService;
@Resource
private BaseQuotaService baseQuotaService;
public TestResourcePoolDTO addTestResourcePool(TestResourcePoolDTO testResourcePool) {
checkTestResourcePool(testResourcePool);
@ -229,13 +231,10 @@ public class TestResourcePoolService {
}
private List<TestResourcePoolDTO> filterQuota(List<TestResourcePoolDTO> list) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
Set<String> pools = quotaService.getQuotaResourcePools();
Set<String> pools = baseQuotaService.getQuotaResourcePools();
if (!pools.isEmpty()) {
return list.stream().filter(pool -> pools.contains(pool.getId())).collect(Collectors.toList());
}
}
return list;
}
@ -285,14 +284,11 @@ public class TestResourcePoolService {
}
public List<TestResourcePoolDTO> listWsValidQuotaResourcePools(String workspaceId) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
List<TestResourcePoolDTO> list = listValidResourcePools();
if (quotaService != null) {
Set<String> pools = quotaService.getQuotaWsResourcePools(workspaceId);
Set<String> pools = baseQuotaService.getQuotaWsResourcePools(workspaceId);
if (!pools.isEmpty()) {
return list.stream().filter(pool -> pools.contains(pool.getId())).collect(Collectors.toList());
}
}
return list;
}
}

View File

@ -3,10 +3,8 @@ package io.metersphere.service;
import com.alibaba.excel.EasyExcelFactory;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.BaseProjectMapper;
import io.metersphere.base.mapper.ext.BaseUserGroupMapper;
import io.metersphere.base.mapper.ext.BaseUserMapper;
import io.metersphere.base.mapper.ext.BaseWorkspaceMapper;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
@ -27,13 +25,13 @@ import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.UserRequest;
import io.metersphere.request.WorkspaceRequest;
import io.metersphere.request.member.AddMemberRequest;
import io.metersphere.request.member.EditPassWordRequest;
import io.metersphere.request.member.QueryMemberRequest;
import io.metersphere.request.resourcepool.UserBatchProcessRequest;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
@ -81,6 +79,8 @@ public class UserService {
private ProjectMapper projectMapper;
@Resource
private BaseUserService baseUserService;
@Resource
private BaseQuotaService baseQuotaService;
public UserDTO insert(io.metersphere.request.member.UserRequest userRequest) {
checkUserParam(userRequest);
@ -116,8 +116,7 @@ public class UserService {
} else {
List<String> ids = (List<String>) map.get("ids");
Group group = groupMapper.selectByPrimaryKey(groupId);
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
checkQuota(quotaService, group.getType(), ids, Collections.singletonList(userId));
checkQuota(group.getType(), ids, Collections.singletonList(userId));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserGroupMapper mapper = sqlSession.getMapper(UserGroupMapper.class);
for (String id : ids) {
@ -139,11 +138,9 @@ public class UserService {
}
}
private void checkQuota(QuotaService quotaService, String type, List<String> sourceIds, List<String> userIds) {
if (quotaService != null) {
private void checkQuota(String type, List<String> sourceIds, List<String> userIds) {
Map<String, List<String>> addMemberMap = sourceIds.stream().collect(Collectors.toMap(id -> id, id -> userIds));
quotaService.checkMemberCount(addMemberMap, type);
}
baseQuotaService.checkMemberCount(addMemberMap, type);
}
private void checkUserParam(User user) {
@ -351,8 +348,7 @@ public class UserService {
return;
}
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
checkQuota(quotaService, "WORKSPACE", Collections.singletonList(request.getWorkspaceId()), request.getUserIds());
checkQuota("WORKSPACE", Collections.singletonList(request.getWorkspaceId()), request.getUserIds());
List<String> allUserIds = baseUserService.getAllUserIds();
@ -606,8 +602,7 @@ public class UserService {
List<String> worksapceIds = request.getBatchProcessValue();
if (CollectionUtils.isNotEmpty(userIds)) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
checkQuota(quotaService, "WORKSPACE", worksapceIds, userIds);
checkQuota("WORKSPACE", worksapceIds, userIds);
}
for (String userId : userIds) {
UserGroupExample userGroupExample = new UserGroupExample();
@ -685,8 +680,7 @@ public class UserService {
List<String> sourceIds = userGroups.stream().map(UserGroup::getSourceId).collect(Collectors.toList());
List<String> list = sourceMap.get(group);
list.removeAll(sourceIds);
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
checkQuota(quotaService, gp.getType(), list, Collections.singletonList(userId));
checkQuota(gp.getType(), list, Collections.singletonList(userId));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserGroupMapper mapper = sqlSession.getMapper(UserGroupMapper.class);
for (String sourceId : list) {
@ -730,8 +724,7 @@ public class UserService {
List<String> projectIds = request.getBatchProcessValue();
if (CollectionUtils.isNotEmpty(userIds)) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
checkQuota(quotaService, "PROJECT", projectIds, userIds);
checkQuota("PROJECT", projectIds, userIds);
}
for (String userId : userIds) {
UserGroupExample userGroupExample = new UserGroupExample();
@ -1006,8 +999,7 @@ public class UserService {
} else {
List<String> ids = (List<String>) map.get("ids");
Group group = groupMapper.selectByPrimaryKey(groupId);
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
checkQuota(quotaService, group.getType(), ids, Collections.singletonList(userId));
checkQuota(group.getType(), ids, Collections.singletonList(userId));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserGroupMapper mapper = sqlSession.getMapper(UserGroupMapper.class);
for (String id : ids) {
@ -1086,9 +1078,8 @@ public class UserService {
* @param userIds 添加的用户id
*/
private void checkQuotaOfMemberSize(String type, String sourceId, List<String> userIds) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (CollectionUtils.isNotEmpty(userIds)) {
checkQuota(quotaService, type, Collections.singletonList(sourceId), userIds);
checkQuota(type, Collections.singletonList(sourceId), userIds);
}
}

View File

@ -22,8 +22,8 @@ import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.quota.service.BaseQuotaService;
import io.metersphere.request.WorkspaceRequest;
import io.metersphere.xpack.quota.service.QuotaService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -57,6 +57,8 @@ public class WorkspaceService {
private EnvironmentGroupService environmentGroupService;
@Resource
private BaseScheduleService baseScheduleService;
@Resource
private BaseQuotaService baseQuotaService;
private static final String GLOBAL = "global";
@ -150,10 +152,7 @@ public class WorkspaceService {
workspace.setCreateUser(SessionUtils.getUserId());
workspaceMapper.insertSelective(workspace);
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
quotaService.workspaceUseDefaultQuota(wsId);
}
baseQuotaService.workspaceUseDefaultQuota(wsId);
// 创建工作空间为当前用户添加用户组
UserGroup userGroup = new UserGroup();

View File

@ -108,14 +108,12 @@
{
"id": "SYSTEM_QUOTA:READ",
"name": "permission.system_quota.read",
"resourceId": "SYSTEM_QUOTA",
"license": true
"resourceId": "SYSTEM_QUOTA"
},
{
"id": "SYSTEM_QUOTA:READ+EDIT",
"name": "permission.system_quota.edit",
"resourceId": "SYSTEM_QUOTA",
"license": true
"resourceId": "SYSTEM_QUOTA"
},
{
"id": "SYSTEM_AUTH:READ",
@ -260,14 +258,12 @@
{
"id": "WORKSPACE_QUOTA:READ",
"name": "permission.workspace_quota.read",
"resourceId": "WORKSPACE_QUOTA",
"license": true
"resourceId": "WORKSPACE_QUOTA"
},
{
"id": "WORKSPACE_QUOTA:READ+EDIT",
"name": "permission.workspace_quota.edit",
"resourceId": "WORKSPACE_QUOTA",
"license": true
"resourceId": "WORKSPACE_QUOTA"
},
{
"id": "WORKSPACE_OPERATING_LOG:READ",
@ -339,8 +335,7 @@
},
{
"id": "SYSTEM_QUOTA",
"name": "permission.system_quota.name",
"license": true
"name": "permission.system_quota.name"
},
{
"id": "SYSTEM_AUTH",
@ -372,8 +367,7 @@
},
{
"id": "WORKSPACE_QUOTA",
"name": "permission.workspace_quota.name",
"license": true
"name": "permission.workspace_quota.name"
},
{
"id": "WORKSPACE_OPERATING_LOG",

View File

@ -70,13 +70,13 @@ const Setting = {
path: 'project/quota',
component: () => import('../../business/workspace/quota/MxProjectQuota'),
meta: {
workspace: true, valid: true, xpack: true, title: 'commons.quota', permissions: ['WORKSPACE_QUOTA:READ'],
workspace: true, valid: true, title: 'commons.quota', permissions: ['WORKSPACE_QUOTA:READ'],
},
},
{
path: 'workspace/quota',
component: () => import('../../business/system/quota/MxWorkspaceQuota'),
meta: {system: true, valid: true, xpack: true, title: 'commons.quota', permissions: ['SYSTEM_QUOTA:READ']}
meta: {system: true, valid: true, title: 'commons.quota', permissions: ['SYSTEM_QUOTA:READ']}
},
{
path: 'license',