Skip to content

Commit 55660ba

Browse files
lishuyanlaCharles7c
authored andcommitted
refactor(extension/crud): 重构查询树列表功能,增加重载方法,支持构建单个根节点或者多个根节点的树结构
1 parent 36c30a2 commit 55660ba

3 files changed

Lines changed: 193 additions & 46 deletions

File tree

  • continew-starter-extension/continew-starter-extension-crud
    • continew-starter-extension-crud-core/src/main/java/top/continew/starter/extension/crud/service
    • continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service
    • continew-starter-extension-crud-mp/src/main/java/top/continew/starter/extension/crud/service

‎continew-starter-extension/continew-starter-extension-crud/continew-starter-extension-crud-core/src/main/java/top/continew/starter/extension/crud/service/CrudService.java‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,26 @@ public interface CrudService<L, D, Q, C> {
6060
List<L> list(@Valid Q query, @Valid SortQuery sortQuery);
6161

6262
/**
63-
* 查询树列表
63+
* 查询树列表(多个根节点)
6464
*
6565
* @param query 查询条件
6666
* @param sortQuery 排序查询条件
67-
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @DictField 局部配置)
67+
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @TreeField 局部配置)
6868
* @return 树列表信息
6969
*/
7070
List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery, boolean isSimple);
7171

72+
/**
73+
* 查询树列表
74+
*
75+
* @param query 查询条件
76+
* @param sortQuery 排序查询条件
77+
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @TreeField 局部配置)
78+
* @param isSingleRoot 是否为单个根节点
79+
* @return 树列表信息
80+
*/
81+
List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery, boolean isSimple, boolean isSingleRoot);
82+
7283
/**
7384
* 查询详情
7485
*

‎continew-starter-extension/continew-starter-extension-crud/continew-starter-extension-crud-mf/src/main/java/top/continew/starter/extension/crud/service/CrudServiceImpl.java‎

Lines changed: 96 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,20 @@
2727
import cn.hutool.extra.spring.SpringUtil;
2828
import com.mybatisflex.core.paginate.Page;
2929
import com.mybatisflex.core.query.QueryWrapper;
30+
import jakarta.el.MethodNotFoundException;
3031
import jakarta.servlet.http.HttpServletResponse;
3132
import org.springframework.data.domain.Sort;
33+
import org.springframework.lang.NonNull;
3234
import org.springframework.transaction.annotation.Transactional;
3335
import top.continew.starter.core.constant.StringConstants;
36+
import top.continew.starter.core.exception.BusinessException;
3437
import top.continew.starter.core.util.ReflectUtils;
38+
import top.continew.starter.core.util.TreeBuildUtils;
3539
import top.continew.starter.core.util.validation.ValidationUtils;
3640
import top.continew.starter.data.base.BaseMapper;
3741
import top.continew.starter.data.service.impl.ServiceImpl;
3842
import top.continew.starter.data.util.QueryWrapperHelper;
43+
import top.continew.starter.excel.util.ExcelUtils;
3944
import top.continew.starter.extension.crud.annotation.TreeField;
4045
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
4146
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
@@ -44,12 +49,14 @@
4449
import top.continew.starter.extension.crud.model.query.SortQuery;
4550
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
4651
import top.continew.starter.extension.crud.model.resp.PageResp;
47-
import top.continew.starter.excel.util.ExcelUtils;
4852

53+
import java.lang.invoke.MethodHandleProxies;
54+
import java.lang.invoke.MethodHandles;
4955
import java.lang.reflect.Field;
50-
import java.util.ArrayList;
56+
import java.lang.reflect.Method;
5157
import java.util.List;
5258
import java.util.Optional;
59+
import java.util.function.Function;
5360

5461
/**
5562
* CRUD 业务实现基类
@@ -89,9 +96,14 @@ public List<L> list(Q query, SortQuery sortQuery) {
8996

9097
@Override
9198
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
99+
return this.tree(query, sortQuery, true, false);
100+
}
101+
102+
@Override
103+
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple, boolean isSingleRoot) {
92104
List<L> list = this.list(query, sortQuery);
93105
if (CollUtil.isEmpty(list)) {
94-
return new ArrayList<>(0);
106+
return CollUtil.newArrayList();
95107
}
96108
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
97109
CrudTreeProperties treeProperties = crudProperties.getTree();
@@ -106,21 +118,88 @@ public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
106118
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
107119
rootId = treeField.rootId();
108120
}
109-
// 构建树
110-
return TreeUtil.build(list, rootId, treeNodeConfig, (node, tree) -> {
111-
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
112-
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
113-
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
114-
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
115-
// 如果构建简单树结构,则不包含扩展字段
116-
if (!isSimple) {
117-
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
118-
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
119-
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
120-
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
121-
.getName()))));
121+
if (isSingleRoot) {
122+
// 构建单根节点树
123+
return TreeUtil.build(list, rootId, treeNodeConfig, (node,
124+
tree) -> buildTreeField(isSimple, node, tree, treeField));
125+
} else {
126+
Function<L, Long> getId = createMethodReference(listClass, CharSequenceUtil.genGetter(treeField.value()));
127+
Function<L, Long> getParentId = createMethodReference(listClass, CharSequenceUtil.genGetter(treeField
128+
.parentIdKey()));
129+
// 构建多根节点树
130+
return TreeBuildUtils.buildMultiRoot(list, getId, getParentId, treeNodeConfig, (node,
131+
tree) -> buildTreeField(isSimple, node, tree, treeField));
132+
}
133+
}
134+
135+
/**
136+
* 构建树字段
137+
*
138+
* @param isSimple 是否简单树结构
139+
* @param node 节点
140+
* @param tree 树
141+
* @param treeField 树字段
142+
*/
143+
private void buildTreeField(boolean isSimple, L node, Tree<Long> tree, TreeField treeField) {
144+
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
145+
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
146+
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
147+
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
148+
// 如果构建简单树结构,则不包含扩展字段
149+
if (!isSimple) {
150+
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
151+
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
152+
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
153+
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
154+
.getName()))));
155+
}
156+
}
157+
158+
/**
159+
* 通过反射创建方法引用,支持在父类中查找方法
160+
*
161+
* @param clazz 实体类类型
162+
* @param methodName 方法名
163+
* @param <T> 实体类类型
164+
* @param <K> 返回值类型
165+
* @return Function<T, K> 方法引用
166+
* @throws IllegalArgumentException 如果参数不合法
167+
* @throws MethodNotFoundException 如果在类层次结构中找不到指定方法
168+
*/
169+
@SuppressWarnings("unchecked")
170+
public static <T, K> Function<T, K> createMethodReference(@NonNull Class<T> clazz, @NonNull String methodName) {
171+
Method method = getMethod(clazz, methodName);
172+
try {
173+
// 设置访问权限并返回方法引用
174+
method.setAccessible(true);
175+
return MethodHandleProxies.asInterfaceInstance(Function.class, MethodHandles.lookup().unreflect(method));
176+
} catch (Exception e) {
177+
throw new BusinessException("创建方法引用失败: " + clazz.getName() + "." + methodName, e);
178+
}
179+
}
180+
181+
/**
182+
* 获取方法(包括父类)
183+
*
184+
* @param clazz 实体类
185+
* @param methodName 方法名
186+
* @param <T> 实体类
187+
* @return 方法
188+
*/
189+
private static <T> Method getMethod(Class<T> clazz, String methodName) {
190+
Class<?> currentClass = clazz;
191+
Method method = null;
192+
// 查找方法(包括父类)
193+
while (currentClass != null) {
194+
try {
195+
method = currentClass.getDeclaredMethod(methodName);
196+
break;
197+
} catch (NoSuchMethodException e) {
198+
// 继续查找父类
199+
currentClass = currentClass.getSuperclass();
122200
}
123-
});
201+
}
202+
return method;
124203
}
125204

