目录树结构

一、需求

要求做一个目录树,

1.可以动态增加、删除子目录;

2.每个级别可以调整自己级别的标签的顺序

3.删除某一标签,则自动删除它的所有子标签

二、设计数据库表

CREATE TABLE `mark_category` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `category_name` varchar(255) NOT NULL COMMENT '标注分类名称',
  `parent_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '父分类ID,一级分类为0',
  `sort_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '序号',
  `is_deleted` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '是否已删除0否1是',
  `creator` varchar(50) NOT NULL COMMENT '创建人',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updator` varchar(50) DEFAULT NULL COMMENT '更新人',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '业务更新时间',
  `db_update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据记录更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_category_name` (`category_name`),
  KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB AUTO_INCREMENT=440 DEFAULT CHARSET=utf8mb4 COMMENT='标注分类表';

sort_num是用来给标签排序用的,就是某一级别的标签的上下顺序;用parent_id表示层级关系

三、代码编写

3.1 定义树状结构和解析方法

public interface TreeEntity<E> {
    /**
     * 获取id
     * @return
     */
    Long getId();

    /**
     * 获取上级分类id
     */
    Long getParentId();

    /**
     * 查看分类级别
     */
    Integer getLevel();

    /**
     * 设置级别
     */
    void setLevel(Integer level);

    /**
     * 设置下一层级分类
     */
    void setChildren(List<E> children);
}
/**
 * 标注分类详情
 */
public class MarkCategoryVO implements TreeEntity<MarkCategoryVO>, Comparable<MarkCategoryVO> {
    /**
     * 分类id
     */
    private Long categoryId;

    /**
     * 分类名称
     */
    private String categoryName;

    /**
     * 分类级别
     */
    private Integer level;

    /**
     * 父级分类id
     */
    private Long parentId;

    /**
     * 标签在当前级别位置
     */
    private Integer sortNum;

    /**
     * 子级标注
     */
    private List<MarkCategoryVO> children;

    @Override
    public Long getId(){
        return categoryId;
    }

    public Long getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Long categoryId) {
        this.categoryId = categoryId;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    @Override
    public Integer getLevel() {
        return level;
    }

    @Override
    public void setLevel(Integer level) {
        this.level = level;
    }

    @Override
    public Long getParentId() {
        return parentId;
    }

    public void setParentId(Long parentId) {
        this.parentId = parentId;
    }

    public Integer getSortNum() {
        return sortNum;
    }

    public void setSortNum(Integer sortNum) {
        this.sortNum = sortNum;
    }

    public List<MarkCategoryVO> getChildren() {
        return children;
    }

    @Override
    public void setChildren(List<MarkCategoryVO> children) {
        this.children = children;
    }

    @Override
    public int compareTo(MarkCategoryVO markCategoryVO) {
        return this.sortNum.compareTo(markCategoryVO.sortNum);
    }
}
/**
 * 把普通列表解析成树状结构
 */
public class TreeParser {

    /**
     * 生成树状结构
     * @param topId
     * @param entityList
     * @param <E>
     * @return
     */
    public static <E extends TreeEntity<E>> List<E> getTreeList(Long topId, List<E> entityList) {
        if (entityList == null){
            throw new NullPointerException();
        }

        List<E> resultList = new ArrayList<>();

        //获取顶层元素集合
        Long parentId;
        for (E entity : entityList) {
            parentId = entity.getParentId();
            if(parentId == null || topId.equals(parentId)){
                entity.setLevel(1); //一级分类level=1
                resultList.add(entity);
            }
        }

        //获取每个顶层元素的子数据集合
        for (E entity : resultList) {
            entity.setChildren(getSubList(entity.getId(),entityList));
        }

        return resultList;
    }

    /**
     * 获取子元素
     * @param id
     * @param entityList
     * @param <E>
     * @return
     */
    private static <E extends TreeEntity<E>> List<E> getSubList(Long id, List<E> entityList) {
        List<E> childList=new ArrayList<>();
        Long parentId;

        //子集的直接子对象
        for (E entity : entityList) {
            parentId = entity.getParentId();
            if(id.equals(parentId)){
                entity.setLevel(getParentLevel(parentId, entityList) + 1);
                childList.add(entity);
            }
        }

        //递归:子集的间接子对象
        for (E entity : childList) {
            entity.setChildren(getSubList(entity.getId(), entityList));
        }

        //递归退出条件
        if(childList.size()==0){
            return null;
        }

        return childList;
    }

    /**
     * 获取父级分类级别
     * @param parentId
     * @param entityList
     * @param <E>
     * @return
     */
    private static <E extends TreeEntity<E>> int getParentLevel(Long parentId, List<E> entityList){
        for (E entity : entityList){
            if (entity.getId().equals(parentId)){
                return entity.getLevel();
            }
        }

        return 0;
    }
}

3.2 查询树状结构

public List<MarkCategoryVO> queryMarkCategoryTree() {
    //从数据库中查询目录列表(仅仅是列表,非树状)
    List<MarkCategoryVO> markCategoryVOList = this.queryMarkVOList();

    if (markCategoryVOList == null || markCategoryVOList.isEmpty()){
        return markCategoryVOList;
    }

    //构造树状结构
    List<MarkCategoryVO> markCategoryTree = TreeParser.getTreeList(0L, markCategoryVOList);

    //按sortNum把树状标注分类排序
    this.sortBySortNum(markCategoryTree);
    return markCategoryTree;
}
/**
 * 按sortNum字段排序
 * @param markCategoryTree
 */
private void sortBySortNum(List<MarkCategoryVO> markCategoryTree){
    //递归退出条件
    if (markCategoryTree == null){
        return;
    }

    //遍历多叉树
    Collections.sort(markCategoryTree);

    for (MarkCategoryVO markCategoryVO : markCategoryTree){
        sortBySortNum(markCategoryVO.getChildren());
    }
}

3.3 插入或更新一个目录

入参:(categoryId不传是插入,传了就是更新)

public class MarkCategoryParam {
    /**
     * 分类id
     */
    private Long categoryId;

    /**
     * 分类名
     */
    private String categoryName;

    /**
     * 父级分类id
     */
    private Long parentId;

    public Long getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Long categoryId) {
        this.categoryId = categoryId;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    public Long getParentId() {
        return parentId;
    }

    public void setParentId(Long parentId) {
        this.parentId = parentId;
    }
}

实现:

public void createOrModifyMarkCategory(MarkCategoryParam markCategoryParam, String username){
    //参数合法性检查
    checkMarkCategoryConfParam(markCategoryParam);

    Long categoryId = markCategoryParam.getCategoryId();

    if (categoryId == null){
        //新建标注
        this.createMarkCategory(username, markCategoryParam);
    } else {
        //编辑前判断下名称有没有变化
        boolean isCategoryNameModified = this.checkIfCategoryNameModified(markCategoryParam);

        //编辑标注
        this.modifyMarkCategory(username, markCategoryParam);

        //编辑后,如果标注分类名称改了,需要删除对应的映射关系
        if (isCategoryNameModified){
            this.deleteWorkorderMarkCategoryRelMap(categoryId, username);
        }
    }
}
/**
 * 参数合法性检查
 * @param markCategoryParam
 */
private void checkMarkCategoryConfParam(MarkCategoryParam markCategoryParam){
    String categoryName = markCategoryParam.getCategoryName();

    //标注名称中不允许出现横杠“-”
    boolean isContainsSpecialChar = this.checkIfCategoryNameContainsSpecialChar(categoryName);
    if (isContainsSpecialChar){
        throw new XXXXBizException(XXErrorCode.ILLEGAL_PARAM, "标注名称不能包含特殊字符\"-\"");
    }

    //如果是编辑标注分类,要检查下有没有被别的用户删除
    Long categoryId = markCategoryParam.getCategoryId();
    if (categoryId != null){
        boolean isDeleted = this.checkIfDeleted(categoryId);
        if (isDeleted){
            throw new XXXXException(XXErrorCode.BIZ_ERROR, "所编辑的标注已经被删除");
        }
    }

    //不允许新建或编辑的标注名称与同级目录中其它标注名称重复
    boolean isRepeat = this.checkIfCategoryNameRepeat(markCategoryParam);

    if (isRepeat){
        throw new XXXXException(XXErrorCode.ILLEGAL_PARAM, "标注名称不能与同级其它标注名称同名");
    }
}
/**
 * 检查是否包含特殊字符“-"
 * @param categoryName
 * @return
 */
private boolean checkIfCategoryNameContainsSpecialChar(String categoryName){
    return categoryName.indexOf("-") >= 0;
}

/**
 * 检查是否已经被删除
 * @param categoryId
 * @return
 */
private boolean checkIfDeleted(Long categoryId){
    MarkCategoryPO markCategoryPO = markCategoryPOMapper.selectByPrimaryKey(categoryId);
    return markCategoryPO.getIsDeleted() == DeleteStatusEnum.DELETED;
}

/**
 * 检查标注分类名称有没有发生变化
 * @param markCategoryParam
 * @return true:已改变
 */
private boolean checkIfCategoryNameModified(MarkCategoryParam markCategoryParam){
    Long categoryId = markCategoryParam.getCategoryId();

    MarkCategoryPO markCategoryPO = markCategoryPOMapper.selectByPrimaryKey(categoryId);

    String paramCategoryName = markCategoryParam.getCategoryName();//用户编辑后的分类名
    String dbCategoryName = markCategoryPO.getCategoryName();//用户编辑前数据库里存的分类名

    return !paramCategoryName.equals(dbCategoryName);
}
/**
 * 检查同级目录中是否有重名的标注
 * @param
 * @return true:存在重名的标注;false:不存在重名的标注
 */
private boolean checkIfCategoryNameRepeat(MarkCategoryParam markCategoryParam){
    Long categoryId = markCategoryParam.getCategoryId();
    String categoryName = markCategoryParam.getCategoryName();
    Long parentId = markCategoryParam.getParentId();

    //categoryId==null表示新建,否则是编辑
    int repeatCnt = (categoryId == null) ? queryCategoryNameRepeatCnt(parentId, categoryName) :
            queryCategoryNameRepeatCnt(categoryId, parentId, categoryName);

    return repeatCnt > 0;
}

/**
 * 新建时,查询同一级别(parentId相同)中某一分类名categoryName出现的次数
 * @param parentId
 * @return
 */
private int queryCategoryNameRepeatCnt(Long parentId, String categoryName){
    MarkCategoryPOExample markCategoryPOExample = new MarkCategoryPOExample();
    markCategoryPOExample.createCriteria().andIsDeletedEqualTo(DeleteStatusEnum.UN_DELETE)
            .andParentIdEqualTo(parentId).andCategoryNameEqualTo(categoryName);
    return markCategoryPOMapper.countByExample(markCategoryPOExample);
}

/**
 * 编辑时,查询同一级别(parentId相同)中某一分类名categoryName出现的次数,不包括本身
 * @param parentId
 * @return
 */
private int queryCategoryNameRepeatCnt(Long categoryId, Long parentId, String categoryName){
    MarkCategoryPOExample markCategoryPOExample = new MarkCategoryPOExample();
    markCategoryPOExample.createCriteria().andIsDeletedEqualTo(DeleteStatusEnum.UN_DELETE)
            .andParentIdEqualTo(parentId).andCategoryNameEqualTo(categoryName).andIdNotEqualTo(categoryId);
    return markCategoryPOMapper.countByExample(markCategoryPOExample);
}

3.4 上移、下移

public void moveMarkCategory(Long categoryId1, Long categoryId2, String username){    
    Date currentTime = DateUtil.getCurrentTimestamp();

    MarkCategoryPO markCategoryPO1 = markCategoryPOMapper.selectByPrimaryKey(categoryId1);
    MarkCategoryPO markCategoryPO2 = markCategoryPOMapper.selectByPrimaryKey(categoryId2);

    Integer sortNum1 = markCategoryPO1.getSortNum();
    Integer sortNum2 = markCategoryPO2.getSortNum();

    markCategoryPO1.setSortNum(sortNum2);
    markCategoryPO1.setUpdator(username);
    markCategoryPO1.setUpdateTime(currentTime);

    markCategoryPO2.setSortNum(sortNum1);
    markCategoryPO2.setUpdator(username);
    markCategoryPO2.setUpdateTime(currentTime);

    //修改数据库
    markCategoryPOMapper.updateByPrimaryKeySelective(markCategoryPO1);
    markCategoryPOMapper.updateByPrimaryKeySelective(markCategoryPO2);
}

3.5  删除一个目录

public void deleteMarkCategory(Long categoryId, String username){
    //判断下是不是已经被别人删除了
    MarkCategoryPO markCategoryPO = markCategoryPOMapper.selectByPrimaryKey(categoryId);

    if (markCategoryPO.getIsDeleted() == DeleteStatusEnum.DELETED){
        throw new XXXXException("该标注分类已经被其他用户删除");
    }

    //删除当前分类和所有子分类(同时删除与工单分类的映射关系)
    this.deleteSubMarkCategoryTree(markCategoryPO, username);
}

/**
 * 删除子树
 * @param MarkCategoryPO
 */
private void deleteSubMarkCategoryTree(MarkCategoryPO markCategoryPO, String username){
    Long parentId = markCategoryPO.getParentId();
    String categoryName = markCategoryPO.getCategoryName();
    MarkCategoryVO markCategoryVO = this.getSpecificMarkCategory(parentId, categoryName);
    this.deleteMarkCategoryRecursive(markCategoryVO, username);
}

/**
 * 递归删除当前标注及子标注
 * @param markCategoryVO
 */
private void deleteMarkCategoryRecursive(MarkCategoryVO markCategoryVO, String username){
    if (markCategoryVO == null){
        return;
    }

    deleteOneMarkCategory(markCategoryVO, username);

    List<MarkCategoryVO> children = markCategoryVO.getChildren();

    //递归退出条件
    if (children == null){
        return;
    }

    for (MarkCategoryVO markCategoryVOTmp : children){
        deleteMarkCategoryRecursive(markCategoryVOTmp, username);
    }
}

/**
 * 删除一个分类
 * @param markCategoryVO
 */
private void deleteOneMarkCategory(MarkCategoryVO markCategoryVO, String username){
    MarkCategoryPO markCategoryPO = new MarkCategoryPO();
    markCategoryPO.setId(markCategoryVO.getCategoryId());
    markCategoryPO.setIsDeleted(DeleteStatusEnum.DELETED);
    markCategoryPO.setUpdator(username);
    markCategoryPO.setUpdateTime(DateUtil.getCurrentTimestamp());

    markCategoryPOMapper.updateByPrimaryKeySelective(markCategoryPO);

    //删除与工单分类的映射关系
    this.deleteWorkorderMarkCategoryRelMap(markCategoryVO.getCategoryId(), username);
}

猜你喜欢

转载自blog.csdn.net/u010266988/article/details/81535638
今日推荐