不灭的焱

革命尚未成功,同志仍须努力下载JDK17

作者:php-note.com  发布于:2022-04-04 22:37  分类:Java框架/系统  编辑

后台提供模块列表接口,这个模块是支持子层级的,所以大概结构是这样:

[
  {
    id: 1,
    label: '默认',
    children: [
      {
        id: 4,
        label: '二级子模块1',
        children: [
          {
            id: 9,
            label: '三级子模块1'
          }, 
          {
            id: 10,
            label: '三级子模块2'
          }
        ]
      }
    ]
  }, 
  {
    id: 2,
    label: '一级子模块2',
    children: [
      {
        id: 5,
        label: '二级子模块 1'
      }, 
      {
        id: 6,
        label: '二级子模块 2'
      }
    ]
  }
]

通常来说,可以写递归代码来找出子层级的数据,然后再进行封装返回出来,比较麻烦。

后来发现 HutoolUtil 中有个工具类 TreeUtil 可以完成我需求,非常便捷,本次就使用它来实现。

官方教程:https://hutool.cn/docs/#/core/语言特性/树结构/树结构工具-TreeUtil

一、引用 HutoolUtil

<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.7.22</version>
</dependency>

二、建表

给模块建一张新表api_module:

CREATE TABLE `api_module` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `projectId` bigint NOT NULL COMMENT '该节点所属项目id',
  `name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT '节点名称',
  `parentId` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '父节点id',
  `level` int DEFAULT '1' COMMENT '节点层级',
  `createTime` datetime NOT NULL DEFAULT '1900-01-01 00:00:00' COMMENT '创建时间',
  `updateTime` datetime NOT NULL DEFAULT '1900-01-01 00:00:00' COMMENT '更新时间',
  `pos` double DEFAULT NULL COMMENT '节点顺序位置',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='模块表';

重要字段:

  • projectId:与项目进行关联
  • parentId:该节点的父节点,一级目录的父节点我会设置为 0 。
  • level:该节点对应层级,从 1 开始。
  • pos:表示该节点在父节点下的位置顺序。

三、后端接口实现

1. Controller 层

新建 ApiModuleController 类,添加一个处理器方法 getNodeByProjectId,通过项目 ID 查询出下面的所有模块。

package com.pingguo.bloomtest.controller;

import com.pingguo.bloomtest.common.Result;
import com.pingguo.bloomtest.service.ApiModuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("module")
public class ApiModuleController {

    @Autowired
    ApiModuleService apiModuleService;

    @GetMapping("/list/{projectId}")
    public Result getNodeByProjectId(@PathVariable Long projectId) {
        return Result.success(apiModuleService.getNodeTreeByProjectId(projectId));
    }
}

2. DAO层

dao 层自然也要有。

package com.pingguo.bloomtest.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pingguo.bloomtest.pojo.ApiModule;
import org.springframework.stereotype.Repository;


@Repository
public interface ApiModuleDAO extends BaseMapper<ApiModule> {
}

3. Service 层

实现 getNodeTreeByProjectId 方法。

public List<Tree<String>> getNodeTreeByProjectId(Long projectId) {
	this.getDefaultNode(projectId);
	// 根据 projectId 查询所有节点
	QueryWrapper<ApiModule> wrapperApiModule = new QueryWrapper<>();
	List<ApiModule> apiModules = apiModuleDAO.selectList(wrapperApiModule.eq("projectId", projectId));
	// 配置
	TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
	// 自定义属性名 ,即返回列表里对象的字段名
	treeNodeConfig.setIdKey("id");
	treeNodeConfig.setWeightKey("pos");
	treeNodeConfig.setParentIdKey("parentId");
	treeNodeConfig.setChildrenKey("children");
	// 最大递归深度
    // treeNodeConfig.setDeep(5);
	treeNodeConfig.setNameKey("name");
	//转换器
	List<Tree<String>> treeNodes = TreeUtil.build(apiModules, "0", treeNodeConfig,
			(treeNode, tree) -> {
				tree.setId(treeNode.getId().toString());
				tree.setParentId(treeNode.getParentId().toString());
				tree.setWeight(treeNode.getPos());
				tree.setName(treeNode.getName());
				// 扩展属性 ...
				tree.putExtra("projectId", treeNode.getProjectId());
				tree.putExtra("level", treeNode.getLevel());
				tree.putExtra("label", treeNode.getName());
				tree.putExtra("createTime", treeNode.getCreateTime());
				tree.putExtra("updateTime", treeNode.getUpdateTime());

			});
	return treeNodes;

}

这里开头有个方法 getDefaultNode,在这里面会判断当前项目下是否有默认模块,没有则添加默认模块。

private void getDefaultNode(Long projectId) {
	QueryWrapper<ApiModule> wrapperApiModule = new QueryWrapper<>();
	wrapperApiModule.eq("projectId", projectId)
					.eq("pos", 1.0);
	// 判断当前项目下是否有默认模块,没有则添加默认模块
	if (apiModuleDAO.selectCount(wrapperApiModule) == 0) {
		ApiModule apiModule = new ApiModule();
		apiModule.setName("默认");
		apiModule.setPos(1.0);
		apiModule.setLevel(1);
		apiModule.setParentId(0L);
		apiModule.setCreateTime(new Date());
		apiModule.setUpdateTime(new Date());
		apiModule.setProjectId(projectId);
		apiModuleDAO.insert(apiModule);
	}
}

然后通过 项目id 把项目下所有的数据查询出来:

接下来使用 TreeUtil 来完成树结构处理。

首先,创建一个配置类 TreeNodeConfig 对象,在这个对象里设置属性,对应的就是返回出来的字段名。

还可以设置最大递归深度,也可以不设。我测试之后就注释掉了,先不加限制。

最后就是构建树结构 treeNodes,完成处理后返回给 controller 层。

因为我要返回的还有其他的字段,可以使用tree.putExtra来添加要返回的其他字段,比如:

tree.putExtra("projectId", treeNode.getProjectId());

第一个参数是定义的字段名称,第二个参数就是使用这个结点的 get 方法获取对应的属性值。

最后返回到上层的是List<Tree<String>>类型,可以直接塞到统一结果里去返回。

四、测试一下

1. 测试结构数据

测试一下接口,先手动网表里插入了对应结构的数据。

请求接口,传入 projectId 为 3。

{
    "code": 20000,
    "message": "成功",
    "data": [
        {
            "id": "9",
            "parentId": "0",
            "pos": 1.0,
            "name": "默认",
            "projectId": 3,
            "level": 1,
            "label": "默认",
            "createTime": "2021-09-29 10:50:00",
            "updateTime": "2021-09-29 10:50:00",
            "children": [
                {
                    "id": "14",
                    "parentId": "9",
                    "pos": 1.0,
                    "name": "默认-2",
                    "projectId": 3,
                    "level": 2,
                    "label": "默认-2",
                    "createTime": "1900-01-01 08:00:00",
                    "updateTime": "1900-01-01 08:00:00"
                },
                {
                    "id": "10",
                    "parentId": "9",
                    "pos": 1.0,
                    "name": "默认-1",
                    "projectId": 3,
                    "level": 2,
                    "label": "默认-1",
                    "createTime": "2021-10-01 08:00:00",
                    "updateTime": "1900-01-01 08:00:00",
                    "children": [
                        {
                            "id": "11",
                            "parentId": "10",
                            "pos": 1.0,
                            "name": "默认-1-1",
                            "projectId": 3,
                            "level": 3,
                            "label": "默认-1-1",
                            "createTime": "1900-01-01 08:00:00",
                            "updateTime": "1900-01-01 08:00:00",
                            "children": [
                                {
                                    "id": "12",
                                    "parentId": "11",
                                    "pos": 1.0,
                                    "name": "默认-1-1-1",
                                    "projectId": 3,
                                    "level": 4,
                                    "label": "默认-1-1-1",
                                    "createTime": "1900-01-01 08:00:00",
                                    "updateTime": "1900-01-01 08:00:00",
                                    "children": [
                                        {
                                            "id": "13",
                                            "parentId": "12",
                                            "pos": 1.0,
                                            "name": "默认-1-1-1-1",
                                            "projectId": 3,
                                            "level": 5,
                                            "label": "默认-1-1-1-1",
                                            "createTime": "1900-01-01 08:00:00",
                                            "updateTime": "1900-01-01 08:00:00"
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

结果正确。

2. 测试新增默认

传入一个 projectId 为 4 ,localhost:8080/bloomtest/module/list/4

{
    "code": 20000,
    "message": "成功",
    "data": [
        {
            "id": "15",
            "parentId": "0",
            "pos": 1.0,
            "name": "默认",
            "projectId": 4,
            "level": 1,
            "label": "默认",
            "createTime": "2021-10-01 12:25:54",
            "updateTime": "2021-10-01 12:25:54"
        }
    ]
}

返回正确。

落库正常。

 

 

摘自:https://www.cnblogs.com/pingguo-softwaretesting/p/15341673.html

 

 




 

【实际项目中的一些代码片段】

1、数据表:

2、单元测试代码:

package com.wanma;

import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.wanma.entity.AuthModule;
import com.wanma.framework_web.helper.JsonHelper;
import com.wanma.service.IAuthModuleService;
import com.wanma.service.ext.AuthService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;

@SpringBootTest
class RunTest {

    @Resource
    private IAuthModuleService iAuthModuleService;

    @Resource
    private AuthService authService;

    @Test
    void test_01() {

        List<AuthModule> authModuleList = iAuthModuleService.getAllModuleList();

        // 配置
        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();

        // 自定义属性名,即返回列表里对象的字段名
        treeNodeConfig.setIdKey("id");
        treeNodeConfig.setNameKey("moduleTitle");
        treeNodeConfig.setParentIdKey("parentId");
        treeNodeConfig.setWeightKey("orderNum");
        treeNodeConfig.setChildrenKey("children");

        // 转换器
        List<Tree<String>> treeList = TreeUtil.build(authModuleList, "0", treeNodeConfig,
            (treeNode, tree) -> {
                tree.setId(treeNode.getId().toString());
                tree.setName(treeNode.getModuleTitle());
                tree.setParentId(treeNode.getParentId().toString());
                tree.setWeight(treeNode.getOrderNum());
                // 扩展属性 ...
                tree.putExtra("moduleIcon", treeNode.getModuleIcon());
                tree.putExtra("moduleType", treeNode.getModuleType());
                tree.putExtra("authCode", treeNode.getAuthCode());
                tree.putExtra("pageUrl", treeNode.getPageUrl());
                tree.putExtra("openType", treeNode.getOpenType());
            });

        // 输出(1):输出json字符串
        String json = JsonHelper.objectToString(treeList);
        System.out.println(json);

        // 输出(2):递归输出Tree标题
        int deep = 0;
        printTree(treeList, deep);

    }

    /**
     * 递归输出Tree
     */
    public void printTree(List<Tree<String>> treeList, int deep) {
        if (ObjectUtil.isEmpty(treeList)) {
            return;
        }

        for (Tree<String> tree : treeList) {
            String name = StrUtil.repeat("--", deep) + tree.getName();
            System.out.println(name);
            if (tree.hasChild()) {
                printTree(tree.getChildren(), deep + 1);
            }
        }

    }
}

3、输出(1):输出json字符串

[{
        "id": "23",
        "moduleTitle": "首页",
        "parentId": "0",
        "orderNum": 1,
        "moduleIcon": "home-2-line",
        "moduleType": 1,
        "authCode": "sys:index",
        "pageUrl": "/",
        "openType": 1,
        "children": [{
                "id": "24",
                "moduleTitle": "首页",
                "parentId": "23",
                "orderNum": 1,
                "moduleIcon": "index",
                "moduleType": 1,
                "authCode": "sys:index:detail",
                "pageUrl": "index",
                "openType": 2
            }
        ]
    }, {
        "id": "1",
        "moduleTitle": "用户",
        "parentId": "0",
        "orderNum": 2,
        "moduleIcon": "user-3-line",
        "moduleType": 1,
        "authCode": "sys:user",
        "pageUrl": "/",
        "openType": 1,
        "children": [{
                "id": "28",
                "moduleTitle": "黑名单",
                "parentId": "1",
                "orderNum": 1,
                "moduleIcon": "",
                "moduleType": 1,
                "authCode": "sys:user:blackList",
                "pageUrl": "blackList",
                "openType": 2
            }, {
                "id": "30",
                "moduleTitle": "身份认证",
                "parentId": "1",
                "orderNum": 1,
                "moduleIcon": "",
                "moduleType": 1,
                "authCode": "sys:user:Identity",
                "pageUrl": "identity",
                "openType": 2
            }, {
                "id": "31",
                "moduleTitle": "学历认证",
                "parentId": "1",
                "orderNum": 1,
                "moduleIcon": "",
                "moduleType": 1,
                "authCode": "sys:user:degree",
                "pageUrl": "degree",
                "openType": 2
            }, {
                "id": "2",
                "moduleTitle": "用户列表 ",
                "parentId": "1",
                "orderNum": 1,
                "moduleIcon": "user-list",
                "moduleType": 1,
                "authCode": "sys:user:list",
                "pageUrl": "userList",
                "openType": 2
            }
        ]
    }, {
        "id": "3",
        "moduleTitle": "礼品",
        "parentId": "0",
        "orderNum": 3,
        "moduleIcon": "gift-line",
        "moduleType": 1,
        "authCode": "sys:gift",
        "pageUrl": "/",
        "openType": 1,
        "children": [{
                "id": "4",
                "moduleTitle": "礼品列表",
                "parentId": "3",
                "orderNum": 1,
                "moduleIcon": "gift-list",
                "moduleType": 1,
                "authCode": "sys:gift:list",
                "pageUrl": "giftList",
                "openType": 2
            }
        ]
    }
]

4、输出(2):递归输出Tree标题

首页
--首页
用户
--黑名单
--身份认证
--学历认证
--用户列表 
礼品
--礼品列表