【代码结构设计】文件夹的树状结构

简介: 如何利用设计模式优雅的抽象文件夹树状操作

背景


公司主要做数据治理相关的产品,奈何初期为了追求速度留下了一些饮鸩止渴的设计,后期影响最大的就是文件夹这块,治理数据首先要归档数据,通过文件夹的方式来将同一业务属性的数据收拢到一起。


前期的业务逻辑简单,简单的结构足以满足文件夹树查询、搜索等等需求,但发展到后期越来越多的满足文件夹结构但又不是文件夹的操作增加,比如分类、比如主题等等。


导致前后端交互上同一个业务逻辑产生不同的数据结构,每次开发都要写很多重复逻辑的代码复用性很低不通用,所以才会大刀阔斧的整改文件夹结构这块的代码。不管业务中到底是不是文件夹都抽象成文件夹,只要文件夹满足父子级关系、并且绑定了一些文件都走同一个逻辑。


抽象文件夹

标准的数据库结构主要就是三张表,FOLDER表存储文件夹信息、FOLDER_FILE_VTB表存储文件夹与文件关联表、FILE表存储文件信息。


改版文件夹操作第一步就得先定义出抽象文件夹来,不管数据库中的代码结构和业务含义,只要是可以被抽象成文件夹的都可以按统一的文件夹操作。主要功能就是抽象代码,把每一步都抽象出来,搞一个策略模式,抽出一个Abstract抽象类定义方法,抽出一个BaseFolderImpl以标准的FOLDER表做基础实现。


方法入参封装一个基本信息对象,

@Data@SuperBuilder(toBuilder=true)
@AllArgsConstructor@NoArgsConstructorpublicstaticclassFolderKey {
@ApiModelProperty(value="文件夹id")
privateStringfolderId;
@ApiModelProperty(value="用户id")
privateStringuserId;
@ApiModelProperty(value="操作文件夹类型")
privateIntegertreeType;
@ApiModelProperty(value="企业域id做数据隔离")
privateStringentId;
}

接下来定义各种通用方法

publicabstractclassAbstractFolderService {
// 检查文件夹名称是否存在,新建时使用publicabstractvoidcheckFolderNameExist(FolderBaseForm.FolderKeyfolderKey, StringfolderName);
// 获取文件夹详情,Q为集成了抽象文件夹的泛型publicabstractQcheckFolderIdAndGetInfo(FolderBaseForm.FolderKeyfolderKey);
// 获取文件夹最大序号用于排序publicabstractIntegergetMaxSeqNo(FolderBaseForm.FolderKeyfolderKey);
// 保存文件夹publicabstractStringsaveFolder(QfolderInfo);
// 删除文件夹publicabstractvoiddeleteFolderFromDb(List<FolderBaseForm.FolderKey>folderIdList);
// 删除文件夹与文件关联关系publicabstractvoiddeleteFolderFileRelFromDb(FolderDto.FolderRelBatchDelfolderRelBatchDel);
// 获取文件与文件夹关联关系publicabstractFolderBaseForm.FolderKeygetFolderByFile(StringfileId, StringuserId, StringentId, IntegertreeType,
StringlibFolderId);
// 获取子文件夹publicabstractList<String>getSubFolderList(FolderBaseForm.FolderKeyfolderKey, booleanincludeSub);
// 获取指定文件夹下的文件publicabstractList<String>getFileListFromFolder(FolderBaseForm.FolderKeyfolderKey, booleanincludeSub);
// 更新文件夹与文件关联关系publicabstractvoidupdateFolderFileRel(StringfileId, FolderBaseForm.FolderKeyfolderKey, IntegerseqNo);
// 更新文件夹folder-path(便于查询用的folder_id链路)publicabstractvoidupdateFolderPath(FolderBaseForm.FolderKeyfolderKey, StringoldFolderPath, StringnewFolderPath);
// 更新父级文件夹publicabstractvoidupdateFolderParentIdAndSeqNo(FolderBaseForm.FolderKeyfolderKey, StringparentFolderId, IntegerseqNo);
// 更新文件夹基础信息publicabstractvoidupdateFolderNameAndComment(FolderBaseForm.FolderKeyfolderKey, StringfolderName, Stringcomment);
}

