拒绝重复代码,封装一个多级菜单、多级评论、多级部门的统一工具类

Vue+SpringBoot个人博客实战复盘:从选型到部署的完整指南 前后端分离架构已成为现代Web开发的主流模式,其中Vue与SpringBoot的组合因其渐进式前端和约定优于配置的后端特性,被广泛应用于实战项目。理解其核心原理,如响应式数据绑定、JWT无状态认证、跨域代理机制,是构建稳定应用的基础。这类技术栈能够高效支撑内容管理、权限校验、文件上传等典型场景。然而在真实开发中,开发者常遇到“springboot版本太高”导致JDK不匹配、“the project can not found node_modules”等环境问题,或对“vue computed”缓存特性、D 阅读详情

前后端微服务商城项目,手把手教学!

前后端微服务商城项目,手把手教学!

前后端微服务商城项目,手把手教学!

一、介绍

你能看到很多人都在介绍如何实现多级菜单的效果,但是都有一个共同的缺点,那就是没有解决代码会重复开发的问题。如果我需要实现多级评论呢,是否又需要自己再写一遍?

为了简化开发过程并提高代码的可维护性,我们可以创建一个统一的工具类来处理这些需求。在本文中,我将介绍如何使用SpringBoot创建一个返回多级菜单、多级评论、多级部门、多级分类的统一工具类。

介绍数据库字段设计

数据库设计

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

多级节点的数据库大家都知道,一般会有id,parentId字段,但是对于tree_path字段,这个需要根据设计者来定。

优点:

  • 如果你对数据的读取操作比较频繁,而且需要快速查询某个节点的所有子节点或父节点,那么使用tree_path 字段可以提高查询效率。

  • tree_path 字段可以使用路径字符串表示节点的层级关系,例如使用逗号分隔的节点ID列表。这样,可以通过模糊匹配tree_path 字段来查询某个节点的所有子节点或父节点,而无需进行递归查询。

  • 你可以使用模糊匹配的方式,找到所有以该节点的 tree_path 开头的子节点,并将它们删除。而无需进行递归删除。

缺点:

  • 每次插入时,需要更新tree_path 字段,这可能会导致性能下降。

  • tree_path 字段的长度可能会随着树的深度增加而增加,可能会占用更多的存储空间。

因此,在设计数据库评论字段时,需要权衡使用treepath字段和父评论ID字段的优缺点,并根据具体的应用场景和需求做出选择。如果你更关注读取操作的效率和查询、删除的灵活性,可以考虑使用tree_path 字段。如果你更关注写入操作的效率和数据一致性,并且树的深度不会很大,那么使用父评论ID字段来实现多级评论可能更简单和高效。

二、统一工具类具体实现

1. 定义接口,统一规范

对于有 lombok 的小伙伴,实现这个方法很简单,只需要加上@Data即可

/**
 * @Description: 固定属性结构属性
 * @Author: yiFei
 */
public interface ITreeNode<T> {
    /**
     * @return 获取当前元素Id
     */
    Object getId();

    /**
     * @return 获取父元素Id
     */
    Object getParentId();

    /**
     * @return 获取当前元素的 children 属性
     */
    List<T> getChildren();

    /**
     * ( 如果数据库设计有tree_path字段可覆盖此方法来生成tree_path路径 )
     *
     * @return 获取树路径
     */
    default Object getTreePath() { return ""; }
}

2. 编写工具类TreeNodeUtil

其中我们需要实现能将一个List元素构建成熟悉结构

我们需要实现生成tree_path字段

我们需要优雅的实现该方法

