文章目录
- 1.核心思想:
- 2.核心方法实现:
- 3.完整代码如下
- 3.1. 树形实体
- 3.2. 完整操作
1.核心思想:
1.先将每个节点按层级进行分组成map,并记录最大层级;
2.层级自下而上的递归,赋值父节点和统计金额类的字段;
2.核心方法实现:
//层级分组map (key:层级;value:该层级下的节点集合)private Map<Integer, List<TreeNodeBO>> hierarchyMap = new HashMap<>();/*** 初始化父节点、金额、数量 <br>** @param hierarchy: 最大层级* @return void* @see*/private void initTreeNodeBO(Integer hierarchy) {if (hierarchy == 0) { //0层级无父级层级 、跳出递归return;}List<TreeNodeBO> currentHierarchy = hierarchyMap.get(hierarchy);//当前层级List<TreeNodeBO> parentHierarchy = hierarchyMap.get(--hierarchy);//当前层级的 父级层级if (CollectionUtils.isNotEmpty(currentHierarchy)) {parentHierarchy.stream().forEach(parent -> {//当前父节点 的子节点集合List<TreeNodeBO> childNode = currentHierarchy.stream().filter(child -> child.getCode().indexOf(parent.getCode()) == 0).collect(Collectors.toList());childNode.stream().forEach(child -> child.setParentCode(parent.getCode()));//子节点集合设置 parentCodeBigDecimal childAmount = childNode.stream().map(TreeNodeBO::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);//子节点金额和BigDecimal childQty = childNode.stream().map(TreeNodeBO::getQty).reduce(BigDecimal.ZERO, BigDecimal::add);//子节点数量和parent.setAmount(parent.getAmount().add(childAmount));parent.setQty(parent.getQty().add(childQty));});}initTreeNodeBO(hierarchy);}
3.完整代码如下
我的节点数据来自excel的导入(excel的导入导出可以看:Springboot 下 EasyExcel 的数据导入导出)
节点数据按层级分组后的是 hierarchyMap ,是在 invoke() 方法中初始化的;
核心思想的实现,只需要看 doAfterAllAnalysed()方法
3.1. 树形实体
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ContentRowHeight(15)//内容单元格高度
@HeadRowHeight(15)//表头单元格高度
@ColumnWidth(10)//单元格宽度
public class ExcelBO {//编码@NotEmpty@ExcelProperty(index = 0,value = {"编码"})private String code;//名称@NotEmpty@ExcelProperty(index = 1,value = {"名称"})private String name;//单位@NotEmpty@ExcelProperty(index = 2,value = "单位")private String unit;//数量@NotNull@ExcelProperty(index = 3,value = "数量")private BigDecimal qty;//单价@NotNull@ExcelProperty(index = 4,value = "单价")private BigDecimal price;//生产日期@NotNull@ExcelProperty(index = 5,value = "生产日期")private LocalDateTime dateInProduced;//备注@ExcelProperty(index = 6,value = "备注")private String remake;}
@Data
@ToString(callSuper = true)
public class TreeNodeBO extends ExcelBO {//父级编码private String parentCode;//总价值private BigDecimal amount;//层级( 0:第一层 ; 1:第二层 ; 3:第三层)private Integer hierarchy ;//子节点集合private List<TreeNodeBO> childs;}
3.2. 完整操作
/*** 解析监听类 <br>** @author lls* @version 1.0.0* @date 2021/5/19*/
@Slf4j
public class ExcelReadListener extends AnalysisEventListener<ExcelBO> {private Gson gson = new Gson();//层级分组map (key:层级;value:该层级下的节点集合)private Map<Integer, List<TreeNodeBO>> hierarchyMap = new HashMap<>();//最大层级private Integer maxHierarchy = -1;//编码层级分隔符private static String splitStr = "-";/*** 每行解析动作 <br>** @param data:* @param context:* @return void* @see*/@Overridepublic void invoke(ExcelBO data, AnalysisContext context) {context.readWorkbookHolder().getReadWorkbook().setAutoTrim(true);//读取进datalog.info("当前解析行数据,data :{} ", gson.toJson(data));//todo 业务字段校验(长度,非空等) =》(service层 进行@valid校验)TreeNodeBO treeNodeBO = new TreeNodeBO();BeanUtils.copyProperties(data, treeNodeBO);Integer hierarchy = countHierarchy(treeNodeBO.getCode()); //t层级结构 = code中 splitStr 出现的次数maxHierarchy = Math.max(maxHierarchy, hierarchy);//最大层级treeNodeBO.setHierarchy(hierarchy);//todo 赋值父节点的空属性,用于 递归计算 (想办法去掉这段,太傻了)↓↓↓↓↓↓↓↓↓if (null != treeNodeBO.getPrice() && null != treeNodeBO.getQty()) {treeNodeBO.setAmount(treeNodeBO.getPrice().multiply(treeNodeBO.getQty()));//总金额 = 单价 * 数量} else {treeNodeBO.setAmount(BigDecimal.ZERO);}if (null == treeNodeBO.getQty()) {treeNodeBO.setQty(BigDecimal.ZERO);}//todo ↑↑↑↑↑↑↑List<TreeNodeBO> nodeList = Optional.ofNullable(hierarchyMap.get(hierarchy)).orElse(new ArrayList<>());nodeList.add(treeNodeBO);hierarchyMap.put(treeNodeBO.getHierarchy(), nodeList);}/*** 解析完成后动作 <br>** @param context:* @return void* @see*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {log.info("解析到的所有数据集合,excelBOList :{} ", gson.toJson(hierarchyMap));initTreeNodeBO(maxHierarchy);log.info("解析后的所有数据集合,excelBOList :{} ", gson.toJson(hierarchyMap));}/*** 初始化父节点、金额、数量 <br>** @param hierarchy: 最大层级* @return void* @see*/private void initTreeNodeBO(Integer hierarchy) {if (hierarchy == 0) { //0层级无父级层级 、跳出递归return;}List<TreeNodeBO> currentHierarchy = hierarchyMap.get(hierarchy);//当前层级List<TreeNodeBO> parentHierarchy = hierarchyMap.get(--hierarchy);//当前层级的 父级层级if (CollectionUtils.isNotEmpty(currentHierarchy)) {parentHierarchy.stream().forEach(parent -> {//当前父节点 的子节点集合List<TreeNodeBO> childNode = currentHierarchy.stream().filter(child -> child.getCode().indexOf(parent.getCode()) == 0).collect(Collectors.toList());childNode.stream().forEach(child -> child.setParentCode(parent.getCode()));//子节点集合设置 parentCodeBigDecimal childAmount = childNode.stream().map(TreeNodeBO::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);//子节点金额和BigDecimal childQty = childNode.stream().map(TreeNodeBO::getQty).reduce(BigDecimal.ZERO, BigDecimal::add);//子节点数量和parent.setAmount(parent.getAmount().add(childAmount));parent.setQty(parent.getQty().add(childQty));});}initTreeNodeBO(hierarchy);}/*** 计算节点层级结构(层级结构 = 分隔符出现的次数) <br>** @param code: 编码* @return java.lang.Integer* @see*/private static Integer countHierarchy(String code) {int before = code.length();int after = code.replace(splitStr, "").length();return before - after;}/*** 每个sheet页的头行触发函数 <br>** @param headMap:* @param context:* @return void* @see*/@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {log.info("解析到一条头数据:{}", gson.toJson(headMap));//todo 校验excel模板的正确性//todo 通过 ExcelBO.class 获取field域上ExcelProperty注解。与headMap对比}/*** 发生异常时触发函数 <br>** @param exception:* @param context:* @return void* @see*/@Overridepublic void onException(Exception exception, AnalysisContext context) throws Exception {log.info("捕捉到一条异常:{}", exception);//todo 记录并跳过错误行,返回前端成功的条数throw exception;}
}