抽象好基础文件夹操作后实现一份标准文件夹实现类去实现抽象出来的基础方法

publicclassFolderBaseServiceImplextendsAbstractFolderService {
}

如此一来所有后续新加的文件夹操作都可以继承FolderBaseServiceImpl,把基础的方法都继承过来,如果在业务上有区别的话可以单独实现这个方法,在上层去调用的时候是无感知的。


抽象树查询

增删改架子搞好之后就该查了,查询可谓是重中之重,特别是构造树的逻辑,回想当时在python代码实现的树逻辑上debug。。。可谓是屎上雕花了。


用户打开数据管理页面第一个接口加载,加载速度直接影响了用户体验,所以这块设计上也需要琢磨一下。可以简单的将树查询拆成三块:

  1. 查询所有指定类型的文件夹
  2. 判断查询是否要携带文件,不需要直接过
  1. 查询所有文件夹下的文件关联关系
  2. 拿到关联关系的file_ids查询所有文件信息
  1. 组成树


简单来讲就是还需要一个抽象类去实现基础的拼装树逻辑,这里不仅需要实现方法还要用泛型抽象各种业务场景下的文件夹、文件夹关联关系、文件。

/**抽象文件*/@Data@SuperBuilder(toBuilder=true)
@AllArgsConstructor@NoArgsConstructorpublicstaticclassFolderFile {
privateStringfileId;
privateStringentId;
    }   
/*** 抽象文件夹(folder、topic、category)*/publicstaticclassFolderInfo {
privateStringfolderId;
privateStringfolderName;
privateStringcomment;
privateIntegerseqNo;
privateStringparentId;
privateStringentId;
privateStringuserId;
privateIntegertreeType;
privateStringfolderPath;
privateStringcreator;
privateStringenglishName;
    }
/*** 抽象文件夹与工作关系*/@Data@SuperBuilder(toBuilder=true)
publicstaticclassFolderFileRel {
privateStringfolderId;
privateStringfileId;
    }

这样就可以使用泛型来统一整个操作,规定使用这套框架只需要继承FolderFile、FolderInfo、FolderFileRel即可。


那么抽象出统一的树结构代码

publicabstractclassAbstractTreeService<TextendsFolderFile, PextendsFolderFileRel, QextendsFolderInfo> {
// 获取所有文件夹publicabstractList<?extendsQ>getFolderInfos(StringentId, IntegertreeType);
// 获取所有文件夹关联关系publicabstractList<?extendsP>getFolderFileRel(StringentId, IntegertreeType);
// 获取所有文件详情publicabstractList<?extendsT>getFiles(List<String>fileIds, StringentId);
}

树结构代码中只处理拿到各类信息后组成树的逻辑,具体如何拿到各类信息集合需要各个实现类去实现,这样的话我们之前定义的AbstractFolderService抽象基础文件夹操作类就可以继承AbstractTreeService树结构类,这三个方法既可以在FolderBaseServiceImpl得到实现。


如何定义通用树结构的返回类型,不同场景下返回的信息也不同,比如分类场景下没有英文名那么通用VO中返回一个english_name为null也不合适,只能再使用泛型来定义。

publicstaticclassFolderTree<T, EextendsFolderInfo> {
privateEfolderInfo;
// 详细内容通过泛型定义保证通用privateList<FolderTree<T, ?extendsFolderInfo>>subFolder;
privateList<T>fileList;
    }

结构分成三部分,本级文件夹详情、子级文件夹列表、本级文件夹关联文件列表。具体的转树逻辑就不贴了,本质上是一个深度优先搜索dfs的过程,根据parent_id形成父子级关联关系。


结构转换

整完这俩大块逻辑其实在大部分场景下都适用了,但是我们这个场景下还不够,因为本次改动主要是后端模块改动,尽量不要避免返回的数据结构发生变化,但是上述的树查询逻辑中就已经把VO都改掉了,还需要一步转成旧VO的逻辑。