126205
@Override

‎continew-starter-extension/continew-starter-extension-crud/continew-starter-extension-crud-mp/src/main/java/top/continew/starter/extension/crud/service/CrudServiceImpl.java‎

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,21 @@
2222
import cn.hutool.core.convert.Convert;
2323
import cn.hutool.core.lang.tree.Tree;
2424
import cn.hutool.core.lang.tree.TreeNodeConfig;
25+
import cn.hutool.core.lang.tree.TreeUtil;
2526
import cn.hutool.core.map.MapUtil;
2627
import cn.hutool.core.text.CharSequenceUtil;
2728
import cn.hutool.core.util.ReflectUtil;
2829
import cn.hutool.extra.spring.SpringUtil;
2930
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
3031
import com.baomidou.mybatisplus.core.metadata.IPage;
3132
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
33+
import jakarta.el.MethodNotFoundException;
3234
import jakarta.servlet.http.HttpServletResponse;
3335
import org.springframework.data.domain.Sort;
36+
import org.springframework.lang.NonNull;
3437
import org.springframework.transaction.annotation.Transactional;
3538
import top.continew.starter.core.constant.StringConstants;
39+
import top.continew.starter.core.exception.BusinessException;
3640
import top.continew.starter.core.util.ClassUtils;
3741
import top.continew.starter.core.util.ReflectUtils;
3842
import top.continew.starter.core.util.TreeBuildUtils;
@@ -97,56 +101,109 @@ public List<L> list(Q query, SortQuery sortQuery) {
97101

98102
@Override
99103
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
104+
return this.tree(query, sortQuery, true, false);
105+
}
106+
107+
@Override
108+
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple, boolean isSingleRoot) {
100109
List<L> list = this.list(query, sortQuery);
101110
if (CollUtil.isEmpty(list)) {
102-
return new ArrayList<>(0);
111+
return CollUtil.newArrayList();
103112
}
104113
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
105114
CrudTreeProperties treeProperties = crudProperties.getTree();
106115
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
116+
TreeNodeConfig treeNodeConfig;
117+
Long rootId;
107118
// 简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置
108-
TreeNodeConfig treeNodeConfig = isSimple
109-
? treeProperties.genTreeNodeConfig()
110-
: treeProperties.genTreeNodeConfig(treeField);
111-
String valueGetter = CharSequenceUtil.genGetter(treeField.value());
112-
String parentIdKeyGetter = CharSequenceUtil.genGetter(treeField.parentIdKey());
113-
Function<L, Long> getId = createMethodReference(listClass, valueGetter);
114-
Function<L, Long> getParentId = createMethodReference(listClass, parentIdKeyGetter);
115-
// 构建树
116-
return TreeBuildUtils.buildMultiRoot(list, getId, getParentId, treeNodeConfig, (node, tree) -> {
117-
tree.setId(ReflectUtil.invoke(node, valueGetter));
118-
tree.setParentId(ReflectUtil.invoke(node, parentIdKeyGetter));
119-
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
120-
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
121-
// 如果构建简单树结构,则不包含扩展字段
122-
if (!isSimple) {
123-
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
124-
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
125-
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
126-
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
127-
.getName()))));
128-
}
129-
});
119+
if (isSimple) {
120+
treeNodeConfig = treeProperties.genTreeNodeConfig();
121+
rootId = treeProperties.getRootId();
122+
} else {
123+
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
124+
rootId = treeField.rootId();
125+
}
126+
if (isSingleRoot) {
127+
// 构建单根节点树
128+
return TreeUtil.build(list, rootId, treeNodeConfig, (node,
129+
tree) -> buildTreeField(isSimple, node, tree, treeField));
130+
} else {
131+
Function<L, Long> getId = createMethodReference(listClass, CharSequenceUtil.genGetter(treeField.value()));
132+
Function<L, Long> getParentId = createMethodReference(listClass, CharSequenceUtil.genGetter(treeField
133+
.parentIdKey()));
134+
// 构建多根节点树
135+
return TreeBuildUtils.buildMultiRoot(list, getId, getParentId, treeNodeConfig, (node,
136+
tree) -> buildTreeField(isSimple, node, tree, treeField));
137+
}
138+
}
139+
140+
/**
141+
* 构建树字段
142+
*
143+
* @param isSimple 是否简单树结构
144+
* @param node 节点
145+
* @param tree 树
146+
* @param treeField 树字段
147+
*/
148+
private void buildTreeField(boolean isSimple, L node, Tree<Long> tree, TreeField treeField) {
149+
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
150+
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
151+
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
152+
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
153+
// 如果构建简单树结构,则不包含扩展字段
154+
if (!isSimple) {
155+
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
156+
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
157+
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
158+
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
159+
.getName()))));
160+
}
130161
}
131162

