com.houkunlin.system.dict.starter.DictController Maven / Gradle / Ivy
package com.houkunlin.system.dict.starter;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.houkunlin.system.dict.starter.bean.DictTypeVo;
import com.houkunlin.system.dict.starter.bean.DictValueVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.UnaryOperator;
/**
* 系统字典控制器
*
* @author HouKunLin
* @since 1.4.1
*/
@Api(tags = "系统字典")
@Tag(name = "系统字典")
@RestController
@RequestMapping("${system.dict.controller.prefix:/dict}")
public class DictController {
/**
* 获取字典类型信息
*
* @param dict 字典类型代码
* @param tree v1.4.9 以字典文本值的代码长度来截取成树结构数据,此值传入分隔长度(树形结构的KEY长度,按照这个长度去分隔字典值,由字典值拿到父级字典值)
* @return 字典类型对象
*/
@ApiOperation("获取字典类型[PATH]")
@Operation(summary = "获取字典类型[PATH]")
@ApiImplicitParam(name = "dict", value = "字典类型代码", required = true, paramType = "path", dataTypeClass = String.class)
@Parameter(name = "dict", description = "字典类型代码", required = true, in = ParameterIn.PATH)
@GetMapping({"{dict}", "{dict}/"})
public DictTypeVo dictType(@PathVariable final String dict, @RequestParam(required = false) final Integer tree) {
return transform(dict, tree);
}
/**
* 获取字典类型信息
*
* @param dict 字典类型代码
* @param tree v1.4.9 以字典文本值的代码长度来截取成树结构数据,此值传入分隔长度(树形结构的KEY长度,按照这个长度去分隔字典值,由字典值拿到父级字典值)
* @return 字典类型对象
*/
@ApiOperation("获取字典类型[QUERY]")
@Operation(summary = "获取字典类型[QUERY]")
@ApiImplicitParam(name = "dict", value = "字典类型代码", required = true, paramType = "query", dataTypeClass = String.class)
@Parameter(name = "dict", description = "字典类型代码", required = true, in = ParameterIn.QUERY)
@GetMapping(value = {"/", ""}, params = {"dict"})
public DictTypeVo dictTypeQuery(final String dict, @RequestParam(required = false) final Integer tree) {
return transform(dict, tree);
}
/**
* 转换字典信息。
*
* - 10 分类1
* - 1010 分类1-1
* - 1011 分类1-2
* - 20 分类2
* - 2010 分类2-1
* - 2011 分类2-2
*
*
* @param dict 字典类型代码
* @param tree 树形结构的KEY长度,按照这个长度去分隔字典值,由字典值拿到父级字典值
* @return 字典信息
* @since 1.4.9
*/
private DictTypeVo transform(final String dict, final Integer tree) {
final DictTypeVo dictType = DictUtil.getDictType(dict);
if (tree == null || tree <= 0) {
return dictType;
}
final List children = dictType.getChildren();
final ListMultimap multimap = handlerTreeDatasource(children,
"",
this::getKey,
vo -> {
final Object object = vo.getValue();
if (object instanceof String) {
final String value = (String) object;
final int length = value.length();
final int num = length / tree;
final int index = num == 0 ? 0 : num - 1;
return value.substring(0, index * tree);
}
return "";
},
DictValueVo::getChildren,
DictValueVo::setChildren
);
dictType.setChildren(new ArrayList<>(multimap.values()));
return dictType;
}
private String getKey(final DictValueVo vo) {
final Object object = vo.getValue();
if (object instanceof String) {
return (String) object;
}
return "";
}
/**
* 获取字典值文本信息
*
* @param dict 字典类型代码
* @param value 字典值代码
* @return 字典值文本信息
*/
@ApiOperation("获取字典值文本[PATH]")
@Operation(summary = "获取字典值文本[PATH]")
@ApiImplicitParams({
@ApiImplicitParam(name = "dict", value = "字典类型代码", required = true, paramType = "path", dataTypeClass = String.class),
@ApiImplicitParam(name = "value", value = "字典值代码", required = true, paramType = "path", dataTypeClass = String.class)
})
@Parameter(name = "dict", description = "字典类型代码", required = true, in = ParameterIn.PATH)
@Parameter(name = "value", description = "字典值代码", required = true, in = ParameterIn.PATH)
@GetMapping({"{dict}/{value}", "{dict}/{value}/"})
public String dictText(@PathVariable String dict, @PathVariable String value) {
return DictUtil.getDictText(dict, value);
}
/**
* 获取字典值文本信息
*
* @param dict 字典类型代码
* @param value 字典值代码
* @return 字典值文本信息
*/
@ApiOperation("获取字典值文本[QUERY]")
@Operation(summary = "获取字典值文本[QUERY]")
@ApiImplicitParams({
@ApiImplicitParam(name = "dict", value = "字典类型代码", required = true, paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam(name = "value", value = "字典值代码", required = true, paramType = "query", dataTypeClass = String.class)
})
@Parameter(name = "dict", description = "字典类型代码", required = true, in = ParameterIn.QUERY)
@Parameter(name = "value", description = "字典值代码", required = true, in = ParameterIn.QUERY)
@GetMapping(value = {"/", ""}, params = {"dict", "value"})
public String dictTextQuery(String dict, String value) {
return DictUtil.getDictText(dict, value);
}
/**
* 自动整理、归类、收集树形结构数据,使 multimap 的 key 只保留所有数据中的顶级数据。
*
* @param list 原始树形结构单节点数据
* @param defaultParentKey 默认的顶级父级ID
* @param getId 从 Entity 获取当前ID的方法
* @param getParentId 从 Entity 获取父级ID的方法
* @param getChildren 从 Entity 获取子级列表的方法
* @param setChildren 给 Entity 设置子级列表的方法
* @param Entity 实体类对象
* @param 当前对象ID 与 父级对象ID 关联关系的类型,比如:String 、 Integer 类型数据来做关联
* @return
* 返回处理后的树形结构信息,返回数据的结构为:
* 键-值:list中所有顶级父级ID -> 该顶级父级ID的子级列表,一般情况下顶级父级ID为defaultParentKey
*
* @since 1.4.9
*/
public static ListMultimap handlerTreeDatasource(@NonNull List extends E> list,
K defaultParentKey,
Function getId,
Function getParentId,
Function> getChildren,
BiConsumer> setChildren) {
ListMultimap multimap = ArrayListMultimap.create();
// 获取父级ID,因为顶级id可能为null,因此需要提供一个默认的父级ID处理方式
UnaryOperator getParentKey = key -> {
if (key == null) {
return defaultParentKey;
}
return key;
};
// 初步合并树形结构,把所有数据中,同一个父级的数据归类到一个List中存储,存储方式(键-值):父级ID -> 子级列表
list.forEach(entity -> multimap.put(getParentKey.apply(getParentId.apply(entity)), entity));
// 这里必须把所有的key取出来,因为在 handlerTreeChildren 中涉及到 multimap 对象的 key 移除
HashSet strings = new HashSet<>(multimap.keySet());
strings.forEach(key -> {
// 获得父节点 key 的子节点列表
if (multimap.containsKey(key)) {
multimap.get(key)
.forEach(entity -> handlerTreeChildren(multimap, entity, getId, getParentId, getChildren, setChildren));
}
});
return multimap;
}
/**
* 处理树形结构数据的子节点和孙子节点信息。
* 从 multimap 中移除当前节点的子级数据,
* 把 multimap 中当前节点的子级信息存到当前节点对象中,
* 继续处理当前节点的子节点的子节点数据,也就是继续处理当前节点的孙子节点数据。
*
* @param multimap 完整的树形结构数据信息
* @param parent 需要处理的父节点信息
* @param getId 从 Entity 获取当前ID的方法
* @param getParentId 从 Entity 获取父级ID的方法
* @param getChildren 从 Entity 获取子级列表的方法
* @param setChildren 给 Entity 设置子级列表的方法
* @param Entity 实体类对象
* @param 当前对象ID 与 父级对象ID 关联关系的类型,比如:String 、 Integer 类型数据来做关联
* @since 1.4.9
*/
private static void handlerTreeChildren(@NonNull ListMultimap multimap,
E parent,
Function getId,
Function getParentId,
Function> getChildren,
BiConsumer> setChildren) {
if (getChildren.apply(parent) != null) {
// 当前对象的子级列表不为null,表示已经处理过,不需要再次处理
return;
}
K id = getId.apply(parent);
K pid = getParentId.apply(parent);
if (id != null && id.equals(pid)) {
// 不允许当前节点的父级是自己,当前节点的父级是自己将会造成死循环
return;
}
// 从 multimap 中移除当前节点的子级数据
List entities = multimap.removeAll(id);
// 把 multimap 中当前节点的子级信息存到当前节点对象中
if (entities.isEmpty()) {
setChildren.accept(parent, null);
} else {
setChildren.accept(parent, entities);
}
// 继续处理当前节点子级节点的子级数据,也就是处理当前节点的孙子信息
entities.forEach(entity -> handlerTreeChildren(multimap, entity, getId, getParentId, getChildren, setChildren));
}
}