<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          拒絕重復(fù)代碼,封裝一個多級菜單、多級評論、多級部門的統(tǒng)一工具類

          共 20400字,需瀏覽 41分鐘

           ·

          2024-07-19 08:02

          關(guān)注我們,設(shè)為星標(biāo),每天7:40不見不散,架構(gòu)路上與您共享

          回復(fù)架構(gòu)師獲取資源


          大家好,我是你們的朋友架構(gòu)君,一個會寫代碼吟詩的架構(gòu)師。


          一、介紹

          你能看到很多人都在介紹如何實(shí)現(xiàn)多級菜單的效果,但是都有一個共同的缺點(diǎn),那就是沒有解決代碼會重復(fù)開發(fā)的問題。如果我需要實(shí)現(xiàn)多級評論呢,是否又需要自己再寫一遍?

          為了簡化開發(fā)過程并提高代碼的可維護(hù)性,我們可以創(chuàng)建一個統(tǒng)一的工具類來處理這些需求。在本文中,我將介紹如何使用SpringBoot創(chuàng)建一個返回多級菜單、多級評論、多級部門、多級分類的統(tǒng)一工具類。

          介紹數(shù)據(jù)庫字段設(shè)計

          數(shù)據(jù)庫設(shè)計

          「主要是介紹是否需要tree_path字段。」

          多級節(jié)點(diǎn)的數(shù)據(jù)庫大家都知道,一般會有id,parentId字段,但是對于tree_path字段,這個需要根據(jù)設(shè)計者來定。

          優(yōu)點(diǎn):

          • 如果你對數(shù)據(jù)的讀取操作比較頻繁,而且需要快速查詢某個節(jié)點(diǎn)的所有子節(jié)點(diǎn)或父節(jié)點(diǎn),那么使用tree_path 字段可以提高查詢效率。
          • tree_path 字段可以使用路徑字符串表示節(jié)點(diǎn)的層級關(guān)系,例如使用逗號分隔的節(jié)點(diǎn)ID列表。這樣,可以通過模糊匹配tree_path 字段來查詢某個節(jié)點(diǎn)的所有子節(jié)點(diǎn)或父節(jié)點(diǎn),而無需進(jìn)行遞歸查詢。
          • 你可以使用模糊匹配的方式,找到所有以該節(jié)點(diǎn)的 tree_path 開頭的子節(jié)點(diǎn),并將它們刪除。而無需進(jìn)行遞歸刪除。

          缺點(diǎn):

          • 每次插入時,需要更新tree_path 字段,這可能會導(dǎo)致性能下降。
          • tree_path 字段的長度可能會隨著樹的深度增加而增加,可能會占用更多的存儲空間。

          因此,在設(shè)計數(shù)據(jù)庫評論字段時,需要權(quán)衡使用treepath字段和父評論ID字段的優(yōu)缺點(diǎn),并根據(jù)具體的應(yīng)用場景和需求做出選擇。如果你更關(guān)注讀取操作的效率和查詢、刪除的靈活性,可以考慮使用tree_path 字段。如果你更關(guān)注寫入操作的效率和數(shù)據(jù)一致性,并且樹的深度不會很大,那么使用父評論ID字段來實(shí)現(xiàn)多級評論可能更簡單和高效。

          二、統(tǒng)一工具類具體實(shí)現(xiàn)

          1. 定義接口,統(tǒng)一規(guī)范

          對于有 lombok 的小伙伴,實(shí)現(xiàn)這個方法很簡單,只需要加上@Data即可

          /**
           * @Description: 固定屬性結(jié)構(gòu)屬性
           * @Author: yiFei
           */

          public interface ITreeNode<T{
              /**
               * @return 獲取當(dāng)前元素Id
               */

              Object getId();

              /**
               * @return 獲取父元素Id
               */

              Object getParentId();

              /**
               * @return 獲取當(dāng)前元素的 children 屬性
               */

              List<T> getChildren();

              /**
               * ( 如果數(shù)據(jù)庫設(shè)計有tree_path字段可覆蓋此方法來生成tree_path路徑 )
               *
               * @return 獲取樹路徑
               */

              default Object getTreePath() return ""; }
          }

          2. 編寫工具類TreeNodeUtil

          其中我們需要實(shí)現(xiàn)能將一個List元素構(gòu)建成熟悉結(jié)構(gòu)

          我們需要實(shí)現(xiàn)生成tree_path字段

          我們需要優(yōu)雅的實(shí)現(xiàn)該方法

          /**
           * @Description: 樹形結(jié)構(gòu)工具類
           * @Author: yiFei
           */

          public class TreeNodeUtil {

              private static final Logger log = LoggerFactory.getLogger(TreeNodeUtil.class);

              public static final String PARENT_NAME = "parent";

              public static final String CHILDREN_NAME = "children";

              public static final List<Object> IDS = Collections.singletonList(0L);

              public static <T extends ITreeNode> List<T> buildTree(List<T> dataList) {
                  return buildTree(dataList, IDS, (data) -> data, (item) -> true);
              }

              public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, Function<T, T> map) {
                  return buildTree(dataList, IDS, map, (item) -> true);
              }
              
              public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, Function<T, T> map, Predicate<T> filter) {
                  return buildTree(dataList, IDS, map, filter);
              }

              public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, List<Object> ids) {
                  return buildTree(dataList, ids, (data) -> data, (item) -> true);
              }

              public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, List<Object> ids, Function<T, T> map) {
                  return buildTree(dataList, ids, map, (item) -> true);
              }

              /**
               * 數(shù)據(jù)集合構(gòu)建成樹形結(jié)構(gòu) ( 注: 如果最開始的 ids 不在 dataList 中,不會進(jìn)行任何處理 )
               *
               * @param dataList 數(shù)據(jù)集合
               * @param ids      父元素的 Id 集合
               * @param map      調(diào)用者提供 Function<T, T> 由調(diào)用著決定數(shù)據(jù)最終呈現(xiàn)形勢
               * @param filter   調(diào)用者提供 Predicate<T> false 表示過濾 ( 注: 如果將父元素過濾掉等于剪枝 )
               * @param <T>      extends ITreeNode
               * @return
               */

              public static <T extends ITreeNode> List<T> buildTree(List<T> dataList, List<Object> ids, Function<T, T> map, Predicate<T> filter) {
                  if (CollectionUtils.isEmpty(ids)) {
                      return Collections.emptyList();
                  }
                  // 1. 將數(shù)據(jù)分為 父子結(jié)構(gòu)
                  Map<String, List<T>> nodeMap = dataList.stream()
                          .filter(filter)
                          .collect(Collectors.groupingBy(item -> ids.contains(item.getParentId()) ? PARENT_NAME : CHILDREN_NAME));
              
                  List<T> parent = nodeMap.getOrDefault(PARENT_NAME, Collections.emptyList());
                  List<T> children = nodeMap.getOrDefault(CHILDREN_NAME, Collections.emptyList());
                  // 1.1 如果未分出或過濾了父元素則將子元素返回
                  if (parent.size() == 0) {
                      return children;
                  }
                  // 2. 使用有序集合存儲下一次變量的 ids
                  List<Object> nextIds = new ArrayList<>(dataList.size());
                  // 3. 遍歷父元素 以及修改父元素內(nèi)容
                  List<T> collectParent = parent.stream().map(map).collect(Collectors.toList());
                  for (T parentItem : collectParent) {
                      // 3.1 如果子元素已經(jīng)加完,直接進(jìn)入下一輪循環(huán)
                      if (nextIds.size() == children.size()) {
                          break;
                      }
                      // 3.2 過濾出 parent.id == children.parentId 的元素
                      children.stream()
                              .filter(childrenItem -> parentItem.getId().equals(childrenItem.getParentId()))
                              .forEach(childrenItem -> {
                                  // 3.3 這次的子元素為下一次的父元素
                                  nextIds.add(childrenItem.getParentId());
                                  // 3.4 添加子元素到 parentItem.children 中
                                  try {
                                      parentItem.getChildren().add(childrenItem);
                                  } catch (Exception e) {
                                      log.warn("TreeNodeUtil 發(fā)生錯誤, 傳入?yún)?shù)中 children 不能為 null,解決方法: \n" +
                                              "方法一、在map(推薦)或filter中初始化 \n" +
                                              "方法二、List<T> children = new ArrayList<>() \n" +
                                              "方法三、初始化塊對屬性賦初值\n" +
                                              "方法四、構(gòu)造時對屬性賦初值");
                                  }
                              });
                  }
                  buildTree(children, nextIds, map, filter);
                  return parent;
              }


              /**
               * 生成路徑 treePath 路徑
               *
               * @param currentId 當(dāng)前元素的 id
               * @param getById   用戶返回一個 T
               * @param <T>
               * @return
               */

              public static <T extends ITreeNode> String generateTreePath(Serializable currentId, Function<Serializable, T> getById) {
                  StringBuffer treePath = new StringBuffer();
                  if (SystemConstants.ROOT_NODE_ID.equals(currentId)) {
                      // 1. 如果當(dāng)前節(jié)點(diǎn)是父節(jié)點(diǎn)直接返回
                      treePath.append(currentId);
                  } else {
                      // 2. 調(diào)用者將當(dāng)前元素的父元素查出來,方便后續(xù)拼接
                      T byId = getById.apply(currentId);
                      // 3. 父元素的 treePath + "," + 父元素的id
                      if (!ObjectUtils.isEmpty(byId)) {
                          treePath.append(byId.getTreePath()).append(",").append(byId.getId());
                      }
                  }
                  return treePath.toString();
              }

          }

          這樣我們就完成了 TreeNodeUtil 統(tǒng)一工具類,首先我們將元素分為父子兩類,讓其構(gòu)建出一個小型樹,然后我們將構(gòu)建的子元素和下次遍歷的父節(jié)點(diǎn)傳入,遞歸的不斷進(jìn)行,這樣就構(gòu)建出了我們最終的想要實(shí)現(xiàn)的效果。

          三、測試

          定義一個類實(shí)現(xiàn) ITreeNode

          /**
           * @Description: 測試子元素工具類
           * @Author: yiFei
           */

          @Data
          @EqualsAndHashCode(callSuper = false)
          @Accessors(chain = true)
          @AllArgsConstructor
          public class TestChildren implements ITreeNode<TestChildren{

              private Long id;

              private String name;

              private String treePath;

              private Long parentId;

              public TestChildren(Long id, String name, String treePath, Long parentId) {
                  this.id = id;
                  this.name = name;
                  this.treePath = treePath;
                  this.parentId = parentId;
              }

              @TableField(exist = false)
              private List<TestChildren> children = new ArrayList<>();
          }

          測試基本功能

          測試基本功能代碼:

          public static void main(String[] args) {
              List<TestChildren> testChildren = new ArrayList<>();
              testChildren.add(new TestChildren(1L"父元素"""0L));
              testChildren.add(new TestChildren(2L"子元素1""1"1L));
              testChildren.add(new TestChildren(3L"子元素2""1"1L));
              testChildren.add(new TestChildren(4L"子元素2的孫子元素""1,3"3L));

              testChildren = TreeNodeUtil.buildTree(testChildren);

              System.out.println(JSONUtil.toJsonStr(Result.success(testChildren)));
          }

          返回結(jié)果:

          {
           "code""00000",
           "msg""操作成功",
           "data": [{
            "id"1,
            "name""父元素",
            "treePath""",
            "parentId"0,
            "children": [{
             "id"2,
             "name""子元素1",
             "treePath""1",
             "parentId"1,
             "children": []
            }, {
             "id"3,
             "name""子元素2",
             "treePath""1",
             "parentId"1,
             "children": [{
              "id"4,
              "name""子元素2的孫子元素",
              "treePath""1,3",
              "parentId"3,
              "children": []
             }]
            }]
           }]
          }  

          測試過濾以及重構(gòu)數(shù)據(jù)

          測試代碼:

          public static void main(String[] args) {
              List<TestChildren> testChildren = new ArrayList<>();
              testChildren.add(new TestChildren(1L"父元素"""0L));
              testChildren.add(new TestChildren(2L"子元素1""1"1L));
              testChildren.add(new TestChildren(3L"子元素2""1"1L));
              testChildren.add(new TestChildren(4L"子元素2的孫子元素""1,3"3L));

              testChildren = TreeNodeUtil.buildTree(testChildren);

              System.out.println(JSONUtil.toJsonStr(Result.success(testChildren)));
          }

          返回結(jié)果 :

          {
           "code""00000",
           "msg""操作成功",
           "data": [{
            "id"1,
            "name""父元素",
            "treePath""",
            "parentId"0,
            "children": [{
             "id"2,
             "name""子元素1",
             "treePath""1",
             "parentId"1,
             "children": []
            }, {
             "id"3,
             "name""子元素2",
             "treePath""1",
             "parentId"1,
             "children": [{
              "id"4,
              "name""子元素2的孫子元素",
              "treePath""1,3",
              "parentId"3,
              "children": []
             }]
            }]
           }]
          }  

          測試過濾以及重構(gòu)數(shù)據(jù)

          測試代碼:

          // 對 3L 進(jìn)行剪枝,對 1L 進(jìn)行修改
          testChildren = TreeNodeUtil.buildTree(testChildren, (item) -> {
              if (item.getId().equals(1L)) {
                  item.setName("更改了 Id 為 1L 的數(shù)據(jù)名稱");
              }
              return item;
          }, (item) -> item.getId().equals(3L));

          返回結(jié)果:

          {
           "code""00000",
           "msg""操作成功",
           "data": [{
            "id"1,
            "name""更改了 Id 為 1L 的數(shù)據(jù)名稱",
            "treePath""",
            "parentId"0,
            "children": [{
             "id"2,
             "name""子元素1",
             "treePath""1",
             "parentId"1,
             "children": []
            }]
           }]
          }

          接下來的測試結(jié)果以口述的方式講解

          測試傳入錯誤的 ids

          • 返回傳入的 testChildren

          測試傳入具有父子結(jié)構(gòu),但是 ids 傳錯的情況 (可以根據(jù)實(shí)際需求更改是否自動識別父元素)

          • 返回傳入的 testChildren

          測試  testChildren 中 children元素為 null

          • 給出提示,不構(gòu)建樹

          測試 generateTreePath 生成路徑

          • 返回路徑
          來源:juejin.cn/post/7301909270907748378


          到此文章就結(jié)束了。Java架構(gòu)師必看一個集公眾號、小程序、網(wǎng)站(3合1的文章平臺,給您架構(gòu)路上一臂之力)。如果今天的文章對你在進(jìn)階架構(gòu)師的路上有新的啟發(fā)和進(jìn)步,歡迎轉(zhuǎn)發(fā)給更多人。歡迎加入架構(gòu)師社區(qū)技術(shù)交流群,眾多大咖帶你進(jìn)階架構(gòu)師,在后臺回復(fù)“加群”即可入群。



          這些年小編給你分享過的干貨


          0.ChatGPT 4o 國內(nèi)直接用 ?。?!

          1.idea2024.1.4永久激活碼(親測可用)

          2.優(yōu)質(zhì)ERP系統(tǒng)帶進(jìn)銷存財務(wù)生產(chǎn)功能(附源碼)

          3.優(yōu)質(zhì)SpringBoot帶工作流管理項(xiàng)目(附源碼)

          4.最好用的OA系統(tǒng),拿來即用(附源碼)

          5.SBoot+Vue外賣系統(tǒng)前后端都有(附源碼

          6.SBoot+Vue可視化大屏拖拽項(xiàng)目(附源碼)


          轉(zhuǎn)發(fā)在看就是最大的支持??

          瀏覽 54
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人乱无码AV在线观看 | 欧洲操逼免费看 | 日韩一级一级一级黄片 | 婷五月丁香乱伦电影网站 | 天天曰天天撸 |