• 工作日志,Excel导入树结构数据


    1. 前言

    最近做了一个比较有趣的需求。需要把树结构的目录通过Excel的方式导入到系统中,并且该目录层级可以是多级且不确定的。这可能是一个常见又不太常见的需求,一般目录都是在界面上操作创建,或者是系统初始化生成。很少在系统使用一段时间后还有导入新目录的需求。

    2. 需求分析

    2.1 需求难点

    这个需求最大的难点就是如何找到父级节点。包括

    1)如何让一个Excel表格实现不确定目录层级功能?

    2)如何让子个节点能正确找到其父级节点?

    3)如何在遍历完一个分枝后,还能从根节点继续遍历另外一个分枝?

    2.2 解决难点

    1)我们可以将目录层级作为用户输入项,由用户决定该数据处于第几层目录。解决目录层级不确定的需求。

    2)我们可以用树节点深度遍历的思想,遍历一个个节点,使其找到其父节点。

    3)我们同样可以用深度遍历的思想再结合先进后出操作,重新找回之前的根节点。

    2.3 表格设计

    我们可以用Level作为目录所在层级,一级目录的Level就是1,同理N级目录的Level就是N。且数据从上至下可以形成一个完整树分枝。

    表格设计如下:

    分类名称 级别Level 其他字段
    A栋 1
    A栋-1楼 2
    B栋 1
    B栋-1楼 2
    B栋-1楼-A区 3
    B栋-2楼 2
    B栋-2楼-A区 3
    B栋-2楼-B区 3

    从表格中,我们应该可以得出以下结论:

    1)A栋和B栋属于一级目录

    2)A栋有一个子目录,A栋-1楼

    3)B栋有两个子目录,分别是:B栋-1楼、B栋-2楼

    4)B栋-1楼有一个子目录,B栋-1楼-A区

    5)B栋-2楼有两个子目录,分别是:B栋-2楼-A区、B栋-2楼-B区

    3. 功能实现

    我们对需求做了简单的分析,现在就用代码来实现。从易到难,从一个分枝再到多个分枝来实现。

    3.1 一个分枝

    一个分枝的Level排序应该是:1-2-3-N

    这种情况是最简单的,孤零零的一条直线。其父节点就是当前节点的上一个元素。

    伪代码如下:

    var categoryPathStack = mutableListOf<EquipmentCategory>()
    for (i in sheet.firstRowNum..sheet.lastRowNum) {
    	val categoryName = row.getCell(0).stringCellValue
    	val categoryLevel = row.getCell(1).stringCellValue.toInt()
    	var parentCategory: EquipmentCategory? = null
    	if (categoryLevel > 1) {
    		parentCategory = categoryPathStack.last()
    	}
    	// todo save or update 
    	categoryPathStack.add(equipmentCategory)
    }
    

    3.2 一个分枝多个树叶

    一个分支多个树叶的Level排序应该是:1-2-3-3-3-3

    这种情况稍微复杂了一点,如果只是获取当前节点的上一个元素是很难找到其父级节点的。我们需要把同一层的兄弟节点都剔除掉。

    伪代码如下:

    var categoryPathStack = mutableListOf<EquipmentCategory>()
    for (i in sheet.firstRowNum..sheet.lastRowNum) {
    	val categoryName = row.getCell(0).stringCellValue
    	val categoryLevel = row.getCell(1).stringCellValue.toInt()
    	var parentCategory: EquipmentCategory? = null
    	// 将集合中大于或等于当前层级的数据剔除掉
    	while (categoryPathStack.isNotEmpty() && categoryPathStack.last().level >= categoryLevel) {
    		categoryPathStack = categoryPathStack.subList(0, categoryPathStack.size-1).toMutableList()
    	}
    	if (categoryLevel > 1) {
    		parentCategory = categoryPathStack.last()
    	}
    	// todo save or update 
    	categoryPathStack.add(equipmentCategory)
    }
    

    3.3 多个分枝多个树叶

    多个分支多个树叶的Level排序应该是:1-2-3-3-3-3-2-3-1-2-3

    这种场景依然可以用一个分支多个树叶的代码实现,而后面来的1就像一个分割线,将前面先进来的数据隔离开。

    4. 代码事例

    4.1 目录实体结构

    目录实体添加临时字段level方便逻辑判断。字段code是方便后期通过code作为StartingWith的查询条件,从而减少递归查询所有子级目录带来的性能损耗。code的生成规则是:父节点code拼接当前节点id,

    class Category: AuditModel() {
    
        var name: String? = null
        var description: String? = null
        var isLeaf: Boolean = true
        var parentId: String? = null
        @Column(columnDefinition = "TEXT")
        var code: String? = null
        
        @Transient
        var level: Int = 0
    }
    

    4.2 Excel导入代码

    以下只是删减过后的代码,具体业务场景会有具体的逻辑代码。

    @Transactional
    fun importCategoryData(file: MultipartFile, request: HttpServletRequest): OperateStatus {
    	// fileUtil.getExcelWorkbook 只是简单封装的读取excel方法
    	val work = fileUtil.getExcelWorkbook(file.inputStream, file.originalFilename!!)
    	// todo 清空旧数据
    
    	val sheet: Sheet = work.getSheetAt(0)
    	var categoryPathStack = mutableListOf<Category>()
    	for (i in sheet.firstRowNum..sheet.lastRowNum) {
    		val row = sheet.getRow(i)
    		if (row == null || row.rowNum == 0) {
    			continue
    		}
    		// todo 数据校验
    
    		val categoryName = row.getCell(0).stringCellValue
    		val categoryLevel = row.getCell(1).stringCellValue.toInt()
    		var parentCategory: Category? = null
    		while (categoryPathStack.isNotEmpty() && categoryPathStack.last().level >= categoryLevel) {
    			categoryPathStack = categoryPathStack.subList(0, categoryPathStack.size-1).toMutableList()
    		}
    		if (categoryLevel > 1) {
    			parentCategory = categoryPathStack.last()
    		}
    		var category = Category()
    		category.name = categoryName
    		category.parentId = parentCategory?.id
    		category = categoryRepository.save(category)
    
    		if (parentCategory == null) {
    			category.code = category.id
    		} else {
    			category.code = "${parentCategory.code}-${category.id}"
    			category.isLeaf = true
    			parentCategory.isLeaf = false
    			categoryRepository.save(parentCategory)
    		}
    		categoryRepository.save(category)
    		category.level = categoryLevel
    		categoryPathStack.add(category)
    	}
    	work.close()
    	return OperateStatus("Import Category Success")
    }
    

    文章到这里就结束了,感谢观看。ITDragon博客

  • 相关阅读:
    Flume
    nodejs中npm工具自身升级
    Nodejs v4.x.0API文档学习(1)简介
    nodejs设置NODE_ENV环境变量(1)
    nodejs使用express4框架默认app.js配置说明
    mongodb2.X添加权限
    javascript中new Date浏览器兼容性处理
    Android Studio中文组(中文社区)
    Javascript日期处理类库Moment.js
    android 按两次返回键退出应用
  • 原文地址:https://www.cnblogs.com/itdragon/p/12576129.html
Copyright © 2020-2023  润新知