工作日誌,Excel導入樹結構數據
- 2020 年 4 月 4 日
- 筆記
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部落格