由来
- 上面的报表在企业级项目中非常的常见,大数据时代报表统计往往伴随着KPI的评定。所以报表的制作尤为重要。我们就详细看下第一个报表【基础折线图】吧。
- 想要绘制出这个报表,我们只需要做些横轴纵轴的数据配置即可。但是这里有个要求就是长度上必须对应上。
- 比如说这种情况当数据为0或者不存在时我们必须给出0这个默认值。这点倒是无所谓或者说也是合情合理。
- 但是作为Java开发者后端的同志们都明白我们在
group by
的时候实际上只会统计已经发生的数据。也就是说上图中Tue
,Fri
这两个横轴的值时不会被统计出来的,这时候就是前后端帅锅的时候了。 - 前端已数据不全为由要求后端补全提供,而后端则已不宜场景具体化为由拒绝定制化补全数据。
- 作为后端的我来说肯定是支持后端的观点的。一份数据给到基础结构即可,剩下的应该由客户端自己解析组装。无奈我经历过很多次这样的场景了,所以就自己在此场景下做了一层处理避免以后前后端的火花不断!!!
填充
- 我们简单总结下就能发现,想要提供一份完美符合前段
Echats
要求的数据唯一的需要就是数据全。那么首先我们得知道完整的横坐标集合。另外一个重要点就是默认数据应该是啥?
- 也就是我们需要制定出一系列规范,在Java中接口就是用来规范化的,除此之外我们还得进行数据填充所以这里最终选择抽象类中申明两个方法来表示横轴集合和默认值输出。
public abstract class DataFill {
/**
* 提供默认值
*/
public abstract Object defaultData();
/**
* 全集合记录
*/
public abstract List<String> keyNameList();
/**
* 填充数据
*/
public <T> void fillValue(String fieldName ,String fillName, List<T> list) {
fillValue(1,fieldName,fillName,list);
}
/**
* 最终填充数据业务CODE
*/
public <T> void fillValue(Integer sameLevel , String fieldName ,String fillName, List<T> list) {
//TODO
}
}
复制代码
- 其实我们填充的逻辑很简单,我简单的画了个逻辑图
- 下面我们就针对流程图中的三步进行Code实现。
特殊情况优先处理
if (list == null) {
list = new ArrayList<>();
}
List<String> newBerthNameList = new ArrayList<>(keyNameList());
if (CollectionUtils.isEmpty(newBerthNameList)) {
return;
}
复制代码
- 如何本身数据就是空的,那么就没有必要进行一系列判断了,这里也是为了提供性能进行的特殊优化
在平时的数据交互中我总结的大多是三种数据类型。
- 基础数据
- JSON或者Map数据
- Bean数据
首先【基础数据】的传参我觉得并不存在补全数据一说,因为他的载体太过于单一,所以这里基础数据我们就忽略掉了。只考虑后面两种情况。
不管是Json还是Map甚至是Bean,我们最终都是获取指定字段的内容,从而和横坐标集合进行比对的。我这里是分类讨论,聪明的你也可以全部转换为Json处理。
过滤未出现数据
for (T item : list) {
Class<?> itemClass = item.getClass();
commonClazz = itemClass;
if (itemClass.isAssignableFrom(JSONObject.class)||itemClass.isAssignableFrom(Map.class)) {
Map<String, Object> itemMap = (Map<String, Object>) item;
Object filedObj = itemMap.get(fieldName);
if (filedObj != null) {
newBerthNameList.remove(filedObj.toString());
}
} else {
//bean
try {
Field field = itemClass.getField(fieldName);
Object filedObj = field.get(item);
if (filedObj != null) {
newBerthNameList.remove(filedObj.toString());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
复制代码
- 上面的代码就是用来筛选出哪些坐标并没有被统计出来
填充默认值
- 随着流程的推移我们来到了数据填充节点。关于填充我们还得注意一点的是填充在什么位置。
- 为了兼容更多的场景,所以在填充的方法上我们通过
sameLevel
字段来标注是否填充在同一级上。否则我们就是在不同层级上。不同级就另外需要指定父级名称。
if (commonClazz==null||commonClazz.isAssignableFrom(JSONObject.class)||commonClazz.isAssignableFrom(Map.class)) {
Map<String, Object> map = new HashMap<>();
map.put(fieldName, berthName);
//sameLevel=0 表示默认数据需要已同级填充
if (sameLevel == 0) {
Map<String,Object> temMap = (Map<String, Object>) defaultData();
map.putAll(temMap);
} else {
map.put(fillName, defaultData());
}
list.add((T) map);
}
复制代码
- 这里bean的操作也是同样的逻辑,不同点也就是bean需要通过反射来获取内容。这里就不赘述了,最后会将完整代码共享。
使用感受
public Map<String, Object> groupByField(GroupBean groupBean) {
List<String> fieldResultList = 获取完整横坐标集合数据;
DataFill dataFill = new DataFill() {
@Override
public Object defaultData() {
return 0;
}
@Override
public List<String> keyNameList() {
return fieldResultList;
}
};
//具体的逻辑统计
List<Map<String,Object>> resultList = selectNameCountGroupByField(groupBean.getTableId(), groupBean.getGroupFieldId());
//按照name,count统计填充
dataFill.fillValue("name", "count", resultList);
Map<String, Object> collect = new HashMap<>();
for (Map<String, Object> temMap : resultList) {
String key = ChineseToPY.getInstance().getEnglish(temMap.get("name").toString());
Object value = temMap.get("count");
collect.put(key, value);
}
return collect;
}
复制代码
总结
- 个人感觉这样封装之后,代码使用还是很简洁明了的。加上我们完善的注释之后在部门内分享使用应该问题不大。这个我觉得是基于实际场景衍生出来的个性化工具的创意Idea。还是之前那句话有想法,do it , then success .
转自:https://juejin.cn/post/7129317543344340999