132163
/**
133-
* 通过反射创建方法引用
164+
* 通过反射创建方法引用,支持在父类中查找方法
134165
*
135166
* @param clazz 实体类类型
136167
* @param methodName 方法名
137168
* @param <T> 实体类类型
138169
* @param <K> 返回值类型
139170
* @return Function<T, K> 方法引用
171+
* @throws IllegalArgumentException 如果参数不合法
172+
* @throws MethodNotFoundException 如果在类层次结构中找不到指定方法
140173
*/
141174
@SuppressWarnings("unchecked")
142-
public static <T, K> Function<T, K> createMethodReference(Class<T> clazz, String methodName) {
175+
public static <T, K> Function<T, K> createMethodReference(@NonNull Class<T> clazz, @NonNull String methodName) {
143176
try {
144-
Method method = clazz.getDeclaredMethod(methodName);
177+
Method method = getMethod(clazz, methodName);
145178
method.setAccessible(true);
146179
return MethodHandleProxies.asInterfaceInstance(Function.class, MethodHandles.lookup().unreflect(method));
147180
} catch (Exception e) {
148-
throw new RuntimeException("Failed to create method reference for " + methodName, e);
181+
throw new BusinessException("创建方法引用失败:" + clazz.getName() + StringConstants.DOT + methodName, e);
182+
}
183+
}
184+
185+
/**
186+
* 获取方法(包括父类)
187+
*
188+
* @param clazz 实体类
189+
* @param methodName 方法名
190+
* @param <T> 实体类
191+
* @return 方法
192+
*/
193+
private static <T> Method getMethod(Class<T> clazz, String methodName) {
194+
Class<?> currentClass = clazz;
195+
Method method = null;
196+
// 查找方法(包括父类)
197+
while (currentClass != null) {
198+
try {
199+
method = currentClass.getDeclaredMethod(methodName);
200+
break;
201+
} catch (NoSuchMethodException e) {
202+
// 继续查找父类
203+
currentClass = currentClass.getSuperclass();
204+
}
149205
}
206+
return method;
150207
}
151208

152209
@Override

0 commit comments

Comments
 (0)