工作日誌,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部落格