mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
addd Tree
This commit is contained in:
parent
e2af8b96a5
commit
dbcca73dc2
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
### 新特性
|
### 新特性
|
||||||
* 【core 】 修改FastDateParser策略,与JDK保持一致(issue#I1AXIN@Gitee)
|
* 【core 】 修改FastDateParser策略,与JDK保持一致(issue#I1AXIN@Gitee)
|
||||||
|
* 【core 】 增加tree(树状结构)(pr#100@Gitee)
|
||||||
### Bug修复
|
### Bug修复
|
||||||
* 【setting】 修复Props.toBean方法null的问题
|
* 【setting】 修复Props.toBean方法null的问题
|
||||||
* 【core 】 修复DataUtil.parseLocalDateTime无时间部分报错问题(issue#I1B18H@Gitee)
|
* 【core 】 修复DataUtil.parseLocalDateTime无时间部分报错问题(issue#I1B18H@Gitee)
|
||||||
|
@ -23,7 +23,7 @@ public class ConsistentHash<T> implements Serializable{
|
|||||||
/** 复制的节点个数 */
|
/** 复制的节点个数 */
|
||||||
private final int numberOfReplicas;
|
private final int numberOfReplicas;
|
||||||
/** 一致性Hash环 */
|
/** 一致性Hash环 */
|
||||||
private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>();
|
private final SortedMap<Integer, T> circle = new TreeMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造,使用Java默认的Hash算法
|
* 构造,使用Java默认的Hash算法
|
||||||
|
@ -959,7 +959,18 @@ public class Validator {
|
|||||||
* @return 是否为汉字
|
* @return 是否为汉字
|
||||||
*/
|
*/
|
||||||
public static boolean isChinese(CharSequence value) {
|
public static boolean isChinese(CharSequence value) {
|
||||||
return isMactchRegex("^" + ReUtil.RE_CHINESE + "+$", value);
|
return isMactchRegex("^" + ReUtil.RE_CHINESES + "$", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证是否包含汉字
|
||||||
|
*
|
||||||
|
* @param value 值
|
||||||
|
* @return 是否包含汉字
|
||||||
|
* @since 5.2.1
|
||||||
|
*/
|
||||||
|
public static boolean hasChinese(CharSequence value) {
|
||||||
|
return ReUtil.contains(ReUtil.RE_CHINESES, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package cn.hutool.core.lang.tree;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 树节点转换器 可以参考{{@link DefaultConverter}}
|
|
||||||
*
|
|
||||||
* @author liangbaikai
|
|
||||||
*/
|
|
||||||
public interface Convert<T, TreeNodeMap> {
|
|
||||||
/**
|
|
||||||
* @param object 源数据实体
|
|
||||||
* @param treeNode 树节点实体
|
|
||||||
*/
|
|
||||||
void convert(T object, TreeNodeMap treeNode);
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
|||||||
package cn.hutool.core.lang.tree;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认的简单转换器
|
|
||||||
*
|
|
||||||
* @author liangbaikai
|
|
||||||
*/
|
|
||||||
public class DefaultConverter implements Convert<Node, TreeNodeMap> {
|
|
||||||
@Override
|
|
||||||
public void convert(Node object, TreeNodeMap treeNode) {
|
|
||||||
treeNode.setId(object.getId());
|
|
||||||
treeNode.setParentId(object.getPid());
|
|
||||||
treeNode.setWeight(object.getWeight());
|
|
||||||
treeNode.setName(object.getName());
|
|
||||||
|
|
||||||
//扩展字段
|
|
||||||
// treeNode.extra("other",11);
|
|
||||||
// treeNode.extra("other2",object.getXXX);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package cn.hutool.core.lang.tree;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 树节点 每个属性都可以在{@link TreeNodeConfig}中被重命名,在你的项目里它可以是部门实体 地区实体等任意类树节点实体
|
|
||||||
* 类树节点实体: 包含key,父Key.不限于这些属性的可以构造成一颗树的实体对象
|
|
||||||
*
|
|
||||||
* @author liangbaikai
|
|
||||||
*/
|
|
||||||
public class Node {
|
|
||||||
|
|
||||||
// ID
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
// 上级ID
|
|
||||||
private String pid;
|
|
||||||
|
|
||||||
// 名称
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
// 顺序 越小优先级越高 默认0
|
|
||||||
private Comparable weight = 0;
|
|
||||||
|
|
||||||
|
|
||||||
public Node() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Node(String id, String pid, String name, Comparable weight) {
|
|
||||||
this.id = id;
|
|
||||||
this.pid = pid;
|
|
||||||
this.name = name;
|
|
||||||
if (weight != null) {
|
|
||||||
this.weight = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Comparable getWeight() {
|
|
||||||
return weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWeight(Comparable weight) {
|
|
||||||
this.weight = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPid() {
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPid(String pid) {
|
|
||||||
this.pid = pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
121
hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java
Normal file
121
hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package cn.hutool.core.lang.tree;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过转换器将你的实体转化为TreeNodeMap节点实体 属性都存在此处,属性有序,可支持排序
|
||||||
|
*
|
||||||
|
* @param <T> ID类型
|
||||||
|
* @author liangbaikai
|
||||||
|
* @since 5.2.1
|
||||||
|
*/
|
||||||
|
public class Tree<T> extends LinkedHashMap<String, Object> implements Comparable<Tree<T>> {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private TreeNodeConfig treeNodeConfig;
|
||||||
|
|
||||||
|
public Tree() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param treeNodeConfig TreeNode配置
|
||||||
|
*/
|
||||||
|
public Tree(TreeNodeConfig treeNodeConfig) {
|
||||||
|
super();
|
||||||
|
this.treeNodeConfig = ObjectUtil.defaultIfNull(
|
||||||
|
treeNodeConfig, TreeNodeConfig.DEFAULT_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点ID
|
||||||
|
*
|
||||||
|
* @return 节点ID
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getId() {
|
||||||
|
return (T) this.get(treeNodeConfig.getIdKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置节点ID
|
||||||
|
*
|
||||||
|
* @param id 节点ID
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Tree<T> setId(T id) {
|
||||||
|
this.put(treeNodeConfig.getIdKey(), id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取父节点ID
|
||||||
|
*
|
||||||
|
* @return 父节点ID
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getParentId() {
|
||||||
|
return (T) this.get(treeNodeConfig.getParentIdKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tree<T> setParentId(T parentId) {
|
||||||
|
this.put(treeNodeConfig.getParentIdKey(), parentId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getName() {
|
||||||
|
return (T) this.get(treeNodeConfig.getNameKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tree<T> setName(Object name) {
|
||||||
|
this.put(treeNodeConfig.getNameKey(), name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparable<?> getWeight() {
|
||||||
|
return (Comparable<?>) this.get(treeNodeConfig.getWeightKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tree<T> setWeight(Comparable<?> weight) {
|
||||||
|
this.put(treeNodeConfig.getWeightKey(), weight);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<Tree<T>> getChildren() {
|
||||||
|
return (List<Tree<T>>) this.get(treeNodeConfig.getChildrenKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildren(List<Tree<T>> children) {
|
||||||
|
this.put(treeNodeConfig.getChildrenKey(), children);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展属性
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 扩展值
|
||||||
|
*/
|
||||||
|
public void putExtra(String key, Object value) {
|
||||||
|
Assert.notEmpty(key, "Key must be not empty !");
|
||||||
|
this.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
|
||||||
|
@Override
|
||||||
|
public int compareTo(Tree<T> tree) {
|
||||||
|
final Comparable weight = this.getWeight();
|
||||||
|
if (null != weight) {
|
||||||
|
final Comparable weightOther = tree.getWeight();
|
||||||
|
return weight.compareTo(weightOther);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
146
hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNode.java
Normal file
146
hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNode.java
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package cn.hutool.core.lang.tree;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树节点 每个属性都可以在{@link TreeNodeConfig}中被重命名<br>
|
||||||
|
* 在你的项目里它可以是部门实体、地区实体等任意类树节点实体
|
||||||
|
* 类树节点实体: 包含key,父Key.不限于这些属性的可以构造成一颗树的实体对象
|
||||||
|
*
|
||||||
|
* @author liangbaikai
|
||||||
|
*/
|
||||||
|
public class TreeNode<T> implements Comparable<Tree<T>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
private T id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父节点ID
|
||||||
|
*/
|
||||||
|
private T parentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private CharSequence name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顺序 越小优先级越高 默认0
|
||||||
|
*/
|
||||||
|
private Comparable<?> weight = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空构造
|
||||||
|
*/
|
||||||
|
public TreeNode() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param id ID
|
||||||
|
* @param parentId 父节点ID
|
||||||
|
* @param name 名称
|
||||||
|
* @param weight 权重
|
||||||
|
*/
|
||||||
|
public TreeNode(T id, T parentId, String name, Comparable<?> weight) {
|
||||||
|
this.id = id;
|
||||||
|
this.parentId = parentId;
|
||||||
|
this.name = name;
|
||||||
|
if (weight != null) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取ID
|
||||||
|
*
|
||||||
|
* @return ID
|
||||||
|
*/
|
||||||
|
public T getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置ID
|
||||||
|
*
|
||||||
|
* @param id ID
|
||||||
|
*/
|
||||||
|
public void setId(T id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取父节点ID
|
||||||
|
*
|
||||||
|
* @return 父节点ID
|
||||||
|
*/
|
||||||
|
public T getParentId() {
|
||||||
|
return this.parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置父节点ID
|
||||||
|
*
|
||||||
|
* @param parentId 父节点ID
|
||||||
|
* @return 父节点ID
|
||||||
|
*/
|
||||||
|
public TreeNode<T> setParentId(T parentId) {
|
||||||
|
this.parentId = parentId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点标签名称
|
||||||
|
*
|
||||||
|
* @return 节点标签名称
|
||||||
|
*/
|
||||||
|
public CharSequence getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置节点标签名称
|
||||||
|
*
|
||||||
|
* @param name 节点标签名称
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNode<T> setName(CharSequence name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取权重
|
||||||
|
*
|
||||||
|
* @return 权重
|
||||||
|
*/
|
||||||
|
public Comparable<?> getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置权重
|
||||||
|
*
|
||||||
|
* @param weight 权重
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNode<T> setWeight(Comparable<?> weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes", "NullableProblems"})
|
||||||
|
@Override
|
||||||
|
public int compareTo(Tree tree) {
|
||||||
|
final Comparable weight = this.getWeight();
|
||||||
|
if (null != weight) {
|
||||||
|
final Comparable weightOther = tree.getWeight();
|
||||||
|
return weight.compareTo(weightOther);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -10,108 +10,136 @@ public class TreeNodeConfig {
|
|||||||
/**
|
/**
|
||||||
* 默认属性配置对象
|
* 默认属性配置对象
|
||||||
*/
|
*/
|
||||||
private static TreeNodeConfig defaultConfig = new TreeNodeConfig();
|
public static TreeNodeConfig DEFAULT_CONFIG = new TreeNodeConfig();
|
||||||
|
|
||||||
// 树节点默认属性常量 当然你可以重新设置
|
|
||||||
/**
|
|
||||||
* 节点 id 名称
|
|
||||||
*/
|
|
||||||
static final String TREE_ID = "id";
|
|
||||||
/**
|
|
||||||
* 节点 parentId 父id名称
|
|
||||||
*/
|
|
||||||
static final String TREE_PARENT_ID = "parentId";
|
|
||||||
/**
|
|
||||||
* 节点 name 显示名称
|
|
||||||
*/
|
|
||||||
static final String TREE_NAME = "name";
|
|
||||||
/**
|
|
||||||
* 节点 weight 顺序名称
|
|
||||||
*/
|
|
||||||
static final String TREE_WEIGHT = "weight";
|
|
||||||
/**
|
|
||||||
* 节点 name 子节点名称
|
|
||||||
*/
|
|
||||||
static final String TREE_CHILDREN = "children";
|
|
||||||
|
|
||||||
|
|
||||||
static {
|
|
||||||
//init
|
|
||||||
defaultConfig.setIdKey(TREE_ID);
|
|
||||||
defaultConfig.setWeightKey(TREE_WEIGHT);
|
|
||||||
defaultConfig.setNameKey(TREE_NAME);
|
|
||||||
defaultConfig.setChildrenKey(TREE_CHILDREN);
|
|
||||||
defaultConfig.setParentIdKey(TREE_PARENT_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 属性名配置字段
|
// 属性名配置字段
|
||||||
private String idKey;
|
private String idKey = "id";
|
||||||
private String parentIdKey;
|
private String parentIdKey = "parentId";
|
||||||
private String weightKey;
|
private String weightKey = "weight";
|
||||||
private String nameKey;
|
private String nameKey = "name";
|
||||||
private String childrenKey;
|
private String childrenKey = "children";
|
||||||
|
|
||||||
// 可以配置递归深度 从0开始计算 默认此配置为空,即不限制
|
// 可以配置递归深度 从0开始计算 默认此配置为空,即不限制
|
||||||
private Integer deep;
|
private Integer deep;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取ID对应的名称
|
||||||
|
*
|
||||||
|
* @return ID对应的名称
|
||||||
|
*/
|
||||||
public String getIdKey() {
|
public String getIdKey() {
|
||||||
return getOrDefault(idKey, TREE_ID);
|
return this.idKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIdKey(String idKey) {
|
/**
|
||||||
|
* 设置ID对应的名称
|
||||||
|
*
|
||||||
|
* @param idKey ID对应的名称
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNodeConfig setIdKey(String idKey) {
|
||||||
this.idKey = idKey;
|
this.idKey = idKey;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取权重对应的名称
|
||||||
|
*
|
||||||
|
* @return 权重对应的名称
|
||||||
|
*/
|
||||||
public String getWeightKey() {
|
public String getWeightKey() {
|
||||||
return getOrDefault(weightKey, TREE_WEIGHT);
|
return this.weightKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWeightKey(String weightKey) {
|
/**
|
||||||
|
* 设置权重对应的名称
|
||||||
|
*
|
||||||
|
* @param weightKey 权重对应的名称
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNodeConfig setWeightKey(String weightKey) {
|
||||||
this.weightKey = weightKey;
|
this.weightKey = weightKey;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点名对应的名称
|
||||||
|
*
|
||||||
|
* @return 节点名对应的名称
|
||||||
|
*/
|
||||||
public String getNameKey() {
|
public String getNameKey() {
|
||||||
return getOrDefault(nameKey, TREE_NAME);
|
return this.nameKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNameKey(String nameKey) {
|
/**
|
||||||
|
* 设置节点名对应的名称
|
||||||
|
*
|
||||||
|
* @param nameKey 节点名对应的名称
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNodeConfig setNameKey(String nameKey) {
|
||||||
this.nameKey = nameKey;
|
this.nameKey = nameKey;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子点对应的名称
|
||||||
|
*
|
||||||
|
* @return 子点对应的名称
|
||||||
|
*/
|
||||||
public String getChildrenKey() {
|
public String getChildrenKey() {
|
||||||
return getOrDefault(childrenKey, TREE_CHILDREN);
|
return this.childrenKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChildrenKey(String childrenKey) {
|
/**
|
||||||
|
* 设置子点对应的名称
|
||||||
|
*
|
||||||
|
* @param childrenKey 子点对应的名称
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNodeConfig setChildrenKey(String childrenKey) {
|
||||||
this.childrenKey = childrenKey;
|
this.childrenKey = childrenKey;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取父节点ID对应的名称
|
||||||
|
*
|
||||||
|
* @return 父点对应的名称
|
||||||
|
*/
|
||||||
public String getParentIdKey() {
|
public String getParentIdKey() {
|
||||||
return getOrDefault(parentIdKey, TREE_PARENT_ID);
|
return this.parentIdKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParentIdKey(String parentIdKey) {
|
|
||||||
|
/**
|
||||||
|
* 设置父点对应的名称
|
||||||
|
*
|
||||||
|
* @param parentIdKey 父点对应的名称
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNodeConfig setParentIdKey(String parentIdKey) {
|
||||||
this.parentIdKey = parentIdKey;
|
this.parentIdKey = parentIdKey;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOrDefault(String key, String defaultKey) {
|
/**
|
||||||
if (key == null) {
|
* 获取递归深度
|
||||||
return defaultKey;
|
*
|
||||||
}
|
* @return 递归深度
|
||||||
return key;
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getDeep() {
|
public Integer getDeep() {
|
||||||
return deep;
|
return this.deep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDeep(Integer deep) {
|
/**
|
||||||
|
* 设置递归深度
|
||||||
|
*
|
||||||
|
* @param deep 递归深度
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public TreeNodeConfig setDeep(Integer deep) {
|
||||||
this.deep = deep;
|
this.deep = deep;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TreeNodeConfig getDefaultConfig() {
|
|
||||||
return defaultConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
package cn.hutool.core.lang.tree;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过转换器将你的实体转化为TreeNodeMap节点实体 属性都存在此处,属性有序,可支持排序
|
|
||||||
*
|
|
||||||
* @author liangbaikai
|
|
||||||
*/
|
|
||||||
public class TreeNodeMap extends LinkedHashMap implements Comparable<TreeNodeMap> {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 8376668307601977428L;
|
|
||||||
|
|
||||||
private TreeNodeConfig treeNodeConfig;
|
|
||||||
|
|
||||||
public TreeNodeMap() {
|
|
||||||
this.treeNodeConfig = TreeNodeConfig.getDefaultConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeNodeMap(TreeNodeConfig treeNodeConfig) {
|
|
||||||
this.treeNodeConfig = treeNodeConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T getId() {
|
|
||||||
return (T) super.get(treeNodeConfig.getIdKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(String id) {
|
|
||||||
super.put(treeNodeConfig.getIdKey(), id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T getParentId() {
|
|
||||||
return (T) super.get(treeNodeConfig.getParentIdKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParentId(String parentId) {
|
|
||||||
super.put(treeNodeConfig.getParentIdKey(), parentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T getName() {
|
|
||||||
return (T) super.get(treeNodeConfig.getNameKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
super.put(treeNodeConfig.getNameKey(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Comparable getWeight() {
|
|
||||||
return (Comparable) super.get(treeNodeConfig.getWeightKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeNodeMap setWeight(Comparable weight) {
|
|
||||||
super.put(treeNodeConfig.getWeightKey(), weight);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TreeNodeMap> getChildren() {
|
|
||||||
return (List<TreeNodeMap>) super.get(treeNodeConfig.getChildrenKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChildren(List<TreeNodeMap> children) {
|
|
||||||
super.put(treeNodeConfig.getChildrenKey(), children);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 扩展属性
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
public void extra(String key, Object value) {
|
|
||||||
if (key != null && key != "") {
|
|
||||||
super.put(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可以支持排序
|
|
||||||
*
|
|
||||||
* @param treeNodeMap weight值越小 优先级越高 默认为0
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compareTo(TreeNodeMap treeNodeMap) {
|
|
||||||
try {
|
|
||||||
//可能为空
|
|
||||||
int res = this.getWeight().compareTo(treeNodeMap.getWeight());
|
|
||||||
return res;
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,8 @@
|
|||||||
package cn.hutool.core.lang.tree;
|
package cn.hutool.core.lang.tree;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.lang.tree.parser.DefaultNodeParser;
|
||||||
|
import cn.hutool.core.lang.tree.parser.NodeParser;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -15,15 +17,22 @@ public class TreeUtil {
|
|||||||
/**
|
/**
|
||||||
* 树构建
|
* 树构建
|
||||||
*/
|
*/
|
||||||
public static List<TreeNodeMap> build(List<Node> list, Object parentId) {
|
public static List<Tree<Integer>> build(List<TreeNode<Integer>> list) {
|
||||||
return build(list, parentId, TreeNodeConfig.getDefaultConfig(), new DefaultConverter());
|
return build(list, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树构建
|
* 树构建
|
||||||
*/
|
*/
|
||||||
public static <T> List<TreeNodeMap> build(List<T> list, Object parentId, Convert<T, TreeNodeMap> convert) {
|
public static <T> List<Tree<T>> build(List<TreeNode<T>> list, T parentId) {
|
||||||
return build(list, parentId, TreeNodeConfig.getDefaultConfig(), convert);
|
return build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, new DefaultNodeParser<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树构建
|
||||||
|
*/
|
||||||
|
public static <T, E> List<Tree<E>> build(List<T> list, E parentId, NodeParser<T, E> nodeParser) {
|
||||||
|
return build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, nodeParser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,20 +41,20 @@ public class TreeUtil {
|
|||||||
* @param list 源数据集合
|
* @param list 源数据集合
|
||||||
* @param parentId 最顶层父id值 一般为 0 之类
|
* @param parentId 最顶层父id值 一般为 0 之类
|
||||||
* @param treeNodeConfig 配置
|
* @param treeNodeConfig 配置
|
||||||
* @param convert 转换器
|
* @param nodeParser 转换器
|
||||||
* @param <T> 转换的实体 为数据源里的对象类型
|
* @param <T> 转换的实体 为数据源里的对象类型
|
||||||
* @return
|
* @return List
|
||||||
*/
|
*/
|
||||||
public static <T> List<TreeNodeMap> build(List<T> list, Object parentId, TreeNodeConfig treeNodeConfig, Convert<T, TreeNodeMap> convert) {
|
public static <T, E> List<Tree<E>> build(List<T> list, E parentId, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) {
|
||||||
List<TreeNodeMap> treeNodes = CollectionUtil.newArrayList();
|
List<Tree<E>> treeNodes = CollectionUtil.newArrayList();
|
||||||
for (T obj : list) {
|
for (T obj : list) {
|
||||||
TreeNodeMap treeNode = new TreeNodeMap(treeNodeConfig);
|
Tree<E> treeNode = new Tree<>(treeNodeConfig);
|
||||||
convert.convert(obj, treeNode);
|
nodeParser.parse(obj, treeNode);
|
||||||
treeNodes.add(treeNode);
|
treeNodes.add(treeNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TreeNodeMap> finalTreeNodes = CollectionUtil.newArrayList();
|
List<Tree<E>> finalTreeNodes = CollectionUtil.newArrayList();
|
||||||
for (TreeNodeMap treeNode : treeNodes) {
|
for (Tree<E> treeNode : treeNodes) {
|
||||||
if (parentId.equals(treeNode.getParentId())) {
|
if (parentId.equals(treeNode.getParentId())) {
|
||||||
finalTreeNodes.add(treeNode);
|
finalTreeNodes.add(treeNode);
|
||||||
innerBuild(treeNodes, treeNode, 0, treeNodeConfig.getDeep());
|
innerBuild(treeNodes, treeNode, 0, treeNodeConfig.getDeep());
|
||||||
@ -64,7 +73,7 @@ public class TreeUtil {
|
|||||||
* @param deep 已递归深度
|
* @param deep 已递归深度
|
||||||
* @param maxDeep 最大递归深度 可能为null即不限制
|
* @param maxDeep 最大递归深度 可能为null即不限制
|
||||||
*/
|
*/
|
||||||
private static void innerBuild(List<TreeNodeMap> treeNodes, TreeNodeMap parentNode, int deep, Integer maxDeep) {
|
private static <T> void innerBuild(List<Tree<T>> treeNodes, Tree<T> parentNode, int deep, Integer maxDeep) {
|
||||||
|
|
||||||
if (CollectionUtil.isEmpty(treeNodes)) {
|
if (CollectionUtil.isEmpty(treeNodes)) {
|
||||||
return;
|
return;
|
||||||
@ -76,9 +85,9 @@ public class TreeUtil {
|
|||||||
|
|
||||||
// 每层排序 TreeNodeMap 实现了Comparable接口
|
// 每层排序 TreeNodeMap 实现了Comparable接口
|
||||||
treeNodes = treeNodes.stream().sorted().collect(Collectors.toList());
|
treeNodes = treeNodes.stream().sorted().collect(Collectors.toList());
|
||||||
for (TreeNodeMap childNode : treeNodes) {
|
for (Tree<T> childNode : treeNodes) {
|
||||||
if (parentNode.getId().equals(childNode.getParentId())) {
|
if (parentNode.getId().equals(childNode.getParentId())) {
|
||||||
List<TreeNodeMap> children = parentNode.getChildren();
|
List<Tree<T>> children = parentNode.getChildren();
|
||||||
if (children == null) {
|
if (children == null) {
|
||||||
children = CollectionUtil.newArrayList();
|
children = CollectionUtil.newArrayList();
|
||||||
parentNode.setChildren(children);
|
parentNode.setChildren(children);
|
||||||
|
@ -1 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 提供通用树生成,特点:
|
||||||
|
* <pre>
|
||||||
|
* 1、每个字段可自定义
|
||||||
|
* 2、支持排序 树深度配置,自定义转换器等
|
||||||
|
* 3、支持额外属性扩展
|
||||||
|
* 4、贴心 许多属性,特性都有默认值处理
|
||||||
|
* 5、使用简单 可一行代码生成树
|
||||||
|
* 6、代码简洁轻量无额外依赖
|
||||||
|
* <pre/>
|
||||||
|
*
|
||||||
|
* @author liangbaikai(https://gitee.com/liangbaikai00/)
|
||||||
|
* @since 5.2.1
|
||||||
|
*/
|
||||||
package cn.hutool.core.lang.tree;
|
package cn.hutool.core.lang.tree;
|
@ -0,0 +1,25 @@
|
|||||||
|
package cn.hutool.core.lang.tree.parser;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.tree.TreeNode;
|
||||||
|
import cn.hutool.core.lang.tree.Tree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认的简单转换器
|
||||||
|
*
|
||||||
|
* @param <T> ID类型
|
||||||
|
* @author liangbaikai
|
||||||
|
*/
|
||||||
|
public class DefaultNodeParser<T> implements NodeParser<TreeNode<T>, T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parse(TreeNode<T> object, Tree<T> treeNode) {
|
||||||
|
treeNode.setId(object.getId());
|
||||||
|
treeNode.setParentId(object.getParentId());
|
||||||
|
treeNode.setWeight(object.getWeight());
|
||||||
|
treeNode.setName(object.getName());
|
||||||
|
|
||||||
|
//扩展字段
|
||||||
|
// treeNode.extra("other",11);
|
||||||
|
// treeNode.extra("other2",object.getXXX);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package cn.hutool.core.lang.tree.parser;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.tree.Tree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树节点解析器 可以参考{@link DefaultNodeParser}
|
||||||
|
*
|
||||||
|
* @param <T> 转换的实体 为数据源里的对象类型
|
||||||
|
* @author liangbaikai
|
||||||
|
*/
|
||||||
|
public interface NodeParser<T, E> {
|
||||||
|
/**
|
||||||
|
* @param object 源数据实体
|
||||||
|
* @param treeNode 树节点实体
|
||||||
|
*/
|
||||||
|
void parse(T object, Tree<E> treeNode);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
package cn.hutool.core.util;
|
package cn.hutool.core.util;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.exceptions.UtilException;
|
import cn.hutool.core.exceptions.UtilException;
|
||||||
import cn.hutool.core.lang.Holder;
|
import cn.hutool.core.lang.Holder;
|
||||||
@ -8,7 +8,12 @@ import cn.hutool.core.lang.PatternPool;
|
|||||||
import cn.hutool.core.lang.Validator;
|
import cn.hutool.core.lang.Validator;
|
||||||
import cn.hutool.core.lang.func.Func1;
|
import cn.hutool.core.lang.func.Func1;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -20,13 +25,19 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public class ReUtil {
|
public class ReUtil {
|
||||||
|
|
||||||
/** 正则表达式匹配中文汉字 */
|
/**
|
||||||
|
* 正则表达式匹配中文汉字
|
||||||
|
*/
|
||||||
public final static String RE_CHINESE = "[\u4E00-\u9FFF]";
|
public final static String RE_CHINESE = "[\u4E00-\u9FFF]";
|
||||||
/** 正则表达式匹配中文字符串 */
|
/**
|
||||||
|
* 正则表达式匹配中文字符串
|
||||||
|
*/
|
||||||
public final static String RE_CHINESES = RE_CHINESE + "+";
|
public final static String RE_CHINESES = RE_CHINESE + "+";
|
||||||
|
|
||||||
/** 正则中需要被转义的关键字 */
|
/**
|
||||||
public final static Set<Character> RE_KEYS = CollectionUtil.newHashSet(new Character[] { '$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|' });
|
* 正则中需要被转义的关键字
|
||||||
|
*/
|
||||||
|
public final static Set<Character> RE_KEYS = CollUtil.newHashSet('$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得匹配的字符串,获得正则中分组0的内容
|
* 获得匹配的字符串,获得正则中分组0的内容
|
||||||
@ -221,7 +232,7 @@ public class ReUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<String> varNums = findAll(PatternPool.GROUP_VAR, template, 1, new HashSet<String>());
|
HashSet<String> varNums = findAll(PatternPool.GROUP_VAR, template, 1, new HashSet<>());
|
||||||
|
|
||||||
final CharSequence content = contentHolder.get();
|
final CharSequence content = contentHolder.get();
|
||||||
Matcher matcher = pattern.matcher(content);
|
Matcher matcher = pattern.matcher(content);
|
||||||
@ -375,7 +386,7 @@ public class ReUtil {
|
|||||||
* @since 3.0.6
|
* @since 3.0.6
|
||||||
*/
|
*/
|
||||||
public static List<String> findAll(String regex, CharSequence content, int group) {
|
public static List<String> findAll(String regex, CharSequence content, int group) {
|
||||||
return findAll(regex, content, group, new ArrayList<String>());
|
return findAll(regex, content, group, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -430,7 +441,7 @@ public class ReUtil {
|
|||||||
* @since 3.0.6
|
* @since 3.0.6
|
||||||
*/
|
*/
|
||||||
public static List<String> findAll(Pattern pattern, CharSequence content, int group) {
|
public static List<String> findAll(Pattern pattern, CharSequence content, int group) {
|
||||||
return findAll(pattern, content, group, new ArrayList<String>());
|
return findAll(pattern, content, group, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -618,7 +629,7 @@ public class ReUtil {
|
|||||||
final Matcher matcher = pattern.matcher(content);
|
final Matcher matcher = pattern.matcher(content);
|
||||||
boolean result = matcher.find();
|
boolean result = matcher.find();
|
||||||
if (result) {
|
if (result) {
|
||||||
final Set<String> varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1, new HashSet<String>());
|
final Set<String> varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1, new HashSet<>());
|
||||||
final StringBuffer sb = new StringBuffer();
|
final StringBuffer sb = new StringBuffer();
|
||||||
do {
|
do {
|
||||||
String replacement = replacementTemplate;
|
String replacement = replacementTemplate;
|
||||||
@ -657,7 +668,7 @@ public class ReUtil {
|
|||||||
* @return 替换后的字符串
|
* @return 替换后的字符串
|
||||||
* @since 4.2.2
|
* @since 4.2.2
|
||||||
*/
|
*/
|
||||||
public static String replaceAll(CharSequence str, Pattern pattern, Func1<Matcher, String> replaceFun){
|
public static String replaceAll(CharSequence str, Pattern pattern, Func1<Matcher, String> replaceFun) {
|
||||||
if (StrUtil.isEmpty(str)) {
|
if (StrUtil.isEmpty(str)) {
|
||||||
return StrUtil.str(str);
|
return StrUtil.str(str);
|
||||||
}
|
}
|
||||||
|
@ -13,24 +13,26 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class TreeTest {
|
public class TreeTest {
|
||||||
// 模拟数据
|
// 模拟数据
|
||||||
static List<Node> nodeList = CollectionUtil.newArrayList();
|
static List<TreeNode<String>> nodeList = CollectionUtil.newArrayList();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// 模拟数据
|
// 模拟数据
|
||||||
nodeList.add(new Node("1", "0", "系统管理", 5));
|
nodeList.add(new TreeNode<>("1", "0", "系统管理", 5));
|
||||||
nodeList.add(new Node("11", "1", "用户管理", 222222));
|
nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222));
|
||||||
nodeList.add(new Node("111", "11", "用户添加", 0));
|
nodeList.add(new TreeNode<>("111", "11", "用户添加", 0));
|
||||||
nodeList.add(new Node("2", "0", "店铺管理", 1));
|
|
||||||
nodeList.add(new Node("21", "2", "商品管理", 44));
|
nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1));
|
||||||
nodeList.add(new Node("221", "2", "商品管理2", 2));
|
nodeList.add(new TreeNode<>("21", "2", "商品管理", 44));
|
||||||
|
nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sampleTree() {
|
public void sampleTree() {
|
||||||
List<TreeNodeMap> treeNodes = TreeUtil.build(nodeList, "0");
|
List<Tree<String>> treeNodes = TreeUtil.build(nodeList, "0");
|
||||||
|
for (Tree<String> tree : treeNodes) {
|
||||||
System.out.println(treeNodes);
|
Console.log(tree);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -44,18 +46,15 @@ public class TreeTest {
|
|||||||
treeNodeConfig.setIdKey("rid");
|
treeNodeConfig.setIdKey("rid");
|
||||||
|
|
||||||
//转换器
|
//转换器
|
||||||
List<TreeNodeMap> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig,
|
List<Tree<String>> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig,
|
||||||
new Convert<Node, TreeNodeMap>() {
|
(treeNode, tree) -> {
|
||||||
@Override
|
tree.setId(treeNode.getId());
|
||||||
public void convert(Node object, TreeNodeMap treeNode) {
|
tree.setParentId(treeNode.getParentId());
|
||||||
treeNode.setId(object.getId());
|
tree.setWeight(treeNode.getWeight());
|
||||||
treeNode.setParentId(object.getPid());
|
tree.setName(treeNode.getName());
|
||||||
treeNode.setWeight(object.getWeight());
|
|
||||||
treeNode.setName(object.getName());
|
|
||||||
// 扩展属性 ...
|
// 扩展属性 ...
|
||||||
treeNode.extra("extraField", 666);
|
tree.putExtra("extraField", 666);
|
||||||
treeNode.extra("other", new Object());
|
tree.putExtra("other", new Object());
|
||||||
}
|
|
||||||
});
|
});
|
||||||
System.out.println(treeNodes);
|
System.out.println(treeNodes);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ public class CronUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除Task
|
* 更新Task的执行时间规则
|
||||||
*
|
*
|
||||||
* @param id Task的ID
|
* @param id Task的ID
|
||||||
* @param pattern {@link CronPattern}
|
* @param pattern {@link CronPattern}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user