一、需求
要求做一个目录树,
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);
}