那就再定义一个旧版VO的基础类,因为旧版的逻辑也并不是全都统一的,那就在原来的基础上再加一个转换外部旧版VO的泛型。

publicabstractclassAbstractTreeService<TextendsFolderFile, PextendsFolderFileRel, QextendsFolderInfo> {
// 获取所有文件夹publicabstractList<?extendsQ>getFolderInfos(StringentId, IntegertreeType);
// 获取所有文件夹关联关系publicabstractList<?extendsP>getFolderFileRel(StringentId, IntegertreeType);
// 获取所有文件详情publicabstractList<?extendsT>getFiles(List<String>fileIds, StringentId);
// 转换外部VOpublicabstractGconvertGlobalTreeVo(FolderTree<T, ?extendsFolderInfo>rootTreeNode);
}

转换逻辑还是交由实现类处理,不同的业务场景需要的字段也不一样,只需要定义一个全局抽象旧版外部VO类

publicstaticclassGlobalBaseTreeVo {
privateStringfolderId;
privateStringparentId="";
@JsonProperty("sub_levels")
@Builder.DefaultprivateList<?superGlobalBaseTreeVo>subLevels=newArrayList<>();
}

转换旧VO逻辑使用广度优先算法bfs实现,这里贴一下吧,自从不做ACM之后很久没有写算法代码了,偶尔逮到一两个场景实现出来还是有些恍如隔世的感觉。

publicGbfsConvertVo(FolderTree<T, FolderTreeDto.FolderInfo>innerRootTreeNode) {
Map<String, G>parentNodeMap=newHashMap<>();
GresultVo=this.convertGlobalTreeVo(innerRootTreeNode);
parentNodeMap.put(resultVo.getFolderId(), resultVo);
LinkedBlockingQueue<FolderTree<T, ?extendsFolderInfo>>bfsQueue=newLinkedBlockingQueue<>(innerRootTreeNode.getSubFolder());
while (!bfsQueue.isEmpty()) {
FolderTree<T, ?extendsFolderInfo>firstObj=bfsQueue.poll();
GcurrentNode=this.convertGlobalTreeVo(firstObj);
GparentNode=parentNodeMap.get(currentNode.getParentId());
parentNode.getSubLevels().add(currentNode);
parentNodeMap.put(currentNode.getFolderId(), currentNode);
bfsQueue.addAll(firstObj.getSubFolder());
        }
returnresultVo;
    }


如此一来这套结构可以满足公司所有类文件夹结构的增删改查逻辑的处理,树查询接口也不会太慢,后期优化机构上也非常明朗。



相关文章
|
3天前
|
算法 前端开发 JavaScript
若依框架---数据转树状层级
若依框架---数据转树状层级
158 0
|
3天前
|
算法 C++ 开发者
【QML文件结构】理解QML中 多层嵌套控件之间的关系
【QML文件结构】理解QML中 多层嵌套控件之间的关系
79 2
|
8月前
56.【树型结构】(三)
56.【树型结构】
39 0
|
8月前
56.【树型结构】(二)
56.【树型结构】
27 0
|
8月前
|
机器学习/深度学习
56.【树型结构】(一)
56.【树型结构】
53 0
|
9月前
|
SQL 前端开发 Java
java实现多层级目录树详解
java实现多层级目录树详解
253 0
|
9月前
|
JavaScript
js扁平数据转树形结构数据
js扁平数据转树形结构数据
|
9月前
导出项目目录树结构
在cmd中切换到自己所需要导出项目树结构的根目录下,在win中可以使用cd来切换文件夹
86 0
|
10月前
|
JavaScript 数据处理 数据安全/隐私保护
一文带你吃透js处理树状结构数据的增删改查【转】
一文带你吃透js处理树状结构数据的增删改查【转】
|
10月前
|
JavaScript
使用JS递归处理树状结构数据
使用JS递归处理树状结构数据
152 0
使用JS递归处理树状结构数据