mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-31 22:57:19 +08:00 
			
		
		
		
	feat(extension/crud): 支持树结构全局配置
This commit is contained in:
		| @@ -119,6 +119,11 @@ public class PropertiesConstants { | ||||
|      */ | ||||
|     public static final String MESSAGING_WEBSOCKET = MESSAGING + StringConstants.DOT + "websocket"; | ||||
|  | ||||
|     /** | ||||
|      * CRUD 配置 | ||||
|      */ | ||||
|     public static final String CRUD = CONTINEW_STARTER + StringConstants.DOT + "crud"; | ||||
|  | ||||
|     /** | ||||
|      * 数据权限配置 | ||||
|      */ | ||||
|   | ||||
| @@ -71,4 +71,11 @@ public @interface TreeField { | ||||
|      * @return 递归深度 | ||||
|      */ | ||||
|     int deep() default -1; | ||||
|  | ||||
|     /** | ||||
|      * 根节点 ID | ||||
|      * | ||||
|      * @return 根节点 ID | ||||
|      */ | ||||
|     long rootId() default 0L; | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,45 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.continew.starter.extension.crud.autoconfigure; | ||||
|  | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.boot.context.properties.NestedConfigurationProperty; | ||||
| import top.continew.starter.core.constant.PropertiesConstants; | ||||
|  | ||||
| /** | ||||
|  * CRUD 配置属性 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2.7.2 | ||||
|  */ | ||||
| @ConfigurationProperties(PropertiesConstants.CRUD) | ||||
| public class CrudProperties { | ||||
|  | ||||
|     /** | ||||
|      * 树配置 | ||||
|      */ | ||||
|     @NestedConfigurationProperty | ||||
|     private CrudTreeProperties tree; | ||||
|  | ||||
|     public CrudTreeProperties getTree() { | ||||
|         return tree; | ||||
|     } | ||||
|  | ||||
|     public void setTree(CrudTreeProperties tree) { | ||||
|         this.tree = tree; | ||||
|     } | ||||
| } | ||||
| @@ -20,6 +20,7 @@ import jakarta.annotation.PostConstruct; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Qualifier; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Primary; | ||||
| @@ -36,6 +37,7 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider; | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Configuration | ||||
| @EnableConfigurationProperties(CrudProperties.class) | ||||
| public class CrudRestControllerAutoConfiguration extends DelegatingWebMvcConfiguration { | ||||
|  | ||||
|     private static final Logger log = LoggerFactory.getLogger(CrudRestControllerAutoConfiguration.class); | ||||
|   | ||||
| @@ -0,0 +1,155 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.continew.starter.extension.crud.autoconfigure; | ||||
|  | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import top.continew.starter.core.util.validate.CheckUtils; | ||||
| import top.continew.starter.extension.crud.annotation.TreeField; | ||||
|  | ||||
| /** | ||||
|  * CRUD 树列表配置属性 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2.7.2 | ||||
|  */ | ||||
| public class CrudTreeProperties { | ||||
|  | ||||
|     /** | ||||
|      * ID 字段名 | ||||
|      */ | ||||
|     private String idKey = "id"; | ||||
|  | ||||
|     /** | ||||
|      * 父 ID 字段名 | ||||
|      * | ||||
|      */ | ||||
|     private String parentIdKey = "parentId"; | ||||
|  | ||||
|     /** | ||||
|      * 名称字段名 | ||||
|      * | ||||
|      */ | ||||
|     private String nameKey = "name"; | ||||
|  | ||||
|     /** | ||||
|      * 排序字段名 | ||||
|      * | ||||
|      */ | ||||
|     private String weightKey = "weight"; | ||||
|  | ||||
|     /** | ||||
|      * 子列表字段名 | ||||
|      * | ||||
|      */ | ||||
|     private String childrenKey = "children"; | ||||
|  | ||||
|     /** | ||||
|      * 递归深度(< 0 不限制) | ||||
|      */ | ||||
|     private Integer deep = -1; | ||||
|  | ||||
|     /** | ||||
|      * 根节点 ID | ||||
|      */ | ||||
|     private Long rootId = 0L; | ||||
|  | ||||
|     public String getIdKey() { | ||||
|         return idKey; | ||||
|     } | ||||
|  | ||||
|     public void setIdKey(String idKey) { | ||||
|         this.idKey = idKey; | ||||
|     } | ||||
|  | ||||
|     public String getParentIdKey() { | ||||
|         return parentIdKey; | ||||
|     } | ||||
|  | ||||
|     public void setParentIdKey(String parentIdKey) { | ||||
|         this.parentIdKey = parentIdKey; | ||||
|     } | ||||
|  | ||||
|     public String getNameKey() { | ||||
|         return nameKey; | ||||
|     } | ||||
|  | ||||
|     public void setNameKey(String nameKey) { | ||||
|         this.nameKey = nameKey; | ||||
|     } | ||||
|  | ||||
|     public String getWeightKey() { | ||||
|         return weightKey; | ||||
|     } | ||||
|  | ||||
|     public void setWeightKey(String weightKey) { | ||||
|         this.weightKey = weightKey; | ||||
|     } | ||||
|  | ||||
|     public String getChildrenKey() { | ||||
|         return childrenKey; | ||||
|     } | ||||
|  | ||||
|     public void setChildrenKey(String childrenKey) { | ||||
|         this.childrenKey = childrenKey; | ||||
|     } | ||||
|  | ||||
|     public Integer getDeep() { | ||||
|         return deep; | ||||
|     } | ||||
|  | ||||
|     public void setDeep(Integer deep) { | ||||
|         this.deep = deep; | ||||
|     } | ||||
|  | ||||
|     public Long getRootId() { | ||||
|         return rootId; | ||||
|     } | ||||
|  | ||||
|     public void setRootId(Long rootId) { | ||||
|         this.rootId = rootId; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 生成 {@link TreeNodeConfig} 对象 | ||||
|      * | ||||
|      * @return {@link TreeNodeConfig} 对象 | ||||
|      */ | ||||
|     public TreeNodeConfig genTreeNodeConfig() { | ||||
|         return TreeNodeConfig.DEFAULT_CONFIG.setIdKey(idKey) | ||||
|             .setParentIdKey(parentIdKey) | ||||
|             .setNameKey(nameKey) | ||||
|             .setWeightKey(weightKey) | ||||
|             .setChildrenKey(childrenKey) | ||||
|             .setDeep(deep < 0 ? null : deep); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 @TreeField 配置生成树结构配置 | ||||
|      * | ||||
|      * @param treeField 树结构字段注解 | ||||
|      * @return 树结构配置 | ||||
|      */ | ||||
|     public TreeNodeConfig genTreeNodeConfig(TreeField treeField) { | ||||
|         CheckUtils.throwIfNull(treeField, "请添加并配置 @TreeField 树结构信息"); | ||||
|         return new TreeNodeConfig().setIdKey(treeField.value()) | ||||
|             .setParentIdKey(treeField.parentIdKey()) | ||||
|             .setNameKey(treeField.nameKey()) | ||||
|             .setWeightKey(treeField.weightKey()) | ||||
|             .setChildrenKey(treeField.childrenKey()) | ||||
|             .setDeep(treeField.deep() < 0 ? null : treeField.deep()); | ||||
|     } | ||||
| } | ||||
| @@ -28,34 +28,42 @@ public enum Api { | ||||
|      * 所有 API | ||||
|      */ | ||||
|     ALL, | ||||
|  | ||||
|     /** | ||||
|      * 分页 | ||||
|      */ | ||||
|     PAGE, | ||||
|     /** | ||||
|      * 树列表 | ||||
|      */ | ||||
|     TREE, | ||||
|  | ||||
|     /** | ||||
|      * 列表 | ||||
|      */ | ||||
|     LIST, | ||||
|  | ||||
|     /** | ||||
|      * 树列表 | ||||
|      */ | ||||
|     TREE, | ||||
|  | ||||
|     /** | ||||
|      * 详情 | ||||
|      */ | ||||
|     GET, | ||||
|  | ||||
|     /** | ||||
|      * 新增 | ||||
|      */ | ||||
|     ADD, | ||||
|  | ||||
|     /** | ||||
|      * 修改 | ||||
|      */ | ||||
|     UPDATE, | ||||
|  | ||||
|     /** | ||||
|      * 删除 | ||||
|      */ | ||||
|     DELETE, | ||||
|  | ||||
|     /** | ||||
|      * 导出 | ||||
|      */ | ||||
|   | ||||
| @@ -16,18 +16,10 @@ | ||||
|  | ||||
| package top.continew.starter.extension.crud.util; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.lang.tree.TreeUtil; | ||||
| import cn.hutool.core.lang.tree.parser.NodeParser; | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import top.continew.starter.core.util.validate.CheckUtils; | ||||
| import top.continew.starter.extension.crud.annotation.TreeField; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 树工具类 | ||||
|  * | ||||
| @@ -37,7 +29,7 @@ import java.util.List; | ||||
| public class TreeUtils { | ||||
|  | ||||
|     /** | ||||
|      * 默认字段配置对象(根据前端树结构灵活调整名称) | ||||
|      * 默认字段配置对象 | ||||
|      */ | ||||
|     public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("title") | ||||
|         .setIdKey("key") | ||||
| @@ -46,37 +38,6 @@ public class TreeUtils { | ||||
|     private TreeUtils() { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 树构建 | ||||
|      * | ||||
|      * @param <T>        转换的实体 为数据源里的对象类型 | ||||
|      * @param <E>        ID类型 | ||||
|      * @param list       源数据集合 | ||||
|      * @param nodeParser 转换器 | ||||
|      * @return List 树列表 | ||||
|      */ | ||||
|     public static <T, E> List<Tree<E>> build(List<T> list, NodeParser<T, E> nodeParser) { | ||||
|         return build(list, DEFAULT_CONFIG, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 树构建 | ||||
|      * | ||||
|      * @param <T>            转换的实体 为数据源里的对象类型 | ||||
|      * @param <E>            ID类型 | ||||
|      * @param list           源数据集合 | ||||
|      * @param treeNodeConfig 配置 | ||||
|      * @param nodeParser     转换器 | ||||
|      * @return List 树列表 | ||||
|      */ | ||||
|     public static <T, E> List<Tree<E>> build(List<T> list, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return new ArrayList<>(0); | ||||
|         } | ||||
|         E parentId = (E)ReflectUtil.getFieldValue(list.get(0), treeNodeConfig.getParentIdKey()); | ||||
|         return TreeUtil.build(list, parentId, treeNodeConfig, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 @TreeField 配置生成树结构配置 | ||||
|      * | ||||
|   | ||||
| @@ -71,21 +71,6 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, | ||||
|         return baseService.page(query, pageQuery); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     @Operation(summary = "查询树列表", description = "查询树列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping("/tree") | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.tree(query, sortQuery, false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
| @@ -101,6 +86,21 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, | ||||
|         return baseService.list(query, sortQuery); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     @Operation(summary = "查询树列表", description = "查询树列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping("/tree") | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.tree(query, sortQuery, false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询详情 | ||||
|      * | ||||
|   | ||||
| @@ -46,16 +46,6 @@ public interface BaseService<L, D, Q, C> { | ||||
|      */ | ||||
|     PageResp<L> page(Q query, PageQuery pageQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @param isSimple  是否为简单树结构(不包含基本树结构之外的扩展字段) | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple); | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
| @@ -65,6 +55,20 @@ public interface BaseService<L, D, Q, C> { | ||||
|      */ | ||||
|     List<L> list(Q query, SortQuery sortQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * <p> | ||||
|      * 虽然提供了查询条件,但不建议使用,容易因缺失根节点导致树节点丢失。 | ||||
|      * 建议在前端进行查询过滤,如需使用建议重写方法。 | ||||
|      * </p> | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @param isSimple  是否为简单树结构(不包含基本树结构之外的扩展字段) | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple); | ||||
|  | ||||
|     /** | ||||
|      * 查看详情 | ||||
|      * | ||||
|   | ||||
| @@ -22,12 +22,14 @@ import cn.hutool.core.bean.copier.CopyOptions; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.lang.tree.TreeUtil; | ||||
| import cn.hutool.core.text.CharSequenceUtil; | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import com.mybatisflex.core.paginate.Page; | ||||
| import com.mybatisflex.core.query.QueryWrapper; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import org.apache.poi.ss.formula.functions.T; | ||||
| import org.springframework.data.domain.Sort; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import top.continew.starter.core.constant.StringConstants; | ||||
| @@ -37,12 +39,13 @@ import top.continew.starter.data.mf.base.BaseMapper; | ||||
| import top.continew.starter.data.mf.service.impl.ServiceImpl; | ||||
| import top.continew.starter.data.mf.util.QueryWrapperHelper; | ||||
| import top.continew.starter.extension.crud.annotation.TreeField; | ||||
| import top.continew.starter.extension.crud.autoconfigure.CrudProperties; | ||||
| import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties; | ||||
| import top.continew.starter.extension.crud.model.entity.BaseIdDO; | ||||
| import top.continew.starter.extension.crud.model.query.PageQuery; | ||||
| import top.continew.starter.extension.crud.model.query.SortQuery; | ||||
| import top.continew.starter.extension.crud.model.resp.PageResp; | ||||
| import top.continew.starter.extension.crud.service.BaseService; | ||||
| import top.continew.starter.extension.crud.util.TreeUtils; | ||||
| import top.continew.starter.file.excel.util.ExcelUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| @@ -79,6 +82,13 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD | ||||
|         return pageResp; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<L> list(Q query, SortQuery sortQuery) { | ||||
|         List<L> list = this.list(query, sortQuery, listClass); | ||||
|         list.forEach(this::fill); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) { | ||||
|         List<L> list = this.list(query, sortQuery); | ||||
| @@ -86,36 +96,35 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD | ||||
|             return new ArrayList<>(0); | ||||
|         } | ||||
|         // 如果构建简单树结构,则不包含基本树结构之外的扩展字段 | ||||
|         TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG; | ||||
|         TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class); | ||||
|         if (!isSimple) { | ||||
|             // 根据 @TreeField 配置生成树结构配置 | ||||
|             treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField); | ||||
|         CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class); | ||||
|         CrudTreeProperties treeProperties = crudProperties.getTree(); | ||||
|         TreeNodeConfig treeNodeConfig; | ||||
|         Long rootId; | ||||
|         if (isSimple) { | ||||
|             treeNodeConfig = treeProperties.genTreeNodeConfig(); | ||||
|             rootId = treeProperties.getRootId(); | ||||
|         } else { | ||||
|             TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class); | ||||
|             treeNodeConfig = treeProperties.genTreeNodeConfig(treeField); | ||||
|             rootId = treeField.rootId(); | ||||
|         } | ||||
|         // 构建树 | ||||
|         return TreeUtils.build(list, treeNodeConfig, (node, tree) -> { | ||||
|             // 转换器 | ||||
|             tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value()))); | ||||
|             tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey()))); | ||||
|             tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey()))); | ||||
|             tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey()))); | ||||
|         return TreeUtil.build(list, rootId, treeNodeConfig, (node, tree) -> { | ||||
|             tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getIdKey()))); | ||||
|             tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getParentIdKey()))); | ||||
|             tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getNameKey()))); | ||||
|             tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getWeightKey()))); | ||||
|             if (!isSimple) { | ||||
|                 List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass); | ||||
|                 fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField | ||||
|                     .parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey())); | ||||
|                 fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeNodeConfig | ||||
|                     .getIdKey(), treeNodeConfig.getParentIdKey(), treeNodeConfig.getNameKey(), treeNodeConfig | ||||
|                         .getWeightKey(), treeNodeConfig.getChildrenKey())); | ||||
|                 fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f | ||||
|                     .getName())))); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<L> list(Q query, SortQuery sortQuery) { | ||||
|         List<L> list = this.list(query, sortQuery, listClass); | ||||
|         list.forEach(this::fill); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public D get(Long id) { | ||||
|         T entity = super.getById(id); | ||||
|   | ||||
| @@ -71,21 +71,6 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, | ||||
|         return baseService.page(query, pageQuery); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     @Operation(summary = "查询树列表", description = "查询树列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping("/tree") | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.tree(query, sortQuery, false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
| @@ -101,6 +86,21 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, | ||||
|         return baseService.list(query, sortQuery); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     @Operation(summary = "查询树列表", description = "查询树列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping("/tree") | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.tree(query, sortQuery, false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询详情 | ||||
|      * | ||||
|   | ||||
| @@ -46,16 +46,6 @@ public interface BaseService<L, D, Q, C> { | ||||
|      */ | ||||
|     PageResp<L> page(Q query, PageQuery pageQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @param isSimple  是否为简单树结构(不包含基本树结构之外的扩展字段) | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple); | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
| @@ -65,6 +55,20 @@ public interface BaseService<L, D, Q, C> { | ||||
|      */ | ||||
|     List<L> list(Q query, SortQuery sortQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * <p> | ||||
|      * 虽然提供了查询条件,但不建议使用,容易因缺失根节点导致树节点丢失。 | ||||
|      * 建议在前端进行查询过滤,如需使用建议重写方法。 | ||||
|      * </p> | ||||
|      * | ||||
|      * @param query     查询条件 | ||||
|      * @param sortQuery 排序查询条件 | ||||
|      * @param isSimple  是否为简单树结构(不包含基本树结构之外的扩展字段) | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple); | ||||
|  | ||||
|     /** | ||||
|      * 查询详情 | ||||
|      * | ||||
|   | ||||
| @@ -22,6 +22,7 @@ import cn.hutool.core.bean.copier.CopyOptions; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.lang.tree.TreeUtil; | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.text.CharSequenceUtil; | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| @@ -42,13 +43,14 @@ import top.continew.starter.data.mp.service.impl.ServiceImpl; | ||||
| import top.continew.starter.data.mp.util.QueryWrapperHelper; | ||||
| import top.continew.starter.extension.crud.annotation.DictField; | ||||
| import top.continew.starter.extension.crud.annotation.TreeField; | ||||
| import top.continew.starter.extension.crud.autoconfigure.CrudProperties; | ||||
| import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties; | ||||
| import top.continew.starter.extension.crud.model.entity.BaseIdDO; | ||||
| import top.continew.starter.extension.crud.model.query.PageQuery; | ||||
| import top.continew.starter.extension.crud.model.query.SortQuery; | ||||
| import top.continew.starter.extension.crud.model.resp.LabelValueResp; | ||||
| import top.continew.starter.extension.crud.model.resp.PageResp; | ||||
| import top.continew.starter.extension.crud.service.BaseService; | ||||
| import top.continew.starter.extension.crud.util.TreeUtils; | ||||
| import top.continew.starter.file.excel.util.ExcelUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| @@ -86,6 +88,13 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD | ||||
|         return pageResp; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<L> list(Q query, SortQuery sortQuery) { | ||||
|         List<L> list = this.list(query, sortQuery, this.getListClass()); | ||||
|         list.forEach(this::fill); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) { | ||||
|         List<L> list = this.list(query, sortQuery); | ||||
| @@ -93,36 +102,35 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD | ||||
|             return new ArrayList<>(0); | ||||
|         } | ||||
|         // 如果构建简单树结构,则不包含基本树结构之外的扩展字段 | ||||
|         TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG; | ||||
|         TreeField treeField = this.getListClass().getDeclaredAnnotation(TreeField.class); | ||||
|         if (!isSimple) { | ||||
|             // 根据 @TreeField 配置生成树结构配置 | ||||
|             treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField); | ||||
|         CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class); | ||||
|         CrudTreeProperties treeProperties = crudProperties.getTree(); | ||||
|         TreeNodeConfig treeNodeConfig; | ||||
|         Long rootId; | ||||
|         if (isSimple) { | ||||
|             treeNodeConfig = treeProperties.genTreeNodeConfig(); | ||||
|             rootId = treeProperties.getRootId(); | ||||
|         } else { | ||||
|             TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class); | ||||
|             treeNodeConfig = treeProperties.genTreeNodeConfig(treeField); | ||||
|             rootId = treeField.rootId(); | ||||
|         } | ||||
|         // 构建树 | ||||
|         return TreeUtils.build(list, treeNodeConfig, (node, tree) -> { | ||||
|             // 转换器 | ||||
|             tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value()))); | ||||
|             tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey()))); | ||||
|             tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey()))); | ||||
|             tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey()))); | ||||
|         return TreeUtil.build(list, rootId, treeNodeConfig, (node, tree) -> { | ||||
|             tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getIdKey()))); | ||||
|             tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getParentIdKey()))); | ||||
|             tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getNameKey()))); | ||||
|             tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeNodeConfig.getWeightKey()))); | ||||
|             if (!isSimple) { | ||||
|                 List<Field> fieldList = ReflectUtils.getNonStaticFields(this.getListClass()); | ||||
|                 fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField | ||||
|                     .parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey())); | ||||
|                 List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass); | ||||
|                 fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeNodeConfig | ||||
|                     .getIdKey(), treeNodeConfig.getParentIdKey(), treeNodeConfig.getNameKey(), treeNodeConfig | ||||
|                         .getWeightKey(), treeNodeConfig.getChildrenKey())); | ||||
|                 fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f | ||||
|                     .getName())))); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<L> list(Q query, SortQuery sortQuery) { | ||||
|         List<L> list = this.list(query, sortQuery, this.getListClass()); | ||||
|         list.forEach(this::fill); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public D get(Long id) { | ||||
|         T entity = super.getById(id, false); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user