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

目录
  • 1. 前言
  • 2. 需求分析
    • 2.1 需求难点
    • 2.2 解决难点
    • 2.3 表格设计
  • 3. 功能实现
    • 3.1 一个分枝
    • 3.2 一个分枝多个树叶
    • 3.3 多个分枝多个树叶
  • 4. 代码事例
    • 4.1 目录实体结构
    • 4.2 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()
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()
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()
	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博客

你可能感兴趣的:(工作日志,Excel导入树结构数据)