设计模式实战 - 工厂模式实现总览页面工作进展指标查询
1. 请求入口 ProgressController
@PostMapping("/progress/indicators")
@ApiOperation(value = "总览工作进展")
@PreAuthorize("hasAnyAuthority('superAdmin','overViewQuery','incidentQuery','alertQuery')")
@OperateLog(handle = { LoggerEnum.operation }, target = "operate.overview.log", action = "operate.overview.query.log")
public ProgressVo queryProgress(@RequestBody @NotNull @Valid ProgressQo progressQo) {return progressService.queryProgress(progressQo);
}
① 请求参数:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("总览工作进展查询")
public class ProgressQo {@ApiModelProperty("时间范围")@NotNullprivate TimeRange timeRange;@ApiModelProperty("需要查询的指标")@NotNullprivate List<@Enum(clazz = IndicatorEnum.class, method = "getIndicatorKey", message = "param indicatorKey illegal") String> indicatorKeyList;}
② 指标枚举类:
@AllArgsConstructor
public enum IndicatorEnum {/*** 事件处置率*/DEAL_INCIDENT_RATE("dealIncidentRate", "事件处置率"),DEAL_INCIDENT_CNT("dealIncidentCnt", "事件处置数"),DEAL_IP_CNT("ip", "遏制黑客IP"),DEAL_DNS_CNT("dns", "遏制恶意域名"),DEAL_FILE_CNT("file", "处置文件数"),/** 页面没接入 */DEAL_HOST_CNT("host", "主机处置数"),/** 页面没接入 */DEAL_PROCESS_CNT("process", "进程处置数"),AUTO_DEAL_ENTITY_CNT("autoDeal", "自动处置实体数"),/** 待上线 */DEAL_ORDER_CNT("order", "处置工单数"),EFFECTIVE_DURATION("effectiveDuration", "提效时长"),PROTECTED_RISK_ASSET("protectedRiskAsset", "防护风险资产"),SECURITY_EVALUATE("securityEvaluate", "安全建设评估"),/** 页面没接入 */DEAL_ALERT_CNT("dealAlertCnt", "告警处置数"),/** 页面没接入 */AUTO_DEAL_INCIDENT_CNT("autoDealIncidentCnt", "智能对抗事件自动处置数");/*** 指标的key*/@Getterprivate final String indicatorKey;/*** 指标的名称*/@Getterprivate final String indicatorName;public static IndicatorEnum buildByIndicatorKey(String indicatorKey) {for (IndicatorEnum indicatorEnum : IndicatorEnum.values()) {if (indicatorEnum.getIndicatorKey().equals(indicatorKey)) {return indicatorEnum;}}return null;}
}
{"indicatorKeyList": ["dealIncidentRate","dealIncidentCnt","securityEvaluate","protectedRiskAsset","ip","dns","file","order","effectiveDuration"],"timeRange": {"begin": {"type": "relative","unit": "D","value": 30},"end": null,"timeField": "lastTime"}
}
③ 响应参数:
@ApiModel(description = "总览指标")
@Data
public class ProgressVo implements Serializable {@ApiModelProperty("用户累计处理数")private Long customerSummary;@ApiModelProperty("平台累计处理数")private Long platformSummary;@ApiModelProperty("平台运维时间,单位是天")private Long guardDays;private List<IndicatorRes<?>> indicators;
}
@ApiModel(description = "总览指标")
@Data
public class IndicatorRes<T> {@ApiModelProperty("指标的key")private String indicatorsKey;@ApiModelProperty("指标的名称")private String indicatorsName;@ApiModelProperty("指标的值,可能是浮点型,可能是整型")private T indicatorsValue;@ApiModelProperty("指标的单位,天、个、次等")private String unit;
}
{"strCode": null,"message": "成功","data": {"customerSummary": 12987,"platformSummary": 857,"guardDays": 218,"indicators": [{"indicatorsKey": "dealIncidentCnt","indicatorsName": "事件处置数","indicatorsValue": 11968,"unit": "个"},{"indicatorsKey": "dealIncidentRate","indicatorsName": "事件处置率","indicatorsValue": 19.05,"unit": "%"},{"indicatorsKey": "dns","indicatorsName": "遏制恶意域名","indicatorsValue": 370,"unit": "个"},{"indicatorsKey": "effectiveDuration","indicatorsName": "提效时长","indicatorsValue": 0.0,"unit": "h"},{"indicatorsKey": "file","indicatorsName": "处置文件数","indicatorsValue": 1,"unit": "个"},{"indicatorsKey": "ip","indicatorsName": "遏制黑客IP","indicatorsValue": 57,"unit": "个"},{"indicatorsKey": "order","indicatorsName": "处置工单数","indicatorsValue": null,"unit": "待上线"},{"indicatorsKey": "protectedRiskAsset","indicatorsName": "防护风险资产","indicatorsValue": 925,"unit": "个"},{"indicatorsKey": "securityEvaluate","indicatorsName": "安全建设评估","indicatorsValue": "优","unit": null}]},"code": 0
}
2. 请求业务 ProgressServiceImpl
@Override
public ProgressVo queryProgress(ProgressQo progressQo) {log.info("查询工作进展指标开始, progressQo = {}", progressQo);StopWatch stopWatch = new StopWatch();TimeRange timeRange = progressQo.getTimeRange();List<String> indicatorKeyList = progressQo.getIndicatorKeyList();long startTimestamp = timeRange.getBeginDate().getTime() / 1000L;long endTimestamp = timeRange.getEndDate().getTime() / 1000L;Integer tenantId = Objects.requireNonNull(TenantInfoContext.getTenantInfo()).getProjectId();ProgressVo progressVo = doQueryRedisCache(tenantId, startTimestamp, endTimestamp, indicatorKeyList);if (progressVo != null) {log.info("命中缓存, tenantId = {}, startTimestamp = {}, endTimestamp = {}", tenantId, startTimestamp, endTimestamp);return progressVo;}// 获取处理指标的服务List<IndicatorCount<?>> progressServiceList = new ArrayList<>();if (!CollectionUtils.isEmpty(indicatorKeyList)) {for (String indicatorKey : indicatorKeyList) {progressServiceList.add(indicatorCountServiceFactory.getBeanOfIndicatorKey(indicatorKey));}}List<SaasThreadContextDataHolder> saasThreadContextDataHolders = SaasThreadContextUtil.save();List<Future<IndicatorRes<?>>> indicatorFutureList = new ArrayList<>();for (IndicatorCount<?> indicatorCountService : progressServiceList) {Future<IndicatorRes<?>> future = THREAD_POOL_EXECUTOR.submit(() -> {try {SaasThreadContextUtil.load(saasThreadContextDataHolders);return indicatorCountService.countIndicator(startTimestamp, endTimestamp);} finally {SaasThreadContextUtil.remove();}});indicatorFutureList.add(future);}progressVo = new ProgressVo();// 计算配置的指标List<IndicatorRes<?>> indicatorResList = new ArrayList<>();stopWatch.start("配置化指标统计");for (Future<IndicatorRes<?>> indicatorResFuture : indicatorFutureList) {try {IndicatorRes<?> indicatorRes = indicatorResFuture.get();indicatorResList.add(indicatorRes);} catch (InterruptedException | ExecutionException e) {log.error("查询工作进展指标出错", e);}}progressVo.setIndicators(indicatorResList);stopWatch.stop();log.info("工作进展配置化指标统计结束,耗时:{}", stopWatch.getTotalTimeMillis());stopWatch.start("其他指标统计");// 计算运维天数Future<ApiResponse<Long>> guardDaysFuture = countGuardDays();// 计算您累计处理风险数和平台累计处理风险数Future<Long> customerSummaryFuture = countRiskDeal(startTimestamp, endTimestamp, tenantId);Future<Long> platformSummaryFuture = countPlatformRiskDeal(startTimestamp, endTimestamp, tenantId);try {Long guardDays = guardDaysFuture.get().getData();progressVo.setGuardDays(guardDays);} catch (InterruptedException | ExecutionException e) {log.error("查询平台运维天数失败", e);}try {Long riskDealSum = customerSummaryFuture.get();progressVo.setCustomerSummary(riskDealSum);} catch (InterruptedException | ExecutionException e) {log.error("查询客户累计处理风险数失败", e);}try {Long platformSummary = platformSummaryFuture.get();progressVo.setPlatformSummary(platformSummary);} catch (InterruptedException | ExecutionException e) {log.error("查询平台累计处理风险数失败", e);}stopWatch.stop();if (!CollectionUtils.isEmpty(progressVo.getIndicators())) {String cacheKey = OverviewUtil.buildProgressCacheKey(tenantId, startTimestamp, endTimestamp, indicatorKeyList);redisTemplateConfig.setValueTimeout(cacheKey, progressVo, 300, TimeUnit.SECONDS);}log.info("指标统计全部结束,总耗时:{}", stopWatch.getTotalTimeMillis());return progressVo;
}
1. 查询缓存中数据
@Override
public ProgressVo queryProgress(ProgressQo progressQo) {log.info("查询工作进展指标开始, progressQo = {}", progressQo);StopWatch stopWatch = new StopWatch();// 需要查询的指标参数List<String> indicatorKeyList = progressQo.getIndicatorKeyList();// 查询时间范围参数TimeRange timeRange = progressQo.getTimeRange();long startTimestamp = timeRange.getBeginDate().getTime() / 1000L;long endTimestamp = timeRange.getEndDate().getTime() / 1000L;Integer tenantId = Objects.requireNonNull(TenantInfoContext.getTenantInfo()).getProjectId();// 从缓存中查询,如果缓存命中直接返回,否则查询数据库ProgressVo progressVo = doQueryRedisCache(tenantId, startTimestamp, endTimestamp, indicatorKeyList);if (progressVo != null) {log.info("命中缓存, tenantId = {}, startTimestamp = {}, endTimestamp = {}", tenantId, startTimestamp, endTimestamp);return progressVo;}// ......
}
private ProgressVo doQueryRedisCache(Integer tenantId, long startTimestamp, long endTimestamp, List<String> indicatorKeyList) {// 构建工作进展整体结果指标缓存的keyString cacheKey = OverviewUtil.buildProgressCacheKey(tenantId, startTimestamp, endTimestamp, indicatorKeyList);// 构建缓存处置实体数量的keyString entityCacheKey = OverviewUtil.buildEntityCacheKey(tenantId, startTimestamp, endTimestamp);boolean refreshCache = fetchRefreshCacheByHeader();if (refreshCache) {log.info("请求强制失效缓存, tenantId = {}, startTimestamp = {}, endTimestamp = {}", tenantId, startTimestamp, endTimestamp);redisTemplateConfig.delete(cacheKey);redisTemplateConfig.delete(entityCacheKey);return null;}return (ProgressVo) redisTemplateConfig.getValue(cacheKey);
}
① 构建工作进展整体结果指标缓存的key
/*** 构建工作进展整体结果指标缓存的key** @param tenantId 租户id* @param startTimestamp 开始时间* @param endTimestamp 结束时间* @param indicatorKeyList 待查询得指标* @return redisKey*/
public static String buildProgressCacheKey(Integer tenantId, long startTimestamp, long endTimestamp, List<String> indicatorKeyList) {String indicatorKeys = "";Collections.sort(indicatorKeyList);for (String s : indicatorKeyList) {indicatorKeys = indicatorKeys.concat(":" + s);}return "progress:indicator" + tenantId + ":" + startTimestamp + ":" + endTimestamp + indicatorKeys;
}
② 构建缓存处置实体数量的key
/*** 构建缓存处置实体数量的key** @param tenantId 租户id* @param startTimestamp 开始时间* @param endTimestamp 结束时间* @return redisKey*/
public static String buildEntityCacheKey(Integer tenantId, long startTimestamp, long endTimestamp) {return "progress:entityCnt:" + tenantId + ":" + startTimestamp + ":" + endTimestamp;
}
③ 从HTTP请求头中获取一个名为"refreshCache"的标志位,以指示是否需要刷新缓存。
private boolean fetchRefreshCacheByHeader() {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes == null) {return false;}HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();boolean refreshCache = false;try {String refreshCacheStringValue = request.getHeader("refreshCache");if (refreshCacheStringValue == null) {return false;}return Boolean.parseBoolean(refreshCacheStringValue);} catch (Exception e) {log.info("fetchRefreshCacheByHeader failed", e);}return refreshCache;
}
2. 缓存失效查询数据库
1. 工厂模式计算工作指标
1. 工作进展指标计算服务接口
/*** 总览工作进展指标计算服务*/
public interface IndicatorCount<T> {/*** 是否支持统计指定的indicator指标** @param indicator 指标key* @return bool*/boolean accept(String indicator);/*** 根据时间范围计算指标** @param startTimestamp 开始时间* @param endTimestamp 结束时间* @return 指标*/IndicatorRes<T> countIndicator(Long startTimestamp, Long endTimestamp);
}
@ApiModel(description = "总览指标")
@Data
public class IndicatorRes<T> {@ApiModelProperty("指标的key")private String indicatorsKey;@ApiModelProperty("指标的名称")private String indicatorsName;// 使用了泛型T来表示indicatorsValue属性的类型@ApiModelProperty("指标的值,可能是浮点型,可能是整型")private T indicatorsValue;@ApiModelProperty("指标的单位,天、个、次等")private String unit;
}
① 计算工作进展指标告警处置数
/*** 告警处置数* 计算方法:处置完成+处置中*/
@Service("alertDealCntService")
@CustomLog
public class AlertDealCntService implements IndicatorCount<Long> {@Setter(onMethod_ = { @Autowired })AlertDao alertDao;@Overridepublic boolean accept(String indicator) {if (indicator == null) {return false;}return indicator.equalsIgnoreCase(IndicatorEnum.DEAL_ALERT_CNT.getIndicatorKey());}@Overridepublic IndicatorRes<Long> countIndicator(Long startTimestamp, Long endTimestamp) {log.info("计算告警处置数");Long cnt = null;try {cnt = alertDao.countDealAlert(startTimestamp, endTimestamp);} catch (IOException e) {log.error("统计告警处置数失败", e);}IndicatorRes<Long> indicatorRes = new IndicatorRes<>();indicatorRes.setIndicatorsKey(IndicatorEnum.DEAL_ALERT_CNT.getIndicatorKey());indicatorRes.setIndicatorsName(IndicatorEnum.DEAL_ALERT_CNT.getIndicatorName());indicatorRes.setIndicatorsValue(cnt);indicatorRes.setUnit("个");log.info("告警处置数: {}", cnt);return indicatorRes;}
}
/*** 已处置告警统计** @param startTimestamp 开始时间* @param endTimestamp 结束时间* @throws IOException 查询异常* @return count*/
@Override
public Long countDealAlert(Long startTimestamp, Long endTimestamp) throws IOException {BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 处置状态=已处置+处置中boolQueryBuilder.must(QueryBuilders.termsQuery("dealStatus", List.of(DealStatusEnum.DISPOSED.getStatusCode(), DealStatusEnum.DISPOSING.getStatusCode())));// 时间范围RangeQueryBuilder timeRangeQueryBuilder = QueryBuilders.rangeQuery("lastTime");timeRangeQueryBuilder.gte(timeStampToDate(startTimestamp * 1000L));timeRangeQueryBuilder.lte(timeStampToDate(endTimestamp * 1000L));boolQueryBuilder.must(timeRangeQueryBuilder);CountRequest countRequest = new CountRequest(SaasEsFactory.getTenantIndex(DatabaseConstants.ALERT));countRequest.query(boolQueryBuilder);log.info("count alerts, es search dsl: {}", boolQueryBuilder);StopWatch stopWatch = new StopWatch();stopWatch.start();CountResponse countResponse = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);log.info("告警统计结束,花费时间: {}", stopWatch.getTotalTimeMillis());return countResponse.getCount();
}
② 计算工作进展指标事件处置数
/*** 事件处置数* 计算方法:已忽略+已遏制+处置成功+处置中*/
@Service("incidentDealCntService")
@CustomLog
public class IncidentDealCntService implements IndicatorCount<Long> {@Setter(onMethod_ = { @Autowired })IIncidentDao incidentDao;@Overridepublic boolean accept(String indicator) {if (indicator == null) {return false;}return indicator.equalsIgnoreCase(IndicatorEnum.DEAL_INCIDENT_CNT.getIndicatorKey());}@Overridepublic IndicatorRes<Long> countIndicator(Long startTimestamp, Long endTimestamp) {log.info("计算事件处置数");Long cnt = incidentDao.countDealIncident(startTimestamp, endTimestamp);IndicatorRes<Long> indicatorRes = new IndicatorRes<>();indicatorRes.setIndicatorsKey(IndicatorEnum.DEAL_INCIDENT_CNT.getIndicatorKey());indicatorRes.setIndicatorsName(IndicatorEnum.DEAL_INCIDENT_CNT.getIndicatorName());indicatorRes.setIndicatorsValue(cnt);indicatorRes.setUnit("个");return indicatorRes;}
}
@Override
public Long countDealIncident(Long startTime, Long endTime) {Criteria criteria = Criteria.where("endTime").gte(startTime).lte(endTime).and("xthConfirm").is(true).and("dealStatus").in(List.of(IncidentDealStatusEnum.SUPPRESS.getStatusCode(),IncidentDealStatusEnum.DISPOSED.getStatusCode(), IncidentDealStatusEnum.IGNORED.getStatusCode(), IncidentDealStatusEnum.DISPOSING.getStatusCode()));Query query = new Query(criteria);return incidentMongoTemplate.count(query, Incident.class);
}
2. 工作进展指标计算服务工厂类
/*** 指标统计服务工厂类*/
@Component(value = "indicatorCountServiceFactory")
@CustomLog
public class IndicatorCountServiceFactory {@Setter(onMethod_ = @Autowired)private List<IndicatorCount<?>> indicatorCountList;public IndicatorCount<?> getBeanOfIndicatorKey(String indicatorKey) {for (IndicatorCount<?> indicatorCountService : indicatorCountList) {if (indicatorCountService.accept(indicatorKey)) {return indicatorCountService;}}log.warn("没找到统计当前指标的服务,indicatorKey = {}", indicatorKey);return null;}
}
3. 获取所有处理指标的服务
List<IndicatorCount<?>> progressServiceList = new ArrayList<>();
if (!CollectionUtils.isEmpty(indicatorKeyList)) {for (String indicatorKey : indicatorKeyList) {progressServiceList.add(indicatorCountServiceFactory.getBeanOfIndicatorKey(indicatorKey));}
}
4. 多线程执行工作进展指标计算
progressVo = new ProgressVo();// 计算配置的指标
List<IndicatorRes<?>> indicatorResList = new ArrayList<>();
stopWatch.start("配置化指标统计");
for (Future<IndicatorRes<?>> indicatorResFuture : indicatorFutureList) {try {IndicatorRes<?> indicatorRes = indicatorResFuture.get();indicatorResList.add(indicatorRes);} catch (InterruptedException | ExecutionException e) {log.error("查询工作进展指标出错", e);}
}
progressVo.setIndicators(indicatorResList);
2. 计算运维天数
Future<ApiResponse<Long>> guardDaysFuture = countGuardDays();
private Future<ApiResponse<Long>> countGuardDays() {List<SaasThreadContextDataHolder> saasThreadContextDataHolders = SaasThreadContextUtil.save();// 计算运维天数return THREAD_POOL_EXECUTOR.submit(() -> {try {StopWatch stopWatch = new StopWatch();stopWatch.start();SaasThreadContextUtil.load(saasThreadContextDataHolders);ApiResponse<Long> res;try {res = reportFeignClient.getGuardDays();} catch (Exception e) {res = ApiResponse.newInstance(0L);log.info("查询运维天数失败", e);}stopWatch.stop();log.info("运维天数指标查询结束,耗时:{}", stopWatch.getTotalTimeMillis());return res;} finally {SaasThreadContextUtil.remove();}});
}try {Long guardDays = guardDaysFuture.get().getData();progressVo.setGuardDays(guardDays);
} catch (InterruptedException | ExecutionException e) {log.error("查询平台运维天数失败", e);
}
3. 计算您累计处理风险数
Future<Long> customerSummaryFuture = countRiskDeal(startTimestamp, endTimestamp, tenantId);
/*** 客户累计处理风险数** @param startTimestamp 开始时间* @param endTimestamp 结束时间* @param tenantId 租户id* @return res*/
private Future<Long> countRiskDeal(Long startTimestamp, Long endTimestamp, Integer tenantId) {List<SaasThreadContextDataHolder> saasThreadContextDataHolders = SaasThreadContextUtil.save();return THREAD_POOL_EXECUTOR.submit(() -> {try {StopWatch stopWatch = new StopWatch();stopWatch.start();SaasThreadContextUtil.load(saasThreadContextDataHolders);IndicatorRes<Long> incidentDealCnt = incidentDealCntService.countIndicator(startTimestamp, endTimestamp);IndicatorRes<Long> alertDealCnt = alertDealCntService.countIndicator(startTimestamp, endTimestamp);Long incidentDealCntLong = incidentDealCnt.getIndicatorsValue() == null ? 0L : incidentDealCnt.getIndicatorsValue();Long alertDealCntLong = alertDealCnt.getIndicatorsValue() == null ? 0L : alertDealCnt.getIndicatorsValue();long res = incidentDealCntLong + alertDealCntLong;List<EntityCntDto> entityCntDtoList = dealEntityCntService.queryEntityDealCnt(startTimestamp, endTimestamp, tenantId);if (CollectionUtils.isEmpty(entityCntDtoList)) {return res;}for (EntityCntDto entityCnt : entityCntDtoList) {switch (Objects.requireNonNull(IndicatorEnum.buildByIndicatorKey(entityCnt.getEntityType()))) {case DEAL_IP_CNT:case DEAL_DNS_CNT:case DEAL_FILE_CNT:case DEAL_HOST_CNT:case DEAL_PROCESS_CNT:res += entityCnt.getCount();break;default:break;}}stopWatch.stop();log.info("客户累计处理风险数统计结束,总耗时:{}", stopWatch.getTotalTimeMillis());return res;} finally {SaasThreadContextUtil.remove();}});
}try {Long riskDealSum = customerSummaryFuture.get();progressVo.setCustomerSummary(riskDealSum);
} catch (InterruptedException | ExecutionException e) {log.error("查询客户累计处理风险数失败", e);
}
4. 计算平台累计处理风险数
Future<Long> platformSummaryFuture = countPlatformRiskDeal(startTimestamp, endTimestamp, tenantId);
/*** 平台累计处理风险数** @param startTimestamp 开始时间* @param endTimestamp 结束时间* @param tenantId 租户id* @return res*/
private Future<Long> countPlatformRiskDeal(Long startTimestamp, Long endTimestamp, Integer tenantId) {List<SaasThreadContextDataHolder> saasThreadContextDataHolders = SaasThreadContextUtil.save();return THREAD_POOL_EXECUTOR.submit(() -> {try {StopWatch stopWatch = new StopWatch();stopWatch.start();SaasThreadContextUtil.load(saasThreadContextDataHolders);IndicatorRes<Long> incidentAutoDelCnt = incidentAutoDealCntService.countIndicator(startTimestamp, endTimestamp);long res = incidentAutoDelCnt.getIndicatorsValue() == null ? 0 : incidentAutoDelCnt.getIndicatorsValue();List<EntityCntDto> entityCntDtoList = dealEntityCntService.queryEntityDealCnt(startTimestamp, endTimestamp, tenantId);if (CollectionUtils.isEmpty(entityCntDtoList)) {return res;}for (EntityCntDto entityCnt : entityCntDtoList) {if (IndicatorEnum.AUTO_DEAL_ENTITY_CNT.getIndicatorKey().equals(Objects.requireNonNull(entityCnt.getEntityType()))) {res += entityCnt.getCount();}}stopWatch.stop();log.info("平台累计处理风险数统计结束,耗时:{}", stopWatch.getTotalTimeMillis());return res;} finally {SaasThreadContextUtil.remove();}});
}try {Long platformSummary = platformSummaryFuture.get();progressVo.setPlatformSummary(platformSummary);
} catch (InterruptedException | ExecutionException e) {log.error("查询平台累计处理风险数失败", e);
}
3. 设置缓存
if (!CollectionUtils.isEmpty(progressVo.getIndicators())) {String cacheKey = OverviewUtil.buildProgressCacheKey(tenantId, startTimestamp, endTimestamp, indicatorKeyList);redisTemplateConfig.setValueTimeout(cacheKey, progressVo, 300, TimeUnit.SECONDS);
}