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博客

工作日志,Excel导入树结构数据的更多相关文章

  1. [办公自动化] 再读《让EXCEL飞》(从excel导入access数据时,union联合查询,数据源中没有包含可见的表格)

    一年多以前就买了@Mrexcel的<让excel飞>这本书.整体思路是利用access结合excel,大幅度提高数据分析效率. 最近又拿出来看了看.第十五章,比高级筛选更“高级”,P241 ...

  2. PHP 清除 Excel 导入的数据空格

    处理excel中的数据时,遇到了页面中显示为空格,审查元素时却显示为换行,使用replace函数也不管用,反正就是不知道是什么东西,看起来像空格 中文空格这里面有好几种:没有简单的解决问题的方式,比如 ...

  3. Talend 从Excel导入Saleforce数据(二) TMAP是精髓

    TMap LookUp 经过测试的结果: ------------------------------------------ LookUp最好从CSV读数据,这样是最快了(20万记录1s).从Sal ...

  4. Talend 从Excel导入Saleforce数据(一) 直接从salesforce lookup 性能的噩梦

    速度的瓶颈是在查询Sales force是否有该电话号码的联系人资料. TMap属性的 lookup Model, 如果用Load Once, 则会把SaleForce的contact全部load下来 ...

  5. excel导入mysql数据

    excel加载mysql数据 1.第一步,选择从mysql导入数据 2.单击会出现弹框: 3.可能有的同学的,这里缺少插件,例如: 4.去下载 这个 插件安装即可.https://dev.mysql. ...

  6. 将Excel导入到数据中

    常用的方式的有两种: 1. 通过 Microsoft.Jet.OLEDB.4.0 或  Microsoft.ACE.OLEDB.12.0 Microsoft.ACE.OLEDB.12.0 需要安装 A ...

  7. JeeSite中Excel导入导出

    在各种管理系统中,数据的导入导出是经常用到的功能,通常导入导出以Excel.CSV格式居多.如果是学习的过程中,最好是自己实现数据导入与导出的功能,然而在项目中,还是调用现成的功能比较好.近期一直使用 ...

  8. java利用jxl实现Excel导入功能

    本次项目实践基于Spring+SpringMvc+MyBatis框架,简单实现了Excel模板导出.和Excel批量导入的功能.实现过程如下:. 1.maven导入所需jar包 <depende ...

  9. 将datagrid中数据导出到excel中 -------<<工作日志2014-6-6>>

    前台datagrid数据绑定 #region 导出到excel中    /// <summary>    /// 2014-6-6    /// </summary>    / ...

随机推荐

  1. JAVA 中的反射(reflact)

    获取反射加载类(获取类的字节码)的3种方式: Class class1=Class.forName("lession_svc.lession_svc.reflact.Person" ...

  2. 7-29 jmu-python-不同进制数 (10 分)

    输入一个十进制整数,输出其对应的八进制数和十六进制数.要求采用print函数的格式控制进行输出,八进制数要加前缀0o,十六进制数要加前缀0x. 输入格式: 输入一个十进制整数,例如:10 输出格式: ...

  3. 不一样的ZTree,权限树.js插件

    每一个有趣的创新,都源于苦逼的生活. 在最近的工作中,遇到一个做权限管理筛选的需求.简单总结需求:1展示一个组织中的组织结构2通过点击组织结构中的任意一个节点可以向上向下查询对应的组织结构 如果你不想 ...

  4. ES6的原始类型数据——Symbol

    javascript中原始值,即基本数据类型,像Number,String,Boolean,undefined,Null都是基本类型值,保存在栈中,但是有个疑问: Symbol打印出来明明是个函数,具 ...

  5. 设计模式-15命令模式(Command Pattern)

    1.模式动机 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使 ...

  6. Linux系统系统盘扩容

    在Linux学习过程中,可能会遇到根目录存储空间不足的问题,这时候如果只是新增一块硬盘并挂载到某个目录上,还需要将数据转移至新的硬盘中才能缓解存储压力.这种操作未免有些繁琐,那可不可以直接对跟目录进行 ...

  7. go源码分析(五) 获取函数名和调用者的函数名

    参考资料 实现代码保存在我的github // input flag 1:FunName 2:CallerFunName func GetFuncName(flag int) string {     ...

  8. Codeforces Round #378 (Div. 2)F

    题目:一个带权连通无向图,给第i条边权值减1需要花费ci元,你一共有S元,求最小生成树. 容易得出钱全部花在一条边上是最优的. 我们先做一遍最小生成树. 然后我们枚举减哪一条边. 如果这条边是树上的, ...

  9. oracle中plsql练习-----在控制台输出1到100以内的素数。

    一.思路:首先需要知道素数的概念即质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数. 中心思想是,外循环所有的自然数,内循环折半查询,增加代码的速度,注意:从1开始,需要大于1,但是pl ...

  10. Caused by: java.lang.IllegalArgumentException

    Caused by: java.lang.IllegalArgumentException 是因为jdk较高而项目需要的是低版本的问题 1.将idea或idea里的语言级别调到适合自己项目的版本比如安 ...