/**
 * @Description: 树形结构工具类
 * @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);
    }

    /**
     * 数据集合构建成树形结构 ( 注: 如果最开始的 ids 不在 dataList 中,不会进行任何处理 )
     *
     * @param dataList 数据集合
     * @param ids      父元素的 Id 集合
     * @param map      调用者提供 Function<T, T> 由调用着决定数据最终呈现形势
     * @param filter   调用者提供 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. 将数据分为 父子结构
        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. 遍历父元素 以及修改父元素内容
        List<T> collectParent = parent.stream().map(map).collect(Collectors.toList());
        for (T parentItem : collectParent) {
            // 3.1 如果子元素已经加完,直接进入下一轮循环
            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 发生错误, 传入参数中 children 不能为 null,解决方法: \n" +
                                    "方法一、在map(推荐)或filter中初始化 \n" +
                                    "方法二、List<T> children = new ArrayList<>() \n" +
                                    "方法三、初始化块对属性赋初值\n" +
                                    "方法四、构造时对属性赋初值");
                        }
                    });
        }
        buildTree(children, nextIds, map, filter);
        return parent;
    }


    /**
     * 生成路径 treePath 路径
     *
     * @param currentId 当前元素的 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. 如果当前节点是父节点直接返回
            treePath.append(currentId);
        } else {
            // 2. 调用者将当前元素的父元素查出来,方便后续拼接
            T byId = getById.apply(currentId);
            // 3. 父元素的 treePath + "," + 父元素的id
            if (!ObjectUtils.isEmpty(byId)) {
                treePath.append(byId.getTreePath()).append(",").append(byId.getId());
            }
        }
        return treePath.toString();
    }

}

这样我们就完成了 TreeNodeUtil 统一工具类,首先我们将元素分为父子两类,让其构建出一个小型树,然后我们将构建的子元素和下次遍历的父节点传入,递归的不断进行,这样就构建出了我们最终的想要实现的效果。

三、测试

定义一个类实现 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)));
}

返回结果:

{
 "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": []
   }]
  }]
 }]
}

测试过滤以及重构数据

测试代码:

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)));
}

返回结果 :

{
 "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": []
   }]
  }]
 }]
}

测试过滤以及重构数据

测试代码:

// 对 3L 进行剪枝,对 1L 进行修改
testChildren = TreeNodeUtil.buildTree(testChildren, (item) -> {
    if (item.getId().equals(1L)) {
        item.setName("更改了 Id 为 1L 的数据名称");
    }
    return item;
}, (item) -> item.getId().equals(3L));

返回结果:

{
 "code": "00000",
 "msg": "操作成功",
 "data": [{
  "id": 1,
  "name": "更改了 Id 为 1L 的数据名称",
  "treePath": "",
  "parentId": 0,
  "children": [{
   "id": 2,
   "name": "子元素1",
   "treePath": "1",
   "parentId": 1,
   "children": []
  }]
 }]
}

接下来的测试结果以口述的方式讲解

测试传入错误的 ids

  • 返回传入的 testChildren

测试传入具有父子结构,但是 ids 传错的情况 (可以根据实际需求更改是否自动识别父元素)

  • 返回传入的 testChildren

测试  testChildren 中 children元素为 null

  • 给出提示,不构建树

测试 generateTreePath 生成路径

  • 返回路径

来源:juejin.cn/post/7301909270907748378

End

推荐全新学习项目

全新基于springboot+vue+vant的前后端分离的微商城项目,包括手机端微商城项目和后台管理系统,整个电商购物流程已经能流畅支持,涵盖商品浏览、搜索、商品评论、商品规格选择、加入购物车、立即购买、下单、订单支付、后台发货、退货等。功能强大,主流技术栈,非常值得学习。

项目包含2个版本:

  • 基于springboot的单体版本

  • 基于spring cloud alibaba的微服务版本

线上演示:https://www.markerhub.com/vueshop

cf2d784aaaae4547da0aeabf3e481215.jpeg

从文档到视频、接口调试、学习看板等方面,让项目学习更加容易,内容更加沉淀。全套视频教程约44小时,共260期,讲解非常详细细腻。下面详细为大家介绍:

架构与业务

使用主流的技术架构,真正手把手教你从0到1如何搭建项目手脚架、项目架构分析、建表逻辑、业务分析、实现等。

单体版本:springboot 2.7、mybatis plus、rabbitmq、elasticsearch、redis

微服务版本:spring cloud alibaba 2021.0.5.0,nacos、seata、openFeign、sentinel

前端:vue 3.2、element plus、vant ui

eb83cc33705477bce65894977344ce01.png

更多详情请查看:

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

S7-1200 PLC PID功能 | 调节PID参数 阅读详情

相关推荐

片上眼图(Eye-opening monitor)

当它们值不同的时候error counter +1,通过统计RX到的码元个数、error counter,可以获得offset sampler上下左右不停的调整位置时的一个误码率(BER),通过这个误码率去描绘出眼图。如上图所示,eye-scan RX端 offset sampler独立且并行的和使用CDR sample的采样电路一起跑,offset-sampler可以任意调整水平(time)和垂直(voltage)方向的偏移(如下图所示),在一个UI内产生一个全新的sample点。

沈土豪的书屋 4420

封装一个工具类,拒绝重复代码!

为了简化开发过程并提高代码的可维护性,我们可以创建一个统一的工具类来处理这些需求。在本文中,我将介绍如何使用SpringBoot创建一个返回多级菜单、多级评论、多级部门、多级分类的统一工具类。数据库设计「主要是介绍是否需要tree_path字段。多级节点的数据库大家都知道,一般会有id,parentId字段,但是对于tree_path字段,这个需要根据设计者来定。优点:如果你对数据的读取操作比较频繁,而且需要快速查询某个节点的所有子节点或父节点,那么使用tree_path字段可以提高查询效率。

433

10分钟训练你的AI歌手:RVC WebUI声音克隆保姆级教程

本文提供了一份详细的RVC WebUI声音克隆保姆级教程,指导用户如何在10分钟内利用Retrieval-based-Voice-Conversion技术训练专属AI歌手。教程涵盖从环境配置、数据准备、模型训练到最终推理应用的全流程,即使是新手也能快速上手,实现高质量的声音克隆与歌曲生成。

weixin_29073241的博客 821

java递归生成组织结构树工具类

public class TreeUtils { public static <T extends TreeNode> List<T> buildTreeNodes(List<T> listTreeNode, Long parentId) { List<T> distinctList = listTreeNode.stream().distinct().collect(Collectors.toList()); ..

beidaol的专栏 1181

从CPU视角看PCIe:深入理解x86/ARM平台上BAR、MMIO和PIO的地址翻译与访问机制

本文深入探讨了CPU如何通过BAR、MMIO和PIO机制访问PCIe设备的配置空间,详细解析了x86和ARM平台在地址翻译与访问路径上的差异。重点分析了PCIe配置空间中BAR寄存器的作用与探测机制,以及操作系统如何管理PCIe资源,为开发者提供了性能优化与调试的实用技巧。

weixin_30175731的博客 325

Windows Server 2022域控环境下的MDT部署工具安装与配置避坑指南

本文详细介绍了在Windows Server 2022域控环境中安装和配置MDT(Microsoft Deployment Toolkit)的全流程,包括ADK与Windows PE的精准安装、MDT高级配置、部署工作流构建及性能优化技巧。通过实战指南帮助IT管理员实现自动化系统部署,提升企业IT运维效率,特别适用于需要静默下发系统的大规模部署场景。

weixin_28481133的博客 439

拒绝重复代码,封装一个多级菜单、多级评论、多级部门的统一工具类!

摘要:本文介绍了一种基于SpringBoot的通用树形结构工具类TreeNodeUtil,可统一处理多级菜单、评论、部门等层级数据。该方案通过ITreeNode接口规范数据结构,采用递归分组策略构建树形关系,同时支持tree_path路径优化查询效率。工具类提供数据过滤、重构和路径生成功能,并解决了传统方案中代码重复开发问题。通过测试案例验证了该工具类在基本树形构建、数据过滤和路径生成等方面的有效性,为多级数据管理提供了灵活高效的解决方案。

332

3D视觉-结构光测量-线结构光测量

线结构光测量中,由激光器射出的激光光束透过柱面透镜扩束,再经过准直,产生一束片状光。这片光束像刀刃一样横切在待测物体表面,因此线结构光法又被成为光切法。线结构光测量常采用二维面阵CCD作为接受器件,因此只要通过增加垂直于面阵探测器的第三维度方向,就可以实现对三维物体的测量。

qq_20660115的博客 3136

封装一个工具类,拒绝重复代码

为了简化开发过程并提高代码的可维护性,我们可以创建一个统一的工具类来处理这些需求。在本文中,我将介绍如何使用SpringBoot创建一个返回多级菜单、多级评论、多级部门、多级分类的统一工具类。数据库设计「主要是介绍是否需要tree_path字段。多级节点的数据库大家都知道,一般会有id,parentId字段,但是对于tree_path字段,这个需要根据设计者来定。如果你对数据的读取操作比较频繁,而且需要快速查询某个节点的所有子节点或父节点,那么使用tree_path字段可以提高查询效率。

CTZL123456的博客 358

金融科技风险管理实践之监管评级全解

内部审计是公司治理的四大基石之一。因此,建议银行以问题与结果为导向,推动科技风险分级分类管理,重点防范重大中断、重大事件,并进一步加强科技风险评估与监测,加强对重要管理流程、重要系统、网络等重点管控环节的评估监测,对于评估发现的问题,及时完善管理手段,举一反三解决问题,提高风险管理的有效性。投入更多审计资源,加大对重点机构、薄弱环节、重要领域的审计力度,如:加强对境外机构的审计,加强网络安全、数据安全、业务连续性等高风险领域审计,加强数字化转型过程中的风险审计,以高水平安全,为数字金融高质量发展保驾护航。

银行信息系统应用架构详解 457

化工管道设计软件:CAESAR II二次开发_(6).API接口与函数库

自定义函数库是开发者根据特定需求编写的函数集合。这些函数可以扩展CAESAR II的功能,实现更复杂的管道设计任务。自定义函数库通常需要开发者具备一定的编程基础,能够理解和编写与CAESAR II接口兼容的代码。定义函数:在Python或其他编程语言中定义所需的函数。注册函数:将自定义函数注册到CAESAR II的函数库中。调用函数:在CAESAR II中调用自定义函数,实现特定功能。假设我们需要创建一个自定义函数库,用于批量修改管道节点的坐标。定义函数:在Python中定义一个函数,用于批量修改节点坐标。

kkchenjj的博客 1734

treeNode 树形结构 及数据表设计 Java遍历树形结构

https://segmentfault.com/q/1010000000126370

zhaoyang10的专栏 1213

基于扩散模型的图像去噪与修复:原理、实现与性能分析【附核心代码】

扩散模型(Diffusion Models)在图像修复、去噪、风格化、超分辨率等领域的应用,展示了其强大的生成能力和灵活性。通过模拟图像逐步加噪声再去噪的过程,扩散模型不仅能够恢复图像的细节,还能生成高质量的图像。本文详细探讨了扩散模型在图像修复和增强中的应用,并通过具体代码示例展示了如何使用扩散模型进行去噪、风格化、图像超分辨率等任务。扩散模型在图像修复中通过逐步去噪修复损坏的区域,在图像去噪中则能够去除图像中的噪声并保留细节;在图像风格化中,扩散模型通过结合目标风格和内容生成富有艺术性的图像;

一键难忘的博客 2934

开源的国产 Web 防火墙工具,火爆了!

来源:极客之家???? 欢迎加入小哈的星球,你将获得:专属的项目实战 /1v1 提问/Java 学习路线 /学习打卡/每月赠书/社群讨论新项目:《从零手撸:仿小红书(微服务架构)》正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,点击查看项目介绍;《从零手撸:前后端分离博客项目(全栈开发)》2期已完结,演示...

犬小哈 594

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Django开发必备技能:inclusion_tag参数实战全解析(高级模板技巧曝光)

掌握Django模板inclusion_tag的参数用法,提升代码复用与开发效率。详解其在导航菜单、表单组件等场景的应用,结合上下文传递与自定义处理逻辑,实现灵活高效的模板渲染。实用技巧曝光,值得收藏。

FuncInk的博客 252

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

用智能体打造自动化代码评审:Hermes接入GitHub PR实战

在研发效能建设持续升温的今天,代码评审依然是保障软件质量的核心环节,但人工评审往往受限于等待成本、标准不一致与低级错误干扰,成为流程瓶颈。传统静态检查工具仅能基于规则匹配,难以识别上下文相关的语义缺陷。借助智能体框架连接大模型与开发工具,能够模拟资深工程师的审查思维,自动完成缺陷检测、风险分级与行级评论回写,实现从事件触发到结果反馈的闭环。这项技术已在GitHub PR流程中落地,通过细粒度权限配置与提示词工程,显著缩短首轮评审耗时,让开发者将精力聚焦于真正的架构取舍。对于正被PR积压困扰的团队、维护多仓库

weixin_33871366的博客 408

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

上一篇: (待会删)60k*16薪!请大家低调浏览!
下一篇: 预测一波,这份Java面试题库即将爆火。。。
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值