报警阈值范围标记
难点
- 一个规则的阈值有四种:通常时段,分级报警,特殊时段,节假日时段。
- 根据优先级来命中不同时段的阈值:节假日->特殊时段->分级报警->通常时段。
- 时段设置是根据星期加时间的数据结构。需要要判断今天是否是节假日,再要判断今天是星期几。
{
"end": "09:29:59",
"start": "09:00",
"week": 1
},
- 返回给前端的数据需要与横坐标一致。对应不上范围就画不出来。
{
"alarmLevel": 4,
"data": [
{
"yAxis": "100",
"xAxis": "00:00:20"
},
{
"yAxis": "120",
"xAxis": "00:29:20"
}
]
}
- 前端数据依据不同的时间跨度,比如一天,三天,一个月。返回的数据的横坐标还不同,需要做适配。
三种格式xdata: 2022-02-14 2022-02-14 00:15 23:59:15
解决思路
- 以30分钟为跨度,将00:00:00到23:59:59划分为48块时间段。
- 使用map,以LocalTime为键,将优先级越高的越晚put,来覆盖优先级低的。
- 将得到的map转化为前端需要的结构。
- 原始频率对应不上,他们的x轴时间刻度不确定不规则。
0: "00:00:20"
1: "00:01:25"
2: "00:02:35"
3: "00:03:40"
4: "00:04:45"
5: "00:05:55"
6: "00:07:00"
7: "00:08:05"
8: "00:09:15"
9: "00:10:20"
10: "00:11:30"
- 第一个x不断加5s直到对应上,第二个x不断减去5s直到对应上,范围缩小所以阈值准确。
while (!xData.contains(x1.format(PipeConstants.LOCAL_TIME_PATTERN)) && x1.isBefore(x2)){
x1 = x1.plusSeconds(5L);
}
while (!xData.contains(x2.format(PipeConstants.LOCAL_TIME_PATTERN)) && x2.isAfter(x1)){
x2 = x2.plusSeconds(-5L);
}
总结
判断节假日,判断星期几来命中不同的阈值,时间切割,并且构造返回前端要求的横坐标格式。工作复杂麻烦。解决了就感觉挺简单。一步一步做的时候解决了不少问题,尤其是思路,一开始问了前端后画的范围能否覆盖前面画的,前端说是,然后做好了才发现不是覆盖是叠加,于是总想着在这一版代码上修改,作茧自缚极其复杂。于是重写了一版换了截然不同的思路,就是切割时间块用map存储优先级高的覆盖的思路。
亮点
阈值优先级命中,时间分段,横坐标匹配,构造返回数据。
间隔流量分析曲线
难点
- 不同时间跨度调用获取数据的接口不同
- 数据有缺失值,需要做数据拟合,估算缺失值
- 四个维度:一站点一时段,多站点一时段,一站点多时段,多站点多时段
- 目标:多站点多时段
- 返回的数据都是当前值,要计算间隔值,两个点相减。
- 不同站点的间隔值还需要根据配置的公式做加法或者减法运算
解决思路
- 封装一个获取数据的方法,判断调用不同的接口获取数据
- 完成多站点一时段的间隔数据计算
- 之后的一站点多时段和多站点多时段都基于此方法进行增强
- x轴坐标根据开始和结束时间以及时间频率来plus获取
for (LocalDateTime x = startTime; x.isBefore(endTime); x = x.plusMinutes(interval)) {
xData.add(x.format(PipeConstants.LOCAL_DATE_TIME_PATTERN));
}
- 数据拟合使用org.apache.commons.math3
SimpleRegression regression = new SimpleRegression();
- 数据之间通过配置的公式进行运算,使用大学数据结构的知识,用到两个栈:数据栈和运算符栈。
- 空数据,异常数据处理细致。
DoubleSummaryStatistics doubleSummaryStatistics = yData.stream().filter(t -> {
try {
Double.parseDouble(t);
return true;
} catch (Exception e) {
return false;
}
}).mapToDouble(Double::parseDouble).summaryStatistics();
Map<String, List<BigDecimal>> mapByIdService = v.stream().collect(Collectors.toMap(TableDataDTO::getIdService, t -> t.getData().stream()
.map(s -> {
BigDecimal b;
try {
b = new BigDecimal(s);
} catch (Exception e) {
b = null;
}
return b;
}).collect(Collectors.toList()), (v1, v2) -> v2));
总结
依据公式来给前端生成间隔数据曲线,主后端的工作,前端只是曲线展示。此需求独立且综合,从设计到开发全靠自己。思路架构正确,最后的结果很不错,测试的bug修改少许代码即可,健壮性和扩展性不错。 两个主要方法均由一个核心方法扩展增强,代码不冗余,修改核心代码逻辑即可无感知的修改其他两个方法。
亮点
设计思路正确,代码结构清晰,方法封装合理不冗余。很多地方留有后手,可插拔可配置,各种计算数据转换取值为null等异常情况处理细致。健壮性、可扩展性和可读性令人满意。
例如:主要方法都由一个核心方法增强而来:说明代码封装好,不冗余
return mergeCurveLineDataVO(curveLineDataVOS, query.getType());
return computeFormula(lineDataVO, formula, wiseComputePoint.getName());
例如:热插拔,fittingYData方法即为缺失值估算拟合方法,传false即可屏蔽此功能,其他不受影响。
List<String> yData = makeYData(fittingYData(historyData, curveLineDataVO.getXData(), true), curveLineDataVO.getXData());
导入导出excel
技术选型:悟耘信息easypoi
难点
- 导出的exce结构不复杂,用easypoi注解导出即可
- 只是有一点:列的数量需要根据数据库查询到的数据数量动态生成
- 反射增强类后,会一直保持增强状态,直到服务重启,于是还要在导出前调用一个恢复原始类的方法,也通过反射来恢复
- 由于easypoi留给我们用于生成excel下拉框的接口不丰富灵活,是在excel注解里写死。于是也应用反射来动态设置该写死属性以实现下拉框
@Excel(name = "*报警等级" ,orderNum = "60",width = 10, replace = {"一级报警_1","二级报警_2","三级报警_3","四级报警_4"},addressList = true)
private Integer alarmLevel = 4;
- 往往导出容易,导入才是难点,导入时还得通过反射来拿到相应动态生成的属性值。
解决思路
- 用反射增强导出VO
- 查找资料发现反射无法动态给类增加属性,或者给属性添加注解(或许是我孤陋寡闻)
- 于是找到一个@Excel的isColumnHidden属性,可以隐藏列,提前写好足够数量的隐藏列,根据查询数量修改注解属性实现伪动态生成excel列
亮点
反射的实战
(持续更新)
|